Pyqt5: How to use SH_ToolTip_WakeUpDelay? - python

I've read that I can use QStyle.SH_ToolTip_WakeUpDelay to create a delay before the tool tip is shown, but I didn't figured out how exactly. I already read this question: How do I use QStyle::SH_ToolTip_WakeUpDelay to set tooltip wake-up time?
I'm not familiar with C++, but I tried to recreate it. I just made a class and overwrited the method styleHint, but it doesn't work.
My code:
class ProxyStyle(QProxyStyle):
def __init__(self):
super().__init__()
def styleHint(self, hint: QStyle.StyleHint, option: Optional['QStyleOption'] = ..., widget: Optional[QWidget] = ..., returnData: Optional['QStyleHintReturn'] = ...) -> int:
if hint == QStyle.SH_ToolTip_WakeUpDelay:
return 1000 # I just assumed it's in milliseconds, so I did 1000 to have a delay of 1s.
return QProxyStyle.styleHint(hint, option, widget, returnData)
As the guy answered in the above mentioned question, I added an instance of the class to my application. I don't know exactly if I understood it correctly, but I just did that:
proxyStyle = QProxyStyle()
app = QApplication([proxyStyle])
app.exec()
Edit
I did it like that now, but it also doesn't work (I expect a 1s delay):
proxyStyle = QProxyStyle()
app = QApplication([])
app.setStyle(proxyStyle)

Related

How does one update a field from outside the __init__() function with pyqt5

