I have a RichTextCtrl in my application, that has a handler for EVT_KEY_DOWN. The code that is executed is the following :
def move_caret(self):
pdb.set_trace()
self.rich.GetCaret().Move((0,0))
self.Refresh()
def onClick(self,event):
self.move_caret()
event.Skip()
rich is my RichTextCtrl.
Here is what I would like it to do :
on each key press, add the key to the control ( which is default behaviour )
move the cursor at the beginning of the control, first position
Here's what it actually does :
it adds the key to the control
I inspected the caret position, and the debugger reports it's located at 0,0 but on the control, it blinks at the current position ( which is position before I pressed a key + 1 )
Do you see anything wrong here? There must be something I'm doing wrong.
Apparently, there are two problems with your code:
You listen on EVT_KEY_DOWN, which is probably handled before EVT_TEXT, whose default handler sets the cursor position.
You modify the Caret object instead of using SetInsertionPoint method, which both moves the caret and makes the next character appear in given place.
So the working example (I tested it and it works as you would like it to) would be:
# Somewhere in __init__:
self.rich.Bind(wx.EVT_TEXT, self.onClick)
def onClick(self, event):
self.rich.SetInsertionPoint(0) # No refresh necessary.
event.Skip()
EDIT: if you want the text to be added at the end, but the cursor to remain at the beginning (see comments), you can take advantage of the fact that EVT_KEY_DOWN is handled before EVT_TEXT (which in turn is handled after character addition). So the order of events is:
handle EVT_KEY_DOWN
add character at current insertion point
handle EVT_TEXT
Adding a handler of EVT_KEY_DOWN that moves the insertion point to the end just before actually adding the character does the job quite nicely. So, in addition to the code mentioned earlier, write:
# Somewhere in __init__:
self.rich.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
def onKeyDown(self, event):
self.rich.SetInsertionPointEnd()
event.Skip()
By the way, event.Skip() does not immediately invoke next event handler, it justs sets a flag in the event object, so that event processor knows whether to stop propagating the event after this handler.
Related
I have an element, editorBox which is of the PyQt5 element type QPlainTextEdit. My target goal is to call a function when the hotkey Shift + Return is pressed, and my goal with this function is that it will also insert text into the editorBox element (this isn't the part I'm stressed about, it's fairly easy to do with the .insertPlainText() method).
I've done searching, and the closest result I could find was to use QShortcut & QKeySequence paired together like so:
# Initialize the QShortcut class
self.keybindShiftEnter = QShortcut(QKeySequence("Shift+Return"), self)
# Connect the shortcut event to a lambda which executes my function
self.keybindShiftEnter.activated.connect(lambda: self.editorBox.insertPlainText("my text to insert"))
For clarification, I have tried using other characters in the QKeySequence constructor, such as Ctrl+b, and I've had success with it. Oddly enough, only the combination Shift+Return doesn't work for me.
I've analyzed a problem with it in relation to my bug. Some of the posts I've viewed:
This is for triggering a button, not a QPlainTextEdit.
Best thing I've tried, almost worked up until I tried Shift+Return
Any solution with keyPressEvent wouldn't work, because keys other than Shift+Enter wouldn't be typed into the editorBox
Solved my own problem:
# ... editorBox Initialization code ...
self.editorBox.installEventFilter(self)
# Within App class
def eventFilter(self, obj, event):
if obj is self.editorBox and event.type() == QEvent.KeyPress:
if isKeyPressed("return") and isKeyPressed("shift"):
self.editorBox.insertPlainText("my text to insert")
return True
return super(App, self).eventFilter(obj, event)
What I'm doing here is setting a filter function- basically, every time a key is pressed (any key, including space/backspace/etc) it will call the eventFilter function. The first if statement makes sure that the filter will only pass through if it is a key stroke (not entirely sure if this part is necessary, I don't think clicks trigger the function). Afterwards, I utilize the isKeyPressed function (a renamed version of the keyboard module's is_pressed function) to detect if the current key is held down. With the and operator, I can use this to make keybind combos.
Right now I have a child panel that post some event. I've tried
myEvent = events.ChangedAvailModelsEvent()
#self.GetEventHandler().ProcessEvent(myEvent)
wx.PostEvent(self, myEvent)
I create my event with
ChangedAvailModelsEvent, EVT_CHANGEDAVAILMODELS = NewEvent()
I bind with
self.Bind(events.EVT_CHANGEDAVAILMODELS, self.OnUpdate)
which takes place in some nth grandparent. I have print statements telling me the event was processed, but I my function is never called afterwards. I'm not sure what the problem is. I feel like the event is not propagating upwards. Any help?
Change it to use NewCommandEvent instead of NewEvent. Command events will automatically propagate up the parent chain in search of a handler. Non-command events will only be processed by the object they are posted to. See http://wiki.wxpython.org/self.Bind_vs._self.button.Bind.
I have designed a gtk3 layout with Glade, including some comboboxtext widgets.
The bookings_tour_selector ComboBoxText has the changed signal connected, so when user selects an option, this is detected. That part works fine.
Now the problem, when I make the call: bookings_tour_selector.remove_all() the changed signal is triggered once for every single item being removed. That's not the expected behaviour. I expect it to not trigger the signal at all.
How to prevent this signal to be triggered when removing items?
Just add conditional in your callback, i.e:
def on_changed_combobox (self, widget):
if self.bookings_tour_selector.get_active () != -1:
#do whatever you want when combo box changed
#if not, it simply, does nothing
I want to know when a frame has been resized, so I can save the size and remember it the next time the application launches. Here is my on_resize method:
def on_resize(self, event):
logic.config_set('main_frame_size',
(event.Size.width, event.Size.height))
event.Skip()
And it's bound like this:
self.Bind(wx.EVT_SIZE, self.on_resize)
The problem is performance. For safety, my logic module saves the config file every time a setting changes, and writing the config file every time the resize event fires is way too performance taxing.
What would be the best/easiest way of monitoring for when the user is done resizing the frame?
Update
My config_set function:
def config_set(key, value):
"""Set a value to the config file."""
vprint(2, 'Setting config value: "{}": "{}"'.format(key, value))
config[key] = value
# Save the config file.
with open(config_file_path, 'w') as f:
pickle.dump(config, f)
You could handle EVT_IDLE which is triggered when the event queue is empty:
wx.IdleEvent: This class is used for EVT_IDLE events, which are generated and sent when the application becomes idle. In other words, the when the event queue becomes empty then idle events are sent to all windows (by default) and as long as none of them call RequestMore then there are no more idle events until after the system event queue has some normal events and then becomes empty again.
The process of resizing or moving a window should keep the event queue jammed so it won't become empty (and trigger the idle event) until the resizing/moving is done.
Set a dirty flag in EVT_SIZE and check it in the EVT_IDLE handler. If the flag is set, save the new size and reset the flag:
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None)
self.resized = False # the dirty flag
self.Bind(wx.EVT_SIZE,self.OnSize)
self.Bind(wx.EVT_IDLE,self.OnIdle)
def OnSize(self,event):
self.resized = True # set dirty
def OnIdle(self,event):
if self.resized:
# take action if the dirty flag is set
print "New size:", self.GetSize()
self.resized = False # reset the flag
app = wx.PySimpleApp()
frame = Frame().Show()
app.MainLoop()
EVT_SIZE may also be triggered when restoring a minimized window (the window size remains the same). If you want to cut down on unnecessary saves, you may want to check if the size is actually different before you save it to the config (you could keep track of it in a variable containing the last saved size).
You may want to add EVT_MOVE to keep track of the window position.
You could start a timer and have it check for changes every so often, kind of like the auto-save in Microsoft Word. Or you could set some kind of flag when EVT_SIZING or EVT_SIZE occurs and then bind to EVT_LEAVE_WINDOW as you'll almost certainly leave the window when you're done resizing. Thus when that event fires, you check the flag that was set and if it is set, you save and reset the flag.
On windows, you can save the configuration in the registry, which results in no performance hit when the window is resized.
On other OS's, where there is no registry, I guess you need to use a file. However, I am surprised that even this gives the kind of performance penalty that you would notice.
Are you sure that whatever poor performance you are seeing is due to this? ( Perhaps your redrawing code is slow? )
I would think that any modern OS would look after such a small file write without getting in your way. Perhaps it is Python problem?
I urge you to look into the above questions first. However, to answer your actual question:
The way to do this is to save the window size in a variable, and only write it to a file when your application quits.
Took a look at the code you just posted. I am not a python expert, but it looks like you are re-opening the file on every update. If so, no wonder it is slow!
Keep the file open all the time.
Only write the file when your app quits.
You might also take a look at the wxWidget wxConfig class.
You definitely shouldn't be saving the window geometry on every resize, it should be normally only done when the frame is closed. If you want extra safety (but, honestly, how do you manage to crash in Python?), you can also call the same function you call on frame close from a EVT_TIMER handler. But window geometry is hardly a critical resource so I don't think there is ever any real need to do this.
I have a shell-like console for my app, which prompts with ">>>" after every command. The problem is every time I have my shell WriteText(">>> "), it also appends a new line. The user can backspace up to the correct line, but this just looks terrible. Any way to fix it?
I suspect you are declaring your TextCtrl to be of style wx.TE_PROCESS_ENTER and then binding the EVT_TEXT_ENTER event - only because I ran into the same problem when I tried that.
My first instinct was to write a method to process wx.EVT_TEXT_ENTER that would then use the TextCtrl:Remove() method. This method only seems to remove visible characters, unfortunately. My next thought was to use the EmulateKeyPress() method along with backspace (WKX_BACK) to erase the newline character. This might be doable though I couldn't come up with a good way to spoof a wx.KeyEvent (can't just use event.m_keyCode since EVT_TEXT_ENTER sends a CommandEvent, and not a KeyEvent) so I gave up on that approach... err, I mean this solution is left as an exercise to the reader.
wx.EVT_TEXT_ENTER being a CommandEvent finally led to a third angle which did work. Instead of binding wx.EVT_TEXT_ENTER, I bound wx.EVT_CHAR and put in logic with special processing for the Return key (ASCII key code 13). I then planned to implement the EmulateKeyPress() bit I talked about earlier but when I removed wx.TE_PROCESS_ENTER from the TextCtrl style, I discovered that the \n was no longer being surreptitiously added. Code follows:
import wx
class TestRun(wx.Frame):
def __init__(self,parent):
wx.Frame.__init__(self, parent, title="StackO Test", size=(400,400))
self.control = wx.TextCtrl(self, id=wx.ID_ANY, style=wx.TE_MULTILINE)
self.control.Bind(wx.EVT_CHAR, self.OnPress)
self.Show(True)
def OnPress(self, event):
if event.GetKeyCode() == 13:
self.control.WriteText('\n>>>')
else:
event.Skip()
if __name__ == '__main__':
app = wx.App(False)
TestRun(None)
app.MainLoop()
The event.Skip() line is crucial; during my research into this, I learned that a KeyEvent is usually followed by a CharEvent. The CharEvent is the part where the character is written to the TextCtrl. When you intercept the KeyEvent, the CharEvent is only called if you do so explicitly - event.skip() is therefore vital if you want your TextCtrl to otherwise behave as normal. Without it, any keyboard input not equal to ASCII keycode 13 would do nothing.
From my tests, it appears that there is something about declaring the TextCtrl to have style wx.TE_PROCESS_ENTER that makes it print a newline after each call to WriteText(). My way gets around this though you will have more work to do with regard to making sure the insertion point is always in the right place, etc.
Best of luck!