Weird tab traversal behavior in wxPython custom control - python-2.7

If you make a custom control as a subclass of wx.PyControl, the tab traversal will behave oddly. For example, with the code below, pressing tab a few times will get you stuck inside MyControl. Once you tab to a "Child of MyControl" textbox, you can only tab between the 2 "Child of MyControl" textboxes, and will never tab back to "Child of Panel".
class MyFrame(wx.Frame):
def __init__(self):
super(MyFrame, self).__init__(None)
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(wx.TextCtrl(panel, value="Child of Panel"), flag=wx.EXPAND)
sizer.AddSpacer(30)
sizer.Add(MyControl(panel), flag=wx.EXPAND)
panel.SetSizer(sizer)
class MyControl(wx.PyControl):
def __init__(self, parent):
super(MyControl, self).__init__(parent, style=wx.BORDER_NONE|wx.TAB_TRAVERSAL)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(wx.TextCtrl(self, value="Child of MyControl"), flag=wx.EXPAND)
sizer.AddSpacer(10)
sizer.Add(wx.TextCtrl(self, value="Child of MyControl"), flag=wx.EXPAND)
self.SetSizer(sizer)

Fixing the tab traversal to behave in a standard way is surprisingly simple. All you have to do is make your custom control subclass wx.Panel instead of wx.PyControl.
class MyControl(wx.Panel):
def __init__(self, parent):
super(MyControl, self).__init__(parent)
...

Related

Close window A by clicking a button on window B

I have window A with some graphs and a button "NEXT". When the user presses the button, window B opens with some questions about the graphs on window A. It also has a button "Done". Once "Done" is pressed, window A, and B close.
Problem: I don't get window A to close and no error messages in the console.
Window A:
class Window_A(QDialog):
def __init__(self, parent = None):
super(Window_A, self).__init__(parent,flags=Qt.WindowMinimizeButtonHint|Qt.WindowCloseButtonHint)
self.setWindowTitle("Window A")
self.initUI()
def initUI(self):
# Load the images
[...]
#"NEXT button"
self.b_next = QPushButton("NEXT")
self.b_next.clicked.connect(self.open_next)
#Layout
[...]
def close_win(self):
self.close()
def open_next(self):
self.next= Window_B()
self.next.show()
Window B:
class Window_B(QDialog):
def __init__(self):
super(Window_B, self).__init__()
self.setWindowTitle("Window_B")
self.initUI()
def initUI(self):
#define text labels
[...]
#done button
self.d_button = QPushButton("Done")
self.d_button.clicked.connect(self.on_click)
self.d_button.clicked.connect(lambda:self.close())
#layout for text (questions)
[...]
def on_click(self):
Window_A().close_win()
In your window_B code, you do not know the reference to the correct window_A object.
So your line:
def on_click(self):
Window_A().close_win()
creates a new Window_A object, calls its close function immediately, and this objects is garbage collected asap.
You will need to pass a reference to A in the B object.
For instance:
in window_A.py
def open_next(self):
self.next= Window_B()
self.next.show()
self.next.ref_to_close_father = self
in window_B.py
def on_click(self):
self.ref_to_close_father.close_win()
This should be better (but your example is not reproducible, so there might be another problem).
By the way,
self.d_button.clicked.connect(lambda:self.close())
could simply be
self.d_button.clicked.connect(self.close)

PyQt 4: Get Position of Toolbar