I am reading a sensor and want to display its output as a decimal number in a GUI using PyQt5. I have found a number of tutorials that point out the label.setText('myStr') function. This does not work for my setup, however, because I need to update the field based on the input from another function. I'm not very familiar with PyQt5 yet, and I would appreciate any insight into how this problem ought to be approached.
Note: (I am using LCM to acquire data from a Raspberry Pi. I'm not sure that that is relevant to the problem, but it helps explain my code below.)
Here is what I am trying to do:
class Home_Win(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loadUi("sensor_interface.ui", self)
self.label_temp.setText('temperature') #Just to verify that I can change it from here
def acquire_sensors(self):
temp = 0 #Make variable available outside nested function
def listen(channel, data):
msg=sensor_data.decode(data)
temp = msg.temperature
lc = lcm.LCM()
subscription = lc.subscribe("sensor_data_0", listen)
while True:
lc.handle()
self.label_temp.setText(str(temp))
Any thoughts on how I can update the GUI to display the readings I am getting from my sensors?
Thanks!
You're almost there. All you need to do is to save the ui in an instance variable in __init__:
self.ui = loadUi("sensor_interface.ui", self)
Then, assuming label_temp is the name of your QLabel widget, just do:
self.ui.label_temp.setText(str(temp))
It turned out that I needed to add repaint(). I also switched to a QLineEdit as this seemed to work better for me. So inside the while loop I now have:
self.ui.lineEdit_temp.setText(str(temp))
self.ui.lineEdit_temp.repaint()
This now outputs live updates to the GUI while reading the data stream.

QFileSystemModel not updating when files change

I'm having trouble with QFileSystemModel not showing changes to files. When a file is first created it immediately shows up. But when the file itself changes, the size and timestamp don't update. I've made multiple attempts at trying to force the model to update with no real success. The best I've achieved is to completely replace the model. Although that results in this error:
QSortFilterProxyModel: index from wrong model passed to mapToSource
The test code below creates a table view of an empty directory. The left button creates a file (foo.txt) when clicked. Successive clicks append data to the file. It was my understanding that the QFileSystemModel didn't need a refresh, but the second button is my attempt at that.
Any help as to what I'm doing wrong would be greatly appreciated!
# Testing with python3.6.3 and pip installed pyqt5 5.9.2 in virtualenv on Ubuntu
import os, sys, tempfile
from PyQt5 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self._view = QtWidgets.QTableView()
layout.addWidget(self._view)
self._modify_button = QtWidgets.QPushButton('Create')
layout.addWidget(self._modify_button)
self._refresh_button = QtWidgets.QPushButton('Refresh')
layout.addWidget(self._refresh_button)
self._modify_button.clicked.connect(self._modify)
self._refresh_button.clicked.connect(self._refresh)
self._model, self._proxy = None, None
self.temp_dir = tempfile.TemporaryDirectory(dir=os.path.dirname(os.path.abspath(__file__)))
self.init_model(self.temp_dir.name)
def init_model(self, path):
self._model = QtWidgets.QFileSystemModel()
self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)
self._proxy = QtCore.QSortFilterProxyModel(self)
self._proxy.setSourceModel(self._model)
self._view.setModel(self._proxy)
# self._view.setModel(self._model)
self._model.directoryLoaded.connect(self._loaded)
self._model.setRootPath(path)
def _loaded(self):
path = self._model.rootPath()
source_index = self._model.index(path)
index = self._proxy.mapFromSource(source_index)
self._view.setRootIndex(index)
# self._view.setRootIndex(source_index)
def _modify(self):
"""Create or modify foo.txt..model should see and update"""
self._modify_button.setText('Modify')
file_name = os.path.join(self.temp_dir.name, 'foo.txt')
with open(file_name, 'a') as txt_file:
print('foo', file=txt_file)
# def _refresh(self):
# # This only seems to work once..and its a flawed approach since it requires permission to write
# temp = tempfile.NamedTemporaryFile(dir=self.temp_dir.name)
# def _refresh(self):
# self._model.beginResetModel()
# self._model.endResetModel()
# def _refresh(self):
# self._proxy.setFilterRegExp('foo')
# self._proxy.setFilterRegExp(None)
# self._proxy.invalidate()
# self._proxy.invalidateFilter()
# self._proxy.reset()
#
# root_index = self._model.index(self._model.rootPath())
# rows = self._model.rowCount(root_index)
# proxy_root_index = self._proxy.mapFromSource(root_index)
# topLeft = self._proxy.index(0, 0, proxy_root_index)
# bottomRight = self._proxy.index(rows - 1, self._model.columnCount(proxy_root_index) - 1, proxy_root_index)
# # self._proxy.dataChanged.emit(topLeft, bottomRight)
# self._model.dataChanged.emit(topLeft, bottomRight)
# def _refresh(self):
# # This only seems to work once
# self._model.setRootPath('')
# self._model.setRootPath(self.temp_dir.name)
def _refresh(self):
# This seems heavy handed..but seems to work
# ..though generates "QSortFilterProxyModel: index from wrong model passed to mapToSource" spam in console
self.init_model(self.temp_dir.name)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())
UPDATE:
As of Qt-5.9.4, the QT_FILESYSTEMMODEL_WATCH_FILES envrionment variable can be used to switch on per-file watching (see QTBUG-46684). This needs to be set once to a non-empty value before the model starts caching information about files. But note that this will add a file-watcher to every file that is encountered, so this may make it an expensive solution on some systems.
The original answer is left below as an explanation of the problem.
This problem is caused by a long-standing Qt bug: QTBUG-2276. Unfortunately, at present, it does not look likely that it will be fixed any time soon. As indicated in the bug report comments, the core of the issue seems to be this:
It's an OS limitation. A change to a file does not mean the directory
is modified.
The only real work-around for this would be to attach a QFileSystemWatcher to every single file, which could obviously be prohibitively expensive (on some platforms, anyway).
In addition to this problem, the QFileSystemModel class doesn't currently provide an API for forcing a refresh, and, as you have discovered, there does not seem to be any reliable work-around for that. Most "solutions" offered on SO and elsewhere, suggest some variant of this:
root = fsmodel.rootPath()
fsmodel.setRootPath('')
fsmodel.setRootPath(root)
But as you know, this seems to work only once - probably due to some quirk in the way file-info caching is currently implemented.
At present it appears the only way to force an update is to replace the entire model. The error messages produced by your current implementation of this can be prevented by refactoring your init_model method like this:
def init_model(self, path):
if self._proxy is None:
self._proxy = QtCore.QSortFilterProxyModel(self)
else:
# remove the current source model
self._proxy.setSourceModel(None)
self._model = QtWidgets.QFileSystemModel()
self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)
self._proxy.setSourceModel(self._model)
self._view.setModel(self._proxy)
self._model.directoryLoaded.connect(self._loaded)
self._model.setRootPath(path)
This is a very unsatisfactory situation, but there just doesn't seem to be any obvious way around it at the moment.
Since Qt v5.9.4 you can set the environment variable QT_FILESYSTEMMODEL_WATCH_FILES, you can read more about it in the changelog:
[QTBUG-46684] It is now possible to enable per-file watching by
setting the environment variable QT_FILESYSTEMMODEL_WATCH_FILES,
allowing to track for example changes in file size.
Couple of things:
For the time being you need to set it before initializing the model, after that you can set it to another folder without any problem.
Be aware this feature comes at the cost of potentially heavy load, though.

