I've got the following code which was working well before I updated utils._DATA twice. Utils._DATA is dictionary.
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
super(SystemTrayIcon, self).__init__(parent)
self.parent = parent
QtWidgets.QSystemTrayIcon.__init__(self, icon, self.parent)
self.menu = QtWidgets.QMenu(parent)
self.actions = {}
self.Update()
def Update(self):
self.menu.clear()
self.actions.clear()
for key in utils._DATA:
self.actions[key] = self.menu.addAction(key)
self.actions[key].triggered.connect(partial(utils.copy, key))
self.setContextMenu(self.menu)
But if I call self.Update() after editing utils._DATA (second, third time etc), QActions exist but do nothing.
How can I update QMenu with working QActions in it?
Utils.copy is next:
def copy(identificator):
try:
clipboard.copy(_DATA[identificator])
return 0
except:
raise Exception('Cannot copy to clipboard')
I'm updating like _DATA = load(), where load() read specific file and convert it to dict. So in simple form it's
_DATA[file.readline()] = some_string
Update data works good, even QActions in my QMenu updating good, but their trigger does nothing!
My Program is written in Python3.5 and PyQt5. I have a method in my class that adds some custom widgets to a QTableWidget. when I call the function from inside the class it works and changes the cellwidgets of QTablewidget but when I call it from another custom class it doesn't change the widgets. I checked and the items and indexes changes but the new cellwidgets doesn't show. what is the problem?
This is my code:
class mainmenupage(QWidget):
elist = []
def __init__(self):
#the main window widget features
self.setObjectName("mainpage")
self.resize(800, 480)
sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
self.setSizePolicy(sizePolicy)
self.setMinimumSize(QSize(800, 480))
self.setMaximumSize(QSize(800, 480))
#the QTreeWidget features
self.category_tree = QTreeWidget(self)
self.category_tree.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.category_tree.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.category_tree.setGeometry(QRect(630, 90, 161, 381))
self.category_tree.setLayoutDirection(Qt.RightToLeft)
self.category_tree.setLocale(QLocale(QLocale.Persian, QLocale.Iran))
self.category_tree.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.category_tree.setUniformRowHeights(False)
self.category_tree.setColumnCount(1)
self.category_tree.setObjectName("category_tree")
self.category_tree.headerItem().setText(0, "1")
self.category_tree.setFrameShape(QFrame.NoFrame)
self.category_tree.header().setVisible(False)
self.category_tree.header().setSortIndicatorShown(False)
self.category_tree.setFocusPolicy(Qt.NoFocus))
#the QTableWidget features. It comes from the custom class myTableWidget
self.main_table = myTableWidget(self)
self.main_table.setGeometry(QRect(20, 140, 600, 330))
sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.main_table.sizePolicy().hasHeightForWidth())
self.main_table.setSizePolicy(sizePolicy)
self.main_table.setMinimumSize(QSize(0, 0))
self.main_table.setLayoutDirection(Qt.LeftToRight)
self.main_table.setLocale(QLocale(QLocale.Persian, QLocale.Iran))
self.main_table.setInputMethodHints(Qt.ImhNone)
self.main_table.setFrameShape(QFrame.NoFrame)
self.main_table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.main_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.main_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.main_table.setTabKeyNavigation(False)
self.main_table.setShowGrid(False)
self.main_table.setCornerButtonEnabled(False)
self.main_table.setUpdatesEnabled(True)
self.main_table.setRowCount(2)
self.main_table.setColumnCount(2)
self.main_table.setObjectName("main_table")
self.main_table.horizontalHeader().setVisible(False)
self.main_table.horizontalHeader().setHighlightSections(False)
self.main_table.verticalHeader().setVisible(False)
self.main_table.verticalHeader().setHighlightSections(False)
self.main_table.setFocusPolicy(Qt.NoFocus)
self.main_table.setSelectionMode(QAbstractItemView.NoSelection)
self.main_table.horizontalHeader().setDefaultSectionSize(300)
self.main_table.verticalHeader().setDefaultSectionSize(165)
self.category_tree.itemPressed.connect(self.insertdata)
def insertdata(self,subcat):
#get the text of clicked item from qtreewidget
item = self.category_tree.currentItem().text(0)
#get data from DB
datas = self.conn.retrievedata('*','words',"subcat='{}'".format(item))
#check if the list of copied data is empty or not
if mainmenupage.elist != []:
del mainmenupage.elist[:]
#if the list is empty, the data received from DB appends to it
if mainmenupage.elist == []:
for data in datas:
mainmenupage.elist.append(data)
#delete the first index of copied list because it isn't needed here
mainmenupage.elist = mainmenupage.elist[1:]
# calls the populatemain function for populating qtablewidget with these datas with a custom index e.g. index 5 to 9.
self.populatemain(5,9)
def populatemain(self,startindexdata,endindexdata):
#make a list for indexes of items that will be added to qtablewidget
mtl=[]
for i in range(2):
for j in range(2):
mtl.append(i)
mtl.append(j)
#adding custom widgets as cellWidgets to qtablewidget
for index, my in enumerate(zip(*[iter(mtl)]*2)):
if mainmenupage.elist != []:
data = mainmenupage.elist[startindexdata:endindexdata][index]
exec("self.iteM{} = CustomWidget('{}','content/img/food.png')".format(data[0],data[4]))
exec("self.main_table.setCellWidget({} ,{}, self.iteM{})".format(*my,data[0]))
class myTableWidget(QTableWidget):
def checking(self):
if (self.endgingposx - self.startingposx) <= -50:
#a custom function for checking that if the user made a swipe to left on the qtablewidget for chaning it's content
if mainmenupage.elist != []:
#make an instance from the previous class for accessing to populatemain method
x = mainmenupage()
#calling populatemain method for changing widgets in qtablewidget with the items made from different indexes of the copied data list e.g. indexes 0 to 4. but i don't know why this doesn't change the items. The populatemain function runs correctly it can be checked by putting print statement in it but it doesn't change the Qtablewidget contents.
x.populatemain(0,4)
def mousePressEvent(self,event):
super().mousePressEvent(event)
self.startingposx = event.x()
def mouseReleaseEvent(self,event):
super().mouseReleaseEvent(event)
self.endgingposx = event.x()
self.checking()
class CustomWidget(QWidget):
def __init__(self, text, img, parent=None):
QWidget.__init__(self, parent)
self._text = text
self._img = img
self.setLayout(QVBoxLayout())
self.lbPixmap = QLabel(self)
self.lbText = QLabel(self)
self.lbText.setAlignment(Qt.AlignCenter)
self.layout().addWidget(self.lbPixmap)
self.layout().addWidget(self.lbText)
self.initUi()
def initUi(self):
self.lbPixmap.setPixmap(QPixmap(self._img).scaled(260,135,Qt.IgnoreAspectRatio,transformMode = Qt.SmoothTransformation))
self.lbText.setText(self._text)
#pyqtProperty(str)
def img(self):
return self._img
#img.setter
def total(self, value):
if self._img == value:
return
self._img = value
self.initUi()
#pyqtProperty(str)
def text(self):
return self._text
#text.setter
def text(self, value):
if self._text == value:
return
self._text = value
self.initUi()
self.category_tree is a QTreeWidget
self.main_table is a QTableWidget
For completing my question. When I click on one of the self.category_tree items it calls insertdata. At the last line of insertdata I call self.populatemain(5,9) it adds 4 custom widgets to my table, but when the checking method from myTableWidget class calls populatemain with other indexes the qtablewidget items doesn't change. What's wrong?
Thank you in advance.
After a lot of efforts finally I solved my problem this way:
I made a Qframe and putted the QTableWidget in it, and then I editted the mousePressEvent of the QTableWidget to call mousePressEvent of the QFrame that is below if this table, and then simply checked for starting and ending positions of clicking and made a function to update the QTableWidget's contents when swiping happens. I posted this to help anyone else that goes to problems like this.
I have a slider and a text box that contains an integer (is there a dedicated integer box?) in PyQt5 shown side by side.
I need these two values to be synchronized, and the way I am doing it right now is with a QtTimer and if statements detecting if one value has changed more recently than the other, and then updating the opposite element. I was told this was "hacky" and was wondering if there was a proper way to do this.
You can see the text box values and sliders that I need to synchronize in the clear areas of the image below.
The simple solution is to connect the valueChanged for each slider/number box to a slot which synchronises the values
self.slider1.valueChanged.connect(self.handleSlider1ValueChange)
self.numbox1.valueChanged.connect(self.handleNumbox1ValueChange)
#QtCore.pyqtSlot(int)
def handleSlider1ValueChange(self, value):
self.numbox1.setValue(value)
#QtCore.pyqtSlot(int)
def handleNumbox1ValueChange(self.value):
self.slider1.setValue(value)
A better solution is to define a custom slider class that handles everything internally. This way you only have to handle the synchronisation once.
from PyQt5 import QtCore, QtWidgets
class CustomSlider(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(CustomSlider, self).__init__(*args, **kwargs)
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.slider.valueChanged.connect(self.handleSliderValueChange)
self.numbox = QtWidgets.QSpinBox()
self.numbox.valueChanged.connect(self.handleNumboxValueChange)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(self.numbox)
layout.addWidget(self.slider)
#QtCore.pyqtSlot(int)
def handleSliderValueChange(self, value):
self.numbox.setValue(value)
#QtCore.pyqtSlot(int)
def handleNumboxValueChange(self, value):
# Prevent values outside slider range
if value < self.slider.minimum():
self.numbox.setValue(self.slider.minimum())
elif value > self.slider.maximum():
self.numbox.setValue(self.slider.maximum())
self.slider.setValue(self.numbox.value())
app = QtWidgets.QApplication([])
slider1 = CustomSlider()
slider2 = CustomSlider()
window = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(window)
layout.addWidget(slider1)
layout.addWidget(slider2)
window.show()
app.exec_()
Edit: With regard to comments from ekhumoro, the above class can be simplified to
class CustomSlider(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(CustomSlider, self).__init__(*args, **kwargs)
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.numbox = QtWidgets.QSpinBox()
self.numbox.setRange(self.slider.minimum(), self.slider.maximum())
self.slider.valueChanged.connect(self.numbox.setValue)
self.slider.rangeChanged.connect(self.numbox.setRange)
self.numbox.valueChanged.connect(self.slider.setValue)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(self.numbox)
layout.addWidget(self.slider)
You'll probably also want to mimic some of the QSlider methods to change the range and value. Note we don't need to explicitly set anything on self.numbox as the signal/slot connections made above take care of it.
#QtCore.pyqtSlot(int)
def setMinimum(self, minval):
self.slider.setMinimum(minval)
#QtCore.pyqtSlot(int)
def setMaximum(self, maxval):
self.slider.setMaximum(maxval)
#QtCore.pyqtSlot(int, int)
def setRange(self, minval, maxval):
self.slider.setRange(minval, maxval)
#QtCore.pyqtSlot(int)
def setValue(self, value):
self.slider.setValue(value)
You can just connect each of the sliders to the other one, straight-forward. I don't know the exact connection you want between the sliders, but it could look something like this.
max_player_slider.valueChanged.connect(self.slider1_fu)
npc_stream_slider.valueChanged.conenct(self.slider2_fu)
def slider1_fu(self):
# do stuff with the npc_stream_slider
def slider2_fu(self):
# do stuff with the max_player_slider
Edit: Here is a Tutorial on YouTube that might be helpful.
Could someone show me how I could return a value from a wxPython Frame? When the use clicks close, I popup a message dialog asking him a question. I would like to return the return code of this message dialog to my calling function.
Thanks
Because the wxFrame has events that process via the app.MainLoop() functionality, the only way to get at the return value of a wx.Frame() is via catching an event.
The standard practice of handling events is typically from within the class which derives from wx.Window itself (e.g., Frame, Panel, etc.). Since you want code exterior to the wx.Frame to receive information that was gathered upon processing the OnClose() event, then the best way to do that is to register an event handler for your frame.
The documentation for wx.Window::PushEventHandler is probably the best resource and even the wxpython wiki has a great article on how to do this. Within the article, they register a custom handler which is an instance of "MouseDownTracker." Rather than instantiating within the PushEventHandler call, you'd want to instantiate it prior to the call so that you can retain a handle to the EventHandler derived class. That way, you can check on your derived EventHandler class-variables after the Frame has been destroyed, or even allow that derived class to do special things for you.
Here is an adaptation of that code from the wx python wiki (admittedly a little convoluted due to the requirement of handling the results of a custom event with a "calling" function):
import sys
import wx
import wx.lib.newevent
(MyCustomEvent, EVT_CUSTOM) = wx.lib.newevent.NewEvent()
class CustomEventTracker(wx.EvtHandler):
def __init__(self, log, processingCodeFunctionHandle):
wx.EvtHandler.__init__(self)
self.processingCodeFunctionHandle = processingCodeFunctionHandle
self.log = log
EVT_CUSTOM(self, self.MyCustomEventHandler)
def MyCustomEventHandler(self, evt):
self.log.write(evt.resultOfDialog + '\n')
self.processingCodeFunctionHandle(evt.resultOfDialog)
evt.Skip()
class MyPanel2(wx.Panel):
def __init__(self, parent, log):
wx.Panel.__init__(self, parent)
self.log = log
def OnResults(self, resultData):
self.log.write("Result data gathered: %s" % resultData)
class MyFrame(wx.Frame):
def __init__(self, parent, ID=-1, title="", pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE):
wx.Frame.__init__(self, parent, ID, title, pos, size, style)
self.panel = panel = wx.Panel(self, -1, style=wx.TAB_TRAVERSAL | wx.CLIP_CHILDREN | wx.FULL_REPAINT_ON_RESIZE)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add((25, 25))
row = wx.BoxSizer(wx.HORIZONTAL)
row.Add((25,1))
m_close = wx.Button(self.panel, wx.ID_CLOSE, "Close")
m_close.Bind(wx.EVT_BUTTON, self.OnClose)
row.Add(m_close, 0, wx.ALL, 10)
sizer.Add(row)
self.panel.SetSizer(sizer)
def OnClose(self, evt):
dlg = wx.MessageDialog(self, "Do you really want to close this frame?", "Confirm Exit", wx.OK | wx.CANCEL | wx.ICON_QUESTION)
result = dlg.ShowModal()
dlg.Destroy()
if result == wx.ID_CANCEL:
event = MyCustomEvent(resultOfDialog="User Clicked CANCEL")
self.GetEventHandler().ProcessEvent(event)
else: # result == wx.ID_OK
event = MyCustomEvent(resultOfDialog="User Clicked OK")
self.GetEventHandler().ProcessEvent(event)
self.Destroy()
app = wx.App(False)
f2 = wx.Frame(None, title="Frame 1 (for feedback)", size=(400, 350))
p2 = MyPanel2(f2, sys.stdout)
f2.Show()
eventTrackerHandle = CustomEventTracker(sys.stdout, p2.OnResults)
f1 = MyFrame(None, title="PushEventHandler Tester (deals with on close event)", size=(400, 350))
f1.PushEventHandler(eventTrackerHandle)
f1.Show()
app.MainLoop()
You can get the result of clicking the OK, CANCEL buttons from the Dialog ShowModal method.
Given dialog is an instance of one of the wxPython Dialog classes:
result = dialog.ShowModal()
if result == wx.ID_OK:
print "OK"
else:
print "Cancel"
dialog.Destroy()
A few years late for the initial question, but when looking for the answer to this question myself, I stumbled upon a built-in method of getting a return value from a modal without messing with any custom event funniness. Figured I'd post here in case anyone else needs it.
It's simply this guy right here:
wxDialog::EndModal void EndModal(int retCode)
Ends a modal dialog, passing a value to be returned from the
*wxDialog::ShowModal invocation.*
Using the above, you can return whatever you want from the Dialog.
An example usage would be subclassing a wx.Dialog, and then placing the EndModal function in the button handlers.
class ProjectSettingsDialog(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(self, None, -1, "Project Settings", size=(600,400))
sizer = wx.BoxSizer(wx.VERTICAL) #main sized
sizer.AddStretchSpacer(1)
msg = wx.StaticText(self, -1, label="This is a sample message")
sizer.Add(msg, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 15)
horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL)
okButton = wx.Button(self, -1, 'OK')
self.Bind(wx.EVT_BUTTON, self.OnOK, okButton)
cancelBtn = wx.Button(self, -1, "Cancel")
self.Bind(wx.EVT_BUTTON, self.OnCancel, cancelBtn)
horizontal_sizer.Add(okButton, 0, wx.ALIGN_LEFT)
horizontal_sizer.AddStretchSpacer(1)
horizontal_sizer.Add(cancelBtn, 0, wx.ALIGN_RIGHT)
sizer.Add(horizontal_sizer, 0)
sizer.AddStretchSpacer(1)
self.SetSizer(sizer)
def OnOK(self, event):
self.EndModal(wx.ID_OK) #returns numeric code to caller
self.Destroy()
def OnCancel(self, event):
self.EndModal(wx.ID_CANCEL) #returns numeric code to caller
self.Destroy()
(Note: I just banged this code out quickly; didn't test the sizers)
As you can see, all you need to do is call the EndModal from a button event to return a value to whatever spawned the dialog.
I wanted to do the same thing, to have a graphical "picker" that I could run from within a console app. Here's how I did it.
# Fruit.py
import wx
class Picker (wx.App):
def __init__ (self, title, parent=None, size=(400,300)):
wx.App.__init__(self, False)
self.frame = wx.Frame(parent, title=title, size=size)
self.apple_button = wx.Button(self.frame, -1, "Apple", (0,0))
self.apple_button.Bind(wx.EVT_BUTTON, self.apple_button_click)
self.orange_button = wx.Button(self.frame, -1, "Orange", (0,100))
self.orange_button.Bind(wx.EVT_BUTTON, self.orange_button_click)
self.fruit = None
self.frame.Show(True)
def apple_button_click (self, event):
self.fruit = 'apple'
self.frame.Destroy()
def orange_button_click (self, event):
self.fruit = 'orange'
self.frame.Destroy()
def pick (self):
self.MainLoop()
return self.fruit
Then from a console app, I would run this code.
# Usage.py
import Fruit
picker = Fruit.Picker('Pick a Fruit')
fruit = picker.pick()
print 'User picked %s' % fruit
user1594322's answer works but it requires you to put all of your controls in your wx.App, instead of wx.Frame. This will make recycling the code harder.
My solution involves define a "PassBack" variable when defining your init function. (similar to "parent" variable, but it is normally used already when initiating a wx.Frame)
From my code:
class MyApp(wx.App):
def __init__ (self, parent=None, size=(500,700)):
wx.App.__init__(self, False)
self.frame = MyFrame(parent, -1, passBack=self) #Pass this app in
self.outputFromFrame = "" #The output from my frame
def getOutput(self):
self.frame.Show()
self.MainLoop()
return self.outputFromFrame
and for the frame class:
class MyFrame(wx.Frame):
def __init__(self, parent, ID, passBack, title="My Frame"):
wx.Frame.__init__(self, parent, ID, title, size=(500, 700))
self.passBack = passBack #this will be used to pass back variables/objects
and somewhere during the execution of MyFrame
self.passBack.outputFromFrame = "Hello"
so all in all, to get a string from an application
app = MyApp()
val = app.getOutput()
#Proceed to do something with val
Check this answer on comp.lang.python: Linkie
I don't think a wxFrame can return a value since it is not modal. If you don't need to use a wxFrame, then a modal dialog could work for you. If you really need a frame, I'd consider using a custom event.
It would go something like this:
(1) User clicks to close the wxFrame
(2) You override OnClose (or something like that) to pop up a dialog to ask the user a question
(3) Create and post the custom event
(4) Close the wxFrame
(5) Some other code processes your custom event
I think I just had the same problem as you. Instead of making that popup a frame, I made it a dialog instead. I made a custom dialog by inheriting a wx.dialog instead of a wx.frame. Then you can utilize the code that joaquin posted above. You check the return value of the dialog to see what was entered. This can be done by storing the value of the textctrl when the user clicks ok into a local variable. Then before it's destroyed, you get that value somehow.
The custom dialog section of this site helped me out greatly.
http://zetcode.com/wxpython/dialogs/