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

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.

Related

getting event late in tkinter listbox [duplicate]

This question already has an answer here:
Python tkinter listbox bind on <Button-1> only works on second click
(1 answer)
Closed 1 year ago.
I was creating simple listbox containing numbers from 0 to 9. I wanted to print number when it get clicked so i bind list box with Button-1. Im facing problem that is when ever i select any number and try to get its location using list_box.curselection() it does not print any thing(return empty tuple), if i click on on any other number then it print previous selected number. I want to get current selected number.
from tkinter import *
root = Tk()
root.title("test listbox")
list_box = Listbox(root)
list_box.pack()
for i in range(0,10):
list_box.insert("end",i)
def def_fun(event):
print(list_box.curselection())
list_box.bind("<Button-1>",def_fun)
root.mainloop()
You don't have to bind to <Button-1> or anything, there is a virtual event with Listbox that you can use here:
def def_fun(event):
print(event.widget.curselection()) # The widget that triggers the event is event.widget
list_box.bind("<<ListboxSelect>>",def_fun) # Gets triggered each time something is selected
Just in case you are wondering why the Button-1 did not work, it is because there is a delay, the delay might be due to binding order, you can read more about it here but here is a gist:
In the default case, your binding on <Key> happens before the class binding, and it is the class binding where the text is actually inserted into the widget. That is why your binding always seems to be one character behind.
Change the binding to releasing mouse button, this will also be more user friendly (for example if they accidentally clicked on a selection they didn't want to select, they can move their mouse to a one they want and only releasing will call the function):
from tkinter import Tk, Listbox
def def_fun(event=None):
print(list_box.curselection())
root = Tk()
root.title("test listbox")
list_box = Listbox(root)
list_box.pack()
for i in range(0, 10):
list_box.insert("end", i)
list_box.bind("<ButtonRelease-1>", def_fun)
root.mainloop()
Another option if you want to call the function on select is either use #CoolCloud answer or you can also set a delay like this (although it will most certainly work in 99.9% of cases, there might be a case where it doesn't):
list_box.bind("<Button-1>", lambda e: root.after(10, def_fun))
The reason is that .curselection() gets the current selection but Button-1 is triggered before anything gets selected so it will print the previous selection because that is what was selected before and where the current selection is now, and then immediately after this, it will move the current selection to the item you clicked.
Important (because it may cause hard-to-debug issues):
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
Also:
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but use if it is used for assigning a value (variable = 'some value'). Have two blank lines around function and class declarations.

Python: Referring to single labels in a Pmw RadioSelect

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:

How do you modify the current selection length in a Tkinter Text widget?

I would like to be able to double click on test,
in a Tkinter Text widget, and have it select test (and exclude the comma).
Here is what I've tried:
import Tkinter as tk
def selection_mod(event=None):
result = aText.selection_get().find(',')
if result > 0:
try:
aText.tag_add("sel", "sel.first", "sel.last-1c")
except tk.TclError:
pass
lord = tk.Tk()
aText = tk.Text(lord, font=("Georgia", "12"))
aText.grid()
aText.bind("<Double-Button-1>", selection_mod)
lord.mainloop()
The first issue is that <Double-Button-1> seems to trigger the handler before the selection is made, producing:
TclError: PRIMARY selection doesn't exist or form "STRING" not defined
The second issue is that even when using a binding that works,
my selection tag doesn't seem to do anything.
It doesn't even raise an error, and I've tried without the except tk.TclError:.
Your binding is happening before the default bindings occur. Thus, the selection doesn't yet exist when your binding fires. Because your binding tries to get the selection, it fails with the error that you see.
You will need to arrange for your binding to happen after the class bindings. A cheap hack is to use after to execute your code once the default bindings have a chance to work. Or, you can use the bindtag feature to make sure your binding fires after the default bindings.
The second problem is that you don't clear the old selection before setting the new. You'll want to do tag_remove to first remove the existing selection. Otherwise, the comma (if it was somehow selected) will remain selected since all you're doing is re-applying the tag to text that already has the tag.
However, double-click doesn't normally capture the comma so I don't quite understand then point of your code. At least, when I test it on OSX it doesn't include the comma.
Here is what I came up with thanks to Bryan's answer:
import Tkinter as tki # tkinter in Python 3
def selection_mod(event=None):
result = txt.selection_get().find(',')
if result > 0:
fir, sec = txt.tag_ranges("sel")
txt.tag_remove("sel", "sel.first", "sel.last")
txt.tag_add("sel", fir, str(sec)+"-1c")
root = tki.Tk()
txt = tki.Text(root, font=("Georgia", "12"))
txt.grid()
txt.bind("<Double-Button-1>", lambda x: root.after(20, selection_mod))
root.mainloop()
It's worth noting that I'm using Windows 7, and according to Bryan,
OSX doesn't include the comma when you double click a word.

How to auto-scroll a gtk.scrolledwindow?

I have a treeview-widget inside a ScrolledWindow, which is populated during runtime. I want the ScrolledWindow to auto-scroll to the end of the list. I "solved" the problem, by adjusting the vadjustment of the ScrolledWindow, everytime a row is inserted into the treeview. e.g:
if new_line_in_row:
adj = self.scrolled_window.get_vadjustment()
adj.set_value( adj.upper - adj.page_size )
If i run the code in an interactive ipython session and set the value by myself, everything works as expected.
If i run the code with the default python interpreter, the auto-scroll doesn't work all the time. I debugged the code and the problem seems be, that the adjustment values have some kind of "lag" and are only changed after some period of time.
My question is: how do I scroll, reliably, to maximum position of the ScrolledWindow? is a special signal generated which i can use? or is there a better way to set the adjustment-value?
After widening my search-radius, i found a ruby-related answer. since the problem is gtk-related, it should be able to be solved in any language like this:
you connect the widget which changes, in my case the treeview, with gtk.widget's 'size-allocate' signal and set the gtk.scrolledwindow value to "upper - page_size". example:
self.treeview.connect('size-allocate', self.treeview_changed)
...
def treeview_changed(self, widget, event, data=None):
adj = self.scrolled_window.get_vadjustment()
adj.set_value( adj.upper - adj.page_size )
link to the original post at ruby-forum.com:
hint hint
fookatchu's answer can be improved so that the callback could be used by multiple widgets:
def treeview_changed( self, widget, event, data=None ):
adj = widget.get_vadjustment()
adj.set_value( adj.upper - adj.pagesize )
Python Gtk 3 version:
adj.set_value(adj.get_upper() - adj.get_page_size())
The accepted answer has helped me figure out a Rust solution in gtk-rs to the auto-scroll to end of content issue.
Here's a Rust snippet that might help others:
// Imports used for reference
use gtk::{TextBuffer, TextView, TextBufferBuilder, ScrolledWindow, ScrolledWindowBuilder};
// The text buffer where the text is appended later
let text_buffer: TextBuffer = TextBufferBuilder::new().build();
// The containing text view that holds the text buffer
let text_view: TextView = TextView::new_with_buffer(&text_buffer);
// The scrolled window container with fixed height/width that holds the text view
let scrolled_window: ScrolledWindow = ScrolledWindowBuilder::new()
.min_content_height(400)
.min_content_width(600)
.child(&text_view)
.build();
// Have the text view connect to signal "size-allocate"
text_view.connect_size_allocate(clone!(#weak scrolled_window => move |_,_| {
let adj = scrolled_window.get_vadjustment().unwrap();
adj.set_value(adj.get_upper() - adj.get_page_size());
}));
// ...
// Later on, fill buffer with some text.
text_buffer.insert(&mut text_buffer.get_end_iter(), "This is my text I'm adding");
None of the suggested solutions worked for me, but I was able to get the desired behavior doing this (using GTk4 bindings for Go):
adj := scrolledWindow.VAdjustment()
adj.SetUpper(adj.Upper() + adj.PageSize())
adj.SetValue(adj.Upper())
The other solutions would move the scrolled window (mine contained a list box) down to the 2nd-to-last item, but not show the last item. The way it was acting made me think that the upper limit needed to be increased, so I tried increasing it by page-size then setting the scroll value to the new upper limit.
Still need the callback (I used a textView rather than treeView):
textView = Gtk.TextView()
scrolledWindow = Gtk.ScrolledWindow()
def textViewChanged( self, widget ):
adjustment = scrolledWindow.get_vadjustment()
adjustment.set_value( adjustment.get_upper() - adjustment.get_page_size() )
textView.connect( "size-allocate", textViewChanged )

