1. ホーム
  2. python

[解決済み] Tkinterでウィジェットグループにスクロールバーを追加する

2023-04-11 15:06:32

質問

私はPythonを使ってログファイルからエントリを解析し、Tkinterを使ってエントリの内容を表示していますが、今のところ素晴らしいものです。出力はラベルウィジェットのグリッドですが、時々、画面上に表示できるよりも多くの行があります。私はスクロールバーを追加したいのですが、それは非常に簡単であるように見えますが、私はそれを理解することができません。

ドキュメントでは、リスト、テキストボックス、キャンバス、およびエントリの各ウィジェットのみがスクロールバーインターフェイスをサポートしていることが示唆されています。これらのどれも、ウィジェットのグリッドを表示するのに適していないように見えます。Canvas ウィジェットに任意のウィジェットを配置することは可能ですが、絶対座標を使用しなければならないようなので、グリッド レイアウト マネージャを使用することはできないでしょうか?

ウィジェットグリッドをフレームに入れようとしましたが、フレームはスクロールバーインターフェースをサポートしていないようなので、これはうまくいきません。

mainframe = Frame(root, yscrollcommand=scrollbar.set)

この制限を回避する方法をどなたか教えてください。スクロールバーを追加するためだけに、PyQtで書き直して、実行可能な画像のサイズを大きくするのは嫌なんです!

どのように解決するのですか?

概要

スクロールバーは一部のウィジェットにしか関連付けることができず、ルートウィジェットと Frame はそのウィジェットグループの一部ではありません。

これを行うには、少なくとも2つの方法があります。単純な垂直または水平方向のウィジェットグループが必要な場合、テキストウィジェットと window_create メソッドを使用してウィジェットを追加します。この方法はシンプルですが、ウィジェットを複雑にレイアウトすることはできません。

より一般的な汎用ソリューションは、canvas ウィジェットを作成し、スクロールバーをそのウィジェットに関連付けることです。次に、そのキャンバスに、ラベル ウィジェットを含むフレームを埋め込みます。フレームの幅と高さを決定し、それをキャンバスに入力します。 scrollregion オプションに入力し、スクロール領域がフレームのサイズと正確に一致するようにします。

なぜウィジェットをキャンバスに直接ではなく、フレームに配置するのですか? canvas に添付されたスクロールバーは create_ メソッドのいずれかで作成されたアイテムのみをスクロールできます。でキャンバスに追加されたアイテムはスクロールできません。 pack , place または grid . フレームを使うことで、これらのメソッドをフレーム内で使用し、その後に create_window をフレームに対して一度だけ呼び出すことができます。

キャンバス上に直接テキスト項目を描画することはそれほど難しくないので、フレームに埋め込まれたキャンバスのソリューションがあまりに複雑に思える場合は、このアプローチを再考することをお勧めします。グリッドを作成しているので、各テキスト項目の座標は、特に各行の高さが同じであれば (単一のフォントを使用している場合はおそらくそうなります)、非常に簡単に計算できます。

キャンバスに直接描画する場合、使用しているフォントの行の高さを計算するだけです (そのためのコマンドもあります)。そして、各 y 座標は row*(lineheight+spacing) . x座標は、各列の最も幅の広い項目に基づいて固定された数値になります。すべてのものにその列のタグを与えれば、一つのコマンドで列内のすべてのアイテムのx座標と幅を調整することができます。

オブジェクト指向の解決策

オブジェクト指向のアプローチを使用した frame-embedded-in-canvas ソリューションの例です。

import tkinter as tk

class Example(tk.Frame):
    def __init__(self, parent):

        tk.Frame.__init__(self, parent)
        self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff")
        self.frame = tk.Frame(self.canvas, background="#ffffff")
        self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.vsb.set)

        self.vsb.pack(side="right", fill="y")
        self.canvas.pack(side="left", fill="both", expand=True)
        self.canvas.create_window((4,4), window=self.frame, anchor="nw",
                                  tags="self.frame")

        self.frame.bind("<Configure>", self.onFrameConfigure)

        self.populate()

    def populate(self):
        '''Put in some fake data'''
        for row in range(100):
            tk.Label(self.frame, text="%s" % row, width=3, borderwidth="1",
                     relief="solid").grid(row=row, column=0)
            t="this is the second column for row %s" %row
            tk.Label(self.frame, text=t).grid(row=row, column=1)

    def onFrameConfigure(self, event):
        '''Reset the scroll region to encompass the inner frame'''
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

if __name__ == "__main__":
    root=tk.Tk()
    example = Example(root)
    example.pack(side="top", fill="both", expand=True)
    root.mainloop()

手続き的解決

クラスを使用しない解決策を紹介します。

import tkinter as tk

def populate(frame):
    '''Put in some fake data'''
    for row in range(100):
        tk.Label(frame, text="%s" % row, width=3, borderwidth="1", 
                 relief="solid").grid(row=row, column=0)
        t="this is the second column for row %s" %row
        tk.Label(frame, text=t).grid(row=row, column=1)

def onFrameConfigure(canvas):
    '''Reset the scroll region to encompass the inner frame'''
    canvas.configure(scrollregion=canvas.bbox("all"))

root = tk.Tk()
canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
frame = tk.Frame(canvas, background="#ffffff")
vsb = tk.Scrollbar(root, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)

vsb.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand=True)
canvas.create_window((4,4), window=frame, anchor="nw")

frame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas))

populate(frame)

root.mainloop()