wxpython scrolledwindow not displaying - python-2.7

Relatively new to python (2.7) and trying to figure out wxpython (so I apologise in advance for any poor use of code). I've got a GUI of which I have multiple switchable panels on a frame. I need the frame to be scrollable, so I've used ScrolledWindow but now some of the of the GUI elements which are below the initial frame size do not show upon scrolling on.
I've found that changing my monitor resolution solves the problem, but I want to be able to have this working regardless of resolution.
Below is an example of the problem I'm having (doesn't display hi4 and cuts off hi4)
import wx
from apanel import apanel
class simpleapp_wx(wx.Frame):
def __init__(self,parent,id,title):
wx.Frame.__init__(self,parent,id,title,size=(1000,1100))
self.parent=parent
self.scroll = wx.ScrolledWindow(self, -1)
self.scroll.SetScrollbars(1,1,1000,1100)
button0=wx.Button(self.scroll,-1,"hi0",(100,610))
self.panel=apanel(self.scroll)
self.CreateStatusBar()
self.sizer= wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.panel, 1, wx.EXPAND)
self.SetSizer(self.sizer)
self.Show(True)
app=wx.App(False)
frame=simpleapp_wx(None,-1,'Demo')
frame.Show()
app.MainLoop()
and panel is in another class (in a seperate file I called apanel.py)
import wx
class apanel(wx.Panel):
def __init__(self,parent):
wx.Panel.__init__(self,parent=parent)
button=wx.Button(self,-1,"hi",(800,60))
button2=wx.Button(self,-1,"hi2",(200,600))
button3=wx.Button(self,-1,"hi3",(800,800))
button4=wx.Button(self,-1,"hi4",(500,900))
button5=wx.Button(self,-1,'hi5',(10,100))

I've found some errors in your code, it's simple to solve. Look the working panel bellow:
class simpleapp_wx(wx.Frame):
def __init__(self,parent,id,title):
wx.Frame.__init__(self,parent,id,title,size=(1000,1100))
self.parent=parent
self.scroll = wx.ScrolledWindow(self, -1)
self.scroll.SetScrollbars(1,1,1000,1100)
self.CreateStatusBar()
sizer = wx.BoxSizer(wx.VERTICAL)
self.scroll.SetSizer(sizer) # The scrolledWindow sizer
self.panel = wx.Panel(self.scroll)
sizer.Add(self.panel, 0, wx.EXPAND)
button0=wx.Button(self.panel,-1,"hi0",(100,610))
Remarks:
If you use a scrolled window, create a sizer, and set the sizer in scrolled window.
The panel apanel need to be added on scrolled sizer created in line above.
The panel not resizing because simpleapp_wx (Frame) was set your size by the created BoxSizer, the order is inverse.
If you add some button after, put the apanel with parent, not scrolledwindow.
I suggest to you to use wxPython demo and docs: http://www.wxpython.org/download.php have a bunch of working examples.
Good luck in your wxpython studies!

Related

PyQt5 QComboBox list items changing postion

