pyqt4 - singleapplication - bring up the original window in an attempt to open the app for the second time - qt

I want only one instance of my app to be running at each time. but when the user attempts to open it the second time, I want the first window to be brought to the front (it could be just minimized or minimized to the corner of the taskbar and the user doesn't know how to open it)
I have this code that does the detection job and it doesn't allow the second instance. I have trouble with the part that it has to open the original window. I have commented out some of my attempt.
import sys
from PyQt4 import QtGui, QtCore
import sys
class SingleApplication(QtGui.QApplication):
def __init__(self, argv, key):
QtGui.QApplication.__init__(self, argv)
self._activationWindow=None
self._memory = QtCore.QSharedMemory(self)
self._memory.setKey(key)
if self._memory.attach():
self._running = True
else:
self._running = False
if not self._memory.create(1):
raise RuntimeError(
self._memory.errorString().toLocal8Bit().data())
def isRunning(self):
return self._running
def activationWindow(self):
return self._activationWindow
def setActivationWindow(self, activationWindow):
self._activationWindow = activationWindow
def activateWindow(self):
if not self._activationWindow:
return
self._activationWindow.setWindowState(
self._activationWindow.windowState() & ~QtCore.Qt.WindowMinimized)
self._activationWindow.raise_()
self._activationWindow.activateWindow()
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.label = QtGui.QLabel(self)
self.label.setText("Hello")
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.label)
if __name__ == '__main__':
key = 'random _ text'
app = SingleApplication(sys.argv, key)
if app.isRunning():
#app.activateWindow()
sys.exit(1)
window = Window()
#app.setActivationWindow(window)
#print app.topLevelWidgets()[0].winId()
window.show()
sys.exit(app.exec_())

I've made this work on Windows using the win32 api (I'm not entirely sure, but there are probably equivalent calls on macos/unix).
Add the following import to your application,
import win32gui
set the window title to a fixed name (instead of doing this, you could store its whndl in the shared memory)
window = Window()
window.setWindowTitle('Single Application Example')
window.show()
and then change your activateWindow method to something like the following:
def activateWindow(self):
# needs to look for a named window ...
whndl = win32gui.FindWindowEx(0, 0, None, "Single Application Example")
if whndl is 0:
return #couldn't find the name window ...
#this requests the window to come to the foreground
win32gui.SetForegroundWindow(whndl)

You might be interested by the solutions proposed here
For instance, I would try:
app = SingleApplication(sys.argv, key)
if app.isRunning():
window = app.activationWindow()
window.showNormal()
window.raise()
app.activateWindow()
sys.exit(1)
window = Window()
app.setActivationWindow(window)
window.setWindowFlags(Popup)
window.show()

Related

How do I prevent double valueChanged events when I press the arrows in a QSpinbox?

