質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.83%

Python (TkInter) PACKしたボタンを横いっぱいになるよう配置したいが、中央に配置されてしまっている

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 307

saya24

score 141

毎度毎度すみません、TkInterの画面デザインについてのご相談です。
こちらの記事にあるとおり、ボタンの配置を横いっぱいになるよう対処したいのですが、同じコードを適用しているはずも、目的が達成されません。
![絵
以下コードで★印のコメント部分が対応した部分ですが、その他の部分で 悪さをしているということでしょうか?

ご見解頂けたら 幸いです、よろしくお願いします。

from tkinter import *
import tkinter.ttk as ttk
import tkinter.scrolledtext as tksc
import math

class Apprication(ttk.Frame):

    def __init__(self, app):
        super().__init__(app)
        self.pack()

        btn1 = ttk.Button(self, text="Sub", command=self.openDialog)
        btn1.bind('<Return>', self.openDialog)
        btn1.pack(fill = 'x')                       #★横いっぱい(本当は縦いっぱいにも)にボタンを貼付けたい★
        btn1.focus_set()

        btn2 = ttk.Button(self, text="Quit", command=app.quit)
        btn2.bind('<Return>', lambda _: app.quit())
        btn2.pack(fill = 'x')                       #★横いっぱい(本当は縦いっぱいにも)にボタンを貼付けたい★

        self.menu()



    def menu(self):
        menu_top = Menu(app)
        menu_file = Menu(menu_top, tearoff=False)
        menu_open = Menu(menu_top, tearoff=False)

        app.configure(menu=menu_top, bg="#F0FFFF")

        menu_top.add_cascade (label='File(F)', menu=menu_file, underline=0)

        menu_file.add_cascade(label='Open(O)', underline=0, menu=menu_open)
        menu_open.add_command(label='Sub(S)', underline=0, command=self.openDialog)
        menu_file.add_command(label='Quit(Q)',underline=0, command=app.quit)





    # 子画面開く
    def openDialog(self, event=None):

        self.dialog = Toplevel(self)
        self.dialog.title("Sub Menu")

        #フォームサイズを実行端末から導き、ド真中に配置表示
        lw = math.ceil(ww * 0.408)
        lh = math.ceil(wh * 0.477)
        self.dialog.geometry(str(lw)+"x"+str(lh)+"+"+str(int(ww/2-lw/2))+"+"+str(int(wh/2-lh/2)) )

        self.dialog.configure(bg="#F0FFFF")
        self.dialog.resizable(0,0)
        self.dialog.protocol('WM_DELETE_WINDOW', (lambda: 'pass')())


        # modalに
        self.dialog.grab_set()



        # 閉じるボタン
        btn3 =  Button(self.dialog, text='Quit', command=self.closeDialog, width=10, takefocus=1)
        btn3.grid(row=5, column=10, pady=10, padx=(0,10))


        self.dialog.grid_rowconfigure(1, weight=1)
        self.dialog.grid_rowconfigure(3, weight=1)
        self.dialog.grid_columnconfigure(2, weight=1)



    # 子画面閉じる
    def closeDialog(self):
        self.dialog.destroy()



if __name__ == '__main__':

    #世間でいうrootをappとしています
    app  = Tk()

    #実行端末の画面サイズを取得
    ww = app.winfo_screenwidth()
    wh = app.winfo_screenheight()

    app.update_idletasks()

    #フォームサイズを実行端末から導き、ド真中に配置表示
    lw = math.ceil(ww * 0.208)
    lh = math.ceil(wh * 0.277)
    app.geometry(str(lw)+"x"+str(lh)+"+"+str(int(ww/2-lw/2))+"+"+str(int(wh/2-lh/2)) )

    #タイトルを指定
    app.title("Main Menu")

    #フォームの最大化、×ボタン操作を無効化
    app.resizable(0,0)
    app.protocol('WM_DELETE_WINDOW', (lambda: 'pass')())



    menu_top = Menu(app)
    menu_file = Menu(menu_top, tearoff=False)
    menu_open = Menu(menu_top, tearoff=False)

    app.configure(menu=menu_top, bg="#F0FFFF")

    menu_top.add_cascade (label='File(F)', menu=menu_file, underline=0)

    menu_file.add_cascade(label='Open(O)', underline=0, menu=menu_open)
    menu_open.add_command(label='Sub(S)', underline=0, command="app.openDialog") 
    menu_file.add_command(label='Quit(Q)',underline=0, command=app.quit)


    # フレームを作成する
    frame =  Apprication(app)
    # 格納したTkインスタンスのmainloopで画面を起こす
    app.mainloop()
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

サンプルコード通りに横幅が広くならない原因:

親ウィジェットである Application(ttk.Frame) が self.pack() で、
広がる設定になってません。


解決策:

packのオプションでfill=BOTH を指定します。
縦にも広げる場合は追加で expand=True

# Application class __init__ 内

        for widget in [self, btn1, btn2]:
            widget.pack(fill=BOTH, expand=True)

BOTH は from tkinter import * してる場合のコード例

イメージ説明

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/06/01 19:38 編集

    Tk() の変数も "app" としているので、そこもちょっと混同しそうですね。
    app = Tk()
    frame = Application(app)

    self.pack()でもオプションが必要な理由は、
    Frame を継承して Application クラスとしているのと、
    pack()での幅拡張がデフォルトで有効なオプションではない為です。

    ====
    一般的なtkinterのコードでは

    root = Tk() # もし Tk() が Application であれば pack()不要
    frame = Frame(root) # 今回はここが Application相当
    frame.pack(fill=BOTH, expand=True)
    root.mainloop()

    キャンセル

  • 2020/06/01 19:51

    引き続きのご教示誠にありがとうございます。申し訳ありません、率直に言って理解に苦しんでいるのですが

    frameは 下地という考えでよろしいでしょうか? 私はその認識でいて
    その下地を 引き延ばすような設定がなぜ 必要なの?? ということで混乱してしまっています。

    下地という認識が間違いでしょうか....

    キャンセル

  • 2020/06/01 20:08 編集

    下地という認識で良いのですが、返答は
    >幅拡張がデフォルトで有効なオプションではない為

    例えば仮に、100x100 の範囲を下地とします。
    その中に横幅一杯になるボタンを設置するとしましょう。
    そのボタンの横幅は、下地の幅いっぱいとなる100 になります。
    Frame に幅拡張の指定オプションがないと、広がる範囲はその枠内にとどまります。

    もう一点、認識に違いがあるとしたら
    その下地(Frame)はウィンドウ(Toplevel)自体ではありません。

    Tk() 内で暗黙的に作られる為、意識することはないかもしれませんが、
    Frame は Toplevelウィンドウ内に作られている為、
    Toplevel -> Frame -> Button という順にレイアウトの制限を受けます。
    つまりこれが、button だけでなく、Frame も pack のオプションが必要な理由です。

    もしボタンが Frame ではなく、ウィンドウに直接配置されていれば
    恐らく想定されてるような動作で、self.pack()は不要になります。

    キャンセル

+1


今回のコード(必要な部分のみ抜き出したコード)

Toplevel(ウィンドウ) -> Frame -> Button

import tkinter as tk
from tkinter import ttk


class Application(ttk.Frame): ## <-- Frameを継承
    def __init__(self, master):
        super().__init__(master)
        self.pack(fill=tk.BOTH, expand=True) ## <--
        ttk.Button(self, text="button1").pack(fill=tk.BOTH, expand=True)
        ttk.Button(self, text="button2").pack(fill=tk.BOTH, expand=True)


if __name__ == '__main__':
    root = tk.Tk()
    root.geometry("400x400")
    Application(root)
    root.mainloop()

クラスを使わずに書くと以下のようになります。

Toplevel(ウィンドウ) -> Frame -> Button

import tkinter as tk
from tkinter import ttk


if __name__ == '__main__':
    root = tk.Tk()
    root.geometry("400x400")

    frame = ttk.Frame(root)
    frame.pack(fill=tk.BOTH, expand=True) ## <-- ここが必要な理由について

    ttk.Button(frame, text="button1").pack(fill=tk.BOTH, expand=True)
    ttk.Button(frame, text="button2").pack(fill=tk.BOTH, expand=True)

    root.mainloop()

継承したことにより、Frame の存在を忘れてしまってるように見受けられます。


tkinter.Tk を直接親とする場合。

Toplevel -> Button を直接配置。
恐らくこちらのコードのような動作を期待しているのでしょう

import tkinter as tk
from tkinter import ttk


class Application(tk.Tk): ## <--- 継承元を直接ウィンドウにする
    def __init__(self):
        super().__init__()
        ttk.Button(self, text="button1").pack(fill=tk.BOTH, expand=True)
        ttk.Button(self, text="button2").pack(fill=tk.BOTH, expand=True)


if __name__ == '__main__':
    app = Application()
    app.geometry("400x400")
    app.mainloop()

まとめ: 下地という認識でいいのですが、

その下地の更に下にウィンドウという存在があり、
Frameもボタンと同様に、
その親となるウィジェットに拡張可能という事を伝える必要があります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/06/01 21:01

    実に分かりやすい説明です!!今まで切り貼りでやってきても相応のものができてしまっていた為、構成を意識していなかったのですが
    今回の説明で理解でき、問い合わせていた必要性についても十分納得できました。ありがとうごさいました。

    キャンセル

  • 2020/06/02 14:18

    フレームもしくは、ウィンドウの存在が要点だったみたいですね。

    因みに余談ですが、コード量だけ見るとウィンドウを直接親にする方がシンプルに見えますが
    クラスの「再利用の観点」からは、双方とも課題があります。

    - tkinter.Tk()を継承 -> 再利用の度に別のウィンドウが立ち上がる
    - クラス内で self.pack() -> packレイアウトでしか使えないクラスになる

    どちらも、再利用が不要であれば問題はありません が、
    旨く設計し活用すると、コード量だけでなく時間・労力の節約にも繋がる部分です。

    もし、このクラスを別の箇所でも流用したいみたいな事があれば、
    以下の方法でクラスの再利用を検討して見て下さい。
    >Frame を継承し、pack() や grid() は、クラス外・利用側に委ねる。

    キャンセル

  • 2020/06/02 14:56

    今の私のレベルでは 正直価値あるアドバイスであっても 理解に苦しみ適用に躊躇してしまいますが、あとあと絶対に役に立つ情報に違いありません。無駄にしません、ありがとうございます!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.83%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • トップ
  • Pythonに関する質問
  • Python (TkInter) PACKしたボタンを横いっぱいになるよう配置したいが、中央に配置されてしまっている