wxpython: как переместить курсор в TextCtrl в положение мыши при открытии контекстного меню

В wxpython поведение контекстного меню TextCtrl по умолчанию заключается в том, что курсор перемещается в позицию в TextCtrl, которая была нажата правой кнопкой мыши. Когда я перезаписываю событие EVT_CONTEXT_MENU и создаю собственное меню, это поведение отсутствует. Есть ли простой способ изменить это?

Моя цель состоит в том, чтобы разрешить пометить предложение или абзац, щелкнутый правой кнопкой мыши, но текущее поведение требует, чтобы пользователь сначала щелкнул левой кнопкой мыши соответствующее предложение/абзац, чтобы переместить каретку, а затем щелкнул правой кнопкой мыши, чтобы выбрать, сколько пометить.

Соответствующий код:

self.textbox.Bind(wx.EVT_CONTEXT_MENU, self.textbox_context_menu)

def textbox_context_menu(self, event):
    """ show context menu when right-clicking on text """
    menu = wx.Menu()

    menu.Append(self.mark_sentence_id, "mark sentence")
    menu.Append(self.mark_paragraph_id, "mark paragraph")

    self.PopupMenu(menu)

РЕДАКТИРОВАТЬ: Вот минималистичный пример, который можно выполнить:

import wx
import wx.richtext as rt


class MCVE(wx.App):
    """ App """
    def OnInit(self):
        frame = Frame()
        frame.Show()
        return True


class Frame(wx.Frame):
    """ Frame """
    def __init__(self):
        style = wx.SYSTEM_MENU | wx.CAPTION | wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.CLOSE_BOX | wx.CLIP_CHILDREN | wx.RESIZE_BORDER
        super(Frame, self).__init__(parent=None, title="LabelingTool", style=style, size=(800, 600))
        self.panel = Panel(parent=self)


