Can I create a panel-like thing on Windows with Python? - python

Sorry for the vague title, couldn't come up with anything more informative %)
What I want is a 5px horizontal panel on the top of the screen that I can draw on (and, possible, handle clicks on too).
One of the following features would be awesome (although I understand it's probably not really possible to combine both of them):
the panel should be just like the Windows's own taskbar, i.e., maximized windows should not overlap it, but start below it instead
the panel should show in fullscreen apps too
Is it possible to do this in Python?
Thanks.

Yes, it's possible. The "how" part depends on the GUI library you choose for which there are many options, but most people will recommend the following two: wxPython or PySide which is Qt for Python.
PySide has good documentation and tutorials.
What you will want to do is create a QMainWindow instance and set the WindowFlags to your requirements. You probably want the following combination Qt::Window | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint.
Something like this:
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class Form(QMainWindow):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint)
if __name__ == '__main__':
# Create the Qt Application
app = QApplication(sys.argv)
# Create and show the form
form = Form()
form.show()
# Run the main Qt loop
sys.exit(app.exec_())
Note, that there is a limit to the "staying on top" nature of such windows. There are Win32-specific ways to fight it and get even higher, but such requirement would be a design error.

Related

QDialog as central widget layout in QMainWindow

Can you display a QDialog as the central widget of a QMainWindow in Python? If so, how would you do it? I am trying to find the easiest way to add a menu bar which is only available with QMainWindow to my understanding. Is it possible to connect the two together?
tl;dr
No, but you can add a menubar to a layout set for the QDialog, using setMenuBar() or even by adding the menubar as you would do for any other widget, just by doing that on "top" of that layout.
Is it possible?
The technical answer is "yes": since QDialog is a QWidget, you can just use setCentralWidget() as you would do with any other QWidget subclass.
The real answer is "NO: don't do it!".
QDialog, just like QMainWindow, is intended to be a top level widget (aka, a "window"), so it should never be added as a child of a widget or in its layout.
There are very few exceptions to that:
when using systems that are intended as possible "containers" of windows, which are:
QMdiArea;
the Graphics View framework;
in very special cases (such as attempting custom layouts of dock widgets), for which you should really know what you're doing, be really aware of the limitations and aspects related to the UX;
Most importantly, QDialog has specific flags and event filters that might be problematic.
For instance, take this simple example:
mainWindow = QMainWindow()
dialog = QDialog()
layout = QVBoxLayout(dialog)
layout.addWidget(QPushButton())
mainWindow.setCentralWidget(dialog)
mainWindow.show()
Now, just press Esc, and you'll see that the dialog disappears.
The same happens by adding a QDialog as a child of any widget, clearly meaning that it should never be done (unless when using the "container" systems listed above).
This is one of the many reasons for which some tutorials on YouTube should be completely disregarded (since they provide terrible suggestions, like adding a QMainWindow or QDialog to a QStackedWidget).
The solution
Actually, it's very simple: just add the menubar to the top level layout of the dialog, just like you would do for any other widget.
Besides, consider that all Qt layout managers inherit from QLayout, which has a very basic and important function that is often ignored: setMenuBar().
Tells the geometry manager to place the menu bar widget at the top of parentWidget(), outside QWidget::contentsMargins(). All child widgets are placed below the bottom edge of the menu bar.
Note that this is actually a "convenience" feature, and it only works for the "top level" layout: if you add the menubar to a nested layout, it won't be considered in the whole size hint/policy computation, and it will be probably shown above (in the z stacking level) any other widget near it.
Also note that, for obvious reasons, this cannot be done from Designer. If you have a QDialog created in Designer and you want to add a menubar, you have to do it by code.
Assuming that you properly set a top level layout (as you should always do) for your dialog in Designer:
from myDialog import Ui_MyDialog
from PyQt5.QWidgets import *
class MyDialog(QDialog, Ui_MyDialog):
def __init__(self):
super().__init__()
self.setupUi(self)
self.menuBar = QMenuBar()
self.layout().setMenuBar(self.menuBar)
self.fileMenu = self.menuBar.addMenu('File')
self.someAction = self.fileMenu.addAction('Some action')
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
dialog = MyDialog()
dialog.exec()

PyQt5: create multiple views with designer and connect them in one app

