koh0404
@koh0404

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

Pythonを独学で学習しており、躓いています。

解決したいこと

現在、Pythonを使用して簡単なアルバムのアートワーク取得アプリを作成しようとしています。
おおまかな枠組みは作成できたのですが、一部機能が想定通りに機能しません。
解決方法を教えていただけると幸いです。
初心者なので単語など理解が間違っている可能性がありますが、ご容赦ください。

解決したい内容

Tkinterを使用して画像のようにほしいアートワークの画像を保存すると画像が取得できるというものになっています。
ですが、ほしい画像が下の方にあったときスクロールバーを使用して下に行きたいのですがスクロールバーのみ動いて左のcanvas部分が動かない状態です。
コード上ではcanvasとScrollbarを紐づけを行っているつもりではいますが、うまく紐づけされていないのでしょうか?

問題が起きている画像

無題.png

該当するソースコード

import tkinter
from PIL import Image, ImageTk
import requests
import json
import urllib
import re
import os
import tempfile
import cv2
from functools import partial
from tkinter import ttk


# ==================================
# Applicationオブジェクトクラスの定義
# ==================================


class DesktopApp(tkinter.Frame):
    # ======================================
    # アプリケーションオブジェクト初期設定
    # ======================================
    def __init__(self, window=None):
        super().__init__(window, width=600, height=900, borderwidth=1, relief="groove")

        self.window = window
        self.pack()
        self.pack_propagate(0)
        self.widgets()

    # ======================================
    # ウィジェット作成
    # ======================================
    def widgets(self):
        # ラベル
        self.label1 = tkinter.Label(self, text="アーティスト名")
        self.label2 = tkinter.Label(self, text="アルバム名")

        # 実行ボタン
        submitBtn = tkinter.Button(self)  # ボタンウィジェット
        submitBtn["text"] = "実行する"  # ボタンテキスト
        submitBtn["command"] = self.executeProcess  # ボタン押下後に実行される処理

        # テキストボックス1
        self.text_box1 = tkinter.Entry(self)  # テキストボックスウィジェット
        self.text_box1["width"] = 20  # 幅

        # テキストボックス2
        self.text_box2 = tkinter.Entry(self)  # テキストボックスウィジェット
        self.text_box2["width"] = 20  # 幅

        # 関数外で定義
        self.canvas = []  # imread_web関数内で使用する。関数内でループさせると毎回初期化が走るから関数外で定義
        self.photo_image = []  # 配列にしないと度々画像が更新されてしまい、参照出来なくなる。https://onl.bz/gTdAMxf 参照
        self.SaveBtn = []
        self.jpeg_high = []
        self.a = []
        self.jpeg = []
        self.albumName = []

        # gridでウィジェットの配置
        self.label1.grid(row=0, column=0, sticky=tkinter.W)
        self.label2.grid(row=1, column=0, sticky=tkinter.W)
        submitBtn.grid(row=2, column=0, columnspan=2)
        self.text_box1.grid(row=0, column=1, sticky=tkinter.E)  # テキストボックス表示位置
        self.text_box2.grid(row=1, column=1, sticky=tkinter.E)  # テキストボックス表示位置

    # 実行ボタン押下後に実行される処理
    def executeProcess(self):
        self.root_canvas = tkinter.Canvas(self.master, bg="black")
        self.root_canvas.propagate(False)
        self.root_canvas.pack()

        self.root_frame = tkinter.Frame(self.root_canvas, bg="yellow")
        self.root_frame.propagate(False)
        self.root_frame.grid(row=0, column=0)

        url = "https://itunes.apple.com/search?term={}&country=jp&lang=ja_jp&limit=50&media=music&entity=album&attribute=artistTerm".format(
            self.text_box1.get()
        )
        res = requests.get(url).json()
        i = 0

        with open(
            "{}.json".format(self.text_box1.get()), "w", encoding="utf-8_sig"
        ) as f:
            json.dump(res, f, indent=4, ensure_ascii=False)

        LoopCount = res["resultCount"]
        for a in range(LoopCount):
            if self.text_box2.get() in res["results"][a]["collectionCensoredName"]:
                self.albumName.insert(i, res["results"][a]["collectionCensoredName"])
                self.albumName[i] = re.sub(r"[\/:*?" "'<>|]", "_", self.albumName[i])
                print(self.albumName[i])

                self.jpeg.insert(i, res["results"][a]["artworkUrl100"])
                self.imread_web(i)  # 画像表示関数
                i = i + 1
            else:
                pass

        # 縦のスクロールバー
        self.ybar = ttk.Scrollbar(
            self.root_canvas, orient="v", command=self.root_canvas.yview
        )
        self.ybar.grid(row=0, column=1, sticky=(tkinter.N, tkinter.S))
        self.root_canvas.config(yscrollcommand=self.ybar.set)
        self.root_canvas.config(scrollregion=(0, 0, 1000, 10000))
        print(self.canvas)
        print(i)

    def imread_web(self, x):
        res = requests.get(self.jpeg[x])
        img = None

        # 画像処理フェーズ
        fp = tempfile.NamedTemporaryFile(dir="./", delete=False)  # Tempfileを作成して即読み込む
        fp.write(res.content)
        fp.close()
        self.img = cv2.imread(fp.name)
        os.remove(fp.name)  # これがないと一時DLされた画像がtmpとして残ってしまう
        self.image_rgb = cv2.cvtColor(self.img, cv2.COLOR_BGR2RGB)
        self.image_pil = Image.fromarray(self.image_rgb)

        # Canvasの作成・Canvasを配置
        self.canvas.insert(x, tkinter.Canvas(self.root_frame, bg="cyan"))
        self.canvas[x].grid(row=x + 3, column=0, columnspan=2)

        # Canvas紐付け保存ボタン
        self.SaveBtn.insert(x, tkinter.Button(self.root_frame))
        self.SaveBtn[x]["text"] = "保存"
        self.SaveBtn[x]["command"] = partial(self.SaveProcess, x)
        self.SaveBtn[x].grid(row=x + 3, column=1)

        # PIL.ImageからPhotoImageへ変換する
        self.photo_image.insert(x, ImageTk.PhotoImage(image=self.image_pil))

        # キャンバスのサイズを取得
        self.update()  # Canvasのサイズを取得するため更新しておく(中心にならなくなる)
        canvas_width = self.canvas[x].winfo_width()
        canvas_height = self.canvas[x].winfo_height()

        # 画像の描画
        self.canvas[x].create_image(
            canvas_width / 2,  # 画像表示位置(Canvasの中心)
            canvas_height / 2,
            image=self.photo_image[x],  # 表示画像データ
        )
        self.update()

        # 縦のスクロールバー
        # self.sb1 = tkinter.Scrollbar(self.window, orient = 'v', command = self.canvas.yview)
        # self.sb1.grid(row = 1, column = 2, sticky = 'ns')
        # self.canvas.config(yscrollcommand = self.sb1.set)

    def SaveProcess(self, m):
        self.jpeg_high.insert(
            m, self.jpeg[m].replace("100x100bb.jpg", "100000x100000-999.jpg")
        )
        urllib.request.urlretrieve(
            self.jpeg_high[m], "{}_high.jpg".format(self.albumName[m])
        )  # 高画質画像取得できた!

        print("We are ONE PIECE")