class Panel(wx.Panel):
    """ Panel """
    def __init__(self, parent):
        super(Panel, self).__init__(parent)
        # textbox
        hboxsizer = wx.BoxSizer(wx.HORIZONTAL)
        text = "Lorem Ipsum\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean eget enim vitae elit convallis ultrices. Sed vestibulum in metus id tempus. Phasellus tincidunt risus elit, id sagittis erat placerat quis. Donec ac porta tortor, non varius purus. Suspendisse euismod auctor maximus. Suspendisse nec orci vel dui posuere ultrices. Vivamus dictum vel enim nec interdum. Nunc tincidunt nulla sed facilisis suscipit. Nunc eget nisi ut turpis venenatis faucibus.\n\nInteger mauris nulla, malesuada quis lacus vitae, condimentum tincidunt dui. Aliquam non nisi aliquam, vulputate dolor ut, fringilla ligula. Nulla vitae tellus sit amet nulla ultrices pellentesque eu vitae lectus. Aliquam fringilla mauris tortor, et maximus lorem bibendum quis. Nam in magna gravida, accumsan libero accumsan, feugiat nisl. Donec tincidunt, tortor ut aliquam convallis, urna odio imperdiet ligula, dignissim vulputate ligula diam sit amet tortor. Ut a eros risus.\n\nCras et erat sodales, tempus nulla a, vulputate metus. Maecenas lacinia, nulla ac congue pharetra, lorem nibh pharetra metus, eu porttitor turpis leo ut lectus. Proin luctus rutrum mi id pharetra. Suspendisse aliquet id est nec efficitur. Maecenas dolor dui, vulputate et pulvinar at, venenatis id lorem. Praesent vel nisi ultrices massa rhoncus vestibulum. Nunc imperdiet consectetur pharetra. In cursus nec massa nec finibus. Aliquam et ligula bibendum, sodales mauris a, efficitur sapien. Ut mattis et ipsum eget sodales. Vestibulum maximus libero id ipsum placerat interdum at luctus risus. In finibus accumsan nunc, vitae posuere est interdum quis. Vivamus sed neque metus. Etiam fringilla efficitur lacus, vel aliquam purus lobortis vitae. Donec ut placerat orci. Etiam efficitur efficitur eleifend.\n\nAenean in imperdiet nisl. Donec dapibus neque tincidunt, fringilla velit vel, vestibulum velit. Sed at lorem id tortor accumsan interdum eu ut orci. Aenean convallis aliquet libero eu congue. In dapibus posuere massa, quis finibus neque volutpat et. Integer non massa tristique, gravida justo id, accumsan nibh. Ut ac nisl purus. Integer vestibulum sem in ante pellentesque, ac interdum augue faucibus.\n\nInteger bibendum eros vitae aliquam venenatis. Integer feugiat orci eu metus placerat, ut dictum leo posuere. Vivamus eget ligula vitae ante porttitor cursus. Nulla consectetur enim eu nisi aliquam mollis. Aliquam elementum consequat mauris, dignissim tempus libero sodales eu. In ultrices ullamcorper nulla, vel aliquet est vestibulum non. Nullam nec est ante. Phasellus eleifend lacinia nulla nec ultricies."
        self.textbox = rt.RichTextCtrl(parent=self, value=text, style=wx.TE_MULTILINE | wx.TE_READONLY)
        self.textbox.Bind(wx.EVT_CONTEXT_MENU, self.textbox_context_menu)
        hboxsizer.Add(self.textbox, proportion=1, flag=wx.EXPAND)
        self.SetSizerAndFit(hboxsizer)
        # event handling
        self.tag_sentence_id = 100
        self.tag_paragraph_id = 200
        self.Bind(wx.EVT_MENU, self.menu_event)

    def textbox_context_menu(self, event):
        """ show context menu when right-clicking on text """
        menu = wx.Menu()
        menu.Append(self.tag_sentence_id, "tag sentence")
        menu.Append(self.tag_paragraph_id, "tag paragraph")
        self.PopupMenu(menu)

    def menu_event(self, event):
        """ handle context menu events """
        event_id = event.GetId()
        self.tag(event_id)

    def tag(self, event_id):
        # get caret position
        caret_position = self.textbox.GetCaretPosition()+1
        # tag by event
        if event_id == self.tag_paragraph_id:
            paragraph = self.find_paragraph(caret_position)
            start = self.textbox.GetValue().find(paragraph)
            end = start + len(paragraph)
            self.apply_tag((start, end))
        elif event_id == self.tag_sentence_id:
            sentence = self.find_sentence(caret_position)
            start = self.textbox.GetValue().find(sentence.strip())
            end = start + len(sentence.strip())
            self.apply_tag((start, end))

    def apply_tag(self, position):
        self.textbox.SetStyle(position[0], position[1], wx.TextAttr(colText=wx.WHITE, colBack=wx.BLACK))

    def find_paragraph(self, caret_position):
        paragraphs = self.textbox.GetValue().split("\n\n")
        for paragraph in paragraphs:
            paragraph = paragraph.strip()
            start = self.textbox.GetValue().find(paragraph)
            end = start + len(paragraph)
            if start < caret_position < end:
                return paragraph

    def find_sentence(self, caret_position):
        sentences = self.find_paragraph(caret_position).split(".")
        for sentence in sentences:
            sentence = sentence.strip()
            start = self.textbox.GetValue().find(sentence)
            end = start + len(sentence)
            # append dot if applicable
            if self.textbox.GetValue()[end] == ".":
                sentence += "."
            if start < caret_position < end:
                return sentence


if __name__ == "__main__":
    app = MCVE()
    app.MainLoop()

Если вы закомментируете строку self.textbox.Bind(wx.EVT_CONTEXT_MENU, self.textbox_context_menu), вы получите контекстное меню по умолчанию с желаемым поведением перемещения курсора в позицию, по которой щелкнули правой кнопкой мыши в тексте, перед открытием контекстного меню, что позволяет пометить предложение/абзац, щелкнутый правой кнопкой мыши, без левого- нажав сначала.


