[newbie] Catch CTRL+C?

Hello,

I’d like to catch when the user hits CTRL+C, and copy the contents of a label (StaticText) into the clipboard.

How can it be done in wxPython?

Thank you.

Hi Shohreh,

StaticText controls do not appear to accept the focus, so binding keyboard events to them has no effect as far as I can tell.

A similar question was ask on StackOverflow a few years ago:

A reply to that question suggested two possible workarounds:

  1. Bind wx.EVT_RIGHT_DOWN for the StaticText to a handler that displays a popup menu that contains an option to copy the StaticText’s label to the clipboard so that it can then be pasted into the destination control. However, the example given assumes the parent window contains only one StaticText control and hard codes it as the source of the text to be copied.

or

  1. Use a TextCtrl instead of a StaticText. Set the TextCtrl’s 'style=wx.TE_READONLY|wx.NO_BORDER` and its background colour to that of its parent panel. However, unlike a StaticText, a TextCtrl doesn’t resize itself to fit the text, so you would either have to use trial and error, or find a way to get the extent of the text in the current font and use that to determine the required width. I also noticed when I tried using a modified TextCtrl on linux that, when I click on it, a thin border appeared around the control, but that may be due to the theme I am using. The advantage of using a modified TextCtrl is that it already supports Ctrl+C and it also has a right-click menu with a Copy option.

This is a simple demo of the second technique:

import wx

class StaticTextCtrl(wx.TextCtrl):
    def __init__(self, parent, id=wx.ID_ANY, value=''):
        wx.TextCtrl.__init__(self, parent, id, value=value, style=wx.BORDER_NONE|wx.TE_READONLY)
        bg = self.GetParent().GetBackgroundColour()
        self.SetBackgroundColour(bg)
        width, height, descent, _leading = self.GetFullTextExtent(value)
        self.SetMinSize((width+2, height+descent))


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((400, 300))
        self.SetTitle("Test StaticTextCtrl")

        self.panel = wx.Panel(self, wx.ID_ANY)
        sizer = wx.BoxSizer(wx.VERTICAL)

        self.static_text_ctrl = StaticTextCtrl(self.panel, wx.ID_ANY, "This is a custom StaticTextCtrl")
        sizer.Add(self.static_text_ctrl, 0, wx.TOP, 16)

        self.text_ctrl = wx.TextCtrl(self.panel, wx.ID_ANY, "Paste here...")
        self.text_ctrl.SetMinSize((160, 27))
        sizer.Add(self.text_ctrl, 0, wx.TOP, 16)

        self.panel.SetSizer(sizer)
        self.Layout()


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


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

Tested on Python 3.10.6 + wxPython 4.2.0 gtk3 (phoenix) wxWidgets 3.2.0 + Linux Mint 21

Here is an alternative suggestion. It uses the GenStaticText class and overrides its AcceptsFocus() method to return True. You have to click on one of the MyGenStaticText controls in the demo and then press Ctrl+C. Then you can paste the text from the clipboard into the destination text control.

import wx
from wx.lib.stattext import GenStaticText


class MyGenStaticText(GenStaticText):
    def AcceptsFocus(self):
        return True


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((400, 300))
        self.SetTitle("Test MyGenStaticText")
        self.panel = wx.Panel(self, wx.ID_ANY)
        sizer = wx.BoxSizer(wx.VERTICAL)

        self.gen_static_text_1 = MyGenStaticText(self.panel, wx.ID_ANY, label="MyGenStaticText1")
        sizer.Add(self.gen_static_text_1, 0, wx.TOP, 16)

        self.gen_static_text_2 = MyGenStaticText(self.panel, wx.ID_ANY, label="MyGenStaticText2")
        sizer.Add(self.gen_static_text_2, 0, wx.TOP, 16)

        self.text_ctrl_2 = wx.TextCtrl(self.panel, wx.ID_ANY, "Paste text here...")
        self.text_ctrl_2.SetMinSize((200, 27))
        sizer.Add(self.text_ctrl_2, 0, wx.TOP, 16)

        self.panel.SetSizer(sizer)
        self.Layout()

        self.gen_static_text_1.Bind(wx.EVT_KEY_UP, self.OnChar)
        self.gen_static_text_2.Bind(wx.EVT_KEY_UP, self.OnChar)


    def OnChar(self, event):
        keycode = event.GetKeyCode()

        if event.GetModifiers() == wx.MOD_CONTROL:
            if keycode == ord('C'):
                static_text = event.GetEventObject()
                label = static_text.GetLabelText()
                clip_data = wx.TextDataObject()
                clip_data.SetText(label)
                wx.TheClipboard.Open()
                wx.TheClipboard.SetData(clip_data)
                wx.TheClipboard.Close()
            else:
                event.Skip()
        else:
            event.Skip()


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

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

Tested on Python 3.10.6 + wxPython 4.2.0 gtk3 (phoenix) wxWidgets 3.2.0 + Linux Mint 21

Thanks for the suggestions.

Can’t the application itself catch keyboard events? Then, I could simply read what it says in the label.

Here’s what I use to close the app when the use hits Escape:

def OnKeyUP(self, event):
	keyCode = event.GetKeyCode()
	if keyCode == wx.WXK_ESCAPE:
		self.Close()
	event.Skip()

Edit: What about this?

If you do that, how will you know that the user intended the Ctrl-C to refer to the wx.StaticText, and not some other control?

The advice to use a TextCtrl with TE_READONLY is good. I would hesitate to add BORDER_NONE though, unless you really want to make your app a frustrating guessing game in modern design style.

Actually, I don’t care: I simply need to catch CTRL+C somehow, and copy the content of a specific label into the clipboard.


Edit: It displays “308” when I hit CTRL, and “67” when I add “C”, but it doesn’t see CTRL+C

	self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyUP) #ESC closes app
	self.Show()

#CTRL=308, C=67
def OnKeyUP(self, event):
	keyCode = event.GetKeyCode()
	print("Keycode is ",keyCode)

	#Close app if hit ESC
	if keyCode == wx.WXK_ESCAPE:
		self.Close()
	if keyCode == wx.WXK_CONTROL_C:
		print("Found !")

	event.Skip()

Since wx.WXK_CONTROL_C doesn’t work, I found a work-around:

def OnKeyUP(self, event):
	keyCode = event.GetKeyCode()
	#print("Keycode is ",keyCode)
	"""
	#not called
	if keyCode == wx.WXK_CONTROL_C:
		print("Found !")
	"""
	#ord() returns integer representing Unicode character
	if keyCode in (ord('C'), ord('c')):
		if wx.GetKeyState(wx.WXK_CONTROL):
			print("Found !!")
	event.Skip()