purupann
@purupann

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

tkinterのボタンに関しての質問

Q&A

Closed

tkinterのボタンに関しての質問

「開始」ボタンを押したら「終了」にtextが変わって
「終了」ボタンを押したら「開始」になるものを作り、
その間に「開始」が押されたら関数を動かすようにしたかったのですが、
ボタンが「開始」のままで関数が終わるまで「終了」になりません。
そこで、非同期処理にしてみたのですが、それでも治りませんでした。
どうすればよいでしょうか?

プログラムはこちらです

import pyautogui,tkinter,time,asyncio

with open("memo.txt","w") as memo:
    memo.write("")

class Main:
    def __init__(self):
        self.aaa = 0
    
    def click_btn(self):
        if button["text"] == "開始":
            button["text"] = "終了"
            while button["text"] != "終了":
                pass
            self.aaa = 0
            asyncio.run(main.loop())
        else:
            button["text"] = "開始"
            self.aaa = 1
    async def loop(self):
        button["text"] = "終了"
        i = 0
        while self.aaa == 0:
            i += 1
            if i >= 10:
                break
            print(pyautogui.position())
            with open("memo.txt","a") as memo:
                memo.write(f"{pyautogui.position()}\n")
            time.sleep(0.5)
main = Main()
    
root = tkinter.Tk()
root.title("pc操作自動化アプリ")
root.resizable(False, False)
root.geometry("400x200")

label = tkinter.Label(text='記録するdataの間隔(ミリ秒)', font=("MSゴシック", "10", "bold"))
label.pack()

boxa = tkinter.Entry()
boxa.pack()

button = tkinter.Button(root, text="開始", font=("Times New Roman", 10), command=main.click_btn)
button.place(x=170,y=80)



root.mainloop()#start
0

4Answer

「開始」ボタンを押したら「終了」にtextが変わって
「終了」ボタンを押したら「開始」になるものを作り、
その間に「開始」が押されたら関数を動かすようにしたかったのですが、

イメージが浮かばなかったのですが、「ボタン」は1つですか?
上記の"その間"というのはどういう時間のことを指しているのでしょうか?

1Like

Comments

  1. @purupann

    Questioner

    ボタンは1つです。
    最初は「開始」と表記されていて、その状態で押すと「終了」と表記されるようにし、
    逆に「終了」の時に押すと「開始」と表記されるものを作り、
    「開始」のときに押したら関数を動かすようにしたのですが、関数が終わるまで開始という文字が、「終了」にならないということです。分かりにくくてすいません

まずクラスの書き方がめちゃくちゃです。こちら基本ですからしっかりと学習なさってください(探せば優秀なHowToはいくらでもあります)。
外で宣言した変数button(44行目)とクラスMain__init__で宣言されてる変数buttonは全く別の世界ですから互いを呼び出すことは出来ません。
グローバルも多用すると混乱の元ですから控えるべきです。
クラスで書くのでしたらtkinterの初期処理を中途半端にせず__init__内に書いてしまえば良いのです。またクラスにこだわらないのでしたら、クラスを扱わないというのも手です。

import tkinter
from tkinter import ttk

class Main:
	def __init__(self):
		self.root: tkinter.Tk = tkinter.Tk()
		self.root.title(string='Main')

		self.button: ttk.Button = ttk.Button(master=self.root, text='開始', command=self.button_click_event)
		self.button.pack()

	def button_click_event(self) -> None:
		if self.button.cget('text') == '開始':
			self.button.configure(text='終了')
		else:
			self.button.configure(text='開始')

	def mainloop(self) -> None:
		self.root.mainloop()

if __name__ == '__main__':
	main = Main()
	main.mainloop()

余計なことをせず、まず最初はシンプルなコードで始めて下さい。

変数buttonはDictでもありますからbutton["text"]でも内容は書き換えることは出来ます。
ここでは安全にcget()で取得、configure()でテキストの置き換えを行っています。

1Like

Comments

  1. @purupann

    Questioner

    ご回答ありがとうございます!何度も申し訳ないのですが、
    self.root: tkinter.Tk = tkinter.Tk()
    このコロンのある書き方の意味について、教えて頂きたいです。(Hawtoでも有難いです。)

  2. 「Python 型ヒント」と検索して、ご自身で学習して下さい。

    C言語のような宣言と似てますが強制力はありません。私が利便上使っているだけです(書かないと行けない決まりはありません)。
    現にmain = Main()main: Main = Main()とするべき(単に私の書き忘れです)ですがこのコード正常にはたらいています。
    関数横の-> 型も同様です。

他の方々がコードの構成について課題を挙げていらっしゃるので
コードの構成が変わることを想定し、どう変更するかはあえて書きません。

いまのコードの問題点を挙げます。

メインループのブロック

ブロックされている処理1

click_btn メソッド内で while button["text"] != "終了" の無限ループを使用しているため、ボタンのテキストを「終了」に変更する処理がブロックされています。

ブロックされている処理2

asyncio.run(main.loop()) も同様にメインスレッドをブロックしてしまいます。これにより、GUIが更新される前に時間がかかる処理が完了するまで待たされます。

GUIの更新が遅れる

tkinterのイベントループ(root.mainloop())がブロックされると、ボタンのテキストの変更や他のGUI更新が行われません。これが、ボタンのテキストが期待通りに更新されない原因です。

1Like

@chinsa_evessa さんが指摘していますが、tkinterから呼ばれた関数やメソッド内でループ処理やsleepするとtkinterの描画処理やイベント処理が行われません。

  1. root.mainloop()でtkinterのメインループ処理が始まり
  2. tkinterはイベント発生に応じて指定された関数やメソッドを呼び出し(イベント駆動)
  3. 関数やメソッドから復帰したらtkinterの描画処理やイベント処理を行う

ので、関数やメソッドはすぐに復帰するように作らなければいけません。
タイマー処理はtkinterのafterメソッドを使って、イベント駆動処理にしましょう。

あと、変数名やメソッド名は英文で読めて意図が伝わる名前にしましょう。
なお、rootbuttonはグローバル変数なのでどこからでもアクセスできます。

import pyautogui
import tkinter
import time

with open("memo.txt", "w") as memo:
    memo.write("")


class Main:
    def __init__(self, max_times=10):
        self.max_times = max_times
        self.is_stopped = True

    def toggle(self):
        if self.is_stopped:
            self.start()
        else:
            self.stop()

    def start(self):
        self.is_stopped = False
        button["text"] = "終了"
        self.times = 0
        self.record()

    def stop(self):
        self.is_stopped = True
        button["text"] = "開始"

    def record(self):
        if self.is_stopped:
            return
        print(pos := pyautogui.position())
        with open("memo.txt", "a") as memo:
            print(pos, file=memo)
        self.times += 1
        if self.times < self.max_times:
            # intervalミリ秒後に再びrecordメソッドを呼び出す
            root.after(interval.get(), self.record)
        else:
            self.stop()


main = Main()

root = tkinter.Tk()
root.title("pc操作自動化アプリ")
root.resizable(False, False)
root.geometry("400x200")

label = tkinter.Label(text='記録するdataの間隔(ミリ秒)', font=("MSゴシック", "10", "bold"))
label.pack()

interval = tkinter.IntVar()
interval.set(500)

boxa = tkinter.Entry(textvariable=interval)
boxa.pack()

button = tkinter.Button(root, text="開始", font=("Times New Roman", 10), command=main.toggle)
button.place(x=170, y=80)


root.mainloop()  # start
0Like

Your answer might help someone💌