I am facing some issue with the display style of Qcombobox items. Currently am hardcoding the data to be shown in the combobox.
here is the code :
self.Dummy = QComboBox(self)
self.Dummy.setGeometry(200,600, 350, 50)
self.Dummy.setStyleSheet("QComboBox {background-color: white;border-style: outset;" border-width: 2px;border-radius: 5px;border-color: #448aff; font: 12px; min-width: 10em; padding: 3px;}")
self.Dummy.addItems(["-Select-", "2", "3","4","5","6","7","8","9","0","11",])
The issue is that the dropdown "list" postion keeps changing after each selection. Here is the image of the issue am facing.
Below is my combobox
The list contains items <-Select->,2,3,4,5,6,7,8,9,0,11 , where <-Select-> will be the first element shown.
Now when I click the box, the box list "down" the elements and suppose I selected '2'. Then, if I try to select another item, the list will be dropped in a "downwards" direction. see below
Now, say if selected the last element from the items, '11'. Now if I try to select a new item by clicking on the box, the list will be popped "up" instead of down. see below
What should be done to fix it ? I don't think its an issue with stylesheet, without it also, this issue is happening. The reason I need this to be fixed is that when the list is popping up, its covering the label above it
What you see is a behavior that is OS and style dependent.
To avoid it, the best way is to subclass QComboBox and overwrite showPopup(), then we call the base class implementation (which is responsible of showing, resizing and positioning the popup view) and move it if necessary.
class Combo(QtWidgets.QComboBox):
def showPopup(self):
super().showPopup()
# find the widget that contains the list; note that this is *not* the view
# that QComboBox.view() returns, but what is used to show it.
popup = self.view().window()
rect = popup.geometry()
if not rect.contains(self.mapToGlobal(self.rect().center())):
# the popup is not over the combo, there's no need to move it
return
# move the popup at the bottom left of the combo
rect.moveTopLeft(self.mapToGlobal(self.rect().bottomLeft()))
# ensure that the popup is always inside the edges of the screen
# we use the center of the popup as a reference, since with multiple
# screens the combo might be between two screens, but that position
# could also be completely outside the screen, so the cursor position
# is used as a fallback to decide on what screen we'll show it
done = False
for i, pos in enumerate((rect.center(), QtGui.QCursor.pos())):
for screen in QtWidgets.QApplication.screens():
if pos in screen.geometry():
screen = screen.geometry()
if rect.x() < screen.x():
rect.moveLeft(screen.x())
elif rect.right() > screen.right():
rect.moveRight(screen.right())
if rect.y() < screen.y():
rect.moveTop(screen.y())
elif rect.bottom() > screen.bottom():
# if the popup goes below the screen, move its bottom
# *over* the combo, so that the its current selected
# item will always be visible
rect.moveBottom(self.mapToGlobal(QtCore.QPoint()).y())
done = True
break
if done:
break
popup.move(rect.topLeft())
This can also be done without subclassing (for example if you have many combos, you created the UI from Designer and don't want to use promoted widgets), but you'll have to remember to change all referencies to the combo.
class MyWindow(QtWidgets.QWidget):
def __init__(self):
# ...
self.combo = QtWidgets.QComboBox()
self.combo.showPopup = self.showPopupAndCheck
def showPopupAndCheck(self):
QtWidgets.QComboBox.showPopup(self.combo)
popup = self.view().window()
rect = popup.geometry()
if not rect.contains(self.combo.mapToGlobal(self.combo.rect().center())):
# the popup is not over the combo, there's no need to move it
return
# change everything from self to self.combo
Alternatively, if you want to keep this behavior consistent through all your program without always using the subclass, you can use some sort of monkey patching hack.
The advantage is that any QComboBox you create (even when loading UI files or creating a combo at runtime) will always use the new behavior.
Important: this MUST be at the very beginning of the main file of your program, possibly just after the import section.
from PyQt5 import QtCore, QtGui, QtWidgets
def customShowPopup(self):
# we can't use super(), because we're not in the class definition, but
# calling the class method with "self" as first argument is practically the
# same thing; note the underscore!
QtWidgets.QComboBox._showPopup(self)
popup = self.view().window()
# ... go on, exactly as above
# create a new reference to the showPopup method, which is the one we've used
# in the function above
QtWidgets.QComboBox._showPopup = QtWidgets.QComboBox.showPopup
# overwrite the original reference with the new function
QtWidgets.QComboBox.showPopup = customShowPopup

How to enable autoscroll in Qtableview like in Qtextedit?

I'm using QtableView and QStandardItemModel to display logs on GUI to maintain proper spacing and filter logs. I created model and inserted data into it. Used QSortFilterProxyModel for filter strings.
self.tableView = QtGui.QTableView(self)
self.model = QtGui.QStandardItemModel(self)
self.proxy = QtGui.QSortFilterProxyModel(self)
self.proxy.setSourceModel(self.model)
self.tableView.setModel(self.proxy)
In a sec, nearly 100 logs are expected and should be shown on GUI. When new logs are appended, the view isn't auto scrolling and the slider stays only at the top. It doesn't give live feel for logging and user need to scroll manually to the end. So to overcome this, i used following syntax,
self.model.rowsInserted.connect(lambda: QtCore.QTimer.singleShot(5, self.tableView.scrollToBottom))
It gives live feel for logs, but the slider remains always in bottom and i'm not able to scroll up to see previous logs. Whenever i try to move the slider, it immediately comes down to bottom again. So this syntax doesn't meet my requirement. In QTextEdit, auto scrolling is proper and user friendly. I want the same scenario here on QtableView. Is there any alternative for auto scrolling which resembles like QTextEdit ?
To get the required behaviour, you can auto-scroll only when the previous scroll position is at the bottom. That way, whenever the user scrolls away from the bottom, auto-scrolling will be disabled; but when they scroll back to the bottom, auto-scrolling will be re-enabled. (NB: to quickly re-enable auto-scroll, right-click the scrollbar and select "Bottom" from the context menu).
Here is a simple demo:
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.table = QtGui.QTableView(self)
self.model = QtGui.QStandardItemModel(self)
self.table.setModel(self.model)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.table)
self._scroll = True
self.model.rowsAboutToBeInserted.connect(self.beforeInsert)
self.model.rowsInserted.connect(self.afterInsert)
def beforeInsert(self):
vbar = self.table.verticalScrollBar()
self._scroll = vbar.value() == vbar.maximum()
def afterInsert(self):
if self._scroll:
self.table.scrollToBottom()
def addRow(self):
self.model.appendRow([QtGui.QStandardItem(c) for c in 'ABC'])
if __name__ == '__main__':
app = QtGui.QApplication([''])
window = Window()
window.setGeometry(500, 50, 400, 300)
window.show()
timer = QtCore.QTimer()
timer.timeout.connect(window.addRow)
timer.start(200)
app.exec_()

How can I determine which child windows will be visible in a ScrolledWindow during/after a EVT_SCROLLED_WIN event?

I'm building a panel for my application which contains a large number of thumbnail images; they won't all fit in the window, so I'm using a ScrolledWindow to hold them and allow the user to scroll to view them.
It takes some time to generate the thumbnail images, so I'm attempting to prioritize the production of the images for the thumbnails that are currently visible in the window. I'm also wanting to capture which thumbnails are visible so that if I add or remove a subset of the thumbnails I know where to position the scrollbar to include as many of the previously visible ones as possible.
The problem I'm having is that EVT_SCROLLWIN appears to be posted before the child windows are repositioned in the client area of the ScrolledWindow; in other words, I'm measuring the visible objects from before the scrollbar moved. Is there a way to: a) force the ScrolledWindow to update the child window positions, b) reliably determine what the new client window offset will be as compared to what it is now, or c) call the event handler after this update has occurred?
If it matters, I'm running Python 2.7.11 and wxPython 3.0.2.0 on this computer.
A code segment follows:
class ThumbsViewer(wx.ScrolledWindow):
def __init__(self, parent, info, style = wx.BORDER_RAISED | wx.HSCROLL):
wx.ScrolledWindow.__init__(self, parent = parent, id = wx.ID_ANY, size = wx.DefaultSize,
pos = wx.DefaultPosition, style = style, name = "ThumbsViewer")
self.__info__ = info
self.__bitmapctrls__ = []
self.__selected__ = []
self.__lastclicked__ = []
self.Bind(wx.EVT_SCROLLWIN, self.__OnScroll__)
# ... Code to generate placeholder thumbnails, arrange buttons, size window, etc. ...
# ... Various functions to handle clicking on thumbnails, etc. ...
# EVT_SCROLLWIN Handler - Measure client window positions and determine which are
# visible on the screen.
def __OnScroll__(self, event):
if event:
event.Skip()
priority = []
clientsize = self.GetClientSize()
for ctrl in self.__bitmapctrls__:
pos = ctrl.GetPosition()
size = ctrl.GetSize()
# Test to see if any part of the thumbnail control falls within the client
# area...append to the priority list if it does.
# This appears to be where the problem is - the child window positions
# have not been updated to reflect the new scrollbar position yet.
if (((pos[0] >= 0 and pos[0] <= clientsize[0]) or (pos[0] + size[0] >= 0 and pos[0] + size[0] <= clientsize[0]))
and ((pos[1] >= 0 and pos[1] <= clientsize[1]) or (pos[1] + size[1] >= 0 and pos[1] + size[1] <= clientsize[1]))):
priority.append(ctrl.GetPage())
self.__info__.SetPriorityPages(priority)
After struggling with this for a while, I decided to Bind EVT_PAINT instead of EVT_SCROLLWIN, and do my scroll bar checks when that occurs. The repaint happens after EVT_SCROLLWIN and the child window positions are updated.
I'm not super happy with this solution, because EVT_PAINT happens for a lot of reasons having nothing to do with the scroll bars, so please feel free to point out a better solution if you have one. Fortunately, in this case the computations fall through very quickly except when I'm adding or removing thumbnails (which doesn't occur on most paint events) so the performance hit is minimal.

Inline renaming of a tab in QTabBar/QTabWidget

I really want to have a QTabWidget that can be renamed by double-click on tab caption.
I have googled and found this solution, but it's more like an outline for a developer who knows his way through Qt and subclassing it's widgets.
I'm kind of stuck how to implement all that. I have used an example given further down that thread (with IndependentLineEdit and so on) and it worked, but it's not what i wanted.
I do not want to have any kind of InputDialog. I do not want to have any floating widget or something.
What i basically want is to use QTabWidget (subclass), that behaves the similar way as spreadsheet tabs do in modern office suites - like, tab label is substituted with seamless line edit, which resets tab label on Enter or leaves it intact on Esc.
I have not been able to find such a solution so far. I do understand that what I actually need is very close to this:
provide a temporary QLineEdit at QTabBar::tabRect() filled with QTabBar::tabText()
but i do not understand how to do that. Moreover, since QTabBar is kind of bare tabbar i would also prefer to have in enclosed into QTabWidget (subclass).
The following is an implementation of such behavior in Python in PyQt4. It should be easy to convert that to C++. Please note that Qt5 has a nice signal tabBarDoubleClicked which is missing in Qt4. This piece of code creates such signal as well.
from PyQt4 import QtGui
from PyQt4.QtCore import pyqtSignal, pyqtSlot
class QTabBar(QtGui.QTabBar):
"""QTabBar with double click signal and tab rename behavior."""
def __init__(self, parent=None):
super().__init__(parent)
tabDoubleClicked = pyqtSignal(int)
def mouseDoubleClickEvent(self, event):
tab_index = self.tabAt(event.pos())
self.tabDoubleClicked.emit(tab_index)
self.start_rename(tab_index)
def start_rename(self, tab_index):
self.__edited_tab = tab_index
rect = self.tabRect(tab_index)
top_margin = 3
left_margin = 6
self.__edit = QtGui.QLineEdit(self)
self.__edit.show()
self.__edit.move(rect.left() + left_margin, rect.top() + top_margin)
self.__edit.resize(rect.width() - 2 * left_margin, rect.height() - 2 * top_margin)
self.__edit.setText(self.tabText(tab_index))
self.__edit.selectAll()
self.__edit.setFocus()
self.__edit.editingFinished.connect(self.finish_rename)
#pyqtSlot()
def finish_rename(self):
self.setTabText(self.__edited_tab, self.__edit.text())
self.__edit.deleteLater()
class QTabWidget(QtGui.QTabWidget):
"""QTabWidget with double click on tab signal and tab rename behavior."""
def __init__(self, parent):
super().__init__(parent)
self.setTabBar(QTabBar())
self.tabBar().tabDoubleClicked.connect(self.tabBarDoubleClicked)
tabBarDoubleClicked = pyqtSignal(int)
Please note that there is a rather hacky solution to aligning the qlineedit within the tab header. It is achieved by fixing the margin to a certain value. I'm sure it is possible to look that value up in the style if necessary.

Python PyQt/PySide QMdiArea subwindows scroll not working in TabbedView

I have setup simple example using PyQt designer.See below.
I have mdiarea in in which i am adding a form as subwindow. I made form a bit lengthier than mainwindow to see if scroll-bar appears for child sub-window.
PROBLEM:
If i set mdiarea to setViewMode(QtGui.QMdiArea.TabbedView) scrollbars stop working and disappear. Howeevr If i dont use TabbedView, scrollbars work fine.
Can anyone tell me whats wrong ? I need TabbedView of mdiarea with working scrollbars.
I am using Python 2.7,PyQT 4.8.4/PySide 1.2.1 on win7.
Python Sample Code:
Comment the line self.mdiArea.setViewMode to see example working.
import sys
from PyQt4 import QtCore, QtGui
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName( "MainWindow" )
MainWindow.resize(500, 400)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName( "centralwidget" )
self.verticalLayout = QtGui.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName( "verticalLayout" )
self.mdiArea = QtGui.QMdiArea(self.centralwidget)
self.mdiArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.mdiArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.mdiArea.setActivationOrder(QtGui.QMdiArea.CreationOrder)
self.mdiArea.setViewMode(QtGui.QMdiArea.TabbedView)
self.mdiArea.setTabsClosable(True)
self.mdiArea.setTabsMovable(True)
self.mdiArea.setObjectName( "mdiArea" )
self.verticalLayout.addWidget(self.mdiArea)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 508, 21))
self.menubar.setObjectName( "menubar" )
self.menuAdd = QtGui.QMenu(self.menubar)
self.menuAdd.setObjectName( "menuAdd" )
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName( "statusbar" )
MainWindow.setStatusBar(self.statusbar)
self.menubar.addAction(self.menuAdd.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle( "MainWindow" )
self.menuAdd.setTitle( "&Add Form" )
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName( ("Form"))
Form.resize(400, 800)
self.gridLayout = QtGui.QGridLayout(Form)
self.gridLayout.setObjectName( ("gridLayout"))
self.plainTextEdit = QtGui.QPlainTextEdit(Form)
self.plainTextEdit.setMinimumSize(QtCore.QSize(0, 731))
self.plainTextEdit.setObjectName( ("plainTextEdit"))
self.gridLayout.addWidget(self.plainTextEdit, 0, 0, 1, 1)
self.buttonBox = QtGui.QDialogButtonBox(Form)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName( ("buttonBox"))
self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
Form.setWindowTitle( "Lengthy subwindow" )
self.plainTextEdit.setPlainText( "Lengthy Form" )
class MyApp(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
def Add_Subwindow(self):
widget = QtGui.QWidget()
self.subwin_abq = Ui_Form()
self.subwin_abq.setupUi(widget)
self.subwindow = QtGui.QMdiSubWindow(self.ui.mdiArea)
widget.setParent(self.subwindow)
self.subwindow.setWidget(widget)
self.subwindow.setWindowTitle("testing")
self.ui.mdiArea.addSubWindow(self.subwindow)
widget.show()
self.subwindow.show()
self.subwindow.widget().show()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyApp()
window.show()
window.Add_Subwindow()
sys.exit(app.exec_())
Just wanted to say thanks for the code in OP - was looking for a simple MDI example in PyQT, and yours helped I lot! I don't exactly have an answer, but this is what I can note so far: I have Python 2.7,PyQT 4.8.3, and just with commenting the setTabsClosable and setTabsMovable line, I could get your example to show like this:
I downloaded designer-qt4 and looked there about QMdiArea, there seems to be nothing called TabbedView. So I found this:
QtWidgets 5.0: QMdiArea Class | Documentation | Qt Project
enum ViewMode { SubWindowView, TabbedView }
This enum describes the view mode of the area; i.e. how sub-windows will be displayed.
SubWindowView 0 Display sub-windows with window frames (default).
TabbedView 1 Display sub-windows with tabs in a tab bar.
documentMode: This property holds whether the tab bar is set to document mode in tabbed view mode.
The way I read this: either you get to display subwindows in MDI fashion (so they can be larger than the window, with scrollbars) or the subwindows become tabs in tabbed view - and there the size of the subwindow doesn't matter anymore, so it expands to take up the available tabbed area. Also, in your code, self.ui.mdiArea.documentMode() returns False in both cases.
I also added this snippet at end of your MyApp.Add_Subwindow():
sp = self.subwindow.sizePolicy()
print sp.__dict__
#print dir(sp)
for attr in dir(sp):
try:
print "obj.%s = %s" % (attr, getattr(sp, attr))
except: pass
This dumps some interesting data (I'm not sure if those are object properties, though):
obj.ButtonBox = 2
obj.CheckBox = 4
obj.ComboBox = 8
obj.ControlType = <class 'PyQt4.QtGui.ControlType'>
obj.ControlTypes = <class 'PyQt4.QtGui.ControlTypes'>
obj.DefaultType = 1
obj.ExpandFlag = 2
obj.Expanding = 7
obj.Fixed = 0
obj.Frame = 16
...
... but also these don't change in running tabbed vs. MDI mode.
So, maybe this is the intended behavior? If that is so, that would mean you'd have to find something like a "lone" tab display widget; add programmatically several QMdiAreas; hide all of them but the default one at start; and then bind a click on respective tabs to show "their" QMdiArea and hide the others (but needless to say, I haven't tested it).