Hy guys,
in my executable program there is a toolbar. Well, the user decides to move the toolbar. Now the toolbar is floating. I know I have to conntect the floating-signals that is emittted when the toolbar ist arranged by the user. How can I save the new position of the toolbar? I know the method of adding the toolbar to the main window with a position:self.addToolBar( Qt.LeftToolBarArea , toolbar_name). In the handle_floating()-method you see what I want: There I want to get the position currently, but how? You also see I have just added one member variable, named self.toolbar_pos, to hold the position of the toolbar. My idea is, when application is terminated I want to serialize this value to a file, and later, when application is ran again its will read that file and set the toolbar accordingly. But this is no problem. Currently I don't have no idea to get the position of the toolbar.
I need your help :)
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.toolbar_pos = None
self.initUI()
def initUI(self):
exitAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(QtGui.qApp.quit)
self.toolbar = QtGui.QToolBar(self)
self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.addToolBar(self.toolbar )
self.toolbar.addAction(exitAction)
self.toolbar.setAllowedAreas(QtCore.Qt.TopToolBarArea
| QtCore.Qt.BottomToolBarArea
| QtCore.Qt.LeftToolBarArea
| QtCore.Qt.RightToolBarArea)
self.addToolBar( QtCore.Qt.LeftToolBarArea , self.toolbar )
self.toolbar.topLevelChanged.connect(self.handle_floating)
def handle_floating(self, event):
# The topLevel parameter is true
# if the toolbar is now floating
if not event:
# If the toolbar no longer floats,
# then calculate the position where the
# toolbar is located currently.
self.toolbar_pos = None
print "Get position: ?"
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.setGeometry(300, 300, 300, 200)
ex.setWindowTitle('Toolbar example')
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The QMainWindow class already has APIs for this: i.e. saveState and restoreState. These can be used to save and restore the state of all the toolbars and dock-widgets in your application.
To use them, you first need to make sure that all your toolbars and dock-widgets are given a unique object-name when they are created:
class Example(QtGui.QMainWindow):
...
def initUI(self):
...
self.toolbar = QtGui.QToolBar(self)
self.toolbar.setObjectName('foobar')
Then you can override closeEvent to save the state:
class Example(QtGui.QMainWindow):
...
def closeEvent(self, event):
with open('/tmp/test.conf', 'wb') as stream:
stream.write(self.saveState().data())
(NB: I've just used a temporary file here for testing, but it would obviously be much better to use something like QSettings in your real application).
Finally, you can restore the state that was saved previously:
class Example(QtGui.QMainWindow):
def __init__(self, parent=None):
...
self.initUI()
try:
with open('/tmp/test.conf', 'rb') as stream:
self.restoreState(QtCore.QByteArray(stream.read()))
except IOError:
pass

How Can I put a QMdiSubWindow into a QMdiArea QMainWindow?

I'm new in pyqt. I want to do an application with a Qmainwindow with its menubar and menu items. when I click in some menu item A QMdiSubWindow should appear into the qmdiarea, How can I do that?
this is my qmainwindow code:
class Ui_mainForm(QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.resize(928, 695)
self.qareaMdi = QtGui.QWidget(self)
#all the design code...
self.actionFriends.triggered.connect(self.actionFriends_click)
#more code...
def actionFriends_click(self):
#how can I call a qmdisubwindow here?
and this is my QMdiSubWindow code:
class Ui_friendForm(QMdiSubWindow):
def __init__(self):
QtGui.QMdiSubWindow.__init__(self)
self.resize(878, 551)
QtCore.QMetaObject.connectSlotsByName(self)
thanks in advance
Update:
I modified the actionFriends_click function like this:
def actionFriends_click(self):
subwindow_friend = Ui_friendForm()
self.mdiArea.addSubWindow(subwindow_friend)
subwindow_friend.show()
Update 2:
I forgot It. We have to add a reference into Ui_mainForm
from VIEW.friendsForm import Ui_friendForm
In this case the QMdiSubWindows Ui_friendForm class is in the VIEW package.
We have to add a reference into Ui_mainForm (In this case the QMdiSubWindows Ui_friendForm class is in the VIEW package.) and I modified the actionFriends_click function like this:
from VIEW.friendsForm import Ui_friendForm
def actionFriends_click(self):
subwindow_friend = Ui_friendForm()
self.mdiArea.addSubWindow(subwindow_friend)
subwindow_friend.show()

wxPython: Data binding with controls

How can I databind the value self.text in MyModel to the controls in the wx.Frame?
When the text control changes the text I would like the label to change aswell as the contents of the other text control automatically. (ideally without having to write any code if that's possible)
Note: this is a dummy example below to illustrate the general problem which may get more complicated as more fields are added to both the view and the model
import wx
class MyModel(object):
def __init__(self):
self.text = "hello"
class MyRegion(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="My Region")
self.model = MyModel()
self.label = wx.StaticText(self, label=self.model.text)
self.textbox = wx.TextCtrl(self, value=self.model.text)
self.textbox2 = wx.TextCtrl(self, value=self.model.text)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.label, 0, wx.ALL, 5)
sizer.Add(self.textbox, 0, wx.ALL, 5)
sizer.Add(self.textbox2, 0, wx.ALL, 5)
self.SetSizer(sizer)
if __name__ == "__main__":
app = wx.App()
frame = MyRegion(None)
frame.Show()
app.MainLoop()
Is there a data/model-centric approach to this as opposed to having to bind the views events to code so that that code update the model which then triggers some more code which updates the rest?
If there isn't then what mechanisms/recommended approaches are there which would be used to trigger updates to the view once the model has been updated?
Define a handler for wxEVT_UPDATE_UI event for the control whose text you'd like to change and call event.SetText() from it. This allows you to declaratively specify the text that the control should have, without bothering with updating it whenever something else changes -- instead it will be always up to date.

How to make a wxFrame behave like a modal wxDialog object

Is is possible to make a wxFrame object behave like a modal dialog box in that the window creating the wxFrame object stops execution until the wxFrame object exits?
I'm working on a small game and have run into the following problem. I have a main program window that hosts the main application (strategic portion). Occasionally, I need to transfer control to a second window for resolution of part of the game (tactical portion). While in the second window, I want the processing in the first window to stop and wait for completion of the work being done in the second window.
Normally a modal dialog would do the trick but I want the new window to have some functionality that I can't seem to get with a wxDialog, namely a status bar at the bottom and the ability to resize/maximize/minimize the window (this should be possible but doesn't work, see this question How to get the minimize and maximize buttons to appear on a wxDialog object).
As an addition note, I want the second window's functionality needs to stay completely decoupled from the primary window as it will be spun off into a separate program eventually.
Has anyone done this or have any suggestions?
I was also looking from similar solution and have comeup with this solution, create a frame, disable other windows by doing frame.MakeModal() and to stop execution start and event loop after showing frame, and when frame is closed exit the event loop e.g. I here is sample using wxpython but it should be similar in wxwidgets.
import wx
class ModalFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, style=wx.DEFAULT_FRAME_STYLE|wx.STAY_ON_TOP)
btn = wx.Button(self, label="Close me")
btn.Bind(wx.EVT_BUTTON, self.onClose)
self.Bind(wx.EVT_CLOSE, self.onClose) # (Allows main window close to work)
def onClose(self, event):
self.MakeModal(False) # (Re-enables parent window)
self.eventLoop.Exit()
self.Destroy() # (Closes window without recursion errors)
def ShowModal(self):
self.MakeModal(True) # (Explicit call to MakeModal)
self.Show()
# now to stop execution start a event loop
self.eventLoop = wx.EventLoop()
self.eventLoop.Run()
app = wx.PySimpleApp()
frame = wx.Frame(None, title="Test Modal Frame")
btn = wx.Button(frame, label="Open modal frame")
def onclick(event):
modalFrame = ModalFrame(frame, "Modal Frame")
modalFrame.ShowModal()
print "i will get printed after modal close"
btn.Bind(wx.EVT_BUTTON, onclick)
frame.Show()
app.SetTopWindow(frame)
app.MainLoop()
It does not really make sense to "stop execution" of a window, as the window only handles events that are sent to it, like for example mouse, keyboard or paint events, and ignoring them would make the program appear hung. What you should do is disable all controls in your frame, this will gray them out and make the user aware of the fact that they can not be interacted with at this moment.
You can also disable the parent frame completely, instead of disabling all controls on it. Look into the wxWindowDisabler class, the constructor has a parameter to indicate a window that can be interacted with, and all other windows of the application will be disabled.
If you later on want to execute a secondary program, then you could use the wxExecute() function to do it.
This took me an annoying amount of time to figure out but here is a working example that grew out of Anurag's example:
import wx
class ChildFrame(wx.Frame):
''' ChildFrame launched from MainFrame '''
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, -1,
title=self.__class__.__name__,
size=(300,150))
panel = wx.Panel(self, -1)
closeButton = wx.Button(panel, label="Close Me")
self.Bind(wx.EVT_BUTTON, self.__onClose, id=closeButton.GetId())
self.Bind(wx.EVT_CLOSE, self.__onClose) # (Allows frame's title-bar close to work)
self.CenterOnParent()
self.GetParent().Enable(False)
self.Show(True)
self.__eventLoop = wx.EventLoop()
self.__eventLoop.Run()
def __onClose(self, event):
self.GetParent().Enable(True)
self.__eventLoop.Exit()
self.Destroy()
class MainFrame(wx.Frame):
''' Launches ChildFrame when button is clicked. '''
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id,
title=self.__class__.__name__,
size=(400, 300))
panel = wx.Panel(self, -1)
launchButton = wx.Button(panel, label="launch modal window")
self.Bind(wx.EVT_BUTTON, self.__onClick, id=launchButton.GetId())
self.Centre()
self.Show(True)
def __onClick(self, event):
dialog = ChildFrame(self, -1)
print "I am printed by MainFrame and get printed after ChildFrame is closed"
if __name__ == '__main__':
app = wx.App()
frame = MainFrame(None, -1)
frame.Show()
app.MainLoop()
Not sure this is a great answer but it worked.
bool WinApp1::OnInit()
{
if (!wxApp::OnInit())
return false;
SettingsDialog dialog(m_settingsData);
dialog.ShowModal();
return false;
}
SettingsDialog::SettingsDialog(SettingsData& settingsData)
: m_settingsData(settingsData)
{
SetExtraStyle(wxDIALOG_EX_CONTEXTHELP);
wxWindow* parent = nullptr;
Create(parent, wxID_ANY, "Preferences", wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
The WinApp1 window is never given a wxFrame and never paints.