Phoenix
Phoenix copied to clipboard
wx.Panel.SetSize() doesn't resize panel
Operating system: windows 10 64 bit, 200% UI scaling wxPython version & source: 4.1.0 msw (phoenix) wxWidgets 3.1.4 from pip Python version & source: python 3.8.2 64 bit from python.org
Description of the problem: My actual problem is that I had a way to specify size of various elements like that:
class Panel(wx.Panel):
def __init__(self, parent, size=(200, 50)):
super().__init__(parent, size=size)
But now, to support high-DPI displays, I have to perform extra operation on it - self.FromDIP
, so I had to change the code to the following (assuming I want to transform default size, and caller will handle transforming the size on his side, should it wish to specify it):
class PanelSetSize(wx.Panel):
def __init__(self, parent, size=None):
super().__init__(parent)
if size is None:
size = self.FromDIP((200, 50))
self.SetSize(size)
To my surprise, setting size does not work. Here is full snippet which demos that:
import wx
class PanelInitSize(wx.Panel):
def __init__(self, parent):
super().__init__(parent, size=(200, 50))
self.SetBackgroundColour(wx.Colour(100, 0, 0))
class PanelSetSize(wx.Panel):
def __init__(self, parent, size=None):
super().__init__(parent)
if size is None:
size = self.FromDIP((200, 50))
self.SetSize(size)
self.SetBackgroundColour(wx.Colour(0, 100, 0))
class PanelDoubleInit(wx.Panel):
def __init__(self, parent, size=None):
super().__init__(parent)
if size is None:
size = self.FromDIP((200, 50))
super().__init__(parent, size=size)
self.SetBackgroundColour(wx.Colour(0, 0, 100))
class MainFrame(wx.Frame):
def __init__(self):
super().__init__(None, wx.ID_ANY, 'Application')
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(PanelInitSize(self), 0)
sizer.Add(PanelSetSize(self), 0)
sizer.Add(PanelDoubleInit(self), 0)
self.SetSizer(sizer)
self.Layout()
self.Show()
if __name__ == '__main__':
app = wx.App(False)
mf = MainFrame()
app.MainLoop()
PanelInitSize
is shown (the red panel) while PanelSetSize
(should be green panel) is not. I guess, that's a bug and I will let this issue be open for it.
However, personally I would be grateful just for a way to work around it. Why is FromDIP
attached to wx.Window
instance? Is there a chance that it will work one way for one window, and differently for another, or even different instances of those? Can't it be global for whole app, or at least be a class method?
I found only one way to call FromDIP()
and get panel drawn, but it involves two calls to __init__
, which is quite ugly - one call to init the panel to give FromDIP()
valid instance to work against, then do the conversion, then reinit it with new size (the blue panel on the example).
Another way would be calling it from parent objects which have been already initialized, but it is not very convenient.
Similar thing happens to buttons (this is on gtk, but most likely affects windows as well):
import wx
class ButtonInitSize(wx.Button):
def __init__(self, parent):
super().__init__(parent, style=wx.BU_EXACTFIT, label='text', size=(100, 100))
class ButtonSetSize(wx.Button):
def __init__(self, parent):
super().__init__(parent, style=wx.BU_EXACTFIT, label='text')
self.SetSize((100, 100))
class MainFrame(wx.Frame):
def __init__(self):
super().__init__(None, wx.ID_ANY, 'Application')
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(ButtonInitSize(self), 0)
sizer.Add(ButtonSetSize(self), 0)
self.SetSizer(sizer)
self.Layout()
self.Show()
if __name__ == '__main__':
app = wx.App(False)
mf = MainFrame()
app.MainLoop()
It almost feels like I am missing something to somehow "update" windows after updating their size, but I cannot find what exactly.
I know this is an old issue, I have a work around for the problem.
You cannot call SetSize from the constructor. IDK why it is like that but it has been like that for a long while. SetPosition is another I think.
This works. I know it is a band-aide but at least it solves the issue at hand,
import wx
class ButtonInitSize(wx.Button):
def __init__(self, parent):
super().__init__(parent, style=wx.BU_EXACTFIT, label='text', size=(100, 100))
class ButtonSetSize(wx.Button):
def __init__(self, parent):
super().__init__(parent, style=wx.BU_EXACTFIT, label='text')
def _do():
self.SetSize((100, 100))
wx.CalllAfter(_do)
class MainFrame(wx.Frame):
def __init__(self):
super().__init__(None, wx.ID_ANY, 'Application')
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(ButtonInitSize(self), 0)
sizer.Add(ButtonSetSize(self), 0)
self.SetSizer(sizer)
self.Layout()
self.Show()
if __name__ == '__main__':
app = wx.App(False)
mf = MainFrame()
app.MainLoop()
For your use case, please try SetMinSize
instead of SetSize
.
SetSize
does set the size, but a later call to Layout
can change the size again.
In general, different windows can have different DPI if they are on different displays.
P.S.: If you had posted on discuss.wxpython.org instead of opening a ticket, you probably would have received an answer within a day or so.
Setting the minimum size is not is what is wanting to be done. Just want to set the size.
SetMinSize will not allow the panel/control to get any smaller then the specified size. So if a user wanted to shrink it down they would not be able to unless some hackish code was added to reset the min size the first time the user changes the size.
Using CallAfter has it's own set of problems as well especially if the GUI that is being built is extremely populated. I never understood why SetSize could not be called in order to set the starting size instead of having to either pass the size to the constructor, doing what I do with CallAfter or as you suggested setting the minimum size.
The mechanics to set the size should still be in place and functioning even tho the control is not being shown. The control is able to properly adjust size when the size is passed to the constructor so there is no real reason why SetSize shouldn't work.
SetSize does set the size. It's working. But Layout or window resizing will change it again. Think about your use case. Either use sizers or not. Don't expect one approach to work like the other.
Only expectation is calling SetSize and having it do what it is supposed to do and set the size. Not set the size and then reset it back for whatever reason. It only seems to be an issue when SetSize is called from the __init__ method. hence the reason why using wx.CallAfter works.
I don't see how there are different approaches to setting the size of a window. There is one approach to do it if the information is not available to pass to the constructor of the window object, that is calling SetSize. It works all other times just not when a window class has been subclassed and the call to SetSize takes place inside of the subclasses __init__ method. It is a very specific thing that has to take place for the size to not get set.
It does set the size.
The sizer will reset it as you asked it to manage the size. Add a .Layout call to your CallAfter to see the difference.
Please: either use sizers or don't. Don't expect one way to act like the other.