Python: Referring to single labels in a Pmw RadioSelect - python

I would like to create a hover box (or info box) which opens up when the user places the mouse cursor on top of a Pmw RadioSelect label. For example, when the cursor is placed on top of "Primary" the program opens an info box explaining what "Primary" means.
Problem: I don't know how to access the individual labels inside the RadioSelect object. I need to bind a method to the individual labels, but I don't know how to refer to them.
Extra: How could I have solved this myself? I tried looking at the RadioSelect attributes with dir() and I read the Pmw manual online, but couldn't find the information.
EDIT This is what I have found out thus far: The manual says that the labels only start to exist if their position is set explicitly:
labelpos
Initialisation option. Specifies where to place the label component.
If None, a label component is not created. The default is None
After setting it explicitly for example as so:
self.rs = Pmw.RadioSelect(parent, labelpos = 'w')
you can refer to it with
self.rs.component('label')
But I still don't know how to reach the individual labels.
EDIT 2: The trick was just to assign the RadioSelect "items" into variables like the accepted answer suggests:
self.cb1 = self.radio_select.add("text")
After assigning the "item" into a variable you can simply bind methods to the variable, like such:
self.balloon = Pmw.Balloon(self, initwait=500, relmouse='both')
self.balloon.bind(self.cb1, "Balloon text example")

If I understand well your problem, I think you are looking for:
To rely on Pmw to draw the widgets (unlike what I did with Tkinter previously)
when the cursor is placed on top of "Primary" the program opens an info box explaining what "Primary" means. (the effect I produced on the demo below)
Identify individual checkbuttons (or what you call in your own terms reaching the individual labels within the Pmw.RadioSelect)
Solution:
The solution for the first problem you know it already.
For the second problem, as I explained previously, you will need to instantiate Pmw.Balloon() and bind it to individual checkbuttons (or labels as you call them). I re-programmed that as you can see below but using an other method. I mean I relied mainly on add() which returns the component widget. Then I binded the instance of Pmw.Balloon() to the returned value from add(). Doing this, you already offer yourself a way to access individually the checkbuttons (and you play more with this if you want)
You can access individual checkbuttons (labels) by using getvalue() or getcurselection() methods which work similarly by returning the return the name of the currently selected button. But in practice, you will get tuples ( I mean these functions return the names of all selected checkbuttons, as I showed in the access_to_labels_individually() that I used as a callback method to display the names of the checkbuttons you select; of course you can play with that also depending on your needs)
Code
Here is an MVCE program:
'''
Created on Jun 18, 2016
#author: Billal BEGUERADJ
'''
# -*- coding: utf-8 -*-
import Pmw
import tkinter as tk
class Begueradj:
def __init__(self, parent):
self.balloon = Pmw.Balloon(parent)
# Create and pack a vertical RadioSelect widget, with checkbuttons.
self.checkbuttons = Pmw.RadioSelect(parent,
buttontype = 'checkbutton',
orient = 'vertical',
labelpos = 'w',
command = self.access_to_labels_individually,
hull_borderwidth = 2,
hull_relief = 'ridge',
)
self.checkbuttons.pack(side = 'left', expand = 1, padx = 10, pady = 10)
# Add some buttons to the checkbutton RadioSelect
self.cb1 = self.checkbuttons.add('Primary')
self.cb2 = self.checkbuttons.add('Secondary')
self.cb3 = self.checkbuttons.add('Tertiary')
# Bind the Balloon instance to each widget
self.balloon.bind(self.cb1, 'Primary:\n This is our primary service')
self.balloon.bind(self.cb2, 'Secondary:\n This is our primary service')
self.balloon.bind(self.cb3, 'Tertiary:\n This is our primary service')
# You can use getvalue() or getcurselection() to access individual labels
def access_to_labels_individually(self, tag, state):
print(self.checkbuttons.getvalue())
# Main program starts here
if __name__ =='__main__':
begueradj = Pmw.initialise(fontScheme = 'pmw1')
begueradj.title('Billal BEGUERADJ')
d = Begueradj(begueradj)
begueradj.mainloop()
Demo
(I am keeping the same screenshots because the above program produces the same results)
Here are screenshots of the running program related to the mouse hovering over each tkinter.Checkbutton() instance whether it is selected or not:

Related

Python Tkinter: Parsing dropdown menu value into a function as a variable?