I have completed a simple app in PyQt5, where I did design the UI in QT designer, converted in py code with pyuic5and ran it via python interpreter.
Now I would like to add another UI view, although I am not familiar with PyQt5, and most of the tutorial I found are only mentioning one view.
If I was using Visual Studio for example, I could create a new form, and use show and hide methods to display them, when I press a button for example, but I am not sure how to do the same with PyQt5.
Converted code from pyuic5 include also the if __name__ == "__main__" function, which create the instance and run the app, so is it enough to just take anything above it, to get the UI data only? And how do I create a view from that, so I can show and hide it as needed? Thanks.
EDIT:
Got a bit further, since I found a different way to load UI files. Seems that PyQt has a method that is able to load a UI file directly, instead of convert it in python code. This means that I can create a class that is a subclass of the type of window that I am using (example: QApplication, QMainWindow, QWidget and so on), and I can access that object as if it was a form in Visual Studio.
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow
from PyQt5.uic import loadUI
class UIObject(QMainWindow):
def __init__(self):
super(UIObject, self).__init__()
loadUI('mainapp.ui', self)
self.mybutton1.clicked.connect(self.printhello)
def printhello():
print("hello")
app = QApplication(sys.argv)
mainview = UIObject()
mainview.show()
sys.exit(app.exec_())
This will load the UI file and show it on screen; I assume that I can use the same construct to load multiple ui files and show then or hide them as I do in Visual studio? Seems straightforward but not knowing much about QT or PyQT, I am not sure why this way to handle ui files is not more commonly advertised in tutorials; I found it by chance while reading the docs.
Found the solution, mixing up various answers and posts from different forums.
You create a first class as QMainWindow, in the __init__ you use loadUi to load the QT designer file. Then you create a second class, which is the one that hold your second form/view, and in the __init__ you pass as parameter the parent view (your first class, or whatever other you may need); so you can hide the main view and show the second view when clicking a button. When you close the secondary view, the previous view will show up again.
You can add as many different windows you want; the trick is to always pass the parent on each of them and remember to show/hide them accordingly. Much more complex than Visual Studio forms, but it is doable.
class FirstForm(QMainWindow):
def __init__(self):
super(FirstForm, self).__init__()
loadUi('firstform.ui', self)
self.button1.clicked.connect(self.openOtherForm)
def openOtherForm(self):
self.hide()
otherview = SecondForm(self)
otherview.show()
class SecondForm(QDialog):
def __init__(self, parent=None):
super(SecondForm, self).__init__(parent)
loadUi('secondform.ui', self)
self.button2.clicked.connect(self.goBackToOtherForm)
def openOtherForm(self):
self.parent().show()
self.close()
app = QApplication(sys.argv)
main = FirstForm()
main.show()
sys.exit(app.exec_())

Can't get MDI tab order right

I'm using QtDesigner and have an MDI application with two tabs. I can't seem to get the tabs to open up the way I want them to. I'd like subwindow to have the focus and be the first one on the left when the application opens up. So 'subwindow_2' would be on the right and would not have the focus when the application opens.
I have the focus correct when the application opens. But the tab order is wrong. In other words the focus is on 'subwindow' but that shows up on the right instead of the left.
I've tried a number of things in QtDesigner such as changing activation order between creation order and stacking order but that seems to have no effect. How do I fix this ?
import sys
from PyQt4 import QtCore, QtGui
from phreqMDI import Ui_MainWindow
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MyForm,self).__init__(parent)
self.ui=Ui_MainWindow()
self.ui.setupUi(self)
self.ui.mdiArea.addSubWindow(self.ui.subwindow_2)
self.ui.mdiArea.addSubWindow(self.ui.subwindow)
Someone mentioned that pyuic4 doesn't do well with MDI. Perhaps there is something I need to change in my code instead of doing it in QtDesigner ?
You need to add them in the correct tab order, and then explicitly activate the relevant subwindow:
self.ui.mdiArea.addSubWindow(self.ui.subwindow)
self.ui.mdiArea.addSubWindow(self.ui.subwindow_2)
self.ui.mdiArea.setActiveSubWindow(
self.ui.mdiArea.subWindowList()[0])
PS:
For some reason, it seems necessary to use the subWindowList when setting the active subwindow during __init__. Passing the return value of addSubWindow to setActiveSubWindow didn't work for me - but that may be a platform-specific thing (I tested on Linux).
UPDATE:
So, assuming you're on Windows, there do seem to be some platform-specific differences. As a workaround, try setting the active subwindow with a timer, like this:
self.ui.mdiArea.addSubWindow(self.ui.subwindow)
self.ui.mdiArea.addSubWindow(self.ui.subwindow_2)
QtCore.QTimer.singleShot(10,
lambda: self.ui.mdiArea.setActiveSubWindow(
self.ui.mdiArea.subWindowList()[0]))

