Is there a way to allow a custom wx.Dialog to be resized in the horizontal direction only? I've tried using GetSize() and then setting the min and max height of the window using SetSizeHints(), but for some reason it always allows the window to be resized just a little, and it looks rather tacky. The only other alternative I have come up with is to hard-code the min and max height, but I don't think that would be such a good idea...
Relevant code:
class SomeDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, title="blah blah",
style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
size = self.GetSize()
self.SetSizeHints(minW=size.GetWidth(), minH=size.GetHeight(),
maxH=size.GetHeight())
os: Windows 7
python version: 2.5.4
wxPython version: 2.8.10
If you don't want the height to change, why would it be a bad idea to set min and max height to the same value (the one you want to force)? You can of course get the system estimate of the "best value" with GetBetSize or related methods. Though I find the fact that setting the size hints doesn't have the same effect (as I think it should) peculiar... what platform are you using wxpython on, and what version of Python, wxpython, and the platform/OS itself...?
Related
I'm using PySide6 6.4.1 to build a table widget that automatically resizes to the number of rows. Here's a minimal example:
from PySide6.QtWidgets import *
class MW(QWidget):
def __init__(self):
super().__init__()
self.button = QPushButton("Test")
self.table = QTableWidget(self)
self.table.setColumnCount(1)
self.table.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
self.setLayout(QVBoxLayout(self))
self.layout().addWidget(self.button)
self.layout().addWidget(self.table)
self.button.clicked.connect(self.test)
return
def test(self):
self.table.insertRow(0)
self.table.setItem(0, 0, QTableWidgetItem("new item"))
self.table.adjustSize()
self.adjustSize()
return
app = QApplication()
mw = MW()
mw.show()
app.exec()
Somehow this always leaves a bit of empty space at the bottom of the table. How do I get rid of this space without doing manual resizing?
(Nevermind the weird font size, it's a known bug when using UI scaling. I've adjusted the font size manually as well and it doesn't get rid of this problem.)
Qt item views inherit from QAbstractScrollArea, which has some peculiar size related aspects:
it has an Expanding size policy that tells the parent layout it can use as much space as possible, possibly increasing the available space at initialization;
it has a minimumSizeHint() that always includes a minimum reasonable size allowing showing the scroll bars (even if they are not visible);
if the sizeAdjustPolicy is AdjustToContents it's also based on the viewport size hint;
It's also mandatory to consider a fundamental aspect about scroll areas: size management is a tricky subject, and some level of compromise is necessary most of the times. This is the case whenever the scroll bars potentially change the available size of the viewport (the part of the widget that is able to scroll), which is the default behavior of Qt in most systems, unless the scroll bars are always hidden/visible or they are transient (they "overlay" above the viewport without affecting its available visible size).
To clarify this aspect, consider a scroll area with content that has a minimum size of 100x100 and scroll bars that have a default extent (width for the vertical one, height for the horizontal) of 20: if the height hint of the content is changed to 110, then you'd theoretically need an area of 100x110. But Qt needs to know the hints before laying out widgets and setting their geometries. This means that you cannot know if the scroll bars have to be shown before the widget is finally laid out, but that hint is required to lay out the widget itself. So, recursion.
Qt layout management is a system that is far from perfect, but I doubt that there is one, at least considering normal UI management (don't consider web layouts: their concept is based on different assumption, most importantly the fact that the whole "window" has potentially infinite dimensions). This is an aspect that must be always considered, especially if the shown contents are set to adapt their size based on the contents; it's the case of fitInView() of QGraphicsView or the known issues of QLayout with rich text based widgets.
Qt doesn't provide "foolproof" solutions for these aspects, because its layout management doesn't allow it as it has been implemented primarily considering performance and usability: the UI has to work and be responsive before being "fancy".
It's one of the reasons for which it's almost impossible to have real fixed-aspect-ratio widgets or windows. You can work around it, but at some point you'll have some inconsistencies, and you have to live with that. Also consider that this kind of behavior is generally not very UX-friendly. UI elements that resize themselves (and, consequentially, alter the whole layout) at anytime are usually annoying and very user-unfriendly, especially if they displace their or other contents: it's like having a car that constantly moves the driving controls depending on the amount of passengers.
That said, it's not impossible to have a partially working solution.
The requirements are to:
override minimumSizeHint(), so that a minimal reasonable size is always returned;
override sizeHint() that is used to adjust the widget (and parents) based on the contents of the view;
change the vertical size policy of the table to Preferred, which will tell the layout manager that the height of the size hint will be considered as default, still allowing it to expand in case other items in the layout don't use the remaining space, and eventually shrink it if required;
eventually do the same for the horizontal policy in order to adapt it to the actual horizontal header size, otherwise use self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch), but be aware that this might complicate things (see the note after the code);
class TableWidget(QTableWidget):
def sizeHint(self):
hHeader = self.horizontalHeader()
vHeader = self.verticalHeader()
f = self.frameWidth() * 2
# the simple solution is to get the length, but this might be a problem
# whenever *any* section of the header is set to Stretch
targetWidth = width = f + hHeader.length()
# a possible alternative (but still far from perfect):
width = f
for c in range(self.columnCount()):
if hHeader.isSectionHidden(c):
continue
width += self.sizeHintForColumn(c)
targetWidth = width
if not vHeader.isHidden():
width += vHeader.width()
hpol = self.horizontalScrollBarPolicy()
height = f + vHeader.length() + hHeader.height()
if (
hpol != Qt.ScrollBarAlwaysOff
and not self.horizontalScrollBar().isHidden()
and (
hpol == Qt.ScrollBarAlwaysOn
and hHeader.length() + f < targetWidth
)
):
height += self.horizontalScrollBar().sizeHint().height()
return QSize(width, height)
def minimumSizeHint(self):
hint = self.sizeHint()
minHint = super().minimumSizeHint()
return QSize(
min(minHint.width(), hint.width()),
min(super().minimumSizeHint().height(), hint.height())
)
class MW(QWidget):
def __init__(self):
# ...
pol = self.table.sizePolicy()
pol.setVerticalPolicy(QSizePolicy.Preferred)
self.table.setSizePolicy(pol)
Be aware that the above doesn't solve all problems. It might work fine for a QTableView having just one column or when using the default interactive (or fixed) section resize mode, but whenever you set different resize modes for each column the result may be wrong.
In order to provide a finer resize, you'll need to do much complex computations that take into account each section resize mode for the horizontal header, the default/minimum/maximum and eventually the hint based on the content.
Further notes: 1. calling adjustSize() on the parent is normally enough, it's not necessary to do it on the children; 2. self.setLayout(QVBoxLayout(self)) is pointless, the self argument already sets the layout; just use layout = QVBoxLayout(self) and use that as a local variable to add widgets; 3. in Python the return at the end of a function is always implicit, you shall not add it as it's useless, redundant and distracting.
I'm trying to understand how to use the wxSplitterWindow class but can't seem to get a vertical split to be even (i.e. left and right panes each take up the same amount of space). According to the wxPython 3.03 documentation, wxSplitterWindow.SplitVertically has the following signature:
SplitVertically(self, window1, window2, sashPosition=0)
The last parameter sashPosition has the following description:
sashPosition - The initial position of the sash. If this value is positive, it specifies the size of the left pane. If it is negative, it is absolute value gives the size of the right pane. Finally, specify 0 (default) to choose the default position (half of the total window width).
From the description, I gather that passing a 0 for sashPosition splits the window in half with the left and right panes taking up equal space. However, when I run the following example program, I get a window with an uneven split where almost all of the left panel is hidden.
import wx
class SplitterFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='SplitterWindow example')
# Create the main splitter window (to be split vertically)
self.splitter = wx.SplitterWindow(self)
self.rightPanel = wx.Panel (self.splitter)
self.rightPanel.SetBackgroundColour(wx.BLUE)
self.leftPanel = wx.Panel (self.splitter)
self.leftPanel.SetBackgroundColour(wx.RED)
# Expecting an even split with this call
self.splitter.SplitVertically (self.leftPanel, self.rightPanel, 0)
if __name__ == '__main__':
app = wx.App(0)
frame = SplitterFrame()
frame.Show()
app.MainLoop()
I'm running Python v2.7.10 using wxPython v3.0 toolkit on a Windows 8.1 machine.
I had the same problem, by trial, and error I get that this modification solved the problem
self.splitter.SplitHorizontally(self.leftPanel, self.rightPanel, 100)
But this is dependent on the size of the frame.
I'm using wxpython version 3.0.0.0
I know this thread is ancient, but in case anyone else has this issue:
From the wiki: https://wxpython.org/Phoenix/docs/html/wx.SplitterWindow.html#wx.SplitterWindow.SetSashGravity
Notice that when sash gravity for a newly created splitter window, it is often necessary to explicitly set the splitter size using SetSize to ensure that is big enough for its initial sash position. Otherwise, i.e. if the window is created with the default tiny size and only resized to its correct size later, the initial sash position will be affected by the gravity and typically result in sash being at the rightmost position for the gravity of 1
Either set the size manually with splitter.SetInitialSize(...) (obviously frame size dependent)
or use splitter.SetSashGravity(.5) so they grow evenly.
With tkinter canvas, to calculate the size of the graphics I display, I normally use the function winfo_screenwidth(), and size my objects accordingly.
But when used on a system with two monitors, winfo_screenwidth() returns the combined width of both monitors -- which messes up my graphics.
How can I find out the screen width in pixels of each monitor, separately?
I have had this problem with several versions of Python 3.x and several versions of tkinter (all 8.5 or above) on a variety of Linux machines (Ubuntu and Mint).
For example, the first monitor is 1440 pixels wide. The second is 1980 pixels wide. winfo_screenwidth() returns 3360.
I need to find a way to determine the screenwidth for each monitor independently.
Thanks!
It is an old question, but still: for a cross-platform solution, you could try the screeninfo module, and get information about every monitor with:
import screeninfo
screeninfo.get_monitors()
If you need to know on which monitor one of your windows is located, you could use:
def get_monitor_from_coord(x, y):
monitors = screeninfo.get_monitors()
for m in reversed(monitors):
if m.x <= x <= m.width + m.x and m.y <= y <= m.height + m.y:
return m
return monitors[0]
# Get the screen which contains top
current_screen = get_monitor_from_coord(top.winfo_x(), top.winfo_y())
# Get the monitor's size
print current_screen.width, current_screen.height
(where top is your Tk root)
Based on this slightly different question, I would suggest the following:
t.state('zoomed')
m_1_height= t.winfo_height()
m_1_width= t.winfo_width() #this is the width you need for monitor 1
That way the window will zoom to fill one screen. The other monitor's width is just wininfo_screenwidth()-m_1_width
I also would point you to the excellent ctypes method of finding monitor sizes for windows found here. NOTE: unlike the post says, ctypes is in stdlib! No need to install anything.
I'm working on a window manager written using python's xlib bindings and I'm (initially) attempting to mimic dwm's behavior in a more pythonic way. I've gotten much of what I need, but I'm having trouble using X's built in window border functionality to indicate window focus.
Assuming I've got an instance of Xlib's window class and that I'm reading the documentation correctly, this should do what I want to do (at least for now) - set the window border of a preexisting window to a garish color and set the border width to 2px.
def set_active_border(self, window):
border_color = self.colormap.alloc_named_color(\
"#ff00ff").pixel
window.change_attributes(None,border_pixel=border_color,
border_width = 2 )
self.dpy.sync()
However, I get nothing from this - I can add print statements to prove that my program is indeed running the callback function that I associated with the event, but I get absolutely no color change on the border. Can anyone identify what exactly I'm missing here? I can pastebin a more complete example, if it will help. I'm not exactly sure it will though as this is the only bit that handles the border.
Looks like this was complete PEBKAC. I've found an answer. Basically, I was doing this:
def set_active_border(self, window):
border_color = self.colormap.alloc_named_color(
"#ff00ff"
).pixel
window.configure(border_width=2)
window.change_attributes(
None,
border_pixel=border_color,
border_width=2)
self.dpy.sync()
Apparently this was confusing X enough that it was doing nothing. The solution that I've stumbled upon was to remove the border_width portion from the window.change_attributes() call, like so:
def set_active_border(self, window):
border_color = self.colormap.alloc_named_color(
"#ff00ff"
).pixel
window.configure(border_width=2)
window.change_attributes(
None,
border_pixel=border_color
)
self.dpy.sync()
I hope this helps someone later on down the road!
The following code sample works under linux (ubuntu) and Windows XP, but not under OSX.
import wx
class frame(wx.Frame):
def __init__(self,p=None):
wx.Frame.__init__(self,p)
self.box = wx.ListBox(self)
self.box.AppendItems( ["Zero","One","Two","Three","Four","Five","Six"])
self.box.Bind(wx.EVT_MOUSE_EVENTS,self.onMouse)
def onMouse(self,evt):
pos = evt.GetPosition()
print self.box.HitTest(pos)
evt.Skip()
class guiApp(wx.App):
def __init__(self,redirect=False):
wx.App.__init__(self,redirect)
def OnInit(self):
f = frame()
f.Show()
self.SetTopWindow(f)
return True
if __name__=="__main__":
app = guiApp()
app.MainLoop()
On Linux and Windows, the correct items are identified when moused over. On OSX hittest always returns -1 (wx.NOT_FOUND)
I'm running 32-bit wxPython, 2.8.12.1 (mac-unicode) which uses the Carbon API in 32bit python 2.7.2.
I can't find this listed as a known bug in wxWidgets and I'm hesitant to submit as it seems this should work. The listbox control is deeply integrated into out GUI and I really don't want to swap it out for ListCtrl or something similar as we have all other functionality working now. Does anyone know a workaround?
There is no work around if the listbox is scrolling. The scroll is handled be the underlying Carbon library and scroll position is not accurately reported back through wx.
I found the bug in the wxWidgets source code and opened a ticket on the wxWidgets trac, http://trac.wxwidgets.org/ticket/13699, with a patch.
The root of the bug is a call to the Mac's underlying DataBrowser with an incorrect rowId argument. wxWidgets was passing row position offsets, assuming this would be the rowId's (and maybe at some point apple used these internally when true Id's weren't specified.) Adding a call to another function translates a row's position (offset) into it's real id. With a patched version of wxWidgets the above script works as expected.