person Naa    schedule 28.10.2020    source источник
comment
Я предполагаю, что вы говорите о wx.richtext, а не wx.TextCtrl. Не могли бы вы добавить mcve stackoverflow.com/help/minimal-reproducible-example. Поскольку ваш вопрос сбивает с толку и предполагает, что мы знаем, что у вас на уме. Что тегировать, например?   -  person Rolf of Saxony    schedule 29.10.2020
comment
Правильно, я использую RichTextCtrl. Возможно, я ошибочно полагал, что это верно и для TextCtrl. С пометкой все, что я делаю, это использую self.textbox.SetStyle(), чтобы применить цвет фона к соответствующему предложению/абзацу. Я создам mcve и обновлю исходный пост.   -  person Naa    schedule 29.10.2020
comment
Вы можете попробовать создать контекстное меню, прежде чем вручную настраивать его с помощью self.textbox.SetContextMenu(menu)?   -  person vk-code    schedule 29.10.2020
comment
Я не совсем уверен, что вы имеете в виду. Просто создать меню и установить его с помощью self.textbox.SetContextMenu(menu), не перезаписывая обработку событий с помощью self.textbox.Bind(wx.EVT_CONTEXT_MENU, self.textbox_context_menu)? Есть ли еще шаги к этому? Если я сделаю именно это, курсор переместится в нужную позицию, но контекстное меню не появится.   -  person Naa    schedule 29.10.2020
comment
если вы сделаете self.menu, это сработает, похоже, что объекты меню должны быть сохранены   -  person vk-code    schedule 29.10.2020


Ответы (1)


Используйте SetContextMenu и создайте меню заранее

import wx
import wx.richtext as rt


class MCVE(wx.App):
    """ App """
    def OnInit(self):
        frame = Frame()
        frame.Show()
        return True


class Frame(wx.Frame):
    """ Frame """
    def __init__(self):
        style = wx.SYSTEM_MENU | wx.CAPTION | wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.CLOSE_BOX | wx.CLIP_CHILDREN | wx.RESIZE_BORDER
        super(Frame, self).__init__(parent=None, title="LabelingTool", style=style, size=(800, 600))
        self.panel = Panel(parent=self)


