wxPython: Data binding with controls - python-2.7

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.

Related

Weird tab traversal behavior in wxPython custom control

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)
...

Multiple Windows in Tkinter (Python 2.7)

So, for an assignment that I am supposed to do for my programming course, I ran into a bit of an issue. We never really went through how to create child windows // secondary windows, and for some reason the things I add in the first window are not showing up?...
So far my code looks like:
from Tkinter import*
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.button1 = Button(self, text="Create new window", command=self.New_Window)
self.button1.pack(fill=BOTH)
def New_Window(self):
win = Toplevel(self)
win.title("New Window")
etiquette1 = Label(root, text = "Text shenanigans")
etiquette1.pack()
if __name__ == "__main__":
root = Tk()
main = Window(root)
main.mainloop()
Also, another question is how can I name the first window? It's just called "tk" by default, but if I try to do something like the example below, it does not work.
self.__init__.title("Main Window")
The first argument when creating widgets is the "parent" -- the widget into which the new widget goes. If you want a label to be in the Toplevel, make the Toplevel be the parent:
etiquette1 = Label(win, ...)
To set the title of the window, call the title method:
root.title("This is the root window")
...
win.title("This is the second window")

Remove the QItemDelegate from QTableView

I have the custom item delegate set for one column for QTableView. In some cases I need to remove it (i.e. set default item delegate). But it seems that QT does not allow this. The old delegate is used even after setting the new one.
According to QT documentation for QItemDelegate all handling should be done in the same delegate, but this may bring to performance issues. Is there any way to remove/reset to default the item delegate for QTableView.
I tried it in PyQt5 (sorry, im not able to write C++). I could set the standard itemGelegate to the view and then set a custom itemDelegate to one column. By using the „clicked“-signal i could replace the custom delegate by the standard itemDelegate for this column and vice versa.
This is my code, perhaps it helps:
import sys
from PyQt5 import QtGui, QtCore, QtWidgets
class MyDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self):
QtWidgets.QStyledItemDelegate.__init__(self)
self.AlignmentFlag = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter
self.abstand = 2
def paint(self, painter, item, index):
rahmen = item.rect.adjusted(self.abstand, self.abstand, -self.abstand, -self.abstand)
eintrag = index.data()
painter.save()
painter.drawText(rahmen,self.AlignmentFlag, eintrag)
painter.restore()
class MyModel(QtCore.QAbstractTableModel):
def __init__(self,):
QtCore.QAbstractTableModel.__init__(self)
self.items = [['a0','a1','a2','a3','a4'],['b0','b1','b2','b3','b4'],['c0','c1','c2','c3','c4']]
def columnCount(self,items):
cc = len(self.items[0])
return cc
def rowCount(self,items):
rc = len(self.items)
return rc
def data(self, index, role=2):
return self.items[index.row()][index.column()]
class MyWidget(QtWidgets.QTableView):
def __init__(self):
QtWidgets.QTableView.__init__(self)
self.setModel(MyModel())
self.setGeometry(200,200,530,120)
self.delegate_1 = MyDelegate()
self.delegate_2 = QtWidgets.QStyledItemDelegate()
self.setItemDelegate(self.delegate_2)
self.setItemDelegateForColumn(0,self.delegate_1)
self.clicked.connect(self.changeDelegate)
def changeDelegate(self,index):
if index.column() == 0:
delegate_new = self.delegate_2 if self.itemDelegateForColumn(index.column()) == self.delegate_1 else self.delegate_1
self.setItemDelegateForColumn(index.column(),delegate_new)
else:
pass
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())

Events with QGraphicsItemGroup

In my application I want to use QGraphicsItemGroup for grouping items into one item.
I played with it a little and not sure using it because when I want to catch events, events are merged together but I want to handle specific event with specific child.
How can I achieve this?
You need to call QGraphicsItemGroup::setHandlesChildEvents(false). This stops the QGraphicsItemGroup trying to handle the event, and lets the child QGraphicsItems handle them instead.
I think that's the point of the QGraphicsItemGroup. Judging from the documentation, this is meant to simplify moving and transforming multiple items at once e.g. imagine the following case: a user draws a selection rectangle around several items in an application because he wants to move all of them. Perhaps what you want more is to create a hierarchy of items, e.g. have one parent item with several child items. This way you'll get the individual events for each item. This can be accomplished by calling QGraphicsItem::setParentItem();
The question doesn't specify which version of Qt is concerned and there is a correct answer for Qt4. Here is an answer for Qt5 (it works for PyQt5 and I guess it'll work also in C++). The solution was to reimplement sceneEvent, reimplementing a specialized event-catcher such as contextMenuEvent was not sufficient.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtWidgets
class GraphicsItem(QtWidgets.QGraphicsItem):
def __init__(self,
rect: QtCore.QRectF,
name: str,
parent: QtWidgets.QGraphicsItem = None):
super().__init__(parent)
self._name = name
self._rect = rect
def boundingRect(self):
return self._rect
def paint(self,
painter: QtGui.QPainter,
option: QtWidgets.QStyleOptionGraphicsItem,
widget: QtWidgets.QWidget = None):
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.red))
painter.drawRect(self._rect)
def sceneEvent(self, event: QtCore.QEvent):
if (event.type() == QtCore.QEvent.GraphicsSceneContextMenu):
self.contextMenuEvent(event)
event.accept()
return True
def contextMenuEvent(self, event: QtWidgets.QGraphicsSceneContextMenuEvent):
print(f'contextMenuEvent in "{self._name}"')
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._scene = QtWidgets.QGraphicsScene()
layout = QtWidgets.QHBoxLayout()
self._view = QtWidgets.QGraphicsView(self._scene)
layout.addWidget(self._view)
self._widget = QtWidgets.QWidget()
self._widget.setLayout(layout)
group = QtWidgets.QGraphicsItemGroup()
self._scene.addItem(group)
scene_item = GraphicsItem(QtCore.QRectF(0, 0, 100, 100), 'in scene')
self._scene.addItem(scene_item)
group_item = GraphicsItem(QtCore.QRectF(150, 0, 100, 100), 'in group')
group.addToGroup(group_item)
group_item = GraphicsItem(QtCore.QRectF(300, 0, 100, 100), '2nd in group')
group.addToGroup(group_item)
self.setCentralWidget(self._widget)
self.setWindowTitle('contextMenuEvent with QGraphicsItemGroup')
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.setGeometry(100, 100, 800, 500)
mainWindow.show()
sys.exit(app.exec_())

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.