91756


Python3.9.5 64bit Windows10 Home

現在、Tkinterを使ってPDFを読み込み、いろいろな編集（回転、分割、保存など）を行うツールを作っています。  
その途中段階として、次ページ/前ページのボタンをクリックすることでPDFを表示しているページが移動するというコードを書きたいのですが、どうも読み込みがうまくいきません。  
書いたコードが以下になります（コードが長いため、ファイル読み込みにかかわらない部分は一部省いています）。  
流れとしては、『読み込みボタンをクリック→ファイル選択画面が表示→任意のPDFファイルを読み込み→pillow画像のリサイズ→画面に表示』となっています。  
下記のコードにはないのですが、実際はページ遷移のコードもあり、画面に表示されたあと次/前ページをクリックすることで表示されているPDFもページ移動するという具合です。

[code]

    import tkinter as tk
    import tkinter.filedialog
    from PIL import Image, ImageTk
    from pdf2image import convert_from_path
    canvas_width = 500
    canvas_height = 500
    
    def create_widgets(root):
        canvas = tk.Canvas(root, width=canvas_width, height=canvas_height, highlightthickness=0)
        canvas.grid(column=0, row=0)
        operation_frame = tk.Frame(root)
        operation_frame.grid(column=1, row=0)
    
        read_button = tk.Button(operation_frame, text='読み込み', command=file_read)
        read_button.grid(column=0, row=0)
    
        next_button = tk.Button(operation_frame, text='次ページ', command=next_page, state=tk.DISABLED)
        next_button.grid(column=0, row=5)
    
        prev_button = tk.Button(operation_frame, text='前ページ', command=prev_page, state=tk.DISABLED)
        prev_button.grid(column=0, row=6)
    
    def file_read():
        path = tkinter.filedialog.askopenfilename(
            filetypes=[('PDFファイル', '*.pdf'),],
            title='ファイル選択',
        )
    
        size = (canvas_width, canvas_height)
        num_page = read(path, size)
    
        show_page = 0
        canvas.create_image(
            0, 0,
            image=get_image(show_page),
            anchor=tk.NW
        )
    
        change_state()
    
    def read(path, size):
        pdf_path = path
    
        pillow_images = convert_from_path(
            pdf_path,
            poppler_path=r'C:\Program Files\poppler-22.04.0\Library\bin'
        )
    
        x_ratio = size[0] / pillow_images[0].width
        y_ratio = size[1] / pillow_images[0].height
    
        image_ratio = min(x_ratio, y_ratio)
    
        resize_size = (
            int(pillow_images[0].width * image_ratio),
            int(pillow_images[0].height * image_ratio)
        )
    
        images = []
        for pillow_image in pillow_images:
            resize_image = pillow_image.resize(resize_size)
            tkinter_image = ImageTk.PhotoImage(resize_image, master=root)
            images.append(tkinter_image)
    
        return len(images)
    
    def get_image(num):
        if num < 0 or num >= len(images):
            return None
    
        return images[num]
    
    root = tk.Tk()
    PDFEditor = create_widgets(root)
    root.mainloop()
    
[/code]

しかし、実行するとcanvas.create_imageの箇所でcanvasが定義されていないというエラーが発生しました。

[code]

    Exception in Tkinter callback
    Traceback (most recent call last):
      File "c:\Users\owner\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 1892, in __call__
        return self.func(*args)
      File "C:\Users\owner\AppData\Local\Temp\ipykernel_10820\3221926224.py", line 11, in file_read
        canvas.create_image(
    NameError: name 'canvas' is not defined
    
[/code]

おそらく関数の定義や呼び出しの際に、何かが起きているのかと思います。  
というのも、関数を定義せずに直書きしていた書き始めの頃は読み込みがおこなわれていてPDF表示されていたからです。  
任意のページに対する表示ができていたので、今回はそれらを関数として定義して、全ページに対応させようとしていたところでした。  
そのため（どう言葉にすればいいのか難しいのですが）、create_widgetsからfile_readに移った時にcreate_widgetsで書いたcanvasが引き継がれていない?のかなと感じました。

どう解決すればよろしいでしょうか。


CC BY-SA 4.0
2022-10-22T06:42:29.070
91736
2022-10-23T03:45:26.293
54778
question
python tkinter
TkinterのcanvasでNameErrorが発生している


`canvas` は 関数 `create_widgets` の中で使われている変数です。関数 `file_read` で使うには、何か工夫が必要です。

引数で渡すことにするなら、例えば下のようになります。`command`
に渡す関数を、[ラムダ式(`lambda`)](https://docs.python.org/ja/3/tutorial/controlflow.html#lambda-
expressions) でその場で作っています。このラムダ式は `create_widgets` の中に有るので、`canvas` を参照できます。

[code]

    def create_widgets(root):
        # canvas の定義など
        # 省略...
        read_button = tk.Button(
            operation_frame,
            text='読み込み',
            command=lambda: file_read(canvas)
        )
    
    # 省略...
    
    def file_read(canvas):
        # 省略...
    
[/code]

他にも、関数 `file_read` 自体を関数 `create_widgets` の中で定義してしまうなどの方法が考えられます。


CC BY-SA 4.0
2022-10-23T03:45:26.293
91756
2022-10-23T03:45:26.293
3054
91736
answer