pygtk: how to force message dialogs to show up in the center of the screen?

I have a glade GUI and i'm using dome gtk.MessageDialog widgets created with pygtk for user interaction. My problem is that whenever I throw a dialog message on the screen, they show up all over the place. One might show up on the top right corner, the next on the bottom left, top left, mid left etc...
Is there a way to force these things to show up in the center of the screen or at the position where the parent window is at?
Never mind. Found the solution.
For others who might wander about the same thing, the solution to this problem lies in specifying a parent value to the gtk.MessageDialog construct.
If you are using a glade gui, in your class, and your glade xml is loaded in to a variable named 'gui', it would look like this:
#!/usr/bin/env/python
par = self.gui.get_widget('your_parent_window')
msg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK, parent=par)
if msg.run():
msg.destroy()
return None
Check out the reference material at PyGTK 2.0 Reference Manual
I have not had a chance to try this but MessageDialog seems to be derived from Window which has a set_position method.
This method accepts one of the following:
# No influence is made on placement.
gtk.WIN_POS_NONE
# Windows should be placed in the center of the screen.
gtk.WIN_POS_CENTER
# Windows should be placed at the current mouse position.
gtk.WIN_POS_MOUSE
# Keep window centered as it changes size, etc.
gtk.WIN_POS_CENTER_ALWAYS
# Center the window on its transient parent
# (see the gtk.Window.set_transient_for()) method.
gtk.WIN_POS_CENTER_ON_PARENT
None of the provided solutions will work if your parent window is not yet shown, that is if the messagedialog is to be shown during the instantiation of a class (your class, not the "parent" window class). During this time Gtk has not yet placed the window, even if code for messagedialog is after the code that shows the window. Which means your dialog box will be somehow "parentless" and the message dialog will appear wherever it likes...
My naive solution for that problem...
GObject.timeout_add(interval=50, function=self.stupid_dialog_1)
and
def stupid_dialog_1(self):
par = self.gui.get_widget('your_parent_window')
msg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK, parent=par)
# do anything here...
return False #stop the timer...

Categories