Passing argument to PyQt SIGNAL connection

Is it possible to pass an argument to PyQt4 Signal connections? In my case I have n buttons with set the same menu dinamically created on the interface, depending on user's input:
for j in range(0, len(self.InputList)):
arrow = QtGui.QPushButton(Form)
arrow.setGeometry(QtCore.QRect(350, 40*(j+3)+15, 19, 23))
menu = QtGui.QMenu(Form)
for element in SomeList:
cb = QtGui.QRadioButton(element,menu)
ca = QtGui.QWidgetAction(menu)
ca.setDefaultWidget(cb)
QtCore.QObject.connect(cb,QtCore.SIGNAL("stateChanged(int)"),self.SomeFunction)
arrow.setMenu(menu)
Although the menu is the same for all the buttons into the interface, the user should be able to select a value from any of the buttons and, for any of them, the action is the same ("add the selected value to a line edit") with the only difference that the line edit might be the 1st as well as the 2nd, the 3rd.
What I would like to ask, then, is if there's any way to pass an argument j here:
QtCore.QObject.connect(cb,QtCore.SIGNAL("stateChanged(int)"),self.SomeFunction(j))
EXAMPLE:
At this execution the user's inputs are 3, so I will have 3 line edits and three push buttons with the same menu:
Line Edit 1:
Line Edit 2:
Line Edit 3:
Using the same function SomeFunction, I'd like to edit the value of the Line Edits. So if the user is touching the menu attached to the 2nd line edit, the function SomeFunction shall be called with the argument 2 SomeFunction(2), so the same method will understand itself which Line Edit is the right one:
Line Edit 1:
Line Edit 2: modified
Line Edit 3:
I need this because the number of Line Edits on the main window depends on what the user is selecting. I'm a newbie and so far I've always created one function for any object into the GUI, but this time the number is dynamic and I'm sure there are some more elegant ways to create this kind of signal connections, that I have not understood though so far from my documentations reading.
Here is a different approach: instead of attaching the data as an argument to the signal handler, attach it to the menu-item itself. This offers much greater flexibility, because the data is not hidden inside an anonymous function, and so can be accessed by any part of the application.
It is very easy to implement, because Qt already provides the necessary APIs. Here is what your example code would look like if you took this approach:
for j in range(0, len(self.InputList)):
arrow = QtGui.QPushButton(self)
arrow.setGeometry(QtCore.QRect(350, 40*(j+3)+15, 19, 23))
menu = QtGui.QMenu(self)
group = QtGui.QActionGroup(menu)
for element in SomeList:
action = menu.addAction(element)
action.setCheckable(True)
action.setActionGroup(group)
action.setData(j)
arrow.setMenu(menu)
group.triggered.connect(self.SomeFunction)
def SomeFunction(self, action):
print(action.data())
Be advised that you are using a deprecated version of SIGNAL/SLOT implementation in PyQt that has been removed in PyQt5 (and even its current implementation in PyQt4 is somewhat buggy).
If possible, I would change the SIGNAL/SLOT syntax in your code.
The way the new Signal/Slot mechanism works is this:
class Worker(QThread):
stateChanged = Signal(int)
....
def some_method(self):
self.stateChanged.emit(1)
In the GUI thread, you would similarly have something like this (assuming worker = Worker() is defined somewhere:
class GUI(QDialog)
worker = Worker()
def __init__(self, parent=None):
super(GUI, self).__init__(parent)
self.worker.stateChanged.connect(self.my_desired_method)
def my_desired_method(self, param):
print param #prints out '1'
Of course, this code wouldn't work "out of the box", but it's a general concept of how Signals/Slots should be handled in PyQt (and PySide).
I think this should help.
self.op = "point"
QtCore.QObject.connect(self.radioButton, QtCore.SIGNAL("clicked(bool)"),lambda : self.anyButton(self.radioButton.isChecked(),self.op))
def anyButton(self,kol,vv):
print kol
print vv
The output is
>>>
True
point
you can take a look at this : PyQt sending parameter to slot when connecting to a signal

Enter-Notify-Event Signal not working on gtk.ToolButton

On a happy (if not irrevelent) note, this is the absolute last obstacle in this particular project. If I fix this, I have my first significant dot release (1.0), and the project will be going public. Thanks to everyone here on SO for helping me through this project, and my other two (the answers help across the board, as they should).
Now, to the actual question...
I have a toolbar in my application (Python 2.7, PyGTK) which has a number of gtk.ToolButton objects on it. These function just fine. I have working "clicked" events tied to them.
However, I need to also connect them to "enter-notify-event" and "leave-notify-event" signals, so I can display the button's functions in the statusbar.
This is the code I have. I am receiving no errors, and yet, the status bar messages are not appearing:
new_tb = gtk.ToolButton(gtk.STOCK_NEW)
toolbar.insert(new_tb, -1)
new_tb.show()
new_tb.connect("clicked", new_event)
new_tb.connect("enter-notify-event", status_push, "Create a new, empty project.")
new_tb.connect("leave-notify-event", status_pop)
I know the issue is not with the "status_push" and "status_pop" events, as I've connected all my gtk.MenuItem objects to them, and they work swimmingly.
I know that gtk.ToolButton objects are in the Widgets class, so "enter-notify-event" and "leave-notify-event" SHOULD technically work. My only guess is that this particular object does not emit any signals other than "clicked", and thus I'd have to put each in a gtk.EventBox.
What am I doing wrong here? How do I fix this?
Thanks in advance!
Your guess was correct, you should wrap your widget in a gtk.EventBox, here is an example that i hope will be hopeful:
import gtk
def callback(widget, event, data):
print event, data
class Win(gtk.Window):
def __init__(self):
super(Win, self).__init__()
self.connect("destroy", gtk.main_quit)
self.set_position(gtk.WIN_POS_CENTER)
self.set_default_size(250, 200)
tb = gtk.ToolButton(gtk.STOCK_NEW)
# Wrap ``gtk.ToolButton`` in an ``gtk.EventBox``.
ev_box = gtk.EventBox()
ev_box.connect("enter-notify-event", callback, "enter")
ev_box.connect("leave-notify-event", callback, "leave")
ev_box.add(tb)
self.add(ev_box)
if __name__ == '__main__':
Win()
gtk.main()
It appears, based on experimentation and evidence, this is impossible in PyGtk 2.24.

Is there a way to wrap a tkinter GUI in a class that can be created and interacted with from another object? (*without* hanging in mainloop)

Is there a way to wrap a tkinter GUI in a class that can be created and interacted with from another object? For this to be really useful, the class has to work so mainloop or its equivalent doesn't hold the application. If so, can someone point me to a working example?
For context, what I'm trying to do is make a SimpleUI class that I can use in any application to allow it to display information or register functions to be executed on button or key presses. So any threading, queues, etc. I would like to have hidden in the SimpleUI class.
Based on what I've gathered from reading around, the answer is No without re-implementing mainloop. Rather, the GUI should be the main application which farms out work through one method or another. However, this would make any application with tkinter (perhaps other GUIs as well?) feel like the tail is wagging the dog. Hopefully I have misunderstood what I have beeing reading.
I know this may seem like a repost of this and other similar questions but I can't comment on those and the answers seem to be doing the opposite of what I want to do. In addition to that question, I've found bits and pieces of related code in many places, but I have not been able to put them together. If there is a way, I'll learn Python threads or whatever to make it work.
I'm using Python 3.1 if it makes any difference.
Example of how I would like it to work.
ui = SimpleUI()
ui.hide()
ui.show()
ui.add_frame...
ui.add_button...
ui.register_function(button, function)
Is this what you're looking for?
#The threading module allows you to subclass it's thread class to create
#and run a thread, in this case we will be starting SimpleUI in a seperate thread
import threading
from Tkinter import *
def printfunction():
print "ButtonPress"
class NewClass:
def __init__(self):
self.ui = SimpleUI()
self.ui.add_frame("frame1")
self.ui.add_button("button1","frame1")
self.ui.register_function("button1",printfunction)
self.ui.start()
#self.ui = Threader().start()
def PrintSuccess(self):
print "Success!"
class SimpleUI:
def __init__(self):
self.root = Tk()
self.frames = {}
self.buttons = {}
def start(gui):
class Threader(threading.Thread):
def run(self):
gui.root.mainloop()
Threader().start()
def hide(self):
self.root.withdraw()
if(raw_input("press enter to show GUI: ")==""):self.show()
def show(self):
self.root.update()
self.root.deiconify()
def add_frame(self,name):
tmp = Frame(self.root)
tmp.pack()
self.frames[name] = tmp
def add_button(self,name,frame):
tmp = Button(self.frames[frame])
tmp.pack()
self.buttons[name] = tmp
def register_function(self,button,function):
self.buttons[button].config(command=function)
NC = NewClass()
NC.PrintSuccess()

Categories