class Panel(wx.Panel):
    """ Panel """
    def __init__(self, parent):
        super(Panel, self).__init__(parent)
        # textbox
        hboxsizer = wx.BoxSizer(wx.HORIZONTAL)
        text = "Lorem Ipsum\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean eget enim vitae elit convallis ultrices. Sed vestibulum in metus id tempus. Phasellus tincidunt risus elit, id sagittis erat placerat quis. Donec ac porta tortor, non varius purus. Suspendisse euismod auctor maximus. Suspendisse nec orci vel dui posuere ultrices. Vivamus dictum vel enim nec interdum. Nunc tincidunt nulla sed facilisis suscipit. Nunc eget nisi ut turpis venenatis faucibus.\n\nInteger mauris nulla, malesuada quis lacus vitae, condimentum tincidunt dui. Aliquam non nisi aliquam, vulputate dolor ut, fringilla ligula. Nulla vitae tellus sit amet nulla ultrices pellentesque eu vitae lectus. Aliquam fringilla mauris tortor, et maximus lorem bibendum quis. Nam in magna gravida, accumsan libero accumsan, feugiat nisl. Donec tincidunt, tortor ut aliquam convallis, urna odio imperdiet ligula, dignissim vulputate ligula diam sit amet tortor. Ut a eros risus.\n\nCras et erat sodales, tempus nulla a, vulputate metus. Maecenas lacinia, nulla ac congue pharetra, lorem nibh pharetra metus, eu porttitor turpis leo ut lectus. Proin luctus rutrum mi id pharetra. Suspendisse aliquet id est nec efficitur. Maecenas dolor dui, vulputate et pulvinar at, venenatis id lorem. Praesent vel nisi ultrices massa rhoncus vestibulum. Nunc imperdiet consectetur pharetra. In cursus nec massa nec finibus. Aliquam et ligula bibendum, sodales mauris a, efficitur sapien. Ut mattis et ipsum eget sodales. Vestibulum maximus libero id ipsum placerat interdum at luctus risus. In finibus accumsan nunc, vitae posuere est interdum quis. Vivamus sed neque metus. Etiam fringilla efficitur lacus, vel aliquam purus lobortis vitae. Donec ut placerat orci. Etiam efficitur efficitur eleifend.\n\nAenean in imperdiet nisl. Donec dapibus neque tincidunt, fringilla velit vel, vestibulum velit. Sed at lorem id tortor accumsan interdum eu ut orci. Aenean convallis aliquet libero eu congue. In dapibus posuere massa, quis finibus neque volutpat et. Integer non massa tristique, gravida justo id, accumsan nibh. Ut ac nisl purus. Integer vestibulum sem in ante pellentesque, ac interdum augue faucibus.\n\nInteger bibendum eros vitae aliquam venenatis. Integer feugiat orci eu metus placerat, ut dictum leo posuere. Vivamus eget ligula vitae ante porttitor cursus. Nulla consectetur enim eu nisi aliquam mollis. Aliquam elementum consequat mauris, dignissim tempus libero sodales eu. In ultrices ullamcorper nulla, vel aliquet est vestibulum non. Nullam nec est ante. Phasellus eleifend lacinia nulla nec ultricies."
        self.textbox = rt.RichTextCtrl(parent=self, value=text, style=wx.TE_MULTILINE | wx.TE_READONLY)
        
        # event handling
        self.tag_sentence_id = 100
        self.tag_paragraph_id = 200

        self.menu = wx.Menu()
        self.menu.Append(self.tag_sentence_id, "tag sentence")
        self.menu.Append(self.tag_paragraph_id, "tag paragraph")
        
        self.textbox.SetContextMenu(self.menu)
        
        hboxsizer.Add(self.textbox, proportion=1, flag=wx.EXPAND)
        self.SetSizerAndFit(hboxsizer)

        self.Bind(wx.EVT_MENU, self.menu_event)


    def menu_event(self, event):
        """ handle context menu events """
        event_id = event.GetId()
        self.tag(event_id)

    def tag(self, event_id):
        # get caret position
        caret_position = self.textbox.GetCaretPosition()+1
        # tag by event
        if event_id == self.tag_paragraph_id:
            paragraph = self.find_paragraph(caret_position)
            start = self.textbox.GetValue().find(paragraph)
            end = start + len(paragraph)
            self.apply_tag((start, end))
        elif event_id == self.tag_sentence_id:
            sentence = self.find_sentence(caret_position)
            start = self.textbox.GetValue().find(sentence.strip())
            end = start + len(sentence.strip())
            self.apply_tag((start, end))

    def apply_tag(self, position):
        self.textbox.SetStyle(position[0], position[1], wx.TextAttr(colText=wx.WHITE, colBack=wx.BLACK))

    def find_paragraph(self, caret_position):
        paragraphs = self.textbox.GetValue().split("\n\n")
        for paragraph in paragraphs:
            paragraph = paragraph.strip()
            start = self.textbox.GetValue().find(paragraph)
            end = start + len(paragraph)
            if start < caret_position < end:
                return paragraph

    def find_sentence(self, caret_position):
        sentences = self.find_paragraph(caret_position).split(".")
        for sentence in sentences:
            sentence = sentence.strip()
            start = self.textbox.GetValue().find(sentence)
            end = start + len(sentence)
            # append dot if applicable
            if self.textbox.GetValue()[end] == ".":
                sentence += "."
            if start < caret_position < end:
                return sentence


if __name__ == "__main__":
    app = MCVE()
    app.MainLoop()
    
person vk-code    schedule 29.10.2020
comment
Большое спасибо! - person Naa; 29.10.2020