I am trying to create a Tkinter dropdown menu to add into the main GUI. The purpose of the menu is to give the user several options to choose the image segmentation neural network (currently there are only two options).
I am having trouble getting the value of the dropdown menu and parsing it through the function as a parameter (variable, not a string).
I have tried creating a staticmethod within the GUI window class to search for the value of the dropdown menu and parse the value through the image segmentation function when the segment button in the GUI is pressed. The segmentation function takes two parameters, net and path, where it is neural network and image file pathway respectively.
I am not too sure how to change the individual parameters of a variable so I just changed the whole variable depending on the value of the dropdown menu. The only thing being changed is the net parameter of the segment function bind to the Tkinter button.
The code:
#staticmethod
def find_option():
if menu1.get() == "fcn":
App.btn2 = Button(App.btn_frame, text="Segment", width = 10, height = 1, cursor = "hand2", command=lambda: App.segment(net=App.fcn, path=App.path))
else:
App.btn2 = btn2 = Button(App.btn_frame, text="Segment", width = 10, height = 1, cursor = "hand2", command=lambda: App.segment(net=App.dlab, path=App.path))
The dropdown menu code:
fcn = models.segmentation.fcn_resnet101(pretrained=True).eval()
dlab = models.segmentation.deeplabv3_resnet101(pretrained=1).eval()
options = ["fcn", "dlab"]
variable = StringVar(btn_frame)
variable.set(options[0]) # default value
menu1 = OptionMenu(btn_frame, variable, *options)
menu1.pack(side=LEFT)
btn_frame is the frame within the main window containing the buttons.
App is the main GUI class. menu1 needed to be referenced as App.menu1 but it would, for some reason, cause several errors (other class variables would become undefined and it would say App object has no attribute menu). I also tried referencing menu1 as App().menu1 but would initiate a new window each time I pressed the segment button, also it never actually showed the segmented image.
Edit: I think you want something like this. It's hard to know when I can't see the entire code, but could it be that you are forgetting ot pass self ot the find_option method?
# This is the option menu part
self.options = ["fcn", "dlab"]
self.variable = tk.StringVar(btn_frame)
self.variable.set(options[0]) # default value
self.menu1 = tk.OptionMenu(
btn_frame, self.variable, *self.options)
self.menu1.pack(side=LEFT)
# And then later you have this method (remember to pass self to find_option)
def find_option(self):
if self.variable.get() == "fcn":
# your code

How to Use a Setter Function to Check Whether a Function Parameter has Changed - Python

Here is my code:
def DisplayFiles(which):
contentLabel = tk.Label(window, text = '\n'.join(files[indexlist[which]][1:]))
contentLabel.place(x = 150, y = 80)
I am using Tkinter and am trying to display files with the above function when a button is pressed. The variable "which" is the string name of a button. "indexlist" is a dictionary holding indexes for button names (I dynamically created them). My problem is trying to display files for two different buttons. When I click one button, the function above displays the files. But when I click another button, the label displays over the previous one. I am working on a destroy() method, but I need to know how to check when the parameter "which" is changed. Help would be appreciated!
Also, the values of indexlist, and files are not the problem. I just want to find a way to check when the function parameter is changed. Thanks!
Don't make a new Label every time, just update the old Label.
# make an empty Label
contentLabel = tk.Label(window)
contentLabel.place(x = 150, y = 80)
def DisplayFiles(which):
# update the Label contents
contentLabel.config(text = '\n'.join(files[indexlist[which]][1:]))

get widgets by name from layout

How should I proceed if I want to get an especific widget from a layout in python Qt?
What I have done so far:
for i in range(self.ui.horizontalLayout_14.count()):
#here it does fail
name = self.ui.horizontalLayout_14.itemAt(i).objectName()
#if the above would had worked, then I could do something like this for example
if "button" in name:
self.ui.horizontalLayout_14.itemAt(i).widget().close()
Note that, for the example, I am using button but It might be whatever widget inside the layout, lineEdit, or comboBox, labels, etc etc, but not all of them though.
The problem is that itemAt() function returns the QLayoutItem and not a widget. So you have to call the QLayoutItem::widget() function to get the containing widget, i.e.:
name = self.ui.horizontalLayout_14.itemAt(i).widget().objectName()
UPDATE
However, you can do the same much easier with using the QObject::findChild() function. I.e.:
widget = self.ui.findChild(QPushButton, "button")
# Check whether the returned widget is null...

Readonly tkinter text widget

