That's really a Pycharm - IDE question - the python/wx bug is fixed.
A seemingly innocuous change broke the raceText.SetLabel() call (which simply sets the text in the static text control) below. So I set a breakpoint in item = self.items[itemDex]:
def __init__(self):
# ...
self.raceText = staticText(self,u'') # see below for staticText definition
def EvtListBox(self,event):
"""Responds to listbox selection."""
itemDex = event.GetSelection()
item = self.items[itemDex] # breakpoint here
face = self.data[item]
self.nameText.SetLabel(face.pcName)
self.raceText.SetLabel(face.getRaceName()) # this
Lo and behold self.raceText.SetLabel(face.getRaceName()) now succeeded.
So how is this possible ? What does setting a breakpoint trigger ?
EDIT: some more data:
What originally broke the SetLabel() call was this commit:
-def staticText(parent,label=u'',pos=defPos,size=defSize,style=0,name=u"staticText",id=defId,):
+def staticText(parent, label=u'', pos=defPos, size=defSize, style=0,
+ noAutoResize=True, name=u"staticText"):
"""Static text element."""
- return wx.StaticText(parent,id,label,pos,size,style,name)
+ if noAutoResize: style |= wx.ST_NO_AUTORESIZE
+ return wx.StaticText(parent, defId, label, pos, size, style, name)
Flipping noAutoResize default value to False squashed the bug - the text was set but wx.ST_NO_AUTORESIZE would prevent the control to adjust its size from u'' - so no text was displayed. So this was a plain old bug.
The question remains why on earth when setting a breakpoint in the debugger self.raceText.SetLabel() shows the text ?
EDIT: do read the answer - sanity check
IIUC, I think it's possible that running in the debugger is simply hiding or confusing the real issue.
When wx.ST_NO_AUTORESIZE is not set then changes to the label will cause the widget's size to be adjusted, which will typically cause the widget to be repainted soon via an automatic paint event from the system. When wx.ST_NO_AUTORESIZE is set, then the resize doesn't happen and so that particular paint event doesn't happen and the widget may not get repainted until the next time the parent is refreshed or something else happens that triggers a paint event.
So a likely fix would be to add a call to self.fooText.Refresh() after each SetLabel. The display will still not be updated while stopped in the debugger because the paint event normally won't happen until return to the active event loop, but in a normal run it will happen soon enough that the user will not notice unless there is some long-running thing blocking the return to the event loop.
Related
im making a small app which opens a box using Tkinter when a certain condition is reached.
I dont want to spam the user with theses boxes so i want tkinter to set a variable to True when it starts up, then as it closes set it back to False.
Im making a down website checker/notifier so once the website is back up you get a pop up box letting you know. Right now if you close the box the code will continue and the box will pop up again. However the real problem is the code wont continue in the background.
If i make the code continue to run in the background the every 5 seconds of the conditions being met, another box will pop up and eventually spam the user which is something i dont want.
is there a way to check if there is a tkinter box open, or set a value to false when the close button (or X button) is pressed ?
You can redefine what the close button does:
win = Toplevel()
win.protocol('WM_DELETE_WINDOW', close(win))
def close(window):
window.withdraw()
someboolean = False
Hope this helps!
You can register a callback function for the WM_DELETE_WINDOW (the window is about to be deleted)
Example1:
top = Toplevel()
def on_close(t):
flag = False
t.protocol("WM_DELETE_WINDOW", on_close)
You can also override the destroy function:
Example2:
class CustomToplevel(Toplevel):
def destroy(self):
# Add you code here
Toplevel.destroy(self)
I have a small python project that requires data to be pulled from a network and displayed every second (which is how often it changes) for a scientific application. 3 of these things are simply numbers, while another is a 128x128 camera image, which is brought in as an ndarray and drawn using matplotlib's imshow to a tkinter canvas.
I've tried two methods - using aniamtion.FuncAnimation() and after(interval, function), and both have the same result, which is that while the frame updates, the window can't be moved. and it feels jerky.
I assume that's something that can't be overcome (and probably doesn't matter)? I thought that perhaps multi-threading might help so the main window is on one thread, while the updated data can be on another?
Thanks!
Below is the basic code which now includes threading, and the error when closing the window is fixed by adding an event to the window close function, and also a few break commands while getting data that checks if the window is closed before it tries to interact with a GUI item that doesn't exist.
import blah, blah, blah
global safe_shutdown, window_status, my_thread
safe_shutdown = False
window_status = True
window = tk.Tk()
def widow_close():
window_status=False
while True:
if safe_shutdown == True:
window.destroy
return False
def get_updates():
while True:
#code to go get data from network
if window_status == False:
safe_shutdown == True:
break
#more code to place data on the GUI
if window_status == False:
safe_shutdown == True:
break
#only get updates once a second
time.sleep(1)
print "thread complete"
my_thread= threading.Thread(target=get_updates, args=()).start()
window.protocol("WM_DELETE_WINDOW", window_close)
window.mainloop()
I'm not an expert, so I'm not sure if there's a better solution. But I've had success with two separate solutions:
1: While it is not recomended to have the tkinter loop in a thread, you are allowed to have the data updating the tkinter app in a thread. This has worked pretty good for me in the past simply using the threading package. The thread will then just set the various stuff in need of a refresh.
2: Call the update_idletasks() on the window to force it to update. This can then be added at various places in you code where it would make sense to update the view.
Solution 1 whould take care of all stuttering, while solution 2 might just make it a bit better. I suppose it depends on your implementation.
I am analyzing an image and it takes a little while to process. I want to have a Dialog box pop up when a user clicks the 'Analyze' button. I need it to be modeless so it does not interrupt the flow of my application (so the analyzing actually occurs). I just want it to display "Analyzing image..." until the analysis is done, at which point it goes away (meaning I don't want any buttons). Here is what I have so far:
class MessageDialog(wx.Dialog):
def __init__(self, message, title):
wx.Dialog.__init__(self, None, -1, title,size=(300, 120))
self.CenterOnScreen(wx.BOTH)
text = wx.StaticText(self, -1, message)
box = wx.BoxSizer(wx.VERTICAL)
box.Add(text, 1, wx.ALIGN_CENTER, 10)
self.SetSizer(box)
I call it from my main application frame using:
msg_dialog = MessageDialog("Analyzing image...", "Analyzing")
msg_dialog.Show()
# Do some stuff.....
msg_dialog.Destroy()
When I use msg_dialog.Show() the "Analyzing image..." text does not show up. If I change it to msg_dialog.ShowModal(), the text shows up. I can't use ShowModal() though because it pauses my program, defeating the purpose of the box. Any ideas about what's going on? Thanks for the help.
You need to call wxWindow::Update() to force the update of the controls on the screen without returning to the event loop.
You could also just use wxBusyInfo.
I override keyPressEven() of widget QTextEdit:
void myTextEdit::keyPressEvent(QKeyEvent *e)
{
if(e->key()==Qt::Key_0)
{
qDebug() << "Ok";
}
}
Button 0 works - show "Ok", but does not write in field of QTextEdit. Why? Thanks advance.
You need to call the base class implementation if you want to keep the default behaviour:
void myTextEdit::keyPressEvent(QKeyEvent *e)
{
if(e->key()==Qt::Key_0)
{
qDebug() << "Ok";
}
QTextEdit::keyPressEvent(e);
}
See the docs for keyPressEvent.
In case someone using PySide2 is having trouble overriding QTextEdit's built-in keybindings, I post my solution here. Hopefully this is also useful for C++.
Scenario:
We are using a QTextEdit in an application and want to distribute a series of keybindings, but the text editor has already several hardcoded bindings. We want the editor to ignore them and hand them over to its parent, so they can be eventually handled by our code.
Problem:
While the docs say that whenever an event is ignored (e.g. by returning True in the installed eventFilter method) it automatically gets passed on to the parent, the truth is that when doing that for predefined keybindings QTextEdit did not hand them over: the event got ignored AND absorbed. So any textedit built-in keybindings filtered this way will be effectively globally disabled.
Direct event passing via sendEvent inside the editor's eventFilter had an interesting effect:
When calling sendEvent and returning super().sendEvent, the keybinding got executed by the editor AND the event passed to the receiver.
When calling sendEvent and returning True, the keybinding didn't get executed, and the event didn't get passed to the receiver.
When calling sendEvent and returning False, the keybinding didn't get executed, and the event passed to the receiver twice.
Furthermore:
Using event.ignore() didn't have any effect: the editor executed the built-in anyway.
Trying to discriminate via event.spontaneous() caused a segfault due to a missing pointer. Probably something got GCed but didn't try to debug that.
Trying to replace the event with a "dummy event" and call super also didn't work. Magically, the text editor kept executing the built-ins.
Maybe I missed something. Anyway, below I detail the approach that worked for me.
Solution:
The plan is to completely block the event, but broadcast it via signals, and then connect to them wherever we want. In your text editor instance, define the signal e.g. as follows:
eventCatched = QtCore.Signal(QtCore.QEvent)
Then, e.g. the following event filter will prevent execution of a few keybindings, and emit them once via eventCatched:
def eventFilter(self, obj, evt):
"""
Remember to install via self.installEventFilter(self)
"""
catch_control_keys = {QtCore.Qt.Key_Left, QtCore.Qt.Key_Right}
catch = False
# documentation for keys and modifiers:
# https://doc.qt.io/qtforpython-5/PySide2/QtCore/Qt.html
if evt.type() == QtCore.QEvent.KeyPress:
modifiers = evt.modifiers()
ctrl = bool(modifiers & QtCore.Qt.ControlModifier)
shift = bool(modifiers & QtCore.Qt.ShiftModifier)
alt = bool(modifiers & QtCore.Qt.AltModifier)
key = evt.key()
# catch all undo/redo builtins
if ((ctrl and shift and key == QtCore.Qt.Key_Z) or
evt.matches(QtGui.QKeySequence.Undo) or
evt.matches(QtGui.QKeySequence.Redo)):
catch = True
# catch specified control-keys
if ctrl and not shift and not alt:
if key in catch_control_keys:
catch = True
#
if catch:
# block event but send it as signal
self.eventCatched.emit(evt)
return True
else:
return super().eventFilter(obj, evt)
Then, we are free to connect the signal wherever we want to, we just need a method that handles events. In my case, I just wanted to pass them to the main window, which can be done with the following one-liner in the constructor:
text_editor.eventCatched.connect(lambda evt: QtCore.QCoreApplication.sendEvent(self, evt))
This way, whenever we catch an event in the text editor, it will be ignored and won't be propagated the standard way. Instead, a signal will be emitted, and we can subscribe to that signal to e.g. restart the propagation tree at a different point, as shown here via sendEvent.
Hope this helps!
Cheers,
Andres
I am working on a C++ MFC project and bumping in the following. I have a CSliderCtrl on my form which I call MFC_scKINECTANGLE. To make it the way I want it the next piece of code is used:
MFC_scKINECTANGLE = (CSliderCtrl * ) GetDlgItem(SC_kinectAngle);
MFC_scKINECTANGLE->SetRangeMax(27);
MFC_scKINECTANGLE->SetRangeMin(-27);
MFC_scKINECTANGLE->SetPos(0);
The problem is that the slider at the start of the program is at the top of the bar whereas it should be in the middle, and when you try to grab it, it suddenly jumps to the correct position and works fine after that. How can I make sure the slider is in the middle of the bar at the start of my program?
According to MSDN CSliderCtrl::SetRangeMax (CSliderCtrl::SetRangeMin is similar):
void SetRangeMax(
int nMax,
BOOL bRedraw = FALSE
);
You need to set bRedraw parameter to TRUE to update slider.
Another (and probably better) variant - force redraw the slider after setup.
But due to bug (or feature?) in MS trackbar implementation you cannot just call CWnd::Invalidate (for deferred redraw) or even CWnd::RedrawWindow (for immediate redraw). This will have no effect.
Fortunately there are several events that force trackbar to repaint, e.g. enabling/disabling the window:
const BOOL isEnabled = MFC_scKINECTANGLE->IsWindowEnabled();
MFC_scKINECTANGLE->EnableWindow(!isEnabled);
MFC_scKINECTANGLE->EnableWindow(isEnabled);
See this discussion for details.
I was setting the range (0 - 100) and position (50) in the dialog's constructor. The slider kept showing up initially at position 0 instead. If I called GetPos() right after SetPos() it was returning 0 instead of 50.
What made it work for me was overriding OnInitDialog() and setting the range & position there instead of in the constructor.
BOOL CVolumeDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
m_VSliderBarNormal.SetRange(0, 100, TRUE);
m_VSliderBarNormal.SetPos(50);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}