I'm learning creating software with Python and Tkinter. Now I need to change menu items for different conditions, but could not find an easy way to do it. Well, let me try to explain my question clearly using an example:
Like shown in the figure, I have a listbox on the left and a listbox on the right. I also have a menu to move the items around, the commands are "move to right", "move to left" and "exchange". The following conditions are considered:
When I only get items selected in left listbox, I want only the command "move to right" enabled, like shown in the figure.
When I only get items selected in right listbox, I want only the command "move to left" enabled.
When I get items selected in both listboxes, I want all commands enabled.
When I get no item selected, I want all commands disabled.
I know I can get the work done by binding events "ListboxSelect" and "Button-1" to some functions, and then use the functions to configure the menu. But it is really a complex work when I have five listboxes in the actual software. So I am wondering whether there is an easy way to do this, like overloading some functions in tkinter.Menu class (I tried overloading post(), grid(), pack() and place(), none of them works).
Any idea is welcomed.
I think what you want to use is the postcommand to modify the menu as appropriate. If you're going to have multiple listboxes, the simplest solution may be to implement your own class. Here's a rough idea:
class EditMenu(Tkinter.Menu):
def __init__(self, parent, listboxes, **kw):
self.commandhook = kw.get('postcommand', None)
kw['postcommand'] = self.postcommand
super(EditMenu, self).__init__(parent, **kw)
self.listboxes = listboxes
self.add_command(label="Move to right", command=self.move_to_right)
self.add_command(label="Move to left", command=self.move_to_left)
self.add_command(label="Exchange", command=self.exchange)
def postcommand(self):
for i in xrange(3):
# do some checks for each entry
# and set state to either Tkinter.DISABLED or Tkinter.NORMAL
self.entryconfig(i, state=state)
if self.commandhook is not None:
self.commandhook()
# Implement your three functions here
If you start to add more items, probably what you'll want to do is create a class for each menu item. In that class you could put in the logic for enable/disable and the callback function implementation. Comment if you'd like to see an example.
Related
I want to dynamically change the number of sliders on my application window, in dependence of the number of checked items in a QStandardItemModel structure.
My application window has an instance of QVBoxLayout called sliders, which I update when a button is pressed:
first removing all sliders eventually in there:
self.sliders.removeWidget(slider)
And then creating a new set.
The relevant code:
def create_sliders(self):
if len(self.sliders_list):
for sl in self.sliders_list:
self.sliders.removeWidget(sl)
self.sliders_list = []
for index in range(self.model.rowCount()):
if self.model.item(index).checkState():
slid = QSlider(Qt.Horizontal)
self.sliders.addWidget(slid)
self.sliders_list.append(slid)
The principle seems to work, however what happens is weird as the deleted sliders do not really disappear but it is as they were 'disconnected' from the underlying layout.
When created, the sliders keep their position among other elements while I resize the main window.
However, once they've been removed, they occupy a fixed position and can for instance disappear if I reduce the size of the window.
Unfortunately I'm having difficulties in linking images (it says the format is not supported when I try to link from pasteboard), so I hope this description is enough to highlight the issue.
Do I have to remove the sliders using a different procedure?
EDIT
Thanks to #eyllansec for his reply, it condenses a bunch of other replies around the topic, which I wasn't able to find as I did not know the method deleteLater() which is the key to get rid of widgets inside a QLayout.
I am marking it as my chosen (hey, it's the only one and it works, after all!), however I want to propose my own code which also works with minimal changes w.r.t. to what I proposed in the beginning.
The key point here is that I was using the metod QLayout.removeWidget(QWidget) which I was wrongly thinking, it would..er..Remove the widget! But actually what it does is (if I understood it right) remove it from the layout instance.
That is why it was still hanging in my window, although it seemed disconnected
Manual reference
Also, the proposed code is far more general than what I need, as it is a recursion over layout contents, which could in principle be both other QLayout objects or QWidgets (or even Qspacer), and be organized in a hierarchy (i.e., a QWidget QLayout within a QLayout and so on).
check this other answer
From there, the use of recursion and the series of if-then constructs.
My case is much simpler though, as I use this QVLayout instance to just place my QSliders and this will be all. So, for me, I stick to my list for now as I do not like the formalism of QLayout.TakeAt(n) and I don't need it. I was glad that the references I build in a list are absolutely fine to work with.
In the end, this is the slightly changed code that works for me in this case:
def create_sliders(self):
if len(self.sliders_list):
for sl in self.sliders_list:
sl.deleteLater()
self.sliders_list = []
for index in range(self.model.rowCount()):
if self.model.item(index).checkState():
slid = QSlider(Qt.Horizontal)
self.sliders.addWidget(slid)
self.sliders_list.append(slid)
It is not necessary to save the sliders in a list, they can be accessed through the layout where it is contained. I have implemented a function that deletes the elements within a layout. The solution is as follows:
def create_sliders(self):
self.clearLayout(self.sliders)
for index in range(self.model.rowCount()):
if self.model.item(index).checkState():
slid = QSlider(Qt.Horizontal)
self.sliders.addWidget(slid)
def clearLayout(self, layout):
if layout:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget:
widget.deleteLater()
else :
self.clearLayout(item.layout())
layout.removeItem(item)
I would like to make an interactive module with ipywidgets.
So far so good but I'm stuck.
I want to hide the visibility of a certain ipywidget object dependent on a certain situation, and I want my printed text to show up above the widget and stay there.
dropdown=widgets.Dropdown(
options={'Coffee machine': 1, 'Washing machine': 2, 'Water Heater': 3, 'Heating System': 4, 'Dryer': 5, 'Oven': 6, 'Microwave': 7, 'Other':8},
value=1,
description='Apparaat:',
)
text_new=widgets.Text()
def text_field(value):
if(value==8):
display(text_new)
text_new.on_submit(handle_submit)
else:
text_new.visible(False) #Doesn't work but I want something like this
print("Today you had an increase in electricity consumption, would you like to name this device?") #This just be above the dropdown menu and be stuck
i=widgets.interactive(text_field, value=dropdown)
display(i)
What this does now:
When "Other" is checked in the dropdown menu, a text box appears where the user can type something.
However, when checking another machine, the text box stays there.
I just need a "hide" function but I can't seem to find one that works.
Also, after checking another option on the dropdown, the print dissapears, not coming back.
Had same problem so i found in
boton.layout.visibility = 'hidden'
or
check.layout.display = 'none'
they made some changes... i got if from here
Cannot create a widget whose initial state is visible=False
Given a widget:
import ipywidgets
button = ipywidgets.Button()
There are two direct ways to hide the the widget, with a notable difference.
Hide and unhide the widget without affecting overall page layout:
# Turn the widget "invisible" without affecting layout
button.layout.visibility = "hidden"
# Make the widget visible again, layout unaffected
button.layout.visibility = "visible"
Hide and unhide the widget and collapse the space that the widget took up:
# Hide widget and collapse empty space
button.layout.display = "none"
# Re-add the widget, adjusting page layout as necessary.
button.layout.display = "block"
When to use each one? As a rule of thumb, use layout.visibility so the page layout is not constantly jumping around as visibility is toggled. However, for very large widgets, consider using layout.display to avoid huge blank spaces.
For more general CSS information that applies here, see What is the difference between visibility:hidden and display:none?
In addition to the accepted answer, if you want to dynamically change the visibility of a control, you can declare the layout variable and reuse.
layout_hidden = widgets.Layout(visibility = 'hidden')
layout_visible = widgets.Layout(visibility = 'visible')
Like attach to an event:
def visible_txt(b):
text_box.layout = layout_visible
def hidden_txt(b):
text_box.layout = layout_hidden
btn_visible.on_click(visible_txt)
btn_hidden.on_click(hidden_txt)
So I'm working on a project for myself, I'm not a great programmer or anything, but I was wanting to add a bit of formatting to the OptionMenu class. In typical tk you call optionmenu with a list and it then iterates through that list to make the various .add_commands. i.e.
Button >>
Sword
Dagger
Axe
Great Sword
Great Axe
What I was wanting to do though was add in a visual separator (Not tk's default horizontal rule) that would distinct those items by a 'weapon class' ie.
Button >>
1-Hand --
Sword
Dagger
Axe
2-Hand --
Great Sword
Great Axe
Where the separator, obviously, wasn't a selectable option. So I figured I could write a new class 'myOptionMenu2'. I can generate a widget that appears identical to tk's 'OptionMenu' but I'm not really sure how it's best to implement the drawing of the actual menu itself.
If it inherits tk's (MenuButton) class (like optionmenu does), I assume(?) that I could write a new 'add_separator()' function that would step-on tk's default add-separator function, and i could use that to just insert Label widgets
Or i could just inherit a Frame and cut out all the need to mess with tk's menu/menubutton coding. But I am unsure exactly how tk() implements the 'top-level' frames for menu (with no borders, drop shadow, etc.) I looked in the actual tk.py but I'm still not certain i understand how it accomplishes it.
my overall desire is to invoke it something like this:
baseList = [
[1-hand,[sword,dagger,axe, ...]],
[2-hand,[Great Sword, Great Axe, ...]]
[ ..., [...]] #as needed
]
myOptionMenu2(master, a_parent_IntVar(), baseList)
and then have the class draw the 'button', parse the list and handle clicks.
Any help would be appreciated.
You have very little leeway in what you can do on the menus. Your only options are the ones provided by the tk menu widget. You can't, for example, add a label to the menu. However, you can add a command that does nothing, which can serve as a label.
There's also the problem that an optionmenu isn't really a class under the covers. In tk (which Tkinter is built upon), the option menu is just a helper function that contructs a menubutton and a menu. So, there's simply no way to override the add_separator method.
That being said, it's pretty easy to write your own class that creates the menubutton and associated menu. You can then do whatever the standard widgets allows you to do, such as using non-functioning commands as labels.
Here's a simple example:
class myOptionMenu2(tk.Menubutton):
def __init__(self, parent, var, data):
tk.Menubutton.__init__(self, parent, borderwidth=1, relief="raised",
textvariable=var, indicatoron=True)
self.menu = tk.Menu(self, tearoff=False)
self.configure(menu=self.menu)
default = None
for (category, weapons) in data:
self.menu.add_command(label=category, command=None)
if default is None:
default = weapons[0]
var.set(default)
for weapon in weapons:
self.menu.add_radiobutton(indicatoron=True, value=weapon,
label=weapon, variable=var)
You could use it like this:
baseList = [
["1-Hand", ["sword", "dagger", "axe"]],
["2-Hand", ["Great Sword", "Great Axe"]]
]
weaponVar = tk.StringVar()
om = myOptionMenu2(self, weaponVar, baseList)
You also have the option of making cascade menus. So, the dropdown would have entries for "1-Hand" and "2-Hand", and each of those would have a menu for the various options. You could start with the above code, and instead of making commands you can create cascade items.
Finally, you could do what you suggest in the last part of your question and create your own menu-like object out of a toplevel frame (with the overrideredirect flag set to True). You then have complete control over what the dropdown looks like. You could, for example, embed a canvas and draw whatever you want. This solution would require a fair amount of code, though.
I'm brand new to Python and I'm trying to make my first program with PyQt4. My problem is basically the following: I have two checkboxes (Plot1 and Plot2), and a "End" push button, inside my class. When I press End, I would like to see only the plots that the user checks, using matplotlib. I'm not being able to do that. My first idea was:
self.endButton.clicked.connect(self.PlotandEnd)
self.plot1Checkbox.clicked.connect(self.Plot1)
self.plot2Checkbox.clicked.conncet(self.Plot2)
def PlotandEnd(self)
plot1=self.Plot1()
pyplot.show(plot1)
plot2=self.Plot2()
pyplot.show(plot2)
def Plot1(self)
plot1=pyplot.pie([1,2,5,3,2])
return plot1
def Plot2(self)
plot2=pyplot.plot([5,3,5,8,2])
return plot2
This doesn't work, of course, because "PlotandEnd" will plot both figures, regardless of the respective checkbox. How can I do what I'm trying to?
Wrap the plot creation in an if statement that looks at the state of the check boxes. For example:
def PlotandEnd(self)
if self.plot1Checkbox.isChecked():
plot1=self.Plot1()
pyplot.show(plot1)
if self.plot2Checkbox.isChecked():
plot2=self.Plot2()
pyplot.show(plot2)
You also don't need the following lines:
self.plot1Checkbox.clicked.connect(self.Plot1)
self.plot2Checkbox.clicked.conncet(self.Plot2)
This does nothing useful at the moment! Qt never uses the return value of your PlotX() methods, and you only want things to happen when you click the End button, not when you click a checkbox. The PlotX() methods are only currently useful for your PlotandEnd() method.
I have a window containing multiple QRowWidgets, which are custom widgets defined by me. These QRowWidgets contain QLineEdits and other standard widgets. To show or hide certain parts of a QRowWidget, I overdefined the focusInEvent() methodes of all the widgets within it. It works perfectly, when I click on the QRowWidget, the hidden elements appear.
The weird thing is that the blinking cursor line hovewer doesn't appear in the QLineEdits within the custom widgets. I can select them both by a mouse click or with Tab, and a glow effect indicates that the QLineEdit is selected in it, I can select a text in it, or start typing at any location wherever I clicked, but the cursor never appears and it's quite annoying.
My 1st thought was that it is a bug on Mac, but I have the same experience on SuSe Linux.
I'm using python 2.7 and PyQt4.
This is in the __init__() of the QRowWidget:
for i in self.findChildren(QWidget):
i.focusInEvent = self.focusInEvent
And then this is the own focusInEvent():
def focusInEvent(self, event):
if self.pself.focusedLine:
self.pself.focusedLine.setStyleSheet("color: #666;")
self.pself.focusedLine.desc.hide()
self.pself.focusedLine.closebutton.hide()
self.setStyleSheet("color: #000;")
self.desc.show()
self.closebutton.show()
self.pself.focusedLine = self
I suspect you do not make a call to the original focusInEvent() when you override it. Your function should look something like:
def focusInEvent(self,...):
QParent.focusInEvent(self,...)
# the rest of your code
where QParent is the nearest base class for your widgets is.
Either that, or make sure you call focusInEvent() on your QLineEdit widgets as part of your function.
Given the comments, it sounds like you are dynamically reassigning the focusInEvent function on the insantiatations in your custom widget. I would either make a derived class for each of the widgets you use that just overrides focusInEvent as above, or include a line like
type(self).focusInEvent(self,..)
in you function.