I want to use tkinter text widget as a readonly widget. It should act as a transcript area. My idea is to keep this transcript in a file and whenever the user writes anything, just remove all the contents of the widget, and rewrite it again.
The code will look like:
transcript_entry = SimpleEditor() # SimpleEditor is inherited from ScrolledText
transcript_entry.text.delete("1.0", END)
# this is just a test string, it should be the contents of the transcript file
transcript_entry.text.insert("1.0", "This is test transcript")
transcript_entry.text.bind("<KeyPress>", transcript_entry.readonly)
And readonly function will look like:
def readonly(self, event):
self.text.delete("1.0", END)
# this is just a test string, it should be the contents of the transcript file
self.text.insert("1.0", "This is test transcript")
The bug here is that the last character entered by the user is added to the transcript. I suspect the reason is that the readonly function is called, then the user input is wrote to the widget. How to reverse this order & let the readonly function be called after the user input is wrote to the widget?
Any hints?
The reason that the last character is inserted is because the default bindings (which causes the insert) happens after custom bindings you put on the widget. So your bindings fire first and then the default binding inserts the characters. There are other questions and answers here that discuss this in more depth. For example, see https://stackoverflow.com/a/11542200/
However, there is a better way to accomplish what you are trying to do. If you want to create a readonly text widget, you can set the state attribute to "disabled". This will prevent all inserts and deletes (and means you need to revert the state whenever you want to programmatically enter data).
On some platforms it will seem like you can't highlight and copy text, but that is only because the widget won't by default get focus on a mouse click. By adding a binding to set the focus, the user can highlight and copy text but they won't be able to cut or insert.
Here's an example using python 2.x; for 3.x you just have to change the imports:
import Tkinter as tk
from ScrolledText import ScrolledText
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
t = ScrolledText(self, wrap="word")
t.insert("end", "Hello\nworld")
t.configure(state="disabled")
t.pack(side="top", fill="both", expand=True)
# make sure the widget gets focus when clicked
# on, to enable highlighting and copying to the
# clipboard.
t.bind("<1>", lambda event: t.focus_set())
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Please do not delete and reinsert your text :
It is huge performance issue.
It will remove any tags and marks set on the text
This will be visible to the user, and users don't like flickering interfaces
This is not necessary, Tkinter is customizable enough to just not allow the user change the content.
The best way I found to create a read only Text is to disable all the bindings leading to a text change.
My solution is to create a new Widget binding map containing only "read only commands". Then, just reconfigure your widget to use the new RO binding map instead of the default one :
from Tkinter import *
# This is the list of all default command in the "Text" tag that modify the text
commandsToRemove = (
"<Control-Key-h>",
"<Meta-Key-Delete>",
"<Meta-Key-BackSpace>",
"<Meta-Key-d>",
"<Meta-Key-b>",
"<<Redo>>",
"<<Undo>>",
"<Control-Key-t>",
"<Control-Key-o>",
"<Control-Key-k>",
"<Control-Key-d>",
"<Key>",
"<Key-Insert>",
"<<PasteSelection>>",
"<<Clear>>",
"<<Paste>>",
"<<Cut>>",
"<Key-BackSpace>",
"<Key-Delete>",
"<Key-Return>",
"<Control-Key-i>",
"<Key-Tab>",
"<Shift-Key-Tab>"
)
class ROText(Text):
tagInit = False
def init_tag(self):
"""
Just go through all binding for the Text widget.
If the command is allowed, recopy it in the ROText binding table.
"""
for key in self.bind_class("Text"):
if key not in commandsToRemove:
command = self.bind_class("Text", key)
self.bind_class("ROText", key, command)
ROText.tagInit = True
def __init__(self, *args, **kwords):
Text.__init__(self, *args, **kwords)
if not ROText.tagInit:
self.init_tag()
# Create a new binding table list, replace the default Text binding table by the ROText one
bindTags = tuple(tag if tag!="Text" else "ROText" for tag in self.bindtags())
self.bindtags(bindTags)
text = ROText()
text.insert("1.0", """A long text with several
lines
in it""")
text.pack()
text.mainloop()
Note that just the bindings are changed. All the Text command (as insert, delete, ...) are still usable.
I recently worked a different, slightly simpler solution. Rather than changing all the bindings, one can add a function to delete all input characters as soon as they are written:
def read_only(self, event):
if event.char is not '': # delete only if the key pressed
# corresponds to an actual character
self.text.delete('insert-1c')
and just bind it to any event:
root.bind('<Key>', self.read_only)

Controlling location of Pmw.MessageDialog (and other similar widgets)

Python newbie here, newer still to Pmw:
I have the following method defined for showing a Pmw MessageDialog box, and it works as expected and the result value is returned and posted in edit1, which is a Tkinter.Text widget; 'self' here is a Tkinter.Frame. (running Win7-32 and Python v2.7.2):
def _showMessageBar(self):
dialog = Pmw.MessageDialog(self, title = 'DBox',defaultbutton = 0,buttons('OK',Cancel'), message_text = 'DBox')
dialog.iconname(dialog['title'])
try:
result = dialog.activate()
finally:
dialog.deactivate()
self.edit1.insert(END, result+ "\n")
The problem is, the call to dialog.activate() doesn't allow me to control the location of the messageBox.
If I change that call to:
result = dialog.activate(geometry = first+50+20)
then the messageBox widget is placed at the specified coordinates, but this has two side effects:
1) The messageBox widget now has the buttons of main window (close, minimize,maximize) rather than a dialog box (just the close 'X' button)
2) The result value is never posted to edit1.
Question: How do I control the location of the messageBox while maintaining the dialog box buttons/border and getting the value posted to the Text (edit1) widget.
TIA
The answer is that the geometry options need to be in quotes, I wasn't seeing the proper results because an exception was being thrown by the improper geometry specifier. This code:
result = dialog.activate(geometry = "first+50+20")
works fine.

Categories