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

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

ただいまの
回答率

88.91%

tkinterで得た情報を連続して、テキストファイルに書き込みたい

解決済

回答 2

投稿 編集

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

YUMA-NAGAO

score 16

前提・実現したいこと

Tkinterで確定ボタンを押したときに、テキストファイルにデータを書きこむ
下記ソースコードに記載されているconnectに入っているデータをテキストファイルに書き込みたいです。

既に出来ている機能

1、テキストファイルに上書きする機能
2、Tkinterを使い、何度も同じ動作を繰り返す機能

この二つが出来ています。
今回は、それらを組み合わせるのが最終目的です。

試したこと

root.mainloopの場所を変更するということを試しました。

前回の質問を基にしてテキストファイルへの書き込み方法を追記しました。

しかし、思ったような動作が出来ません。

その結果、
1、エラーが発生し、テキストファイルにtkinterに打ち込んだ時の値が、書き込める。
2、エラーが発生し、テキストファイルに打ち込んだ時の値が書き込めない。
3、エラーは発生しないが、tkinterに打ち込んだ時の値が変更されない

という状態になっています。

ググった時のキーワードは「Python tkinter textファイル書き込み」などです。

発生している問題・エラーメッセージ

エラーメッセージがあるもの、エラーメッセージを記述します。

エラーメッセージ 1

Traceback (most recent call last):
  File "C:/Users/Anaconda3/envs/py3.7/BrightnessControl.py", line 75, in <module>
    AjustLight()
  File "C:/Users/Anaconda3/envs/py3.7/BrightnessControl.py", line 72, in AjustLight
    root.after(0, Light())
  File "C:/Users/Anaconda3/envs/py3.7/BrightnessControl.py", line 39, in Light
    one = EditBox1.get()
  File "C:\Users\nagao\Anaconda3\envs\py3.7\lib\tkinter\__init__.py", line 2682, in get
    return self.tk.call(self._w, 'get')
_tkinter.TclError: invalid command name ".!toplevel.!entry"


エラーメッセージ 2

     Traceback (most recent call last):
  File "C:/Users/Anaconda3/envs/py3.7/BrightnessControl.py", line 75, in <module>
    AjustLight()
  File "C:/Users/Anaconda3/envs/py3.7/BrightnessControl.py", line 72, in AjustLight
    root.after(0, Light())
  File "C:/Users/Anaconda3/envs/py3.7/BrightnessControl.py", line 39, in Light
    one = EditBox1.get()
  File "C:\Users\nagao\Anaconda3\envs\py3.7\lib\tkinter\__init__.py", line 2682, in get
    return self.tk.call(self._w, 'get')
_tkinter.TclError: invalid command name ".!toplevel.!entry"

該当のソースコード

import tkinter
from tkinter import messagebox


def AjustLight():
    def Light():
        # ここで設定Windowを Toplevel Widget にて作成
        global top
        global connect

        top = tkinter.Toplevel(root)
        top.deiconify()
        top.title(u"調整")
        top.geometry("250x150")
        Static1 = tkinter.Label(top, text=u'光量の調整が可能です!')
        Static1.pack()
        Static2 = tkinter.Label(top, text=u'数字を入れてください')
        Static2.pack()
        EditBox1 = tkinter.Entry(top)
        EditBox1.insert(tkinter.END, '0')
        EditBox1.pack()
        EditBox2 = tkinter.Entry(top)
        EditBox2.insert(tkinter.END, '1')
        EditBox2.pack()
        EditBox3 = tkinter.Entry(top)
        EditBox3.insert(tkinter.END, '2')
        EditBox3.pack()
        EditBox4 = tkinter.Entry(top)
        EditBox4.insert(tkinter.END, '3')
        EditBox4.pack()

        button = tkinter.Button(top, text='確定', width='10', command=FinishEvent)
        # command=で入れた関数に()があると、関数がうまく使うことが出来ない。
        # (正確に言うと、起動したときに実行されてしまうので、ダメ)
        button.pack()

        root.mainloop()
        one = EditBox1.get()
        second = EditBox2.get()
        third = EditBox3.get()
        fourth = EditBox4.get()

        connect = '0,' + one + ',' + second + ',' + third + ',' + fourth

        path = r"C:\Users\Desktop\test.txt"
        with open(path, "w") as f:
            f.write(connect)
        with open(path, "r") as f:
            reading=f.read()
            print(reading)

    def FinishEvent():
        top.destroy()
        # toplevel wigget(サブウィンドウ)を破壊する
        ajust_onemore = messagebox.askyesno('Adjusts_Onemore', 'もう一度調整しますか?')
        # 確認用ダイアログを出す
        # 調整用のやつ
        if ajust_onemore:
            # 再度 mainloop内から ajust_moving() 関数を呼ぶ
            root.after(0, Light)
        else:
            # 完了ダイアログを出す
            messagebox.showinfo('', '完了!!')
            root.destroy()
            # rootを破棄(これでmainloopを抜けるはず)

    root = tkinter.Tk()
    root.withdraw()
    root.after(0, Light())


