WXPython: getting the initial value of a combo box - python-2.7

In a dialog box I have a combo box and a text field. I would like to make so that if one particular value in the combo box is selected, the text field would be disabled (or hidden), and if another value is selected, the text field would be enabled.
I have:
self.myCombo = wx.ComboBox(parent=self, choices=['value1', 'value2'], style = wx.CB_READONLY)
self.myCombo.Bind(wx.EVT_COMBOBOX, self.onChange)
# ...
def onChange(self, ev):
self.myTextField.Enable(False) if self.myCombo.GetValue() != "value1" else self.myTextField.Enable(True)
And this does work like a charm, the text field gets enabled and disabled.
However, I would like to have the text field enabled or disabled depending on the initial value of the combo box, meaning the value gotten from a config file and selected when the dialog box is open.
I've tried the same:
self.myTextField = wx.TextCtrl(parent=self)
self.myTextField.Enable(False) if self.myCombo.GetValue() != "value1" else self.myTextField.Enable(True)
but this doesn't work. I've tried GetSelection also, but when logging this, both GetValue and GetSelection return -1.

The combobox probably isn't fully initialized yet when you try to query it. If you want to disable it when it loads, you don't have to check its value. Just disable it. But for what you want to do, I would recommend using wxPython's wx.CallAfter() method.
Something like the following should suffice:
def __init__(self):
# initialize various variables
wx.CallAfter(self.check_combobox, args)
def check_combobox(self, args):
self.myTextField.Enable(False) if self.myCombo.GetValue() != "value1" else self.myTextField.Enable(True)

Related

PyQt5 QComboBox list items changing postion