# ==================================
# アプリケーション起動
# ==================================

# Wndiow
window = tkinter.Tk()  # TKオブジェクト
window.title("アートワーク取得アプリ")  # アプリタイトル
window.geometry("600x900")  # 表示画面サイズ(幅x高さ)

# アプリケーションオブジェクト
App = DesktopApp(window=window)  # アプリケーションオブジェクトに指定のWindow設定を渡す
App.mainloop()  # アプリケーション起動

自分で試したこと

・self.root_canvas.config(scrollregion=(0, 0, 1000, 10000))の範囲を拡大

0

2Answer

106行目ですが、

       self.ybar.grid(row=0, column=1, sticky=(tkinter.N, tkinter.S))

下記ページにgridのオプションの説明がありました。

sticky グリッド内のウィジェットを配置する位置
アンカーの機能にも似ていますが、例えば上下(tk.N+ tk.S)を指定すると、ウィジェットが上下方向にグリッド内いっぱいに広がります。【設定値】tk.N, tk.S, tk.W, tk.E, tk.NW, tk.NE, tk.SW, tk.SE, tk.NSEW
および上記組み合わせ(tk.N+ tk.Sなど)

0Like

Comments

  1. @koh0404

    Questioner

    ご回答ありがとうございます。
    修正して実行してみたのですが、結果は変わりませんでした。

  2. お役に立てずごめんなさい。Scrollbarのサンプルを探していて以下のページを見つけました。

    以下のコードがコメント欄に書かれていました。

    import tkinter as tk
    
    
    def scrollable_canvas(master, width, height, bg):
       canvas = tk.Canvas(master, bg=bg)
       bar_y = tk.Scrollbar(canvas, orient=tk.VERTICAL)
       bar_x = tk.Scrollbar(canvas, orient=tk.HORIZONTAL)
       bar_y.pack(side=tk.RIGHT, fill=tk.Y)
       bar_x.pack(side=tk.BOTTOM, fill=tk.X)
       bar_y.config(command=canvas.yview)
       bar_x.config(command=canvas.xview)
       canvas.config(yscrollcommand=bar_y.set, xscrollcommand=bar_x.set)
       canvas.config(scrollregion=(0, 0, width, height))
       # canvas.pack(anchor=tk.NW, side=tk.LEFT)
       return canvas
    
    
    def main():
       root = tk.Tk()
       root.geometry("400x300")  # 横400x縦300
       canvas = scrollable_canvas(root, width=300, height=500, bg="white")
       canvas.place(x=0, y=0, width=200, height=300)
       canvas.create_rectangle(10, 10, 100, 100, fill='green')  # 確認用の矩形表示
       root.mainloop()
    
    
    if __name__ == "__main__":
       main()
    

    参考になると良いです。

  3. @koh0404

    Questioner

    私のコードと確認しましたが、私のチャック漏れが原因の可能性もございますが、記載方法は合っているように感じました。
    コード自体はシンプルでとても参考になりました。

  4. テキストボックスにスクロールバーを付けるサンプルですが、packの順番について記述がありました。

    基本的に設置はpack()でできます。

    フレームの中に上から順に積み上げていくイメージですね。

    その際気をつけなければいけないことはpackする順番です。

    packは上から順番に積み上げていくので、先にテキストボックスをpackしてしまうと、

    [テキストボックス]
    [スクロールバー]

    という配置になってしまい、変な見た目になってしまいます。

    そのため、まずスクロールバーをpackしてからテキストボックスをpackするようにしましょう。

    executeProcessメソッド内のself.root_canvas.pack()self.ybar.grid(...)の後に記述してみたらどうでしょうか?

  5. @koh0404

    Questioner

    度々ありがとうございます。
    結論変わりませんでした。
    記事の通り、configも先に定義するように以下のように修正しました。

            self.ybar = ttk.Scrollbar(
                self.root_canvas, orient="v", command=self.root_canvas.yview
            )
            self.root_canvas.config(yscrollcommand=self.ybar.set)
            self.root_canvas.config(scrollregion=(0, 0, 1000, 10000))
            self.ybar.grid(row=0, column=1, sticky=(tkinter.N + tkinter.S))
            self.root_canvas.pack()
    
    