AjustLight()

print(connect)

教えていただきたいこと

3点あります。

今回の質問について

1、どのようにサンプルコードを変えればいのか?
2、サンプルコードを作成した過程(ググった時のキーワードや解決までの考え方など)
3、コードを簡潔にする方法

これらを教えていただきたいです!
自力で解決できるようになりたいので!

回答を基に修正したコード【その1】

このコードを使うことで思った通りの動作をすることが出来た。
値を入れるボックスをglobal宣言したことで、コールバックした関数内で使えるようになりました。
このコードの実装の仕方は、少しいかがなものかと思います。
処理を変えないで、綺麗なコードにするやり方を教えていただきたいです。

import tkinter
from tkinter import messagebox


def AjustLight():


    def Light():
        # ここで設定Windowを Toplevel Widget にて作成
        global top
        global EditBox1
        global EditBox2
        global EditBox3
        global EditBox4
        top = tkinter.Toplevel(root)
        top.deiconify()
        top.title(u"調光")
        top.geometry("250x150")
        # 以下に 設定画面を作成(省略)
        Static1 = tkinter.Label(top, text=u'光量の調整が可能です!')
        Static1.pack()
        Static2 = tkinter.Label(top, text=u'0-255の間で1単位で数字を入れてください')
        Static2.pack()
        EditBox1 = tkinter.Entry(top)
        EditBox1.insert(tkinter.END, '0')
        EditBox1.pack()
        EditBox2 = tkinter.Entry(top)
        EditBox2.insert(tkinter.END, '255')
        EditBox2.pack()
        EditBox3 = tkinter.Entry(top)
        EditBox3.insert(tkinter.END, '255')
        EditBox3.pack()
        EditBox4 = tkinter.Entry(top)
        EditBox4.insert(tkinter.END, '0')
        EditBox4.pack()


        button = tkinter.Button(top, text='確定', width='10', command=FinishEvent)
        # command=で入れた関数に()があると、関数がうまく使うことが出来ない。
        # (正確に言うと、起動したときに実行されてしまうので、ダメ)
        button.pack()

        root.mainloop()

    def FinishEvent():
        one = EditBox1.get()
        second = EditBox2.get()
        third = EditBox3.get()
        fourth = EditBox4.get()
        global connect
        connect = '0,' + one + ',' + second + ',' + third + ',' + fourth

        path = r"C:\Users\Desktop\test.txt"
        with open(path, "w") as f:
            f.write(connect)
        with open(path, "r") as f:
            reading = f.read()
            print(reading)

        top.destroy()
        # toplevel wigget(サブウィンドウ)を破壊する
        ajust_onemore = messagebox.askyesno('Adjusts_Onemore', 'もう一度調整しますか?')
        # 確認用ダイアログを出す
        # 調整用のやつ
        if ajust_onemore:
            # 再度 mainloop内から ajust_moving() 関数を呼ぶ
            root.after(0, Light)
        else:
            # 完了ダイアログを出す
            messagebox.showinfo('', '完了!!')
            root.destroy()
            # rootを破棄(これでmainloopを抜けるはず)

    root = tkinter.Tk()
    root.withdraw()

    root.after(0, Light())


AjustLight()

print(connect)

回答を基に修正したコード【完成版】

回答を基に必要な動作を加えたコードです。
入力規則などについても教えていただいて、感謝です。

import tkinter
from tkinter import messagebox


