I've got a wxPython program that uses a ScrolledPanel and it's doing something really strange on some resizes. If you drag the resize around a bit, one of the buttons will start looking messed up.
Before:
http://s7.postimage.org/stoc27517/Good_Window.png
After:
http://s9.postimage.org/s2apf38m7/Bad_Window.png
Here's the code:
import wx
import wx.lib.scrolledpanel as scrolled
class TestApp(wx.Frame):
def __init__(self, *args, **kwargs):
super(TestApp,self).__init__(*args,**kwargs)
self.setupGUI()
def setupGUI(self):
#Fires on window resize
self.Bind(wx.EVT_SIZE, self.OnSize)
self.box = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.box)
self.scrolling_window = scrolled.ScrolledPanel( self )
self.scrolling_window.SetAutoLayout(1)
self.scrolling_window.SetupScrolling()
self.gb = wx.GridBagSizer(5,5)
self.scrolling_window.SetSizer(self.gb)
self.btnOne = wx.Button(self.scrolling_window,label='One')
self.btnTwo = wx.Button(self.scrolling_window,label='Two')
self.gb.Add(self.btnOne, pos=(0,0), flag=wx.EXPAND)
self.gb.Add(self.btnTwo, pos=(1,1), flag=wx.ALIGN_BOTTOM)
self.gb.AddGrowableCol(0)
self.gb.AddGrowableRow(1)
self.box.Add(self.scrolling_window,1,wx.EXPAND)
self.Show(True)
def OnSize(self, e):
#This refresh shouldn't be necessary
#self.Refresh()
#Pass event up the chain so window still resizes
e.Skip()
if __name__ == '__main__':
app = wx.App(redirect=False)
TestApp(None)
app.MainLoop()
The only way I could prevent this behavior was to uncomment the self.Refresh() in the OnSize method. This would essentially force a repaint on every window resize. But I don't think this should be necessary. Is this a bug, or am I doing something wrong?
Related
I'm implementing a python application using PyQt5 and I encountered some problems when making use of a QScrollArea. This is the layout of my application:
It's composed of 2 QScrollArea (left and right pane) and a QMdiArea (center widget) arranged into a QHBoxLayout. When I expand the widgets on the left pane by clicking on the controls, and the height of the QWidget of the QScrollArea is bigger than then height of the QScrollArea itself, the scrollbar appears (as expected), but it's overlapping the content of the QScrollArea. To fix this problem I reimplemented the resizeEvent adding the necessary space for the scrollbar (till this point everything works.
Now, when I manually resize the Main Window, the left Pane gets more space and the scrollbar should disappear (but it doesn't) and it overlaps the widgets of the pane:
I also tried to manually toggle the visibility of the scrollbar (when the resizeEvent is received): when I do this, I can successfully hide the scrollbar but then I can't show it again (not matter if I call setVisible(True) on the scrollbar). This results in the space for the scrollbar being added, but the scrollbar is missing and the content of the pane is not scrollable:
Here is the implementation of the pane widget:
class Pane(QScrollArea):
MinWidth = 186
def __init__(self, alignment=0, parent=None):
super().__init__(parent)
self.mainWidget = QWidget(self)
self.mainLayout = QVBoxLayout(self.mainWidget)
self.mainLayout.setAlignment(alignment)
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.mainLayout.setSpacing(0)
self.setContentsMargins(0, 0, 0, 0)
self.setFrameStyle(QFrame.NoFrame)
self.setFixedWidth(Pane.MinWidth)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Ignored)
self.setWidgetResizable(True)
self.setWidget(self.mainWidget)
def resizeEvent(self, resizeEvent):
if self.viewport().height() < self.widget().height():
self.setFixedWidth(Pane.MinWidth + 18)
# THIS DOESN'T WORK
#self.verticalScrollBar().show()
else:
self.setFixedWidth(Pane.MinWidth)
#self.verticalScrollBar().hide()
def addWidget(self, widget):
self.mainLayout.addWidget(widget)
def removeWidget(self, widget):
self.mainLayout.removeWidget(widget)
def update(self, *__args):
for item in itemsInLayout(self.mainLayout):
item.widget().update()
super().update(*__args)
What I want to achieve is pretty simple (but practically it seems not as simple): I would like to dynamically show the vertical scrollbar on my left/right pane widgets only when it's needed, and add the necessary space for the scrollbar so it doesn't overlap the widgets in the QScrollArea.
Before someone asks, I already tried to do something like this:
def resizeEvent(self, resizeEvent):
if self.viewport().height() < self.widget().height():
self.setFixedWidth(Pane.MinWidth + 18)
scrollbar = self.verticalScrollbar()
scrollbar.setVisible(True)
self.setVerticalScrollBar(scrollbar) ## APP CRASH
else:
self.setFixedWidth(Pane.MinWidth)
#self.verticalScrollBar().hide()
which results in my application to crash.
I hope that someone already faced this issue and is able to help me.
EDIT: I'm using PyQt5.5 compiled against Qt5.5 under OSX Yosemite 10.10.4 using clang.
Everything seems to work as expected for me without any need for workarounds. However, I strongly suspect there are additional constraints in your real code that you have not revealed in your question.
UPDATE
Below is a simple example that resizes the scrollareas when the scrollbars are shown/hidden:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
widget = QtWidgets.QWidget(self)
layout = QtWidgets.QHBoxLayout(widget)
self.mdi = QtWidgets.QMdiArea(self)
self.leftScroll = Pane(
QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft, self)
self.rightScroll = Pane(
QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft, self)
layout.addWidget(self.leftScroll)
layout.addWidget(self.mdi)
layout.addWidget(self.rightScroll)
self.setCentralWidget(widget)
for scroll in self.leftScroll, self.rightScroll:
for index in range(4):
widget = QtWidgets.QTextEdit()
widget.setText('one two three four five')
scroll.addWidget(widget)
class Pane(QtWidgets.QScrollArea):
MinWidth = 186
def __init__(self, alignment=0, parent=None):
super().__init__(parent)
self.mainWidget = QtWidgets.QWidget(self)
self.mainLayout = QtWidgets.QVBoxLayout(self.mainWidget)
self.mainLayout.setAlignment(alignment)
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.mainLayout.setSpacing(0)
self.setContentsMargins(0, 0, 0, 0)
self.setFrameStyle(QtWidgets.QFrame.NoFrame)
self.setFixedWidth(Pane.MinWidth)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
QtWidgets.QSizePolicy.Ignored)
self.setWidgetResizable(True)
self.setWidget(self.mainWidget)
self.verticalScrollBar().installEventFilter(self)
def addWidget(self, widget):
self.mainLayout.addWidget(widget)
def removeWidget(self, widget):
self.mainLayout.removeWidget(widget)
def eventFilter(self, source, event):
if isinstance(source, QtWidgets.QScrollBar):
if event.type() == QtCore.QEvent.Show:
self.setFixedWidth(Pane.MinWidth + source.width())
elif event.type() == QtCore.QEvent.Hide:
self.setFixedWidth(Pane.MinWidth)
return super(Pane, self).eventFilter(source, event)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 800, 300)
window.show()
sys.exit(app.exec_())
I want to draw some lines after click button. But when I minimize the window, or scroll the window in a ScrolledWindow, all the drawing will lost. Is there any way to keep them?
Here is the code:
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, size=(640, 480))
self.panel = wx.Panel(self, wx.ID_ANY)
button = wx.Button(self.panel, id=wx.ID_ANY, label=u'Start Calculation', size=(160, 22))
self.Bind(wx.EVT_BUTTON, self.OnButtonCalcuating, button)
def OnButtonCalcuating(self, event):
self.dc = wx.ClientDC(self.panel)
self.dc.DrawLine(50, 60, 190, 60)
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = Frame()
frame.Show()
app.MainLoop()
If you want to paint into your window, you must make a handler for EVT_PAINT event. What you do is, that you paint your line, but that's it. When the window gets re-drawn, and knows nothing about your line anymore.
Check out this:
http://wiki.wxpython.org/VerySimpleDrawing
I'm trying to make a wxpython window (only a window in the sense that it's a window object).. that fills the entire screen and is completely invisible. I then want to allow the user to click and drag within the "window" (ie. anywhere on the screen).
When I try doing self.SetTransparent(0) user input doesn't get captured by the window.
Is this intended behaviour?
Is this the correct way to achieve what I want? An opacity of 1 is obviously indistinguishable to the human eye, but I'm still curious as to why I can't make it completely transparent.
Here's the snippet:
import wx
class Frame(wx.Frame):
def __init__(self):
style = (wx.STAY_ON_TOP | wx.NO_BORDER)
wx.Frame.__init__(self, None, title="Invisible", style=style)
self.SetTransparent(0) # This doesn't work
#self.SetTransparent(1) # But this works fine
self.Bind(wx.EVT_KEY_UP, self.OnKeyPress)
def OnKeyPress(self, event):
"""quit if user press q or Esc"""
if event.GetKeyCode() == 27 or event.GetKeyCode() == ord('Q'): #27 is Esc
self.Close(force=True)
else:
event.Skip()
app = wx.App()
frm = Frame()
frm.ShowFullScreen(True)
app.MainLoop()
Or is there a way of giving the window no background at all rather than a completely transparent coloured background?
You can override EVT_ERASE_BACKGROUND to accomplish the same effect.
I also cleaned up other aspects of the code.
Behaves slightly differently on XP versus 7, but probably not an issue for the type of app you're describing.
import wx
class Frame(wx.Frame):
def __init__(self):
super(Frame, self).__init__(None)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
def OnEraseBackground(self, event):
pass # do nothing
def OnLeftDown(self, event):
print event.GetPosition()
def OnKeyDown(self, event):
if event.GetKeyCode() == wx.WXK_ESCAPE:
self.Close()
else:
event.Skip()
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = Frame()
frame.ShowFullScreen(True)
app.MainLoop()
I have a little app that I am working on with wxPython.
I have a scrolled window using wx.ScrolledWindow. It seems to refuse to repaint the contents when it is scrolled.
Example:
Code that created above example:
import wx
class SaveEdFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE|wx.EXPAND
wx.Frame.__init__(self, *args, **kwds)
self.__do_layout()
self.Bind(wx.EVT_SIZE, self.onSize)
def __mainSizer(self):
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
for key in xrange(30):
self.headerLabel = wx.StaticText(self, -1, "TestStr %s" % key)
self.mainSizer.Add(self.headerLabel)
return self.mainSizer
def __do_layout(self):
## begin wxGlade: SaveEdFrame.__do_layout
self.scroll = wx.ScrolledWindow(self, style=wx.FULL_REPAINT_ON_RESIZE)
self.scroll.SetScrollbars(1, 10, 1, 10)
self.scroll.SetSizer(self.__mainSizer())
def onSize(self, event):
self.scroll.SetSize(self.GetClientSize())
self.Refresh()
if __name__ == "__main__":
app = wx.App(0)
mainFrame = SaveEdFrame(None)
app.SetTopWindow(mainFrame)
mainFrame.Show(True)
app.MainLoop()
I've been digging through the wxDocs, and it seems to me that one solution would be to subclass wx.ScrolledWindow, manually catch wx.EVT_SCROLLWIN events, and then explicitly redraw the window, but my attempts to do that failed when calling self.Refresh() did not cause the interior of the wx.ScrolledWindow to repaint.
Anyways, it seems to me that the whole point of the wx.ScrolledWindow object is that it should handle repainting itself when scrolled.
What am I doing wrong?
Platform is W7-x64, python 2.7 32 bit, wxPython 2.8.11.0
I think the problem there is that your wx.StaticText widgets are children of the SaveEdFrame, not the ScrolledWindow. The ScrolledWindow is being redrawn over them as you scroll it. Try:
headerLabel = wx.StaticText(self.scroll, -1, "TestStr %s" % key)
self.mainSizer.Add(headerLabel)
Given the following simple program:
import wx
class TestDraw(wx.Panel):
def __init__(self,parent=None,id=-1):
wx.Panel.__init__(self,parent,id,style=wx.TAB_TRAVERSAL)
self.SetBackgroundColour("#FFFFFF")
self.Bind(wx.EVT_PAINT,self.onPaint)
self.SetDoubleBuffered(True)
self.circleX=320
self.circleY=240
def onPaint(self, event):
event.Skip()
dc=wx.PaintDC(self)
dc.BeginDrawing()
dc.DrawCircle(self.circleX,self.circleY,100)
dc.EndDrawing()
class TestFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(640,480))
self.mainPanel=TestDraw(self,-1)
self.Show(True)
app = wx.App(False)
frame = TestFrame(None,"Test App")
app.MainLoop()
How can I change it so that I can execute logic and repaint the panel at a constant rate? I'd like the circle to bounce around the screen, but I just can't figure out the place I would change its x and y variables.
Your can use a wxTimer to periodically call an onTimer(self) method.