Could be a either a Qt bug, or a bug in my app, I am hoping someone can help me diagnose this.
I am building a PyQt5 application with an interface built using qtdesigner and pyuic5.
I have to QSpinBox widgets in my window, which I connect to two methods like this:
self.img1IndexBox.valueChanged.connect(self.changeImage1)
self.img2IndexBox.valueChanged.connect(self.changeImage2)
Everything seems to work fine in the application, except for one thing: If I scroll the mouse over the spinbox, I can make it increment the value. If I change the value with text, all works fine. If I use keyboard arrows on the spinbox, it works fine. But if I click on either the up or down arrows from the spinbox, I get get two changeValue events, a double increment. Like I clicked twice. In fact, it even looks from the animation that it is creating an event for the downpress, and another when the button goes back up.
Could this be just a library bug, or what could be causing this in my program? How could I debug this?
You might be able to prevent that double thing by setting spinbox enable to false.
then make it enable to true after processing large data.
Upon clicking the arrow up/down
on_valuechanged
ui->spinbox->setEnabled(false);
then set to true before the function on_valuechanged ends.
ui->spinbox->setEnabled(true);
Apparently the problem is the event is triggering a very long routine, this delays the "button release" event, and it is enough time to make the system think the user is actually holding the button, generating more events... But I would still be interested in learning what would be a good walk-around. Would there be a nice pyqt-onic way to start a thread for that method?
http://www.qtcentre.org/archive/index.php/t-43078.html
Instead of using valueChanged.connect use editingFinished.connect it will make sure the function is called only after value is provided.
PyQt
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDoubleSpinBox, QApplication, QMainWindow, QWidget, QVBoxLayout, QLabel, QSpinBox
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
# def valueChanged(self, value):
# print(value)
def valueChanged(self):
print(f"Value changed new value is : {self.spinBox.value()}")
def initUI(self):
self.setGeometry(0, 0, 200, 100)
self.layout = QVBoxLayout()
self.spinBox = QSpinBox()
self.spinBox.setAlignment(Qt.AlignCenter)
self.spinBox.setRange(0, 1000)
# self.spinBox.valueChanged.connect(self.valueChanged)
self.spinBox.editingFinished.connect(self.valueChanged)
self.layout.addWidget(self.spinBox)
self.setLayout(self.layout)
if __name__ == "__main__":
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec())
PySide
import sys
from PySide6.QtWidgets import QDoubleSpinBox, QApplication, QMainWindow, QWidget, QVBoxLayout, QLabel, QSpinBox
from PySide6.QtGui import Qt
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
# def valueChanged(self, value):
# print(value)
def valueChanged(self):
print(f"Value changed new value is : {self.spinBox.value()}")
def initUI(self):
self.setGeometry(0, 0, 200, 100)
self.layout = QVBoxLayout()
self.spinBox = QSpinBox()
self.spinBox.setAlignment(Qt.AlignCenter)
self.spinBox.setRange(0, 1000)
# self.spinBox.valueChanged.connect(self.valueChanged)
self.spinBox.editingFinished.connect(self.valueChanged)
self.layout.addWidget(self.spinBox)
self.setLayout(self.layout)
if __name__ == "__main__":
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec())

Ending a QDrag Prematurely

