How can I binding text selection change even in wx.TextCtrl?

Problem:

In a situation, I want to select text in wx.TextCtrl, and show the length of selected words in the title dymamicly.

Selection changes may be called by mouse, keyboard, and shortcut keys. But if I bind all these events, there is redundant computation, and I need to do other things with these events as well.

For test:

import wx
app = wx.App()
frm = wx.Frame(None)
text = wx.TextCtrl(frm, -1, 'Hello world! ' * 100, style=wx.TE_MULTILINE)
text.Bind(wx.EVT_KEY_UP, lambda e: print(len(text.GetStringSelection())))
frm.Show()
app.MainLoop()

I’m not really sure I understand the issue but from what I can gather you don’t want to use up the evt’s.
Why not stop the evt in the bind function/routine.
Johnf

Thank you @jfabiani. In my demo, program will print the length of text witch I have selected ONLY when I KEY_UP. But I want this print occurred when (and only when) the text selection has changed. What can I do for this problem?

One possibility would be to use a wx.Timer. This could capture changes of selection whether made by the mouse, keyboard or programmatically. It would probably be necessary to tweak the timer interval to make it responsive enough, without putting too much load on the system.

Here is a simple demo:

import wx

TEXT = (
    "Lorem IPSUM dolor sit amet, consectetur adipiscing elit, "
    "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
    "Vestibulum lectus mauris ultrices eros in cursus turpis. "
    "Dui vivamus arcu felis bibendum ut. "
    "Ac turpis egestas integer eget aliquet nibh praesent tristique magna. "
    "Mauris ultrices eros in cursus turpis massa tincidunt dui. "
    "Mi bibendum neque egestas congue. "
    "Nunc sed augue lacus viverra vitae congue eu consequat. "
    "Consectetur adipiscing elit pellentesque habitant morbi. "
    "Pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus at. "
    "Ut lectus arcu bibendum at varius. "
    "Pellentesque eu tincidunt tortor aliquam nulla facilisi cras fermentum odio. "
    "Eget aliquet nibh praesent tristique. "
    "Sit amet aliquam id diam maecenas ultricies mi eget. "
    "Iaculis nunc sed augue lacus viverra vitae congue eu. "
    "Fames ac turpis egestas sed tempus urna. A diam maecenas sed enim ut sem. "
    "Aenean pharetra magna ac placerat vestibulum lectus mauris ultrices. "
    "Velit egestas dui id ornare arcu odio ut sem nulla. "
    "Placerat duis ultricies lacus sed. "
    "Libero enim sed faucibus turpis in eu mi bibendum. "
)

# milliseconds
TIMER_INTERVAL = 200


class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((500, 300))
        self.SetTitle("frame")
        self.panel = wx.Panel(self, wx.ID_ANY)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.text_ctrl = wx.TextCtrl(self.panel, wx.ID_ANY, "", style=wx.TE_MULTILINE)
        main_sizer.Add(self.text_ctrl, 1, wx.ALL | wx.EXPAND, 4)
        self.panel.SetSizer(main_sizer)
        self.Layout()

        self.text_ctrl.SetValue(TEXT)
        self.selection_len = 0

        self.Bind(wx.EVT_CLOSE, self.OnClose)

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.OnTimer)
        self.timer.Start(TIMER_INTERVAL)


    def OnClose(self, _event):
        self.timer.Stop()
        self.Destroy()


    def OnTimer(self, _event):
        current_len = len(self.text_ctrl.GetStringSelection())
        if current_len != self.selection_len:
            print(current_len)
            self.selection_len = current_len


class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True


if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

Can it be as simple as in the function/routine that you bind to have a copy of the old string and compare the old string to the new string. If the old string matches just “evt.stop” and return. If not then print the string. Which means that the very first char will print.
BTW when you say KEY_UP - do you mean “mouse UP”.
Johnf

Thank you. Maybe there isn’t a event that does exactly what I want. Thanks a lot. :slight_smile:

No, I mean keyboard release.
When I hold the SHIFT button (Maybe I am pressing Shift+Right to select an area of text), it will generate wx.EVT_KEY_DOWN event all the time.


I think maybe I didn’t explain clearly.
Like there is a text: A quick brown fox jumps over the lazy dog
And I use cursor to select the word fox, and after a while I select dog. And this is the “selection changed” I mean.
I think this is not relative with “old string” and “new string”.

wx.TextCtrl is a basic control and there are not many such “useful” event hooks. But it’s in wx.stc and I’m sure you’re interested in the deep world.

The following code uses wx.py.editwindow.EditWindow, which is a subclass of stc.StyledTextCtrl, has some nice functions and styles, and is also a superclass of wx.py.shell.

import wx
from wx import stc
from wx.py.editwindow import EditWindow
## from wx.stc import StyledTextCtrl as EditWindow

class TestEditor(EditWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdate)
    
    def OnUpdate(self, evt):
        if evt.Updated & (stc.STC_UPDATE_SELECTION | stc.STC_UPDATE_CONTENT):
            p = self.CurrentPos
            c = self.GetCharAt(p)
            sty = self.GetStyleAt(p)
            text = self.SelectedText
            print("{:2d} {:3d} text={!r}".format(sty, c, text))
        evt.Skip() # skip to match-paren

if __name__ == "__main__":
    app = wx.App()
    frame = wx.Frame(None)
    ed = TestEditor(frame)
    ed.LoadFile(__file__) # utf-8
    frame.Show()
    app.MainLoop()

EDIT
I forgot to mention the meaning of style numbers:

wx.stc.STC_P_DEFAULT                        0    etc: space \r\n\\$ (non-identifier)
wx.stc.STC_P_OPERATOR                       10   ops
wx.stc.STC_P_IDENTIFIER                     11   word
wx.stc.STC_P_COMMENTLINE                    1    comment #
wx.stc.STC_P_COMMENTBLOCK                   12   comment ##
wx.stc.STC_P_NUMBER                         2    number
wx.stc.STC_P_STRING                         3    string ""
wx.stc.STC_P_STRINGEOL                      13   string --> eol
wx.stc.STC_P_CHARACTER                      4    string ''
wx.stc.STC_P_WORD                           5    keyword
wx.stc.STC_P_TRIPLE                         6    string '''*'''
wx.stc.STC_P_TRIPLEDOUBLE                   7    string """*"""
wx.stc.STC_P_CLASSNAME                      8    class
wx.stc.STC_P_DEFNAME                        9    def 
wx.stc.STC_P_WORD2                          14   word2
wx.stc.STC_P_DECORATOR                      15   @
2 Likes

Thank you very much, that is exactly what I want. That is amazing!