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).
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.
So I've been working on a group project, some of use used pack and others used grid as a layout manager, I'm making the part of the application that puts everyones code together.
I've been working on a UI using pack, and what I want it to do is when I click on a button, a new tk.Tk() window is launched which then runs its code that is managed by grid.
Here is a snipped of the code to try and show you what I'm doing, I keep getting the error "cannot use the geometry manager grid inside . which already has slaves managed by pack"
def launchQuest(self, questType):
if(questType == "ham"):
ham = tk.Tk()
ham.configure(background='white')
app = HM(ham)
ham.mainloop()
If you need to see more code just ask, the whole class is around 400 lines so far but I don't think it is relevant.
Any help would be great!
Thanks!
Based on my first comment above, the answer is:
There should be only one Tk() root window. If you want other windows,
use Toplevel widget.
Only one type of positioning (grid, pack, or place) can be used at a time, within a container. Tk() gives you a window (Toplevel) which you use to contain other widgets, some of which can be containers themselves, like Frame, for example. You can pack two frames into a window, but you could not pack one frame and place another into the same window. This limitation only applies one level deep – you could place a frame, and then pack a frame inside that, and then grid inside that, if you wanted. It doesn't matter what method was used to position the container, only at the level of things directly contained by that container.
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.
I am writing a simple application and am using glade (gtk) for the UI. I need many windows (~10), of which one will open depending upon the command line flags, other contextual stuff etc.
Now, all these windows are pretty much similar, they have 3 top level tabs, the last tab is the same in all, all have a OK and Quit button etc., so I am looking for a way to build these windows in glade. I could copy paste one window and make the changes in that, but I am looking for a better way, that will allow me to reuse the common parts of the windows.
Also, I am using pygtk for loading up the windows.
Design a widget with the common aspects you mention. Wherever you need to implement something different, put a GtkAlignment with an appropriate name. Don't forget to change the alignment and fill values of the GtkAlignment.
In PyGTK you can gtk.Builder.get_object(name) to get access to these empty regions and add the extra components within them (which can also be designed with Glade).
Ok, with the help of detly's answer, I am able to get something working. For anyone who needs it, here is what I did.
main.glade contains the window and all the common cruft that I need to be displayed in all windows. comp.glade contains a window, with a vbox component with the extra stuff I need, lets call it 'top_comp'.
Now, in main.glade, I put a gtk.Alignment component in the place where I need the extra component to load, and call it, say, 'comp_holder'. With the builder I have, I do
builder = gtk.Builder()
builder.add_from_file('main.glade'))
builder.add_from_file('comp.glade'))
builder.get_object('top_comp').reparent(builder.get_object('comp_holder'))
This method seems to work for now, but I don't know if it is the correct way to do this thing.
Any suggestions for the above welcome.
I am trying to make several different pages where I need to show different texts and buttons.
What I did was I created a panel, and then several sizers on it, and then in the next page, I destroyed the panel and recreated the panel again with different contents/sizers.
It worked well in Linux, but when I tried the same source in the windows, the first page was okay, but in the second page and onward, it seems the sizers were not applied.
I tried various .Update() and .Refresh(), but nothing seems working.
It seems only when I maximize the window the sizers get applied and the layout becomes normal.
(Again, after panel.Destory() and a new panel generation, the layouts are messed up again.)
How do I make two different "pages" (where I click on a button and it goes to the second page) with different contents and sizers in Windows?
Calling Layout on the widget's parent is the best way to do this is you are adding or destroying widgets. Sometimes you also need to call Refresh() to make it redraw too, although that might only be required when you're using Freeze/Thaw.
It seems there are better ways to do this, but panel.Layout() solved the problem for now. :)
I agree with using Layout(), but might I suggest just hiding the unused panel instead of destroying it? Using the Show()/Hide() functions of the sizer, you can add both side-by-side and just hide the unused panel instead of destryong it and recreating it each time?