I am trying to show a wx.TipWindow at the mouse location when the scroll wheel changes a RadioBox item (I want the newly selected item to show at the mouse).
Here is simply what I have:
wx.TipWindow(self, self.rdb.GetString(nxt)) <- Works perfectly. Self is an instance of wx.Frame
However, if the above line gets called more than once, (ie, I scroll more than once ~5 times) it constantly makes new windows on top of one another. Eventually, the program crashes with this error:
Traceback (most recent call last):
File "C:\Users\Seth\anaconda3\envs\DLC-GPU\lib\site-packages\matplotlib\backends\backend_wx.py", line 989, in _onMouseWheel
x = evt.GetX()
AttributeError: 'MouseEvent' object has no attribute 'GetX'
Can I not get this to work? I tried closing and destroying the TipWindow before showing the next one, but same error.
I have thought "why not make my own" but sheesh thats annoying lol.
Code block where wx.TipWindow is shown:
def MouseWheel(self, event):
assert isinstance(event, wx.MouseEvent)
whlRot = event.GetWheelRotation()
if whlRot < 0:
nxt = self.rdb.GetSelection() + 1
if nxt < self.rdb.GetCount():
self.rdb.SetSelection(nxt) # Sets the next object in the RadioBox
wx.TipWindow(self, self.rdb.GetString(nxt))
if whlRot > 0:
prv = self.rdb.GetSelection() - 1
if prv >= 0:
self.rdb.SetSelection(prv) # Sets the previous object in the RadioBox
wx.TipWindow(self, self.rdb.GetString(prv))
As you say "...scroll wheel changes RadioBox item..." but that is not a native function of a radiobox, it's something that you have engineered via code that we cannot see.
I assume that it looks something like this:
import wx
class RadioBoxFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Radio Box Frame', size=(400, 400))
panel = wx.Panel(self, -1, pos=(10,10), size=(300,300), style=wx.BORDER_RAISED)
sampleList = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine']
self.option=wx.RadioBox(panel, -1, "Select an option", pos=(10, 10), size=wx.DefaultSize, choices=sampleList, majorDimension=3, style=wx.RA_SPECIFY_COLS)
self.Bind(wx.EVT_RADIOBOX, self.getOption)
panel.Bind(wx.EVT_MOUSEWHEEL, self.getWheelOption)
for i in range(len(sampleList)):
self.option.SetItemToolTip(i, "Option "+sampleList[i])
self.CreateStatusBar()
self.SetStatusText("Status area")
self.Show()
def getWheelOption(self, event):
assert isinstance(event, wx.MouseEvent)
whlRot = event.GetWheelRotation()
if whlRot < 0:
nxt = self.option.GetSelection() + 1
else:
nxt = self.option.GetSelection() - 1
if nxt > self.option.GetCount() - 1:
nxt = 0
if nxt < 0:
nxt = self.option.GetCount() - 1
self.option.SetSelection(nxt) # Sets the next object in the RadioBox
txt = "Option " + self.option.GetString(nxt) + " Selected"
tw = wx.TipWindow(self, txt)
tw.SetBoundingRect(wx.Rect(1,1,1,1)) #restrict action required to cancel tipwindow
self.SetStatusText(txt)
def getOption(self, event):
state1 = self.option.GetSelection()
txt = self.option.GetString(state1)
self.SetStatusText(txt+" is Selected")
if __name__ == '__main__':
app = wx.App()
RadioBoxFrame()
app.MainLoop()
Which produces the following:
Play with this code a bit and see you you are still getting the same error, because using wxPython 4.1.1 on Linux, the code above doesn't give an error. You may need to look at your environment if the above code fails.
Finally found an answer to this that does not require updating wxPython (as this broke other things in the code that isn't mine).
Here is what I ended up doing:
if self.popUp is None:
self.popUp = wx.TipWindow(self, wht, maxLength=1000000)
else:
try: # Try is needed for if the TipWindow is closed by clicking elsewhere
self.popUp.Show(show=False) # This was the key line. For some reason, hiding the window was required before destroying
self.popUp.Destroy()
except:
pass
self.popUp = wx.TipWindow(self, wht, maxLength=1000000)
This works perfectly and is what I want for the results.
I noticed different TipWindow behavior on Windows between wxPython 4.1.0 and 4.1.1. In the 4.1.1 version, the TipWIndow is not be destroyed when moving out of a BoundingRect. Similar issues as described above but with the difference that I experience this only when upgrading to 4.1.1. See snippet below to reproduce this behavior + picture of effect in 4.1.1. Anybody had similar experience and know what the fix is?
import wx
class RadioBoxFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1)
panel = wx.Panel(self, -1, pos=(10, 10))
sampleList = ['Zero', 'One', 'Two']
self.option = wx.RadioBox(panel, -1, "Select an option", pos=(10, 10),
size=wx.DefaultSize, choices=sampleList,
majorDimension=3)
self.Bind(wx.EVT_RADIOBOX, self.getOption)
panel.Bind(wx.EVT_MOUSEWHEEL, self.getWheelOption)
self.Show()
def getWheelOption(self, event):
whlRot = event.GetWheelRotation()
if whlRot < 0:
nxt = self.option.GetSelection() + 1
else:
nxt = self.option.GetSelection() - 1
if nxt > self.option.GetCount() - 1:
nxt = 0
if nxt < 0:
nxt = self.option.GetCount() - 1
self.option.SetSelection(nxt)
txt = "Option " + self.option.GetString(nxt) + " Selected"
tw = wx.TipWindow(self, txt)
tw.SetBoundingRect(wx.Rect(1, 1, 1, 1))
def getOption(self, event):
state1 = self.option.GetSelection()
if __name__ == '__main__':
app = wx.App()
RadioBoxFrame()
app.MainLoop()
Related
Is is a piece of my code . I have two classes CheckerScene and Checkers . CHesckers - is my main window . I can't realize EndGameSignal defined in CheckerScene class . When it emits , pySlot can't catch it in class Checkers , as i want . When my EndGameSignal emmits - i want to see a dialog message on my main screen (pyQtSlots functions realized in my code), not on the scene . How can i correct my program to do it .
class CheckerScene(QtWidgets.QGraphicsScene):
EndGameSignal=QtCore.pyqtSignal('QString')
def init(self):
QtWidgets.QGraphicsScene.init(self)
# scene congifuratios
self.setSceneRect(margin, margin, gridCount * gridSlotSize, gridCount * gridSlotSize)
self.addRect(self.sceneRect())
# create signal . It will be emit() from blackboard.crash()
self.signaldel.connect(self.del_item)
#choosing the visual checker and its coordinates
self.current = None
#list of grids and checkers
self.grid = []
self.white_checkers = []
self.black_checkers = []
for row in range(8):
for column in range(8):
# this is a "trick" to make the grid creation easier: it creates
# a grid square only if the row is odd and the column is even,
# and viceversa.
if (not row & 1 and column & 1) or (row & 1 and not column & 1):
# create a gridItem with a rectangle that uses 0-based
# coordinates, *then* we set its position
gridItem = self.addRect(0, 0, gridSlotSize, gridSlotSize)
gridItem.setPos(margin + column * gridSlotSize, margin + row * gridSlotSize)
gridItem.setBrush(QtGui.QColor(QtCore.Qt.lightGray))
self.grid.append(gridItem)
if 3 <= row <= 4:
# don't add checkers in the middle
continue
# create checkers being careful to assign them the gridItem
# as a *parent*; their coordinate will *always* be relative
# to the parent, so that if we change it, they will always
# be centered
if row < 3:
self.black_checkers.append(CheckerItem(0, gridItem))#!
else:
self.white_checkers.append(CheckerItem(1, gridItem))#!
self.additionsl__init__()
self.EndGameSignal.connect(Checkers.handler_EndGameSignal)
self.EndGameSignal.emit('=NAME')
class Checkers(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.Initialization()
def Initialization(self):
layout = QtWidgets.QGridLayout()
self.setLayout(layout)
self.player2Label = QtWidgets.QLabel('Player 2')
layout.addWidget(self.player2Label)
self.player2Label.setAlignment(QtCore.Qt.AlignCenter)
self.checkerView = QtWidgets.QGraphicsView()
layout.addWidget(self.checkerView)
self.checkerScene = CheckerScene()
self.checkerView.setScene(self.checkerScene)
self.checkerView.setFixedSize(gridSize, gridSize)
# set the Antialiasing render hints to paint "smoother" shapes
self.checkerView.setRenderHints(QtGui.QPainter.Antialiasing)
self.player1Label = QtWidgets.QLabel('Player 1')
layout.addWidget(self.player1Label)
self.player1Label.setAlignment(QtCore.Qt.AlignCenter)
#QtCore.pyqtSlot(str)
def handler_EndGameSignal(self, result):
result=QtWidgets.QMessageBox.question(self,f"Выиграл {result}","Сиграть еще раз ?",QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No,QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.Yes :
self.close()
else :
pass
print(f"WINNER {result}")
#QtCore.pyqtSlot(bool)
def handler_EndGameSignal(self, result):
result = QtWidgets.QMessageBox.question(self, f"НИЧЬЯ !", "Сиграть еще раз ?",
QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.Yes:
self.close()
else:
pass
print("DRAW")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
checkers = Checkers()
checkers.show()
sys.exit(app.exec_())
As with your previous question, you're still confusing classes and instancies. In your code you connect the signal to the class, while you have to connect it to the instance.
Since you have no reference with the receiver (the Checker instance) from the sender (the scene), you'll have to connect it from the former:
class Checkers(QtWidgets.QWidget):
def Initialization(self):
# ...
self.checkerScene.EndGameSignal.connect(self.handler_EndGameSignal)
I am trying to code a menu routine for an alarm-clock in python, which displays the relevant information on a 7-segment display according to some inputs via pushbuttons.
I managed to create a loop which displays the current time, and whe the button "debMenu" is clicked 3 menu options are displayed. This works well only for the first time untill the 3rd. menu option is reached. When I push the button again the routine does not work - so the function "main_menu" is not called again.
What I am doing wrong...? Thanks !!
stop_loop = [False]
def main_menu(stop_loop, n=[0]):
stop_loop[0] = True
debSelect = DebouncedSwitch(butSelect, cbButSelect, "butSelect")
menuList = ['HO ', 'AL ', 'UP ']
if n[0] < 3:
display.scroll(menuList[n[0]], 200) #display menu on 7-seg display
display.show(menuList[n[0]])
n[0] += 1
elif n[0] == 3:
n=[0]
stop_loop[0] = False
main()
def main():
stop_loop[0] = False
debMenu = DebouncedSwitch(butMenu, main_menu, stop_loop)
while not stop_loop[0]: # display current time on 7-seg display
curTime = rtc.datetime()
display.numbers(curTime.hour, curTime.minute, False)
time.sleep_ms(500)
display.numbers(curTime.hour, curTime.minute)
time.sleep_ms(500)
main()
I guess the default value mutable variable is the one killing you. Check here: http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments.
I don't even understand why you need a list variable in this case, why not just n=0 without having to use a list?.
maybe make a new global variable or class to encapsulate both state-related variables (stop_loop and n). Like:
loop = dict(stop=False, n=0)
now you pass this instead of stop_loop to main_menu.
Or use a class that encapsulates a circular array for menu like:
class LoopMenu:
""" circular array menu list with pause/continue/next"""
def __init__(self, menu):
self.menu = menu
self.stop = False
self.n = 0
def next(self):
menu = self.menu[self.n]
self.n += 1
if self.n > len(self.menu):
self.n = 0
return menu
def pause(self):
self.stop = True
def play(self):
self.stop = False
then you main_menu method could be:
my_menu_loop = LoopMenu(['HO ', 'AL ', 'UP '])
def main_menu(menu_loop):
menu_loop.pause()
debSelect = DebouncedSwitch(butSelect, cbButSelect, "butSelect")
menu = menu_loop.next()
display.scroll(menu, 200) #display menu on 7-seg display
display.show(menu)
main()
def main():
my_menu_loop.play()
debMenu = DebouncedSwitch(butMenu, main_menu, my_menu_loop)
while not loop_menu.stop: # display current time on 7-seg display
curTime = rtc.datetime()
display.numbers(curTime.hour, curTime.minute, False)
time.sleep_ms(500)
display.numbers(curTime.hour, curTime.minute)
time.sleep_ms(500)
main()
I am writing an app that dynamically adds and removes widgets to a QScrollView. The code below, using Qt3 and python, will give me dynamic widgets, but when I add too many to be seen, no scroll bar appears. It is not yet scrollable. I've put the relevant pieces of code below.
Any answers must be in Qt3 because my company only uses Qt3. I'm new to programming and Qt in general.
PL = parser.Plist()
class EC_Conf_App(QDialog):
def __init__(self,parent = None,name = None,modal = 0,fl = 0):
QDialog.__init__(self,parent,name,modal,fl)
self.gridLayout = QGridLayout(self)
self.scrollArea = QScrollView(self)
self.scrollArea.setGeometry(0, 0, 369, 286)
self.Form1Layout = QGridLayout(self.scrollArea)
self.gridLayout.addWidget(self.scrollArea, 0, 0)
for item in PL.plist:
self.section_create(item.name, item.variables)
def section_create(self, name, variables):
# ADD ROW BUTTON
for key, value in sorted(variables.iteritems()):
if len(value) > 3: # if there is more than one option for the combobox
self.addButton = QPushButton(self.scrollArea, name + '_AddButton')
self.addButton.setText('Add Row')
self.Form1Layout.addWidget(self.addButton, self.Ay, self.Ax)
self.addButton.show()
self.connect(self.addButton,SIGNAL("clicked()"),self.add_rows)
def add_rows(self):
self.addButton = self.sender()
self.addButton.name()
copy_class = self.addButton.name()
clean_name = copy_class[:-10]
for item in PL.plist:
if item.name == clean_name:
PL.insert(item.name, item.heading, item.variables)
self.remove_widgets()
break
def remove_widgets(self):
for item in self.widgets:
item.deleteLater()
self.Form1Layout.remove(item)
self.construct()
def construct(self):
for item in PL.plist:
self.section_create(item.name, item.variables)
The only way to use a layout with a QScrollView is to set the layout on its viewport(), not the view itself. This is documented.
Replace self.Form1Layout = QGridLayout(self.scrollArea) with
self.Form1Layout = QGridLayout(self.scrollArea.viewport())
This question deals with the same problem for Qt4/5.
I am making a Qt GUI with python and i am getting the error: QObject::startTimer: timers cannot be started from another thread. It occurs when I run the readModemSnap method. I've been working on this for almost a week trying many different design patterns for threading in Qt that i've found on the web but nothign works.
class ModemScopeWindow(QMainWindow, Ui_ModemScope):
def __init__(self, parent=None):
super(ModemScopeWindow, self).__init__(parent)
# Set up the user interface from Designer.
self.setupUi(self)
self.thread = MainThread()
"""
signal connections
"""
self.thread.newSnap.connect(self.updateScene)
self.thread.updateStatus.connect(self.setStatus)
self.thread.connectionLock.lock()
self.thread.runLock.lock()
self.connect(self.runButton, SIGNAL("clicked()"), self.thread.runLock.unlock, Qt.QueuedConnection)
self.connect(self.connectButton, SIGNAL("clicked()"), self.thread.connectionLock.unlock, Qt.QueuedConnection)
class MainThread(QThread):
newSnap = pyqtSignal(QGraphicsScene)
updateStatus = pyqtSignal(str)
initConnect = pyqtSignal()
def __init__(self, parent = None):
super(MainThread, self).__init__(parent)
self.samples = []
self.connectionLock = QMutex()
self.runLock = QMutex()
self.cliMute = QMutex()
self._displayCrosshairs = True
self._displayGrid = True
self.persistantMode = False
self.sampleDepth = 1
self._currentHaam = "4"
color = QColor(10,255,71)
self.plotPen = QPen(color)
self._leftXscene = -VIEW_SIZE/2
self._topYscene = -VIEW_SIZE/2
self._rightXscene = VIEW_SIZE/2
self._bottomYscene = VIEW_SIZE/2
self._leftXworld = -10.0
self._topYworld = 10.0
self._rightXworld = 10.0
self._bottomYworld = -10.0
self._scene = QGraphicsScene(self._leftXscene, self._topYscene, VIEW_SIZE, VIEW_SIZE, self)
self.start(QThread.HighestPriority)
def run(self):
self.updateStatus.emit("Enter target IP address and press Connect")
self.connectionLock.lock()
self.connectModem()
while(1):
self.runLock.lock()
#compile scene
self.readModemSnap()
self.newSnap.emit(self._scene)
self.runLock.unlock()
def readModemSnap(self):
self.updateStatus.emit("Reading Modem Snap...")
print len(self.samples)
if len(self.samples) >= self.sampleDepth:# and not self.persistantMode:
self.samples.pop(0)
self.cliMute.lock()
temp = cli.getModemSnap()
self.cliMute.unlock()
self.samples.append(temp)
self.cliMute.lock()
modType = cli.modemRead(80)
self.cliMute.unlock()
if((modType | 0x0FFFFFFF) == 0x0FFFFFFF):
modType = "0";
else:
modType = "%x"%modType
modType = str(modType)
modType = "0"
self.updateStatus.emit("Done")
self.refresh()
self._currentHaam = modType[0]
if self._displayGrid:
self.plotModulation(self._currentHaam)
self.handleSnapshotResponse()
self.updateStatus.emit("Ready to Run")
def refresh(self):
#delete scene
items = self._scene.items()
for x in items:
self._scene.removeItem(x)
#repaint the crosshairs
if self._displayCrosshairs:
self.plotLine(-VIEW_SIZE,0,+VIEW_SIZE,0, self.plotPen)
self.plotLine(0, -VIEW_SIZE,0, +VIEW_SIZE, self.plotPen)
self.plotScaleTicks()
#repaint grid
if self._displayGrid:
self.plotModulation(self._currentHaam)
self.newSnap.emit(self._scene)
def handleSnapshotResponse(self):
for x in range(len(self.samples)):
for sample in self.samples[x]:
upper = (sample >> 16) & 0xffff;
lower = sample & 0xffff
if (upper & 0x8000):
upper -= 0x10000
if (lower & 0x8000):
lower -= 0x10000
upper = float(upper)/128.0
lower = float(lower)/128.0
self.plot(upper, lower)
as you can see Im not starting any thread from another thread. i use the main to start the UI which creates a MainThread that starts itself upon construction. When i commented lines out to localize the problem I found that its when i call self.refresh() and self.handleSnapshotResponse() in the readModemSnap method. Can anyone point me in the direction of what im doing wrong? or any tutorials on QThreading? thanks in advance
This is the rule: you cannot call any GUI functions from any thread other than the main thread running the Qt event loop. When you see errors about QTimer, it's probably because something in the GUI uses a timer internally and it is being triggered from another thread.
The most likely culprit in your case is that you are operating on a QGraphicsScene from the worker thread. I would try rearranging such that the code in MainThread.reload is called in response to the newSnap signal, rather than before it.
I'm trying to remap several navigation keys:
ENTER: to work like standard TAB behavior (focus to next control)
SHIFT+ENTER: to work like SHIFT+TAB behavior (focus to previous control)
UP / DOWN arrows: previous /next control
etc
I tried with a couple of options but without luck:
from javax.swing import *
from java.awt import *
class JTextFieldX(JTextField):
def __init__(self, *args):
# Thanks, Jack!!
JTextField.__init__(
self,
focusGained=self.onGotFocus,
focusLost=self.onLostFocus,
*args)
def onGotFocus (self, event):
print "onGotFocus "
self.selectionStart = 0
self.selectionEnd = len(self.text)
def onLostFocus (self, event):
print "onLostFocus ", self.name
class Test(JFrame):
def __init__(self):
JFrame.__init__(self,
'JDesktopPane and JInternalFrame Demo',
size=(600, 300),
defaultCloseOperation=JFrame.EXIT_ON_CLOSE)
self.desktop = JDesktopPane()
self.contentPane.add(JScrollPane(self.desktop)) # This is the same as self.getContentPane().add(...)
frame = JInternalFrame("Frame", 1, 1, 1, 1, size=(400, 400), visible=1)
panel = JPanel()
self.label = JLabel('Hello from Jython')
panel.add(self.label)
self.textfield1 = JTextFieldX('Type something here', 15)
panel.add(self.textfield1)
self.textfield2 = JTextFieldX('and click Copy', 15)
panel.add(self.textfield2)
panel.add(copyButton)
frame.add(panel)
frame.pack()
self.desktop.add(frame)
# ENTER=SPACE remapping for buttons (works ok, but only for buttons)
# inputMap = UIManager.getDefaults().get("Button.focusInputMap")
# pressedAction = inputMap.get(KeyStroke.getKeyStroke("pressed SPACE"));
# releasedAction = inputMap.get(KeyStroke.getKeyStroke("released SPACE"));
# # pressedAction = self.noAction
# inputMap.put (KeyStroke.getKeyStroke("pressed ENTER"), pressedAction)
# inputMap.put (KeyStroke.getKeyStroke("released ENTER"), releasedAction)
# # Attemp to remap ENTER=TAB for TextFields (didn't work, no errors)
# inputMap = UIManager.getDefaults().get("TextField.focusInputMap")
# pressedAction = inputMap.get(KeyStroke.getKeyStroke("pressed TAB"));
# releasedAction = inputMap.get(KeyStroke.getKeyStroke("released TAB"));
# inputMap.put (KeyStroke.getKeyStroke("pressed W"), pressedAction)
# inputMap.put (KeyStroke.getKeyStroke("released W"), releasedAction)
# # Attemp to remap ENTER=TAB for all controls (didn't work, no errors)
# spaceMap = self.textfield1.getInputMap().get(KeyStroke.getKeyStroke(event.KeyEvent.VK_TAB, 0, True));
# self.textfield1.getInputMap().put(KeyStroke.getKeyStroke(event.KeyEvent.VK_ENTER, 0, True),spaceMap);
frame.setSelected(1)
frame.moveToFront()
def noAction (self, event):
print "noAction"
pass
if __name__ == '__main__':
test = Test()
test.setLocation(100, 100)
test.show()
I made a new post for readability.
self.textfield1 = JTextField('Type something here',15,focusGained=self.myOnFocus,keyPressed=self.myOnKey)
#create textfield2...must be created before can be referenced below.
self.textfield1.setNextFocusableComponent(self.textfield2)
then in your event handler:
def myOnKey(self,event):
print str(event) # see all other info you can get.
key_code = event.keyCode
if key_code == 10:
print "you pressed enter"
# simulate the "tab" just focus next textbox...
gotFocus = event.getComponent()
nextToFocus = gotFocus.nextFocusableComponent
nextToFocus.requestFocus()
Should do it.
Finally used part of Jack's answer (the keyPressed event) but without manually setting setNextFocusableComponent:
keyFocusMgr = KeyboardFocusManager.getCurrentKeyboardFocusManager()
keyFocusMgr.focusNextComponent()
Add keyPressed to the swing competent that you want to listen for the key press on
self.textfield1 = JTextField('Type something here',15,focusGained=self.myOnFocus,keyPressed=self.myOnKey)
myOnKey can be named anything in that method do something like:
def myOnKey(self,event):
print str(event) # see all other info you can get.
key_code = event.keyCode
if key_code == 10:
print "you pressed enter"
# simulate the "tab" by just focusing the next textbox...
Then you should just be able to play around with the print str(event) command to get all the proper keycodes that you want.