まずフレームをキャンバスに配置しないといけないです。

self.root_frame = tkinter.Frame(self.root_canvas, bg="yellow")
# フレームをキャンバスに配置
self.root_canvas.create_window((0, 0), window=self.root_frame, anchor="nw")

フレームを表示できるようにキャンバスのサイズを拡大します。

self.root_canvas = tkinter.Canvas(self.master, bg="black")
self.root_canvas.pack(fill="both", expand=True)

スクロールバーをキャンバスに合わせて拡大します。

self.ybar = ttk.Scrollbar(self.root_canvas, orient="v", command=self.root_canvas.yview)
self.ybar.pack(side="right", fill="y")

最後にスクロール領域を更新します。

# スクロール領域の更新
self.root_canvas.config(scrollregion=self.root_canvas.bbox("all"))

全体として以下のようになります。

def executeProcess(self):
    self.root_canvas = tkinter.Canvas(self.master, bg="black")
-   self.root_canvas.propagate(False)
-   self.root_canvas.pack()
+   self.root_canvas.pack(fill="both", expand=True)

    self.root_frame = tkinter.Frame(self.root_canvas, bg="yellow")
-   self.root_frame.propagate(False)
-   self.root_frame.grid(row=0, column=0)
+   self.root_canvas.create_window((0, 0), window=self.root_frame, anchor="nw")

    url = "https://itunes.apple.com/search?term={}&country=jp&lang=ja_jp&limit=50&media=music&entity=album&attribute=artistTerm".format(
        self.text_box1.get()
    )
    res = requests.get(url).json()
    i = 0

    with open(
        "{}.json".format(self.text_box1.get()), "w", encoding="utf-8_sig"
    ) as f:
        json.dump(res, f, indent=4, ensure_ascii=False)

    LoopCount = res["resultCount"]
    for a in range(LoopCount):
        if self.text_box2.get() in res["results"][a]["collectionCensoredName"]:
            self.albumName.insert(i, res["results"][a]["collectionCensoredName"])
            self.albumName[i] = re.sub(r"[\/:*?" "'<>|]", "_", self.albumName[i])
            print(self.albumName[i])

            self.jpeg.insert(i, res["results"][a]["artworkUrl100"])
            self.imread_web(i)  # 画像表示関数
            i = i + 1
        else:
            pass

    # 縦のスクロールバー
    self.ybar = ttk.Scrollbar(
        self.root_canvas, orient="v", command=self.root_canvas.yview
    )
-   self.ybar.grid(row=0, column=1, sticky=(tkinter.N, tkinter.S))
+   self.ybar.pack(side="right", fill="y")
    self.root_canvas.config(yscrollcommand=self.ybar.set)

-   self.root_canvas.config(scrollregion=(0, 0, 1000, 10000))
    # スクロール領域の更新
+   self.root_canvas.config(scrollregion=self.root_canvas.bbox("all"))
    print(self.canvas)
    print(i)
0Like

Your answer might help someone💌