def AjustLight():
    setting_values = {}
    top = None

    def Light():
        # ここで設定Windowを Toplevel Widget にて作成
        nonlocal top


        # Entryへの入力値のValidationコマンド
        # この中でデータをdictに格納しておく
        def validation(name, word):
            # 空か数値以外の入力は不可
            result = (len(word) == 0) or word.isnumeric()
            # 問題なければ値をconnectに格納
            if result:
                setting_values[name.split('.')[-1]] = word

            return result

        top = tkinter.Toplevel(root)
        top.deiconify()
        top.title(u"調整")
        top.geometry("250x150")
        top.protocol('WM_DELETE_WINDOW', lambda: root.destroy())
        vcmd = (top.register(validation), '%W', '%P')
        Static1 = tkinter.Label(top, text=u'光量の調整が可能です!')
        Static1.pack()
        Static2 = tkinter.Label(top, text=u'数字を入れてください')
        Static2.pack()
        EditBox1 = tkinter.Entry(top, name='num0', validate='key', validatecommand=vcmd)
        EditBox1.insert(tkinter.END, '0')
        EditBox1.pack()
        EditBox2 = tkinter.Entry(top, name='num1', validate='key', validatecommand=vcmd)
        EditBox2.insert(tkinter.END, '0')
        EditBox2.pack()
        EditBox3 = tkinter.Entry(top, name='num2', validate='key', validatecommand=vcmd)
        EditBox3.insert(tkinter.END, '0')
        EditBox3.pack()
        EditBox4 = tkinter.Entry(top, name='num3', validate='key', validatecommand=vcmd)
        EditBox4.insert(tkinter.END, '0')
        EditBox4.pack()
        button = tkinter.Button(top, text='確定', width='10', command=FinishEvent)
        button.pack()

    def Save(file):
        nonlocal setting_values
        # connectに格納している値をリスト化
        val = [setting_values[f'num{i}'] for i in range(4)]
        # print(val)
        # 上記のリストをファイルに書き込む
        with open(file, "w") as f:
            f.write(f'0,{val[0]},{val[1]},{val[2]},{val[3]}')
        with open(file, 'r') as f:
            print(f.read())
        # 意図した値になっているかの確認

    def FinishEvent():
        # ここでセーブする
        file_path=r'C:\Users\Desktop\test.txt'
        Save(file_path)
        top.destroy()
        # toplevel wigget(サブウィンドウ)を破壊する
        ajust_onemore = messagebox.askyesno('Adjusts_Onemore', 'もう一度調整しますか?')
        # 確認用ダイアログを出す
        # 調整用のやつ
        if ajust_onemore:
            # 再度 mainloop内から Light関数を呼ぶ
            root.after(0, Light)
        else:
            # 完了ダイアログを出す
            messagebox.showinfo('', '完了!!')
            root.destroy()
            # rootを破棄(これでmainloopを抜けるはず)

    root = tkinter.Tk()
    root.withdraw()
    root.after(0, Light())
    root.mainloop()
    return setting_values

参考URL

知らないことを調べた。
Python の isnumeric() メソッド
tkinterのEntryにvalidationを実装する part 1 基底クラスを作る。

  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • t_obara

    2019/12/24 17:36

    ファイルに書き込むことと、tkinterを利用することは、分けることができるので、まずはファイルに書き込むことができるようになりましょう。
    「python file write」といったキーワードでググると良いです。

    キャンセル

  • YUMA-NAGAO

    2019/12/24 17:39

    既に、それは出来います。
    それを組み合わせて一連の処理で、実行したいのです。

    キャンセル

回答 2

checkベストアンサー

+1

まずは mainloop()を元の場所にもどしましょう。

    root = tkinter.Tk()
    root.withdraw()
    root.after(0, Light())
    root.mainloop()


こうすることで、 一応

  • root(今回は描画しない)を生成して、afterイベントを発行後mainloopを回す
  • after イベント発火:Light関数がにて、設定ウィンドウ描画
  • 「確定」ボタンイベント発火:FinishEvent関数にて、設定ウィンドウ破棄&確認ダイアログ描画
    -> [yes]が選ばれた場合は再度 afterイベント発行する

と、t_obara さんの言っている基本構成になります。

で、上記の構成を踏まえた上で、「Tkinterで確定ボタンを押したときに、テキストファイルにデータを書きこむ」を実装したいのであれば、「確定」ボタンイベントの処理上でファイルを書き込む処理をするのが適当なのではないでしょうか。

実装方法としては、FinishEvent関数の先頭でファイルを書き出すだけなので、なんとでもなるかと思いますが、EditBox1~4を全てをglobalにするのもなんなので、Entryへの入力値を検証する関数を準備し、入力データが数値以外を無効にする処理を実装しつつ、その関数内で値をdictに保持するように実装してみました。

import tkinter
from tkinter import messagebox

