

TkinterとSeleniumを使ったアプリケーションで，スタートを押すとChromeが起動し，スタートを押すと  
`input('>何かを入力すると，コンソールにp要素のテキストを表示')`  
が動作し，コンソールでエンターキーなりを押すまでp要素の取得を待ちます．  
次に  
`input('>何かを入力すると，コンソールにh2要素のテキストを表示')`  
が動作し，同じくh2要素の取得を待ちます．

このような動作の「コンソールでエンターキーなりを押す」を「次へボタンを押す」に対応させて，次へボタンを押すまで次の処理を待つといった処理を行いたいのですが，どのように実装すればよいでしょうか．

処理を止めたい理由は，出力を一つずつ，目視で確認した後に，次の処理へ進むようにしたいためです．

現状，next()は何も行いませんが，本来は次へボタンを押すとnext()が動作し，次の処理へ進めればと思っています．

  * 処理１  
next()が実行されるまで待つ

  * 処理2  
next()が実行されるまで待つ

  * 処理3  
︙

※処理のあとにウィンドウが閉じてしまうのを防ぎたいわけではありません．

ご教示くださいませ．

[![画像の説明をここに入力](https://i.stack.imgur.com/nvWXu.jpg)](https://i.stack.imgur.com/nvWXu.jpg)

[code]

    import ctypes
    import tkinter as tk
    import tkinter.ttk as ttk
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.chrome.service import Service
    from webdriver_manager.chrome import ChromeDriverManager
    from selenium.webdriver.chrome.options import Options
    
    try:
        ctypes.windll.shcore.SetProcessDpiAwareness(True)
    except BaseException:
        pass
    
    
    def next():
        pass
    
    
    def start():
        global driver
    
        chrome_options = Options()
        chrome_options.add_experimental_option("detach", True)
    
        driver = webdriver.Chrome(
            service=Service(ChromeDriverManager().install()),
            options=chrome_options
        )
    
        driver.get('https://scrape-b276b.web.app/')
        input('>何かを入力すると，コンソールにp要素のテキストを表示')
        print(driver.find_element(By.CSS_SELECTOR, '#app > div > main > div > div > div > div > section > div.text > p').text)
        input('>何かを入力すると，コンソールにh2要素のテキストを表示')
        print(driver.find_element(By.CSS_SELECTOR, 'h2').text)
        input('>何かを入力すると，終了')
    
    
    def main():
        frame = tk.Tk()
        frame.title('title')
        frame.geometry("230x60+400+440")
    
        button1 = ttk.Button(frame, text='次へ', command=next)
        button1.place(x=10, y=10, width=100)
        button2 = ttk.Button(frame, text='スタート', command=start)
        button2.place(x=120, y=10, width=100)
    
        frame.mainloop()
    
    
    if __name__ == '__main__':
        main()
    
[/code]


CC BY-SA 4.0
2022-10-14T09:54:09.817
91598
2022-10-17T10:00:08.860
2022-10-17T10:00:08.860
3054
question
python selenium tkinter
Tkinterにてボタンが押されるまで次の処理を待機したい


「Tkinterにてボタンが押されるまで次の処理を待機する方法」というのはTkinterの基本的な機能として備わっているもので、ユーザーの作成するプログラム上で後から追加するものではありません。

質問のプログラムで言えば`frame.mainloop()`の行が実行されることで開始し、これが実行されている間はボタンや他のウィジェット・マウス・キーボード等のイベント発生を待ち、発生したイベントに対応する処理がデフォルトで存在したりユーザープログラムから登録されていればそれを呼び出す動作をしています。  
こんな記事に解説があります。  
[【Python】tkinterのmainloopについて解説](https://daeudaeu.com/mainloop/)

なので質問のようなプログラムで「`def
start():`関数の中でサイトを表示させかつボタンのクリックを待って処理を進めて行く」というのはGUI的には行儀の悪い振る舞いであり、Tkinterを使う意味が無いようなやり方です。

GUI的には前の質問の回答やこの質問のコメントで示したように、以下のようにすれば良いでしょう。

  * `def start():`関数はサイト表示を行うだけ
  * サイト内の要素を検索して表示する作業にはそれぞれ専用のボタンと関数を必要な数だけ用意して個々に呼び出す
  * 処理順番が決まっているならボタンのテキストに対応する数値を表示しておく

それでもボタンを1個で済ませたいならば、処理を何処まで行ったかを示す変数を用意して以下のように変数を判定して処理を変えるといった方法が考えられます。  
変更する部分だけ提示して前後は省略します。

[code]

    import traceback
    phase = -1  #### 処理の段階を保存する変数：-1はブラウザが起動していない状態
    def next():
        global phase  #### グローバル変数を使用する宣言
    
        #### 以下は処理の段階を保存する変数の値に従ってそれぞれの処理を行い、変数を次の段階に進める
        try:
            if phase == 0:
                print('最初の「次へ」がクリックされたので，コンソールにp要素のテキストを表示')
                print(driver.find_element(By.CSS_SELECTOR, '#app > div > main > div > div > div > div > section > div.text > p').text)
                phase += 1  #### 変数を次の段階へ
            elif phase == 1:
                print('２回目の「次へ」がクリックされたので，コンソールにh2要素のテキストを表示')
                print(driver.find_element(By.CSS_SELECTOR, 'h2').text)
                phase += 1  #### 変数を次の段階へ
            elif phase == 2:
                print('３回目の「次へ」がクリックされたので，ブラウザを終了')
                phase = -1  #### 変数をブラウザ起動前の段階へ
                driver.quit()
        except Exception as e:
            print(f'{phase}段階の処理で {e.__class__.__name__}例外が発生')
            print(traceback.format_exc())
            phase += 1  #### 変数を次の段階へ
            if phase > 2:
                phase = -1  #### 範囲オーバーなので変数をブラウザ起動前の段階へ
            pass
    
    def start():
        global driver
        global phase  #### グローバル変数を使用する宣言
    
        chrome_options = Options()
        chrome_options.add_experimental_option("detach", True)
    
        driver = webdriver.Chrome(
            service=Service(ChromeDriverManager().install()),
            options=chrome_options
        )
        driver.get('https://scrape-b276b.web.app/')
    
        print('ブラウザが起動し，サイトが表示されて準備完了')
        phase = 0  #### 変数を最初の段階に設定
    
[/code]


CC BY-SA 4.0
2022-10-15T09:46:32.930
91610
2022-10-16T03:54:34.773
2022-10-16T03:54:34.773
26370
26370
91598
answer


### 待機の難しさ

「ボタンが押されるまで待機してから次の処理に進む」という要望は、イベント駆動のプログラムが持つ難しさを表わにします。

  * イベントを「待機」するためには、コールバック関数を終了してイベントループに戻らなければならない
  * しかし、関数を終了してしまうと、「次の処理」がどこかへ行ってしまう

ということです。

### `wait_variable`

cubick
さんのコメントで[紹介されているリンク先](https://stackoverflow.com/questions/44790449/making-
tkinter-wait-untill-button-is-pressed)では `tkinter` の `wait`
系の機能を使う方法が回答されています。詳しくは無いですが、これで問題無い状況も多いかも知れません。しかし、この機能を呼び出すとちょっと複雑な状態（「local
event loop」が作成されるとか何とか）になるようで、`wait`
している間はプログラムを終了できないなどの問題があります。イベントループに対するある種のハックなのか、Tcl/Tk
レベルで理解していないと安心して使えない機能という印象です。

### 一般的な解決策

上のような Tck/Tk 特有の機能を排すと、解決策としては2種が考えられます。

  * コールバック関数に工夫をする
  * GUIとその他を別のプロセス（スレッド）で実行する

この回答では前者を扱います。

### 「次の処理」の登録の連鎖

[kunif
さんの回答](https://ja.stackoverflow.com/a/91610/3054)は一つのコールバック関数内で、呼び出される度にどの処理を行なうか計算する物です。これがシンプルで解り易いと思います。

もう一つ、「次の処理」を関数にして明示的に扱う方法が有ります。これはやや複雑ですが、future や promise
といった考え方にもつながる手法だと思うので、説明します。

  * 最初に行なう処理をコールバック関数として登録
  * 最初のコールバック関数は、呼ばれて自身の処理を終えると、「次の処理」をコールバック関数として登録
  * 次のコールバック関数も、同じように「次の処理」をコールバック関数として登録
  * 以下繰り返し

という流れです。

#### 関数の連鎖

これを素直に書くと、下のようなコードになります。

[code]

    def start(root, next_button):
    
        # ...
    
        def proc_01():
            print('-- 処理 1 --')
            # 次の処理を行なうコールバック関数 (command) に変更
            next_button.configure(command=proc_02)
    
        def proc_02():
            print('-- 処理 2 --')
            # 次の処理を行なうコールバック関数 (command) に変更
            next_button.configure(command=proc_03)
    
        def proc_03():
            print('-- 終了 --')
            root.quit()
    
        # 最初のコールバック関数 (command) を設定
        next_button.configure(command=proc_01)
        print('-- スタート --')
    
[/code]

#### イテレータ / ジェネレータの利用

Python
には、「次の処理（値）」を扱う方法として[イテレータ](https://docs.python.org/ja/3/tutorial/classes.html#iterators)が用意されています。また、それを簡潔に書ける[ジェネレータ](https://docs.python.org/ja/3/tutorial/classes.html#generators)も有りますので、上の煩雑なコードの代わりに使えます。

[code]

    def start(root, next_button):
    
        # ...
    
        def proc_gen():
            print('-- 処理 1 --')
            yield
    
            print('-- 処理 2 --')
            yield
    
            print('-- 終了 --')
            root.quit()
            yield
    
        # イテレータを生成
        # `proc_itr` は `next` される度に、次の yield の箇所まで処理を進める
        proc_itr = proc_gen()
    
        # コールバック (command) には `next` を呼び出す処理を設定
        next_button.configure(command=lambda: next(proc_itr))
        print('-- スタート --')
    
[/code]

こういった「次の処理」に対する構文糖衣を[非同期
IO](https://docs.python.org/ja/3/library/asyncio.html)などのイベント駆動処理に与える物として、「async/await
構文」があります。いずれ GUI ツールキットとの組み合わせも良くなるかも知れません。

* * *

動くコード:  
動作確認したコードです。手元で動かすため、質問とは処理の内容を少し変えています。 **`next` は Python
の組込み関数です。自作の関数で上書きするのは避けています。**

[code]

    import tkinter as tk
    import tkinter.ttk as ttk
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.chrome.service import Service
    from webdriver_manager.chrome import ChromeDriverManager
    from selenium.webdriver.chrome.options import Options
    
    url = 'https://google.com/'
    
    
    def start(root, next_button):
        chrome_options = Options()
        chrome_options.add_experimental_option("detach", True)
    
        driver = webdriver.Chrome(
            service=Service(ChromeDriverManager().install()),
            options=chrome_options
        )
    
        driver.get(url)
    
        def proc_gen():
            print('-- div 要素 --')
            print(driver.find_element(By.CSS_SELECTOR, 'div').text)
            yield
    
            print('-- a 要素 --')
            print(driver.find_element(By.CSS_SELECTOR, 'a').text)
            yield
    
            print('-- 終了 --')
            root.quit()
            yield
    
        # イテレータを生成
        # `proc_itr` は `next` される度に、次の yield の箇所まで処理を進める
        proc_itr = proc_gen()
    
        # コールバック (command) には `next` を呼び出す処理を設定
        next_button.configure(command=lambda: next(proc_itr))
        print('-- スタート --')
    
    
    def main():
        frame = tk.Tk()
        frame.title('title')
        frame.geometry("230x60+400+440")
    
        button1 = ttk.Button(frame, text='次へ')
        button1.place(x=10, y=10, width=100)
        button2 = ttk.Button(frame, text='スタート', command=lambda: start(frame, button1))
        button2.place(x=120, y=10, width=100)
    
        frame.mainloop()
    
    
    if __name__ == '__main__':
        main()
    
    
[/code]


CC BY-SA 4.0
2022-10-15T16:53:45.477
91619
2022-10-17T09:50:43.357
2022-10-17T09:50:43.357
3054
3054
91598
answer