如何将任意小部件添加到 Canvas?

how to add arbitrary widgets to a Canvas?

我正在尝试构建一个 GUI 以使用 PySimpleGUI 标记文件。在主要预览小部件下方,我想放置一个小部件来显示当前标签。我制作了一个 TagWidget 子类 sg.Column 并包含一个 sg.Text 作为标签名称和一个 sg.Combo 作为当前值和建议的替代方案:

class TagWidget(_Widget, sg.Column):
  ''' A Display for a `Tag`.
  '''

  @typechecked
  def __init__(self, tags: TagSet, tag_name: str, alt_values=None):
    if alt_values is None:
      alt_values = set()
    self.tags = tags
    self.tag_name = tag_name
    self.alt_values = alt_values
    layout = [
        [
            sg.Text(tag_name + ("" if tag_name in tags else " (missing)")),
            sg.Combo(
                list(self.alt_values),
                default_value=tags.get(tag_name),
            )
        ]
    ]
    super().__init__(layout=layout, finalize=True)

我遇到的困难是制作一个小部件来显示其中的几个。我不想要网格,因为标签+值的宽度非常可变。理想情况下,我会使用 canvas 之类的东西,或包含 canvas 的窗格,其中包含一组标签小部件。理想情况下,它会像线一样流动。

我使用 create_text() 为每个小部件制作了一个成功的 canvas 文本。我可以继续进行并在编辑标签时重新绘制,但我想使用我的复合标签小部件而不是纯文本。

所以...每个 PySimpleGUI 小部件都有一个指向底层 Tk 小部件的 .Widget 属性。 canvas .create_window 方法看起来应该接受 Tk 小部件。但是,在呈现我的复合标签小部件之前,没有 .Widget 传递给 .create_window,并且在呈现 canvas 之前无法呈现它。

我当前的(无效的)尝试如下所示:

  def set_tags(self, tags):
    super().set_tags(tags)
    canvas = self.tk_canvas
    canvas.delete(canvas.find_all())
    for tag in self.tags:
      X("TagsView_Canvas.set_tags: add tag=%s", tag)
      tagw = self._get_tag_widget(tag)
      X("tagw = %s", tagw)
      X("tagw.Widget = %s", tagw.Widget)
      canvas.create_window(0, 0, window=tagw.Widget)
      X("added canvas window")

当然,此时 tagw.WidgetNone,什么都没有创建。

是否有一种干净的方法来准备我的复合标签小部件,以便我可以将其应用到 canvas?我应该换一种方式吗?

修正

评论的格式似乎过于受限。按照 Jason 的示例代码,我想做的是稍后在程序中创建一个新的小部件并将其添加到 canvas(或图形,我不介意哪个)中。我一直在摆弄 Jason 的主循环,通过单击其中一个按钮进行新的迭代。我还没有设法向图表添加额外的小部件,至少看不到任何东西。

sg.Text 为例,我在他的主循环中使用了这个:

while True:

  print("window.read...")
  event, values = window.read()
  print("event", event)
  if event == sg.WINDOW_CLOSED:
    break
  print(event, values)

  G = window['-GRAPH-']
  print("graph =", G)
  new_text = sg.Text("foo")
  layout[0][1] = new_text
  print("dir(graph)", dir(G))
  window.finalize()
  print("new_text.Widget =", new_text.Widget)
  graph.create_window(
      width // 4, height // 2, anchor=sg.tk.CENTER, window=new_text.Widget
  )

当前版本(以上)似乎没有初始化 new_text 小部件,因此 new_text.Widget 仍然是 Nonegraph.create_window 似乎什么也没做。

没有提供将新元素添加到布局中的方法,但是 window.extend_layout。它将新行添加到布局中,但在 graph.Widget.create_window.

中不起作用

一个简单的演示代码,展示如何将sg.Column移动到sg.Graph,请记住布局时sg.Graphsg.Column应放在同一行。

import io
import base64

from PIL import Image
import PySimpleGUI as sg


sg.theme("DarkBlue3")
sg.set_options(font=("Courier New", 12))

bg = 'green'
column_layout = [
    [sg.Text("Hello World", key="-LABEL-")],
    [sg.Button('Hello'), sg.Button('World')]
]

width, height = size = (320, 240)

buffer = io.BytesIO()
image_data = base64.b64decode(sg.EMOJI_BASE64_HAPPY_LAUGH)
im = Image.open(io.BytesIO(image_data))
new_img = im.resize(size, resample=Image.CUBIC)
new_img.save(buffer, format="PNG")
im_b64 = base64.b64encode(buffer.getvalue())

layout = [
    [sg.Graph(size, (0, 0), size, background_color=bg, key='-GRAPH-'),
     sg.Column(column_layout, background_color=bg, key='-COLUMN-')],
    [sg.Button('New')],
]
window = sg.Window('Title', layout, finalize=True)
graph = window['-GRAPH-']
column = window['-COLUMN-']
figure = graph.draw_image(data=im_b64, location=(0, height))
graph.Widget.create_window(width//2, height//2, anchor=sg.tk.CENTER, window=column.Widget)

while True:

    event, values = window.read()
    if event == sg.WINDOW_CLOSED:
        break
    elif event == 'New':
        new_element = sg.Text("foo")
        window.extend_layout(column, [[new_element]])

window.close()

看起来不太好,因为 sg.Canvas 的 background_color 会遮住背景。