Text tree in Python GUI

I'm looking for GUI feature that will be part of Python desktop application.
The feature is a text tree with drag and drop functionality between individual nodes.
Further I'd like to be able to attach a note with longer text to individual nodes in the tree. The note would be switchable to expanded or collapsed state.
It is exactly shown in this NestedSortable JQuery library except of the switchable note.
Could you show me what are the possibilities of contemporary Python GUIs according to this feature? I prefer lightweight GUI with modern look as in the JQuery example.
Which GUI would be the most suitable for this task? Tkinter, wxPython, pyQt, pyGTK or other? Or would you choose some GUI + Javascript libraries? Why would you prefer particular GUI to achieve requested functionality and lightweight modern design?
pyQT has a good solution for this with its QTreeWidget or a MVC setup. QTreeWidget is simplest:
Here's a small example using PyQt4
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.treewidget = QTreeWidget(self)
self.treewidget.setHeaderLabels(['a'])
self.treewidget.setDragEnabled(True)
self.treewidget.setAcceptDrops(True)
self.treewidget.setDropIndicatorShown(True)
self.treewidget.resize(300,300)
self.titems = []
for i in xrange(100):
if not i%10:
pitem = QTreeWidgetItem(self.treewidget,["Parent %d"%i])
self.titems.append(pitem)
else:
item = QTreeWidgetItem(pitem,["Child of %d"%i])
self.titems.append(item)
self.show()
app = QApplication(sys.argv)
w = MainWindow()
app.exec_()
Try using etetoolkit. Its much simpler.

Cross-platform Python GUI suitable for taskbar (Win) and menubar (mac) functionality?

I am fairly new to Python programming, and completely new to cross-platform GUI building (only previous GUI experience is through visual basic and Java).
I've written some python code to screen-scrape data from a website, and now I want to build a GUI that will reside in the Mac OS X menubar, and in Window's task bar (i.e., the system tray).
The most useful general page on cross-plaform Python GUIs for me was this one (despite its name indication Window GUIs). And some stackoverflow questions came in useful as well (especially this one, and the accepted answer of this one about splitting up the GUI and cli code).
I think I will go for either wxPython or QT because I want the GUI to look as native as possible.
However, as I've said the fairly simple GUI will mainly live in the taskbar/menubar.
Should this influence my decision?
Here's an example for PyQt. This works for me on MacOS X; I haven't tried it on other platforms. Note that the QSystemTrayIcon class will raise exceptions if it doesn't have an icon – I grabbed the RSS feed svg from Wiki commons for my icon.svg (but you can give QIcon a PNG directly and not mess around with QtSvg).
import PyQt4
from PyQt4 import QtCore, QtGui, QtSvg
app = QtGui.QApplication([])
i = QtGui.QSystemTrayIcon()
m = QtGui.QMenu()
def quitCB():
QtGui.QApplication.quit()
def aboutToShowCB():
print 'about to show'
m.addAction('Quit', quitCB)
QtCore.QObject.connect(m, QtCore.SIGNAL('aboutToShow()'), aboutToShowCB)
i.setContextMenu(m)
svg = QtSvg.QSvgRenderer('icon.svg')
if not svg.isValid():
raise RuntimeError('bad SVG')
pm = QtGui.QPixmap(16, 16)
painter = QtGui.QPainter(pm)
svg.render(painter)
icon = QtGui.QIcon(pm)
i.setIcon(icon)
i.show()
app.exec_()
del painter, pm, svg # avoid the paint device getting
del i, icon # deleted before the painter
del app
See this related SO answer on how to accomplish Windows system tray/OS X menu bar functionality in wxPython.

Categories

Resources