I am facing some issue with the display style of Qcombobox items. Currently am hardcoding the data to be shown in the combobox.
here is the code :
self.Dummy = QComboBox(self)
self.Dummy.setGeometry(200,600, 350, 50)
self.Dummy.setStyleSheet("QComboBox {background-color: white;border-style: outset;" border-width: 2px;border-radius: 5px;border-color: #448aff; font: 12px; min-width: 10em; padding: 3px;}")
self.Dummy.addItems(["-Select-", "2", "3","4","5","6","7","8","9","0","11",])
The issue is that the dropdown "list" postion keeps changing after each selection. Here is the image of the issue am facing.
Below is my combobox
The list contains items <-Select->,2,3,4,5,6,7,8,9,0,11 , where <-Select-> will be the first element shown.
Now when I click the box, the box list "down" the elements and suppose I selected '2'. Then, if I try to select another item, the list will be dropped in a "downwards" direction. see below
Now, say if selected the last element from the items, '11'. Now if I try to select a new item by clicking on the box, the list will be popped "up" instead of down. see below
What should be done to fix it ? I don't think its an issue with stylesheet, without it also, this issue is happening. The reason I need this to be fixed is that when the list is popping up, its covering the label above it
What you see is a behavior that is OS and style dependent.
To avoid it, the best way is to subclass QComboBox and overwrite showPopup(), then we call the base class implementation (which is responsible of showing, resizing and positioning the popup view) and move it if necessary.
class Combo(QtWidgets.QComboBox):
def showPopup(self):
super().showPopup()
# find the widget that contains the list; note that this is *not* the view
# that QComboBox.view() returns, but what is used to show it.
popup = self.view().window()
rect = popup.geometry()
if not rect.contains(self.mapToGlobal(self.rect().center())):
# the popup is not over the combo, there's no need to move it
return
# move the popup at the bottom left of the combo
rect.moveTopLeft(self.mapToGlobal(self.rect().bottomLeft()))
# ensure that the popup is always inside the edges of the screen
# we use the center of the popup as a reference, since with multiple
# screens the combo might be between two screens, but that position
# could also be completely outside the screen, so the cursor position
# is used as a fallback to decide on what screen we'll show it
done = False
for i, pos in enumerate((rect.center(), QtGui.QCursor.pos())):
for screen in QtWidgets.QApplication.screens():
if pos in screen.geometry():
screen = screen.geometry()
if rect.x() < screen.x():
rect.moveLeft(screen.x())
elif rect.right() > screen.right():
rect.moveRight(screen.right())
if rect.y() < screen.y():
rect.moveTop(screen.y())
elif rect.bottom() > screen.bottom():
# if the popup goes below the screen, move its bottom
# *over* the combo, so that the its current selected
# item will always be visible
rect.moveBottom(self.mapToGlobal(QtCore.QPoint()).y())
done = True
break
if done:
break
popup.move(rect.topLeft())
This can also be done without subclassing (for example if you have many combos, you created the UI from Designer and don't want to use promoted widgets), but you'll have to remember to change all referencies to the combo.
class MyWindow(QtWidgets.QWidget):
def __init__(self):
# ...
self.combo = QtWidgets.QComboBox()
self.combo.showPopup = self.showPopupAndCheck
def showPopupAndCheck(self):
QtWidgets.QComboBox.showPopup(self.combo)
popup = self.view().window()
rect = popup.geometry()
if not rect.contains(self.combo.mapToGlobal(self.combo.rect().center())):
# the popup is not over the combo, there's no need to move it
return
# change everything from self to self.combo
Alternatively, if you want to keep this behavior consistent through all your program without always using the subclass, you can use some sort of monkey patching hack.
The advantage is that any QComboBox you create (even when loading UI files or creating a combo at runtime) will always use the new behavior.
Important: this MUST be at the very beginning of the main file of your program, possibly just after the import section.
from PyQt5 import QtCore, QtGui, QtWidgets
def customShowPopup(self):
# we can't use super(), because we're not in the class definition, but
# calling the class method with "self" as first argument is practically the
# same thing; note the underscore!
QtWidgets.QComboBox._showPopup(self)
popup = self.view().window()
# ... go on, exactly as above
# create a new reference to the showPopup method, which is the one we've used
# in the function above
QtWidgets.QComboBox._showPopup = QtWidgets.QComboBox.showPopup
# overwrite the original reference with the new function
QtWidgets.QComboBox.showPopup = customShowPopup

Behavioral discrepancy in Tkinter listbox, arrow keys vs. mouse click

My environment is Python 2.7, running on Windows 7.
I'm trying get a Tkinter Listbox to trigger a callback in response to the user changing the 'active' item (i.e. the item with focus). I'm using a binding to the <<ListboxSelect>> event to make this happen, and it's working -- sort of.
The callback itself is supposed to check what the new active item is, and carry out some processing accordingly. This logic operates the way I expect when I change the active item via the up/down arrow keys. But when I point & click on a new item instead, the code mistakenly identifies the prior active item as the current one.
Here's a stripped-down code sample that illustrates the behavior I'm getting:
import Tkinter as tk
#Root window
root = tk.Tk()
#Callback to show focus change
def updateDisplay(*args):
focusIndex = str(lb.index(tk.ACTIVE))
ctrlFI.set('Focus is at index '+focusIndex)
#Control variables
ctrlLB = tk.StringVar()
ctrlFI = tk.StringVar()
#Widgets
lb = tk.Listbox(root,
width=20, height=10,
relief=tk.FLAT,highlightthickness=0,
selectmode=tk.EXTENDED,
activestyle='dotbox',
listvariable=ctrlLB)
lbl = tk.Label(root,
justify=tk.LEFT, anchor=tk.W,
textvariable=ctrlFI)
lb.grid(row=0,column=0,sticky=tk.NW,padx=(5,0),pady=5)
lbl.grid(row=1,column=0,columnspan=2,sticky=tk.NW,padx=5,pady=5)
#Listbox binding to trigger callback
lb.bind('<<ListboxSelect>>',updateDisplay)
#Initializations to prep GUI
ctrlLB.set('Index0-entry Index1-entry Index2-entry Index3-entry Index4-entry')
ctrlFI.set('Ready')
#Begin app
tk.mainloop()
Here are the results when you use the arrow keys:
But here's what you get when you click with the mouse:
The information 'lags' one behind, showing the prior selection instead. (If you click the same item a second time, it 'catches up.')
So my questions are:
What is causing the discrepancy?
How do I fix it so the mouse click gives the right result?
The active item is not necessarily the same as the selected item. When you press the mouse down it changes the selected value but it does not change the active item. The active item only changes once you release the mouse button.
You should be able to see this by clicking and holding the mouse button over an item that is not currently selected. When you do, you'll see something like this:
In the above image, the active item is the one surrounded by a dotted outline. The selected item is in blue. When your code displays the 'focus', it's displaying the active element rather than the selected element.
If you want the selected item, you need to use curselection to get the index of the selected item. It returns a tuple, so in extended mode you need to get the first element that is returned (eg: lb.curselection()[0]). Be sure to handle the case where curselection returns an empty string.

[Python2.7 TKinter]How to gray out a option in optionmenu that has been selected by older instances?

Here's my code:
class DefaultServiceClassWidget(ServiceClassWidget):
def __init__(self, mpet_widget, sc_data, idx):
super(DefaultServiceClassWidget, self).__init__(mpet_widget, sc_data, idx, DefaultServiceClassRow.number)
delete_default_sc_button = Button(mpet_widget.listFrame,justify=LEFT,text="x",fg="red",command= lambda: mpet_widget.delete_sc(self.idx))
delete_default_sc_button.grid(column=4,row=DefaultServiceClassRow.number)
self.select_default_class_label = Label(mpet_widget.listFrame,anchor=W,justify=LEFT,text="Select a Class")
self.select_default_class_label.grid(column=0,row=DefaultServiceClassRow.number)
options = ["All","CS Registration","GPRS Attach","PDP Activation","SMS","Reset","USSD","LTE"]
self.menu_pick_a_class = OptionMenu(mpet_widget.listFrame, sc_data.get_name(), *options, command=lambda event: sc_data.set_id())
self.menu_pick_a_class.grid(column=1,row=DefaultServiceClassRow.number)
self.row = DefaultServiceClassRow.number
DefaultServiceClassRow.number = DefaultServiceClassRow.number+2
def delete(self):
DefaultServiceClassRow.number = DefaultServiceClassRow.number - 2
default_list = (list(self.mpet_widget.listFrame.grid_slaves(row=self.row)) +
list(self.mpet_widget.listFrame.grid_slaves(row=self.row+1)))
for l in default_list:
l.grid_remove()
What happens is that there's a button connected to this function, every time the button is clicked, this function gets called and created a new grid on the GUI. What I want is that if for example "SMS" in optionmenu is selected, the next time this function gets called, "SMS" option will be grayed out (i.e. each option can be only selected once in the program)
I've tried updating status of the selected option by using status = "disabled" but it only works for the same grid, once a new grid(instance) is created, everything gets reset and all the options became available again :(
Also, in the same grid, if I disabled an option by selecting it and then changed to something else, the original selection is still grayed and cannot be selected again - I know why this happens but how do I fix it?
Sorry for the long question but I can't seem to find a solution online :(
Thank you in advance!

How can I clear the focus from a desensitized GtkWidget - GtkSpinButton with property sensitive = false

Background: I have a few tabs set up in my GtkNotebook, and when certain conditions are met on the main tab, then input fields in the 'test signals' tab are accordingly disabled.
When the input inside a GtkSpinButton is highlighted on the 'test signals' tab and the GtkSpinButton's property sensitive is set to false, the text within the GtkSpinButton remains highlighted. Because sometimes there are no other editable fields on this tab, the text remains highlighted until another widget has its property sensitive set to true.
This just looks sloppy to me, so I would like to stop this behavior and have the widgets that are set to sensitive = false all cleared of focus or highlighting. Any idea how to un-highlight the text within the GtkSpinButton, maybe before setting sensitive = false?
To get rid of a selection you can use the gtk_editable_select_region() function. This is a method on GtkEditable, an interface that both GtkEntry and GtkSpinButton satisfy. You can convert either of these to GtkEditable with GTK_EDITABLE(). For example:
gtk_editable_select_region(GTK_EDITABLE(spinbutton), 0, 0);

how to display an accelerator for a Gio.MenuItem

I'm trying to display the shortcut key accelerator for a Gio.Menuitem
As you can see, the RandomAlbum menu item does not have an accelerator displayed - however, I have added added the accelerator and connected it to the Gio.MenuItem successfully because the menuitem responds to the keyboard shortcut Alt+Z
The snippet of code I'm using is as follows:
action = Gio.SimpleAction.new(action_name, None)
app = Gio.Application.get_default()
app.add_accelerator("<alt>Z", "app."+action_name, None)
item = Gio.MenuItem()
item.set_detailed_action('app.' + action_name)
item.set_label("RandomAlbum")
app.add_plugin_menu_item('tools', "unique name", item)
Any ideas why the accelerator does not display - but still responds to keyboard control?
The full source is here:
https://github.com/fossfreedom/Rhythmbox-Random-Album-Player
The missing piece of this jigsaw puzzle is realising that Gio.MenuItems themselves have attribute-values.
So in this case, before adding the menu-item to the GMenu the syntax required is:
item.set_attribute_value("accel", GLib.Variant("s", "<Alt>Z"))
To complete the answer, you can also set the label and action for the menu-item in this way:
item = Gio.MenuItem()
item.set_attribute_value("label", GLib.Variant("s", "RandomAlbum"))
item.set_attribute_value("action", GLib.Variant("s", "app."+action_name))
However the methods set_label and set_detailed_action perform the same role.