def AjustLight():
    setting_values = {}
    top = None

    def Light():
        # ここで設定Windowを Toplevel Widget にて作成
        nonlocal top

        # Entryへの入力値のValidationコマンド
        # この中でデータをdictに格納しておく
        def validation(name, word):
            # 空か数値以外の入力は不可
            result = (len(word)==0) or word.isnumeric()
            # 問題なければ値をconnectに格納
            if result:
                setting_values[name.split('.')[-1]] = word
            return result

        top = tkinter.Toplevel(root)
        top.deiconify()
        top.title(u"調整")
        top.geometry("250x150")
        vcmd = (top.register(validation), '%W', '%P')
        Static1 = tkinter.Label(top, text=u'光量の調整が可能です!')
        Static1.pack()
        Static2 = tkinter.Label(top, text=u'数字を入れてください')
        Static2.pack()
        EditBox1 = tkinter.Entry(top, name='num0', validate='key', validatecommand=vcmd)
        EditBox1.insert(tkinter.END, '0')
        EditBox1.pack()
        EditBox2 = tkinter.Entry(top, name='num1', validate='key', validatecommand=vcmd)
        EditBox2.insert(tkinter.END, '1')
        EditBox2.pack()
        EditBox3 = tkinter.Entry(top, name='num2', validate='key', validatecommand=vcmd)
        EditBox3.insert(tkinter.END, '2')
        EditBox3.pack()
        EditBox4 = tkinter.Entry(top, name='num3', validate='key', validatecommand=vcmd)
        EditBox4.insert(tkinter.END, '3')
        EditBox4.pack()

        button = tkinter.Button(top, text='確定', width='10', command=FinishEvent)
        # command=で入れた関数に()があると、関数がうまく使うことが出来ない。
        # (正確に言うと、起動したときに実行されてしまうので、ダメ)
        button.pack()

    def Save(file):
        nonlocal setting_values
        # connectに格納している値をリスト化
        val = [setting_values[f'num{i}'] for i in range(4)]
        #print(val)
        # 上記のリストをファイルに書き込む
        with open(file, "w") as f:
            f.write(f'0,{val[0]},{val[1]},{val[2]},{val[3]}')

    def FinishEvent():
        # ここでセーブする
        Save("test.txt")
        top.destroy()
        # toplevel wigget(サブウィンドウ)を破壊する
        ajust_onemore = messagebox.askyesno('Adjusts_Onemore', 'もう一度調整しますか?')
        # 確認用ダイアログを出す
        # 調整用のやつ
        if ajust_onemore:
            # 再度 mainloop内から ajust_moving() 関数を呼ぶ
            root.after(0, Light)
        else:
            # 完了ダイアログを出す
            messagebox.showinfo('', '完了!!')
            root.destroy()
            # rootを破棄(これでmainloopを抜けるはず)

    root = tkinter.Tk()
    root.withdraw()
    root.after(0, Light())
    root.mainloop()
    return setting_values

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/12/25 15:20 編集

    次に setting_values ですが、これは単に入力された値を保持しているだけです。
    上で説明した通り、データが入力される毎に validation関数が呼ばれますので、入力値の検証を行い有効な場合のみ
    setting_values
    にその値をdict型で保持しています。
    この際に nameには ".!toplevel.num0" のような値が入ってくるようですが、最後の'num0'だけあれば十分ですので name.split('.')[-1] として最後の'num0' だけを取り出して
    setting_values['num0'] = '3'
    の様に値を保持しているということです。
    で、これらの値は Save関数内で読み込まれファイルに書き出されております。

    キャンセル

  • 2019/12/25 15:35 編集

    あと書くのを忘れておりましたが、
    top.geometry()
    の次の行あたりに
    top.protocol('WM_DELETE_WINDOW', lambda: root.destroy())
    などを入れて、設定ウィンドウが(右上のXボタンなどから)閉じられた場合に 見えていないトップレベルウィンドウ(root) を破棄するようにした方が良いかもしれません

    キャンセル

  • 2019/12/25 16:01

    なるほど!
    理解しました!早速質問文に追記します!

    キャンセル

+1

なるほど、tkinterのお作法を理解した方が良さそうですね。
構造がよくないです。
基本的に、ウィンドウやボタンといった部品配置を行い、ボタンが押されたら何をするかといった動作をコールバックなどで定義しておき、mainloopでウィンドウメッセージループを回すことが基本構成となります。

確定ボタンを押した時にファイルを書き込みたいのであれば、そのコールバック処理、現状であればFinishEventのなかにファイル書き込み処理を設定する必要があります。
また、FinishEventのなかでウィンドウを閉じるような処理をしていますが、テキストを取得した後でないとうまく動作しなくなるので、タイミングなどは考慮すべきです。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/12/25 08:45 編集

    テキストをうまく取得することが出来ました。

    しかし、コードが汚くなっている気がします。
    追記したコードを綺麗にする方法がございましたら、教えていただきたいです。

    キャンセル

  • 2020/01/08 16:20

    サンプルコードや、githubにあるようなコードを読みながら、あなたが綺麗だと思うコードを参考にされてはいかがでしょうか。

    キャンセル

  • 2020/01/09 13:08

    ご回答ありがとうございます。
    やらせていただきます。

    キャンセル

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

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

関連した質問

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