expand wxWrapSizer on secondary direction when adding new items - user-interface

I have a panel that is a list of buttons. The number of buttons changes during runtime (due to user action elsewhere). I use a wxWrapSizer to manage these button since I want the height of this panel to remain the same and create a second column of buttons when it runs out of vertical space. The height is managed by the parent sizer based on the other widgets height. This almost works fine but the second column of buttons does not appear until after the window is manually resized.
I have created a minimal example to reproduce the issue:
import wx
start_buttons = 5
class ButtonsPanel(wx.Panel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.SetSizer(wx.WrapSizer(wx.VERTICAL))
for i in range(start_buttons):
self.add_button()
def add_button(self):
self.GetSizer().Add(wx.Button(self, label='foo'),
wx.SizerFlags().Expand())
class MyPanel(wx.Panel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# hardcoded size for sake of example only
add_button = wx.Button(self, label="add", size=(80, 250))
add_button.Bind(wx.EVT_BUTTON, self.OnAddButton)
self.buttons_panel = ButtonsPanel(self)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(add_button)
sizer.Add(self.buttons_panel, wx.SizerFlags().Expand())
self.SetSizer(sizer)
def OnAddButton(self, evt):
self.buttons_panel.add_button()
self.buttons_panel.Layout()
class MyFrame(wx.Frame):
def __init__(self, *args):
super().__init__(*args)
panel = MyPanel(self)
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
Clicking the large "Add" button will add new "foo" buttons but then it stops once it reaches the bottom of the frame. Manually resizing the frame will make the hidden second column appear.

Perform a Layout() of the main panel's sizer rather than the buttons panel sizer.
As the buttons panel's sizer "lives" inside the main panel's sizer, this is the one that needs to be recalculated, as it will recalculate it's children.
def OnAddButton(self, evt):
self.buttons_panel.add_button()
#self.buttons_panel.Layout()
self.Layout()
Edit:
for more complex setups you may need to note the parentor grandparent and update from within the buttonpanel i.e.
import wx
start_buttons = 5
class ButtonsPanel(wx.Panel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.SetSizer(wx.WrapSizer(wx.VERTICAL))
self.parent = parent
for i in range(start_buttons):
self.add_button()
def add_button(self):
self.GetSizer().Add(wx.Button(self, label='foo'),
wx.SizerFlags().Expand())
self.parent.Layout()
class MyPanel(wx.Panel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# hardcoded size for sake of example only
add_button = wx.Button(self, label="add", size=(80, 250))
add_button.Bind(wx.EVT_BUTTON, self.OnAddButton)
self.buttons_panel = ButtonsPanel(self)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(add_button)
sizer.Add(self.buttons_panel, wx.SizerFlags().Expand())
self.SetSizer(sizer)
def OnAddButton(self, evt):
self.buttons_panel.add_button()
#self.buttons_panel.Layout()
class MyFrame(wx.Frame):
def __init__(self, *args):
super().__init__(*args)
panel = MyPanel(self)
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
If it gets truly hellish, you could use pubsub to fire a Layout in the right place.

Related

Frame.__init__(self, window) vs. super().__init__()

Is there any differences between the following two classes?
from tkinter import *
class MyFrame1(Frame):
def __init__(self, window):
super().__init__(window)
class MyFrame2(Frame):
def __init__(self, window):
Frame.__init__(self, window)
I think it is a matter of choice. Am I right?

How to filter by no extension in QFileSystemModel

The following code runs (after importing necessary libraries):
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MainWindow')
self.layout = QHBoxLayout()
self.file_system_widget = FileBrowser()
self.layout.addWidget(self.file_system_widget)
widget = QWidget()
widget.setLayout(self.layout)
self.setCentralWidget(widget)
class FileBrowser(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
layout = QVBoxLayout()
self.model = QFileSystemModel()
self.model.setRootPath(self.model.myComputer())
self.model.setNameFilters(['*.*'])
self.model.setNameFilterDisables(1)
self.tree = QTreeView()
self.tree.setModel(self.model)
self.tree.setAnimated(False)
self.tree.setSortingEnabled(True)
layout.addWidget(self.tree)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
try:
os.mkdir('Imports')
except:
pass
main = MainWindow()
main.show()
app.exec()
It gives the following result on my C drive:
(Some of these files are provided here https://drive.google.com/drive/folders/1ejY0CjfEwS6SGS2qe_uRX2JvlruMKvPX).
My objective is to modify the line self.model.setNameFilters(['*.*']) such that, in the tree view, it only shows files with dcm extension and also files without extension. That is, the part I draw red gets removed.
How do I achieve such a goal? For keeping dcm files, I can write lines like self.model.setNameFilters(['*.dcm']) to keep them and remove the others. But I am not sure how to deal with files without extension or how to deal with the two requirements at the same time .
The QFileSystemModel class only supports basic wildcard filtering, so you will need to use a QSortFilterProxyModel to get fully customisable filtering. This will allow you to use a regular expression to do the filtering, which achieves most of what you want quite simply. However, reproducing the behaviour of setNameFilterDisables will require a reimplemention of the flags method of the proxy model, and the sorting will also need some adjustment.
Below is a simple demo based on your example that implements all of that:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class FilterProxy(QSortFilterProxyModel):
def __init__(self, disables=False, parent=None):
super().__init__(parent)
self._disables = bool(disables)
def filterAcceptsRow(self, row, parent):
index = self.sourceModel().index(row, 0, parent)
if not self._disables:
return self.matchIndex(index)
return index.isValid()
def matchIndex(self, index):
return (self.sourceModel().isDir(index) or
super().filterAcceptsRow(index.row(), index.parent()))
def flags(self, index):
flags = super().flags(index)
if (self._disables and
not self.matchIndex(self.mapToSource(index))):
flags &= ~Qt.ItemIsEnabled
return flags
class FileBrowser(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
layout = QVBoxLayout()
self.model = QFileSystemModel()
self.model.setFilter(
QDir.AllDirs | QDir.AllEntries | QDir.NoDotAndDotDot)
self.proxy = FilterProxy(True, self)
self.proxy.setFilterRegularExpression(r'^(.*\.dcm|[^.]+)$')
self.proxy.setSourceModel(self.model)
self.tree = QTreeView()
self.tree.setModel(self.proxy)
self.tree.setAnimated(False)
header = self.tree.header()
header.setSectionsClickable(True)
header.setSortIndicatorShown(True)
header.setSortIndicator(0, Qt.AscendingOrder)
header.sortIndicatorChanged.connect(self.model.sort)
self.model.setRootPath(self.model.myComputer())
root = self.model.index(self.model.rootPath())
self.tree.setRootIndex(self.proxy.mapFromSource(root))
layout.addWidget(self.tree)
self.setLayout(layout)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MainWindow')
self.layout = QHBoxLayout()
self.file_system_widget = FileBrowser()
self.layout.addWidget(self.file_system_widget)
widget = QWidget()
widget.setLayout(self.layout)
self.setCentralWidget(widget)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
app.exec()

Adding methods in __init__() in Python

I'm making classes which are similar, but with different functions, depending on the use of the class.
class Cup:
def __init__(self, content):
self.content = content
def spill(self):
print(f"The {self.content} was spilled.")
def drink(self):
print(f"You drank the {self.content}.")
Coffee = Cup("coffee")
Coffee.spill()
> The coffee was spilled.
However, it is known during the initialization of an object whether or not the cup will be spilled or drank. If there are many cups, there's no need for all of them to have both functions, because only one of them will be used. How can I add a function during initialization?
Intuitively it should be something like this, but this apparently didn't work:
def spill(self):
print(f"The {self.content} was spilled.")
class Cup:
def __init__(self, content, function):
self.content = content
self.function = function
Coffee = Cup("coffee", spill)
Coffee.function()
> The coffee was spilled
If you create a class in Python with methods e.g.
class A
def method(self, param1, param)
It will make sure that when you call A().method(x,y) it fill the self parameter with instance of A. When you try specify method yourself outside of the class then you have to also make sure that the binding is done properly.
import functools
class Cup:
def __init__(self, content, function):
self.content = content
self.function = functools.partial(function, self)

Making closeEvent() work outside of a class in PySide

Seemingly the closeEvent method in the following code is called upon clicking x (in the top right corner of the window). I'm guessing the information that allows python to make that connection is inside the self argument.
Is there a way to implement this into a procedural or functional program.
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Message box')
self.show()
def closeEvent(self, event):
reply = QtGui.QMessageBox.question(self, 'Message',
"Are you sure to quit?", QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
event.accept()
else:
event.ignore()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Apparently, you can assign your own function to the QWidget.closeEvent function (property), given that you take the automatically passed in instance argument and event into account:
def myHandler(widget_inst, event):
print("Handling closeEvent")
mywidget = QWidget()
mywidget.closeEvent = myHandler
This is going to get tedious and is not the way things were intended to be done.

Django form __init__() got multiple values for keyword argument

Hello, I'm trying to use a modified __init__ form method, but I am encountering the following error:
TypeError
__init__() got multiple values for keyword argument 'vUserProfile'
I need to pass UserProfile to my form, to get to dbname field, and I think this is a solution (my form code):
class ClienteForm(ModelForm):
class Meta:
model = Cliente
def __init__(self, vUserProfile, *args, **kwargs):
super(ClienteForm, self).__init__(*args, **kwargs)
self.fields["idcidade"].queryset = Cidade.objects.using(vUserProfile.dbname).all()
Calls to constructor ClienteForm() without POST are successful and show me the correct form. But when the form is submitted and the constructor is called with POST, I get the previously described error.
You've changed the signature of the form's __init__ method so that vUserProfile is the first argument. But here:
formPessoa = ClienteForm(request.POST, instance=cliente, vUserProfile=profile)
you pass request.POST as the first argument - except that this will be interpreted as vUserProfile. And then you also try to pass vUserProfile as a keyword arg.
Really, you should avoid changing the method signature, and just get the new data from kwargs:
def __init__(self, *args, **kwargs):
vUserProfile = kwargs.pop('vUserProfile', None)
For the help of those others who Google to here: the error comes from init picking up the argument from both a positional argument and the default argument. Daniel Roseman's is accurate for the question as asked.
This can be either:
You put the argument by position and then by keyword:
class C():
def __init__(self, arg): ...
x = C(1, arg=2) # you passed arg twice!
You forgot to put self as the first argument:
class C():
def __init__(arg): ...
x = C(arg=1) # but a position argument (for self) is automatically
# added by __new__()!
I think this is the case with ModelForm, but need to check. For me, the solution was:
def __init__(self, *args, **kwargs):
self.vUserProfile = kwargs.get('vUserProfile', None)
del kwargs['vUserProfile']
super(ClienteForm, self).__init__(*args, **kwargs)
self.fields["idcidade"].queryset = Cidade.objects.using(self.vUserProfile.dbname).all()

Resources