Suppose I have a lovely window full of tkinter widgets all set with a function. One of these many widgets is a button. When this button is pressed, I want to 'move on to the next screen'. The next screen is in another function(including all the widgets I want to appear on that screen). I have tried to simply run the next procedure from the button, but If it does run correctly, it only adds the widgets to the existing window, and you end up with both screen#1 and screen#2 jumbled together. I have a feeling I need to use destroy, but I'm not sure how to do such, as the only way I could come up with was to group all the widgets in window 1 together in a frame, and destroy it, but I cant get access to destroy the frame from within function #2, as its a variable only within function/window #1. Sorry if that's confusing, The other option is the source, but there's a ton of widgets and other windows in progress which leads me to believe that would be even more confusing.
The simplest thing is to have your function create a single frame, and then place all of the widgets in that frame. The frame can then be placed in the main window such that it fills the whole window. Then, to delete everything you simply need to delete that one frame.
Another way to "move on to the next screen" is to use this same method, but create all of the frames ahead of time. You can stack these frames on top of each other, and use lift and/or lower to determine which one is on top. The one on top will obscure the ones below.
For an example of stacking, see Switch between two frames in tkinter
As for the problem of frame2 not knowing how to destroy frame1, you simply need to pass in a reference to the existing frame when creating a new frame, or pass in a reference to a "controller" - a function that knows about all the frames. You then ask the controller to delete the current frame, and the controller will know what the current frame is.
A button calling a function that deletes all existing frames and rebuilds another sounds like a design flaw. The propensity for errors (forgetting to delete certain elements in some places of the code etc) is pretty large.
If you don't have an insane number of UI elements, I suggest creating them all at once, and hiding/showing various elements as necessary.
Take a look at this SO answer for how you might go about creating GUI elements that can be shown/hidden, and how the callback function might look.
Edit: If you really need to do it based on these functions, then I guess an alternative approach might be this:
Say 'top_frame' is the frame that includes all your widgets which you want to destroy when you run function #2. Change all of your GUI elements in function #1 so that when you create them, you explicitly pass them top_frame so that they have a link to it (self.top_frame = top_frame). This means your button will also have an attribute self.top_frame. You pass that as one of the arguments to function #2, and function #2 now can refer to top_frame and destroy it.
But definitely prone to error and probably slower due to all the creation/destruction of GUI elements. I recommend going through the code in the answer above when you have the time, it really is a much better solution.
Related
I am currently working on a project using Python and tkinter.
The problem is that I don't know what's the proper way to display multiple windows, or screens, I don't know how to call them. Let me explain better.
When the application starts the login screen appears. After that, if I click register, I want to go to the register screen, but I don't want it to be a separate window (I don't want to have 2 windows displayed at the same time), but rather another window with different content ?!
How should I handle properly this situation? Create a second window using Toplevel and hiding the first (can I do that?) or changing the widgets of the first?
Code I've written so far
You can do that- just call window.withdraw() on the Toplevel you need to hide after creating a new Toplevel. Changing the widgets in the first is also an option- if you like, you could always try a Notebook widget and disable manual flipping or just put each "screen" in a frame and grid_ or pack_forget them to remove them from the window.
I must be missing something obvious, I have two frames in my Tkinter program, each with a bunch of labels in a grid layout. I want to bind the mouseclick to one of them but not the other. I currently use
root.bind("<Button-1>", mouse_function)
but that also triggers if I click in the other frame. I assumed that using
schedule_frame.bind("<Button-1>", mouse_function)
would work but then I get no response anywhere.
The function I am calling is:
def mouse_function(event):
y = event.widget.grid_info()['row']
x = event.widget.grid_info()['column']
widgets[(y, x)].configure(state="active")
shiftSelection(y,x)
When you bind to the root window, that binding applies to all widgets in that root window. That is why it triggered for either frame. This is standard behavior for tkinter.
When you move the binding to the frame, it stopped working because the frame never saw the event. When you click on the label, it is the label that sees the binding, not the frame (unless you click in the space between labels)
There are at least three ways to solve this problem. One is that you can put the binding on the labels rather than the frame. Another is to keep the binding on the root window, but within the function check to see if the widget is a child of that one frame.
A third solution involves changing the bind tags for the labels. For an in depth example see this answer: https://stackoverflow.com/a/32771893/7432
EDIT: I debugged a little more to realize, that most of the time consumed by action is actually spent creating the widgets, not placing them into sizer. So the question is how to create them faster?
I need to dynamically change the contents of a dialog by adding circa 300 checkboxes.
I use a wx.GridSizer and a loop that adds them, but it is really slow. Is there a way to do it faster? Some method that waits for all widgets to be added and then calculate the positions and sizes manually since I believe that by calling Add method of a Sizer calculates the sizes immediately.
This is a shortcut of what I do:
... #Destroy all widgets we used to have
for el in self.elements:
_chk = wx.CheckBox(self,-1,el["name"])
_t1 = wx.StaticText(self,-1,el["age"])
_t2 = wx.StaticText(self,-1,el["city"])
self.checkboxes.append(_chk)
self.gridSizer.Add(_chk)
self.gridSizer.Add(_t1)
self.gridSizer.Add(_t2)
self.SetSizer(self.gridSizer)
self.Layout()
self.Refresh()
self.Update()
It takes about a minute to add some 300 elements, I believe there must be a faster way
since e.g. apps like QIP or ICQ displayed lots of contacts in a second :)
Thanks for any clue!
Additionally excuse my english.
Ray
I cannot think of any interface that has 300 widgets on it all at once. I don't believe there is a good way to accomplish this. Instead, you should think about redesigning the interface so the user doesn't have to deal with checking 300 checkboxes (or deal with hundreds of any widget). The only widget that might be able to handle that many checkboxes at once would probably be the ListCtrl with the checkbox mixin. See the demo for an example.
I am trying to write a text viewer widget with PyGTK that displays line numbers alongside the main viewing window. Of course I want the line numbers and main window to scroll in sync with each other. I can't figure out how to get this to work, though. Right now I am doing this. TextViewer is a subclass of HBox that creates the two TextViews and packs them into itself under the attribute names linenums and mainview.
self.textviewer = TextViewer.TextViewer(self.toplevel)
sw = gtk.ScrolledWindow()
sw.set_vadjustment(self.textviewer.mainview.get_vadjustment())
sw.set_hadjustment(self.textviewer.mainview.get_hadjustment())
sw.add_with_viewport(self.textviewer)
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
If I take out the two set_Xadjustment lines, then the embedded TextViews' scroll_to_mark function doesn't work, which isn't acceptable for my application. With them in, the main text window scrolls twice as quickly as the line number window, and vice versa if I set the ScrolledWindow's adjustments to those of self.textviewer.linenums. I strongly suspect that this is a bug. I also tried setting up the viewport myself and setting its adjustments to those of one of the TextViews, but again the scroll_to_mark functions stop working. How can I synchronize both TextViews to scroll as one, so that any scrolling changes to one of them equally affect the other?
EDIT: Here is the code in my main application where I set up the widget.
self.textviewer = TextViewer.TextViewer(self.toplevel)
sw = gtk.ScrolledWindow()
#These are the lines that toggle between the two problems when (un)commented
sw.set_vadjustment(self.textviewer.mainview.get_vadjustment())
sw.set_hadjustment(self.textviewer.mainview.get_hadjustment())
sw.add_with_viewport(self.textviewer)
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
I'm having difficulty understanding exactly how you've got the two gtk.TextViews packed in the HBox. Are they both packed in separate gtk.ScrolledWindow that are then packed inside of the HBox which is then packed into another gtk.ScrolledWindow(The one mentioned in your post)? From what it sounds like to me, both of these gtk.TextViews are packed in their own gtk.ScrolledWindow within your TextViewer wrapper widget.
If this is the case, a simple solution to your issue, granted that the two gtk.TextViews are the same height(so the line numbers line up with the main view), I suggest simply packing them inside your Hbox without ScrolledWindows. Then you can use your code above, adding them that ScrolledWindow and the viewport will move the two collectively as if they are one widget.
If this isn't your issue, could you please supply some more information about your TextViewer wrapper, and maybe some more sample code?
Also: You may be interested in gtksourceview. With the gtksourceview2 package, you get an instance of the View widget:
import gtksourceview2
view = gtksourceview2.View()
You might want to check how it is implemented in Meld. In particular, the filediff code (search search for sync there).
I'm new to Python and I'm trying to create a simple GUI using Tkinter.
So often in many user interfaces, hitting the tab button will change the focus from one Text widget to another. Whenever I'm in a Text widget, tab only indents the text cursor.
Does anyone know if this is configurable?
This is very easy to do with Tkinter.
There are a couple of things that have to happen to make this work. First, you need to make sure that the standard behavior doesn't happen. That is, you don't want tab to both insert a tab and move focus to the next widget. By default events are processed by a specific widget prior to where the standard behavior occurs (typically in class bindings). Tk has a simple built-in mechanism to stop events from further processing.
Second, you need to make sure you send focus to the appropriate widget. There is built-in support for determining what the next widget is.
For example:
def focus_next_window(event):
event.widget.tk_focusNext().focus()
return("break")
text_widget=Text(...)
text_widget.bind("<Tab>", focus_next_window)
Important points about this code:
The method tk_focusNext() returns the next widget in the keyboard traversal hierarchy.
the method focus() sets the focus to that widget
returning "break" is critical in that it prevents the class binding from firing. It is this class binding that inserts the tab character, which you don't want.
If you want this behavior for all text widgets in an application you can use the bind_class() method instead of bind() to make this binding affect all text widgets.
You can also have the binding send focus to a very specific widget but I recommend sticking with the default traversal order, then make sure the traversal order is correct.
It is really simple in PyQt4 simply use this one single line below and you will be able to change focus by pressing tab button:
self.textEdit.setTabChangesFocus(True)
The focus traversal is somewhat customizable, usually letting the X windows manager handle it (with focus follows mouse, or click). According to the manual it should be possible to bind an event to the key press event, for tab presses, and triggering a focusNext event in those cases.