max_height doesn't work for MDTextField in KivyMD - textfield

I was trying use max_height to limit the number of lines that the multiline=True MD TextField can expand to. In the KivyMD documentation for the TextField class (https://kivymd.readthedocs.io/en/latest/components/text-field/#module-kivymd.uix.textfield), there is sample code that is accompanied by a gif of what running it should look like, but when I copy/pasted it into a python file by itself to test it and ran it in PyCharm, the MDTextField didn't stop like it does in the gif or at all.
The example code given:
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen
MDTextField:
size_hint_x: .5
hint_text: "multiline=True"
max_height: "200dp"
mode: "fill"
fill_color: 0, 0, 0, .4
multiline: True
pos_hint: {"center_x": .5, "center_y": .5}
'''
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
Example().run()
gif of what it should do
Is this some kind of bug or is there something I can do about it? I'm trying to implement this in my project, but not even the example code is working for me.

max_height only limits the height that the text box can reach, not the number of lines the user can input, as stated here: https://kivymd.readthedocs.io/en/1.1.1/components/textfield/#kivymd.uix.textfield.textfield.MDTextField.max_height
There is no built-in way to limit the number of lines the user can input, you'll have to do it manually yourself such as counting number of lines of input.

Related

PyQt5 - Get the pixel color inside a QWidget

I made a QWidget and inside I made some other items like QLabels which display images.
Consider what is inside that parent Widget I was trying to get the color where I would click.
Searching I found this thread but it is a bit old and I am not able to translate it to Python.
thread:
https://www.qtcentre.org/threads/49693-How-to-get-color-of-pixel-or-point
code:
QPixmap qPix = QPixmap::grabWidget(ui->myWidget);
QImage image(qPix.toImage());
QColor color(image.pixel(0, 1));
How would this translate this to PyQt5 if it is the correct answer?
QPixmap.grabWidget() is considered obsolete, and you should use QWidget.grab() instead.
pixmap = self.someWidget.grab()
img = pixmap.toImage()
color = img.pixelColor(0, 1)

Line Wrapping in Collaboratory Google results

Working on Google Collaboratoy (colab) as Notebook, some cell results a long line text which is bigger than the screen resolution, so it is shown a scrollbar with no wrapping.
Does anyone know how to activate text wrapping to see all text without using scrollbar?
Thanks in advance.
Regards,
Normally on my own machine, I put this the following css snippit in the ~/.jupyter/custom/custom.css file.
pre {
white-space: pre-wrap;
}
But, the above does not work for google colab: I tried creating a file /usr/local/share/jupyter/custom/custom.css, but this didn't work.
Instead, put this in the first cell of your notebook.
from IPython.display import HTML, display
def set_css():
display(HTML('''
<style>
pre {
white-space: pre-wrap;
}
</style>
'''))
get_ipython().events.register('pre_run_cell', set_css)
Explanation: As described in Google Colab advanced output , get_ipython().events.register('pre_run_cell', <function name>)...
defines an execution hook that loads it [our custom set_css() function in our
case] automatically each time you execute a cell
My interpretation is that you need to specify 'pre_run_cell' as the first argument in the events.register, which tells the events.register function that you wish to run your custom set_css() function before the contents of the cell is executed.
This answer was inspired by How to import CSS file into Google Colab notebook (Python3)
from IPython.display import HTML, display
def set_css():
display(HTML('''
<style>
pre {
white-space: pre-wrap;
}
</style>
'''))
get_ipython().events.register('pre_run_cell', set_css)
As Bon Ryu mentioned above, this should solve the issue.
It will wrap your output properly
I use the following snippet:
from IPython.display import HTML, display
def my_css():
display(HTML("""<style>table.dataframe td{white-space: nowrap;}</style>"""))
get_ipython().events.register('pre_run_cell', my_css)
I create a function to help with that. It works with List and String.
def set_wrap(N=100):
''' create a wrap function for list '''
def wrap(obj):
s = str(obj)
out = '<pre>'
while True:
if len(s) < N:
out += s
break
i = s.rfind(' ', 0, N)
if i==-1:
i = N
out += s[:i]+"\n"
s = s[i:]
out += "</pre>"
return out
''' register it '''
Formatter = get_ipython().display_formatter.formatters['text/html']
Formatter.for_type(list, wrap)
Formatter.for_type(str, wrap)
You can use it by just calling set_wrap(80).

what is the proper way to set kivy scrollview effects_cls property?

I want to stop the user from over scrolling. kivy doc say that the effects_cls property will change this behavior, but I have not found a way to make it work.
Although you have solved your problem I will provide an example for future users.
You can change what effect is being used by setting effect_cls to any effect class. If you want to disable the overscroll effect to prevent the scroll bouncing effect ScrollEffect solve the problem.
Example using kivy Language:
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.lang import Builder
Builder.load_string('''
#:import ScrollEffect kivy.effects.scroll.ScrollEffect
#:import Button kivy.uix.button.Button
<RootWidget>
effect_cls: ScrollEffect
GridLayout:
size_hint_y: None
height: self.minimum_height
cols: 1
on_parent:
for i in range(10): self.add_widget(Button(text=str(i), size_hint_y=None))
''')
class RootWidget(ScrollView):
pass
class MainApp(App):
def build(self):
root = RootWidget()
return root
if __name__ == '__main__':
MainApp().run()
Output:
so I was trying to use effect_cls: ScrollEffect when it should be effect_cls: 'ScrollEffect'.
have to pass it as a string.

Interference of Sliders in Kivy

I'm learning Kivy and currently try to understand the Slider class. I created two sliders. Slider one is supposed to react to on_touch_move only, while slider two should react to on_touch_up and on_touch_down. If I implement this, like I did in the example below, both sliders interfere, i.e. they react to all three event dispatchers. I tried to understand why that is and how to solve the issue, but I can't. Thank you for helping me out.
The sliders.kv file:
#: kivy 1.9.0
SliderScreen:
<SliderScreen>:
Slider:
min: 0
max: 1
value: 0.75
step: 0.01
on_touch_move: root.test_a()
Slider:
min: 0
max: 1
value: 0.25
step: 0.01
on_touch_up: root.test_b()
on_touch_down: root.test_c()
and main.py:
import kivy
kivy.require('1.9.0')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.slider import Slider
class SliderScreen(BoxLayout):
def test_a(self):
print("test_a accessed")
def test_b(self):
print("test_b accessed")
def test_c(self):
print("test_c accessed")
class SlidersApp(App):
pass
if __name__ == '__main__':
SlidersApp().run()
on_touch_move, on_touch_up and on_touch_down events are captured by SliderScreen class and then propagated to all its widgets. According to the documentation:
By default, touch events are dispatched to all currently displayed
widgets. This means widgets recieve the touch event whether it occurs
within their physical area or not.
This can be counter intuitive if you have experience with other GUI
toolkits. These typically divide the screen into geometric areas and
only dispatch touch or mouse events to the widget if the coordinate
lies within the widgets area.
This requirement becomes very restrictive when working with touch
input. Swipes, pinches and long presses may well originate from
outside of the widget that wants to know about them and react to them.
In order to provide the maximum flexibility, Kivy dispatches the
events to all the widgets and lets them decide how to react to them.
If you only want to respond to touch events inside the widget, you
simply check:
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
# The touch has occurred inside the widgets area. Do stuff!
pass
Therefore you should use in your code:
Builder.load_string("""
<SliderScreen>:
Slider:
min: 0
max: 1
value: 0.75
step: 0.01
on_touch_move: if self.collide_point(*args[1].pos): root.test_a()
Slider:
min: 0
max: 1
value: 0.25
step: 0.01
on_touch_up: if self.collide_point(*args[1].pos): root.test_b()
on_touch_down: if self.collide_point(*args[1].pos): root.test_c()
""")
class SliderScreen(BoxLayout):
def test_a(self):
print("test_a accessed")
def test_b(self):
print("test_b accessed")
def test_c(self):
print("test_c accessed")

oop instantiation pythonic practices

I've got the code below, and I was planning on making several classes all within the same "import". I was hoping to instantiate each class and get a return value with the widgets I'm making.
This isn't really a PyQt question at all, more of a "good practices" question, as I'll have a class for each widget.
Should I make functions that return the widgets that were created, if so how? How do I ensure it is difficult to directly instantiate the class if that is the best method for what I'm after?
I'd like to be able to do something like ....
tabs = wqTabWidget( ['firstTab', 'Second', 'Last Tab'] )
or (which ever is a better practice)
tabs = wqInstance.createTabs( ['firstTab', 'Second', 'Last Tab'] )
Here's my class so far....
from PyQt4 import QtCore as qc
from PyQt4 import QtGui as qg
class wqTabWidget(qg.QTabWidget):
def __init__(self, *args):
apply(qg.QTabWidget.__init__,(self, ))
tabList = []
tabNames = args[0]
for name in tabNames:
tabWidget = qg.QWidget()
self.addTab(tabWidget, name)
tabList.append( { name:tabWidget } )
print 'hi'
if __name__ == '__main__':
app = qg.QApplication(sys.argv)
window = wqTabWidget(['hi', 'there', 'and', 'stuff'])
window.show()
app.exec_()
The answer will be decided if the list of tabs can be changed at runtime. If this widget really only supports adding a set of tabs, but never changing or appending new ones, the list of tabs should come from the initializer. Otherwise you should also add a method to do the job. Consider the QLabel widget which can set the label's text in the initializer and through the setText method.
Other code idea tips.
Your initializer's arguments is a little confusing because you accept an arbitrary number of arguments, but only do something with the first one, and expect it to be a list of strings. A clear list of arguments is important.
Your use of apply to call the base class initializer is unnecessary. Change the code to simply qg.QTabWidget.__init__(self)
When creating a PyQt widget, I almost always prefer to allow a "parent" argument, even when I know the widget is going to be a toplevel widget. This is what all the built in Pyqt methods do, and feels like good practice to follow.
I also can't see the reason to store a list of tabs, with each one being a single element dictionary. I suspect you won't need to keep your own list of tabs and tab names. The QTabWidget can answer all questions about the contents.
If I were to bend this example code to my own preferences it would look like this.
from PyQt4 import QtCore as qc
from PyQt4 import QtGui as qg
class wqTabWidget(qg.QTabWidget):
def __init__(self, parent, tabNames):
qg.QTabWidget.__init__(self, parent)
self.createTabs(tabNames)
def createTabs(tabNames):
for name in tabNames:
tabWidget = qg.QWidget()
self.addTab(tabWidget, name)
if __name__ == '__main__':
app = qg.QApplication(sys.argv)
window = wqTabWidget(None, ['hi', 'there', 'and', 'stuff'])
window.show()
app.exec_()