tkinter中的“scrolledtext”复制和粘贴功能不可靠。

4

我发现了一个在tkinter中看起来像是bug的行为。

如果你运行以下最小化代码复制:

import tkinter, tkinter.simpledialog, tkinter.scrolledtext
root = tkinter.Tk('test')
text = tkinter.scrolledtext.ScrolledText(master=root, wrap='none')
text.pack(side="top", fill="both", expand=True, padx=0, pady=0)
text.insert(tkinter.END, 'abc\ndef\nghi\nijk')
root.mainloop()

然后:

  • scrolledtext 组件中选择一行,例如选择“ghi”这一行,
  • 使用 CTRL+C 复制该行内容
  • 不进行任何其他操作并关闭应用程序

然后在任何其他 Windows 应用程序中粘贴它(CTRL+V),但发现无法粘贴。为什么呢?

如何解决这个问题?


注意:预期的行为是使用 CTRL+C 复制的文本应该会保留在剪贴板中,即使应用程序已经关闭。这是许多 Windows 软件的默认行为。以下是在 notepad.exe 中的示例:

链接到动画截屏:https://i.imgur.com/li7UvYw.mp4

enter image description here

注意:这与以下问题有关:


如果你关闭 tkinter 窗口,所有由 tkinter 添加的剪贴板项目都会在我的电脑上被删除。另外,text.focus() 的作用是什么?你是不是想说 text.focus_set() - TheLizzard
@TheLizzard 我删除了这个最小示例中无用的.focus()部分。 更重要的是,例如,即使notepad.exe已关闭,任何复制的文本都将保留,即使notepad.exe已关闭。 示例:https://imgur.com/li7UvYw 这是期望的行为。 在这里使用tkinter时,它不起作用。 有什么想法如何解决这个问题? - Basj
@Basj 这就是 tkinter 的工作方式。我知道这很烦人,也不知道为什么会这样。你可以绑定到 Control-c 并使用 clipboard 库来复制数据。即使 tkinter 窗口关闭,它也应该将数据实际复制到剪贴板中。 - TheLizzard
我以前也处理过这个问题。我之前发布了一个回答与此问题相关。如果有帮助,请告诉我。tk-only-copies-to-clipboard-if-paste-is-used-before-program-exits - Mike - SMT
@Mike-SMT 感谢提供信息!你尝试过从 https://bugs.python.org/issue23760 中提出的解决方案吗?即使用 r.after(100, r.destroy); r.mainloop() 让 Tkinter 在退出时执行复制到系统剪贴板的代码?我还没有尝试过,但这可能是一个解决方法。它对你有用吗? - Basj
显示剩余7条评论
3个回答

4
你还可以使用支持Windows、Linux和Mac的pyperclip
import tkinter as tk
import pyperclip

def copy(event:tk.Event=None) -> str:
    try:
        text = text_widget.selection_get()
        pyperclip.copy(text)
    except tk.TclError:
        pass
    return "break"

root = tk.Tk()

text_widget = tk.Text(root)
text_widget.pack()
text_widget.bind("<Control-c>", copy)

root.mainloop()

1
你不应该在copy函数中返回"break"吗?如果用户没有选择任何内容,这也会引发错误。另外有趣的是:当你尝试复制时,pyperclip在Windows上会创建一个新窗口。 - TheLizzard
此外,需要使用pyperclip.paste()吗?它不是只返回剪贴板中的内容吗?通过阅读该库的源代码,我发现它基本上调用了ctypes.windll.user32.GetClipboardData并将其转换为字符串。总之,回答得很好。 - TheLizzard
@TheLizzard,你最后的评论很有用:我们能否在不使用第三方依赖pyperclip或者你的echo ... | clip解决方案的情况下,仅使用ctypes和这个winAPI调用来回答这个问题? - Basj
@Basj 现在我的机器上的Windows非常不稳定,所以我无法测试任何东西。我建议查看这个。它包含了你需要实现它的所有代码。 - TheLizzard

1
使用pypercliproot.bind_all(),我们可以解决这个问题。
import tkinter, tkinter.simpledialog, tkinter.scrolledtext 
import pyperclip as clip

root = tkinter.Tk('test')

text = tkinter.scrolledtext.ScrolledText(master=root, wrap='none')
def _copy(event):
   try:
      string = text.selection_get()
      clip.copy(string)
   except:pass

root.bind_all("<Control-c>",_copy)

text.pack(side="top", fill="both", expand=True, padx=0, pady=0)
text.insert(tkinter.END,'abc\ndef\nghi\njkl')
root.mainloop()


为什么要使用bind_all?我认为应该避免使用bind_all。此外,这与@Chandan的答案相同。唯一的区别是你使用了.bind_all和一个滚动文本(虽然这不在问题中,但没关系)。 - TheLizzard
你还导入了 simpledialog 但是没有使用它。另外,为什么在创建 tkinter.Tk 时要传入 "test"?如果你想设置窗口的标题栏标题,请使用 root.title(....) - TheLizzard
抱歉,关于不需要滚动文本的问题是我的错,但我的其他观点仍然成立。 - TheLizzard
使用bind在scrolledtext上不起作用,应该在master上使用bind_all。关于import和title,我没有改变问题,它们是问题的一部分。我只是添加了Function和bind_all,其余部分都是问题本身。 - Deli

1
尝试以下仅适用于Windows的解决方案:

import tkinter as tk
import os

def copy(event:tk.Event=None) -> str:
    try:
        # Get the selected text
        # Taken from: https://dev59.com/vm855IYBdhLWcg3w7o-g#4073612
        text = text_widget.selection_get()
        # Copy the text
        # Inspired from: https://codegolf.stackexchange.com/a/111405
        os.system("echo.%s|clip" % text)
        print(f"{text!r} is in the clipboard")
    # No selection was made:
    except tk.TclError:
        pass
    # Stop tkinter's built in copy:
    return "break"

root = tk.Tk()

text_widget = tk.Text(root)
text_widget.pack()
text_widget.bind("<Control-c>", copy)

root.mainloop()

基本上,每当用户按下Control-C时,我会调用自己的copy函数。在该函数中,我使用作为操作系统一部分的clip.exe程序复制文本。
注意:使用os.system复制数据到剪贴板的方法并不好,因为无法复制|字符。建议查看此处以找到更好的方法。您只需要替换那1行代码。

谢谢你的回答。你知道在tkinter的“scrolledtext”中,CTRL+C最初是如何实现的吗?我快速查看了源代码,但没有找到。也许可以在那里进行微调(可以提出作为Python核心存储库的PR?),这样就能正常工作了?在此问题解决之前,你的解决方案确实很有帮助! - Basj
我查看了源代码@TheLizzard,scrolledtextText的子类,但我在那里找不到有关复制/粘贴处理的内容。 https://github.com/python/cpython/blob/main/Lib/tkinter/__init__.py#L3561 - Basj
@Basj 这是在tcl中完成的。基本上,tcl是一种用于创建GUI的编程语言。有一个名为_tkinter的模块,它将许多tcl功能转换为Python(它是tkinter的C部分)。然后有tkinter使用_tkinter。通过从我的函数返回“break”,我告诉tcl不要调用通常的Control-C绑定。 - TheLizzard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接