I want my application to terminate all drag and drops in a dragLeaveEvent, without the user releasing the mouse button.
The problem is that the loop suspends all events that could cancel a QDrag while it is happening, even though the documentation states:
"On Linux and Mac OS X, the drag and drop operation can take some
time, but this function does not block the event loop. Other events
are still delivered to the application while the operation is
performed. On Windows, the Qt event loop is blocked during the
operation. However, QDrag.exec() on Windows causes processEvents() to
be called frequently to keep the GUI responsive. If any loops or
operations are called while a drag operation is active, it will block
the drag operation."
Because of this, I cannot call events which would end the drag.
So far, I've tried what is suggested here, as seen in the code. I'm using PyQt5, but if a solution works in Qt it should work in PyQt.
Edit: I'm a little scared to delete the drag, as the scene does not own it. I suppose I could set it up to own it though, but as was posted here it should not work.
Edit2: Added code with my non-working attempts to fix it. I'd really like to solve this issue without having to make my own drag-drop framework. Also trimmed post.
import sys
from PyQt5.QtWidgets import (QMainWindow, QApplication,
QGraphicsView, QGraphicsScene, QGraphicsWidget, QGraphicsRectItem)
from PyQt5.QtCore import (QMimeData, Qt, QByteArray, QCoreApplication,
QEvent, QPoint)
from PyQt5.QtGui import QBrush, QColor, QDrag, QPen, QMouseEvent
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.scene = CustomScene()
self.view = QGraphicsView(self.scene, self)
self.setGeometry(100, 100, 600, 600)
self.view.setGeometry(0, 0, 500, 500)
self.show()
class CustomScene(QGraphicsScene):
def __init__(self):
super().__init__()
self.customWidgets = []
for i in range(5):
newItem = CustomDragWidget()
self.addItem(newItem)
self.customWidgets.append(newItem)
newItem.setGeometry(i * 50, i * 50, 50, 50)
def dragLeaveEvent(self, event):
# Work your magic here. I've tried the following:
# 1)
self.customWidgets[0].dropEvent(event)
# 2)
self.dropEvent(event)
# 3)
eve = QMouseEvent(QEvent.MouseButtonRelease, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
QCoreApplication.sendEvent(self.views()[0], eve)
QCoreApplication.processEvents()
# 4)
eve = QMouseEvent(QEvent.MouseButtonRelease, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
QCoreApplication.sendEvent(self.customWidgets[0], eve)
QCoreApplication.processEvents()
def dropEvent(self, QGraphicsSceneDragDropEvent):
# a dummy dropevent that tries to stop the drop, but doesnt work
QGraphicsSceneDragDropEvent.accept()
class CustomDragWidget(QGraphicsWidget):
def __init__(self,):
super().__init__()
self.squareItem = QGraphicsRectItem()
self.squareItem.setBrush(QBrush(QColor(Qt.blue)))
self.squareItem.setPen(QPen(QColor(Qt.black), 2))
self.squareItem.setRect(0, 0, 50, 50)
self.squareItem.setParentItem(self)
self.setAcceptDrops(True)
def mousePressEvent(self, event):
mime = QMimeData()
itemData = QByteArray()
mime.setData('application/x-dnditemdata', itemData)
drag = QDrag(self)
drag.setMimeData(mime)
drag.exec(Qt.MoveAction)
def dropEvent(self, event):
event.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
This is a little bit hackish, but it seems to work (on Linux, anyway):
def dragLeaveEvent(self, event):
QCoreApplication.postEvent(self,
QKeyEvent(QEvent.KeyPress, Qt.Key_Escape, Qt.NoModifier))

How to show clickable QFrame without loosing focus from main window?

Finally I am able to create a chrome like tab in Qt/PyQt QMainWindow. After unsuccessfully trying to port this Qt C++ non client area painting code, I revise my thinking to be this way : trick visually by displaying a free floating QFrame that get resized and moved together with main window. Surely this is not a perfect solution (for example this code still don't solve when to disable topmost hint where the another application is on top of the main application window, but I think that's quite easy to solve)
What I want to ask in this page, is how to keep the click action on this QFrame window button from stealing focus from main window? At the moment I simply reactivate the main window when click action does occur. But it creates flashing effect on the mainwindow titlebar. I believe this SO page gives the answer, but I haven't been able to create a successful result from this C++ code:
HWND winHandle = (HWND)winId();
ShowWindow(winHandle, SW_HIDE);
SetWindowLong(winHandle, GWL_EXSTYLE, GetWindowLong(winHandle, GWL_EXSTYLE)
| WS_EX_NOACTIVATE | WS_EX_APPWINDOW);
ShowWindow(winHandle, SW_SHOW);
Into this PyQt code:
def no_focus(self):
import ctypes, win32con, win32gui
dc = win32gui.GetWindowDC(self.winId())
user32 = ctypes.windll.user32
user32.SetWindowLongW(dc, win32con.GWL_EXSTYLE, user32.GetWindowLongW(dc, win32con.GWL_EXSTYLE) | win32con.WS_EX_NOACTIVATE | win32con.WS_EX_APPWINDOW)
Would love to let you see and test the fully functional code below:
__author__ = 'Eko Wibowo'
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class FSTabHeader(QFrame):
def __init__(self, parent):
super(FSTabHeader, self).__init__(None)
self.mainwindow = parent
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.SplashScreen)
self.setFocusPolicy(Qt.NoFocus)
self.setAttribute(Qt.WA_ShowWithoutActivating)
layout = QHBoxLayout(self)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
tab_text = 'Click me'
self.btn_tab = QPushButton(self)
self.btn_tab.setStyleSheet('border:1px')
self.btn_tab.setContentsMargins(0,0,0,0)
self.btn_tab.setText(tab_text)
self.btn_tab.setMinimumHeight(25 + 1)
self.btn_tab.setMaximumHeight(25 + 1)
self.btn_tab.setMinimumWidth(60)
self.btn_tab.setMaximumWidth(60)
self.btn_tab.setCursor(Qt.PointingHandCursor)
self.btn_tab.clicked.connect(self.dummy)
layout.addWidget(self.btn_tab)
self.setLayout(layout)
self.show()
def dummy(self):
print 'it create flashes effect on mainwindow titlebar'
self.mainwindow.activateWindow()
def no_focus(self):
import ctypes, win32con, win32gui
dc = win32gui.GetWindowDC(self.winId())
user32 = ctypes.windll.user32
user32.SetWindowLongW(dc, win32con.GWL_EXSTYLE, user32.GetWindowLongW(dc, win32con.GWL_EXSTYLE) | win32con.WS_EX_NOACTIVATE | win32con.WS_EX_APPWINDOW)
def adjust_position(self):
top_left = self.mainwindow.mapToGlobal(self.mainwindow.rect().topLeft())
self.move(top_left.x() + 20 + 5, top_left.y() - self.height() + 1)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.tab_header = FSTabHeader(self)
self.tab_header.no_focus()
def resizeEvent(self, *args, **kwargs):
self.tab_header.adjust_position()
def moveEvent(self, *args, **kwargs):
self.tab_header.adjust_position()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
mainWindow = MainWindow(None)
mainWindow.show()
app.exec_()
Any suggestions?

PyQt: proper way to connect QTabWidget.tabCloseRequested to a slot

I've got a simple sample PyQt application with a QTabWidget. I can't connect QTabWidget's tabCloseRequested signal to a slot, so that the tab is closed properly:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Application(object):
def __init__(self):
app = QApplication(sys.argv)
self.window = QMainWindow()
self.notebook = QTabWidget()
self.notebook.tabBar().setTabsClosable(True)
self.notebook.tabBar().setMovable(True)
self.notebook.tabCloseRequested.connect(self.close_handler)
self.window.setCentralWidget(self.notebook)
page1 = QWidget()
self.notebook.addTab(page1, "page1")
page2 = QWidget()
self.notebook.addTab(page2, "page2")
self.window.show()
sys.exit(app.exec_())
def close_handler(self, index):
print "close_handler called, index = %s" % index
self.notebook.removeTab(index)
if __name__ == "__main__":
app = Application()
When I click on the close button, nothing happens. Not even the print, which should be invoked! What am I doing wrong?
You need to call setTabsClosable(True) on the tab-widget, rather than its tab-bar:
self.notebook.setTabsClosable(True)
(PS: the close_handler method is also missing a self argument).

PySide how to set focus to new window and open it if not exists

I have started learning PySide from example of Zetcode and try to code app which had two windows: "Schematic View" which is parent of "Layout View", each with menu bar. On start that should be only schematic window, and layout win should be started by switchtoLAYOUT in root of menubar.
My questions are:
How to make "switchtoLAYOUT" in root not to show the dropdown and still do action with only one instance of "Layout View" window ?
How to switch the focus between two windows ("switchtoLAYOUT" and "switchtoSCHEMATIC")?
Please inspect my code and suggest me something smart (that should not be hard).
The Code:
import sys
from PySide import QtCore, QtGui
class schematicWindow(QtGui.QMainWindow):
def __init__(self):
super(schematicWindow, self).__init__()
self.defineSchWin()
def defineSchWin(self):
exitAction = QtGui.QAction('&Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.close)
self.statusBar()
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAction)
menubar.addMenu('&Edit')
menubar.addMenu('&Passives')
menubar.addMenu('&Descretes')
menubar.addMenu('&IC\'s')
swToLayMenu = menubar.addMenu('switchtoLAYOUT')
swToLayAction = QtGui.QAction(self)
swToLayAction.triggered.connect(self.layoutWindow)
swToLayMenu.addAction(swToLayAction) # open layoutWindow (if not exists)
# and set focus to layoutWindow
self.setGeometry(0, 300, 500, 300)
self.setWindowTitle('Schematic View')
self.show()
def layoutWindow(self):
window = QtGui.QMainWindow(self)
window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
window.statusBar()
menubar = window.menuBar()
switchtoSchMenu = menubar.addMenu('switchtoSCHEMATIC')
window.setGeometry(100, 600, 500, 300)
window.setWindowTitle('Layout View')
window.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = schematicWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You need to keep a reference to the layout window in your class, (you should put self.layout_window = None in the __init__). This function now checks if the window has been initialized, makes it if it has not been, makes sure it is visible, and then sets the new window to be the active window. Something like: (this is not tested)
def layoutWindow(self):
if self.layout_window is None:
window = QtGui.QMainWindow(self)
self.layout_window = window
window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
window.statusBar()
menubar = window.menuBar()
switchtoSchMenu = menubar.addMenu('switchtoSCHEMATIC')
window.setGeometry(100, 600, 500, 300)
window.setWindowTitle('Layout View')
else:
window = self.layout_window
window.show()
window.activateWindow()
window.raise() # just to be sure it's on top
(doc)

Resources