I'm writing a document based application in wxPython, by which I mean that the user can have open multiple documents at once in multiple windows or tabs. There are multiple kinds of documents, and the documents can all be in different "states", meaning that there should be different menu options available in the main menu.
I know how to disable and enable menu items using the wx.EVT_UPDATE_UI event, but I can't figure out how to pull off a main menu that changes structure and content drastically based on which document that currently has focus. One of my main issues is that the main menu is created in the top level window, and it has to invoke methods in grand children and great grand children that haven't even been created yet.
Contrived example; when a document of type "JPEG" is open, the main menu should look like:
File Edit Compression Help
And when the user switches focus (CTRL+Tab) to a document of type "PDF", the main menu should change to:
File Edit PDF Publish Help
And the "Edit" menu should contain some different options from when the "JPEG" document was in focus.
Current I'm just creating the menu in a function called create_main_menu in the top level window, and the document panels have no control over it. What would be necessary to pull off the kind of main menu scheme I describe above, specifically in wxPython?
I've figured out a pretty clean way to do this. First of all I create my "base" main menu bar, which contains the File and Help menu items. Then I defined a class EditorPanel than is a subclass of wx.Panel and defined the methods bind_main_menu_bar and release_main_menu_bar. The first of those methods receives the main menu bar when the panel is focused, and adds some items to it. Here is one of my implementations:
def bind_main_menu_bar(self, main_menu_bar):
main_frame = wx.GetApp().GetTopWindow()
self.main_menu_bar = main_menu_bar
# Create the edit menu.
self.edit_menu = edit_menu = shared.create_menu([
(const.ID_UNDO, self.LABEL_UNDO_EMPTY),
(const.ID_REDO, self.LABEL_REDO_EMPTY)
])
# Create the tools menu.
self.tools_menu = tools_menu = shared.create_menu([
(const.ID_SELECT_ADDRESS_COLUMNS, 'Select address columns...'),
(),
(const.ID_VALIDATE_ADDRESSES, 'Validate selected addresses'),
(const.ID_VALIDATE_ALL_ADDRESSES, 'Validate all addresses')
])
# Bind some menu event handlers to the main frame.
main_frame.Bind(wx.EVT_MENU, self.on_menu)
main_frame.Bind(wx.EVT_UPDATE_UI, self.on_menu_update)
# Insert the new menus into the main menu bar.
main_menu_bar.Insert(1, edit_menu, 'Edit')
main_menu_bar.Insert(2, tools_menu, 'Tools')
Now, when that editor panel is opened, the main menu receives an Edit menu and a Tools menu that is bound to event handlers in the EditorPanel, which is incredibly handy. When the editor loses focus, the release_main_menu_bar method is called, which should restore the main menu bar to it's original state. This is counterpart of the code above:
def release_main_menu_bar(self):
main_frame = wx.GetApp().GetTopWindow()
# Unbind the menu event handlers from the main frame.
main_frame.Unbind(wx.EVT_MENU, handler=self.on_menu)
main_frame.Unbind(wx.EVT_UPDATE_UI, handler=self.on_menu_update)
# Remove the edit and tools menu from the main menu bar.
self.main_menu_bar.Remove(1)
self.main_menu_bar.Remove(1)
# Reset the fields used for the menu.
self.edit_menu = None
self.tools_menu = None
self.main_menu_bar = None
So every editor that wants to edit the main menu just has to subclass those two methods and they have full control. The main frame will monitor when the user switches between editors and call the methods accordingly. The biggest problem was to figure out when the editor panel receives and loses focus, which is the topic of another question of mine: How do I monitor when wx.Panel receives and loses focus?
Probably the only way to do it with the standard wx.Menu is to destroy and recreate the entire menubar. You might be able to Hide it though. Either way, I think it would be easiest to just put together a set of methods that creates each menubar on demand. Then you can destroy one and create the other.
You might also take a look at FlatMenu since it is pure Python and easier to hack.
Related
I am building a Qt application on Linux. I have a menu bar in the main window with two menus in it, each with several actions, all of which have keyboard shortcuts associated with them. The keyboard shortcuts work when the menus are not open, but when one of the menus is open, none of them work.
The shortcuts were added to the actions with setShortcut prior to the actions being added to their respective menus with [menuobject]->addAction. All the actions have the main window as their parent. After reading QAction shortcut doesnt always work I added calls to addAction, adding the action to the main window. This did not correct the problem.
Example of the code for one of the menu items:
//In the main window constructor
gameQuit = new QAction(QString(tr("&Quit\tCtrl+Q")), this);
gameQuit->setShortcut(QKeySequence(Qt::Key_Q | Qt::CTRL));
addAction(gameQuit);
connect(gameQuit, SIGNAL(triggered()), this, SLOT(close()));
gameMenu = menuBar()->addMenu(QString(tr("&Game")));
gameMenu->addAction(gameQuit);
In QtCreator, which I assume was written with Qt, the keyboard shortcuts for the menu items do work when the menus are open, so I think there must be a way.
Thanks for any help.
Taking some advice from the comments of the cited post (which had been rebuked, which is why I didn't try it initially), I modified the shortcut context using [actionobject]->setShortcutContext(). Apparently the default does not work in my scenario.
I first tried setting to Qt::WindowShortcut, which didn't work. Qt::ApplicationShortcut did work, however, this may have shortcomings as noted in the comments of the cited post. They don't happen to matter for this particular application of mine though, so I am going to post and accept this as the answer.
Example of the correcting code:
//In the constructor of the main window, after creation of the action and
//setting of the shortcut
gameQuit->setShortcutContext(Qt::ApplicationShortcut);
I have a Python 3.7 tkinter GUI, and within the GUI I have implemented up-down arrow key controls for the main part of the application. Next to it I have a list box that also controls the application but in a different way, and by default AFTER a listbox selection has been made the listbox selection will scroll with up and down arrows. So, after I've used the list box in the app, the arrow key triggers an up arrow event in both the main part of the application and in the list box. This triggers my application to respond in the change in list box selection by loading new data into the main app. This is obviously unacceptable.
How can I disable the arrow key controls feature of tkinter's
ListBox?
I've tried configuring the listbox to not take focus, but this doesn't seem to disable the feature.
Edit:
I solved this by binding the list box's FocusIn event to a function that immediately focus's something else. This is far from ideal, as the code now focus's in then changes focus for no reason. If there is a way to disable focus on a widget completely or disable the list box key bindings that would be a preferred solution.
from tkinter import *
class App:
def __init__(self):
self.root = Tk()
self.dummy_widget = Label()
self.lb = ListBox(master=self.root)
self.lb.bind("<FocusIn>", lambda event: self.dummy_widget.focus())
# Additional setup and packing widgets...
if __name__ == '__main__':
mainloop()
This seems very "hacky", although it does the job perfectly.
How can I disable the arrow key controls feature of tkinter's ListBox?
Create your own bindings for the events you want to override, in the widget in which you want them overridden. Do anything you want in that function (including nothing), and then return the string break, which is the documented way to prevent events from being processed any further.
For a more extensive description of how bindings work, see this answer to the question Basic query regarding bindtags in tkinter. It describes how a character is inserted into an entry widget, but the mechanism is identical for all events and widgets in tkinter.
I wanted to ask if it is possible to draw a child window inside a parent window so that it will only be able to move inside the parent window and won't be able to move out of the parent window bounds.
If you mean having actual windows with title bar, menu, status bar etc. inside the parent window then the answer is:
No, Tcl/Tk and by extension Tkinter does not support this with its standard widgets.
There have been efforts in the past to implement widgets which emulate MDI as you can see on the TCL wiki, but most of them are over a decade old. You will probably have to implement it yourself or choose a different GUI toolkit if you really need to implement this kind of UI design.
If you do it yourself, you can use the Frame widget as the subwindow, and use place to put it in the containing window. Or, you can create it as an object on a canvas. You'll have to write all of the code to give the inner window borders and a title bar, and to manage moving it around, iconifying it, etc.
I have a Qt program with many buttons, user-interactable widgets, etc.
At one stage in the program, I would like all the widgets to temporarily 'stop working'; stop behaving to mouse clicks and instead pass the event on to one function.
(This is so the User can select a widget to perform meta operations. Part explanation here: Get variable name of Qt Widget (for use in Stylesheet)? )
The User would pick a widget (to do stuff with) by clicking it, and of course clicking a button must not cause the button's bound function to run.
What is the correct (most abstracted, sensible) method of doing this?
(which doesn't involve too much new code. ie; not subclassing every widget)
Is there anything in Qt designed for this?
So far, I am able to retrieve a list of all the widgets in the program (by calling
QObject.findChildren(QtGui.QWidget)
so the solution can incorporate this.
My current horrible ideas are;
Some how dealing with all the applications events all the time in one
function and not letting through the events when I need the
application to be dormant.
When I need dormancy, make a new transparent widget which recieves
mouse clicks and stretch it over the entire window. Take coordinates
of click and figure out the widget underneath.
Somehow create a new 'shell' instance of the window.
THANKS!
(Sorry for the terrible write-up; in a slight rush)
python 2.7.2
PyQt4
Windows 7
You can intercept events send to specific widgets with QObject::installEventFilter.
graphite answered this one first so give credit where credit is due.
For an actual example in PySide, here's an example you might draw some useful code from:
my_app.py
from KeyPressEater import KeyPressEater
if __name__ == "__main__":
app = QApplication(sys.argv)
eater = KeyPressEater()
app.installEventFilter(eater)
KeyPressEater.py
class KeyPressEater(QObject):
# subclassing for eventFilter
def eventFilter(self, obj, event):
if self.ignore_input:
# swallow events
pass
else:
# bubble events
return QObject.eventFilter(self,obj,event)
I am using wxPython to create a taskbar menu. The menu contains some menu items (obviously).
Now I would like to update/change some of these items when a particular item is clicked, while still displaying the menu.
How can I prevent the taskbar menu from disappearing after clicking an item?
The only method I've found that could be useful is wxMenu.UpdateUI(), but that doesn't prevent the menu from disappearing.
Although I never got around to trying it myself, I remember attempting a similar effect with a popup menu & textctrl. You might want to consider trying wx.lib.agw.flatmenu.FlatMenuBar, it provides an event handler OnMenuDismissed(self, event), as well as a few others, which by name appear to be what you need. You would need to create your own OnMenuDismissed() and override the event.