Python で独立したダイアローグボックスプロセスと通信をやってみる

# はじめに

独立したプロセス間通信の応用例として、ダイアローグボックスを表示するプロセスとの通信をやってみました。
ダイアローグボックスを表示するプロセス（dialogsrv）は、表示を要求するプロセス（dialogcli）からの要求を待ちます。そして、依頼された文字列を表示して、入力待ちになります。文字列入力後、'ok'ボタンをクリックされると入力された文字列をdialogcliへ返します。もし、'cancel'をクリックされた場合には、文字コード'\18'を返します。
 dialogcliから文字コード'\04'をdialogsrvへ送ることにより、dialogsrvは処理を終了します。

# 具体的に
ダイアローグの表示には、tkinterを使用しています。

## ダイアローグを表示するプロセス
ダイアログを表示するプロセスは、起動されるとダイアローグを作成した後、ソケット通信で表示要求を待ちます。表示要求が来るとその文字列をラベルに表示した後、root.mainloop()で入力待ちのループに入ります。
ダイアローグには、'ok'ボタンおよび'cancel'ボタンがクリックされた際の処理を宣言しており、それぞれのボタンに応じた処理を行ったのち、root.quit()で入力ループから抜けます。そして、次のソケット通信による要求待ちになります。
ソケット通信での要求時に、文字コード'\04'を受け取ると root.destroyでダイアローグを破棄して、プロセスを終了します。

```dialogsrv.py

import multiprocessing      as mp
import tkinter              as tk
import tkinter.simpledialog as simpledialog
import tkinter.messagebox   as tkmsg
import time
from socket import socket, AF_INET, SOCK_STREAM

HOST        = 'localhost'
PORT        = 51000
MAX_MESSAGE = 2048
NUM_THREAD  = 4

CHR_CAN     = '\18'
CHR_EOT     = '\04'

class Dialog(mp.Process):

    #----------------------------------------------------
    def __init__(self):
        mp.Process.__init__(self)

    #----------------------------------------------------
    def send_result(self, mess):

        while True:
            try:
                # connect
                sock = socket(AF_INET, SOCK_STREAM)
                sock.connect((HOST, PORT + 1))
                
                # send message
                sock.send(mess.encode('utf-8'))

                # disconnect
                sock.close()
                break

            except:
                print ('retry: ' + mess)

    #----------------------------------------------------
    def showDialog(self, mess):

        self.root  = tk.Tk()
        self.root.title('scratch')
        self.root.minsize(width = 500, height = 250)
        
        self.frame = tk.Frame(self.root, width = 300, heigh = 200, bd = 11)
        self.frame.pack()
        
        self.label = tk.Label(self.frame, font = ("", 20), text = mess)
        self.label.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5)

        self.lbl1  = tk.Label(self.frame, font = ("", 20), text = '')
        self.lbl1.grid(row = 1, column = 0) 

        self.entry = tk.Entry(self.frame, font = ("", 20),  bd = 4)
        self.entry.grid(row = 2, column = 0, columnspan = 3, padx = 5, pady = 5)
        self.entry.focus()

        self.lbl2  = tk.Label(self.frame, font = ("", 20), text = '')
        self.lbl2.grid(row = 3, column = 0) 
        
        #+++++++++++++++++++++++++++++++++++
        def click():
            answer = self.entry.get()
            self.send_result(answer)
            self.root.quit()
            # self.root.destroy()
        self.button1 = tk.Button(self.frame,font = ("", 20),  width = 4, height = 1, text = 'ok', fg = "blue", command = click)
        self.button1.grid(row = 4, column = 0, sticky = tk.E ) 

        #+++++++++++++++++++++++++++++++++++
        def cancel():
            answer = CHR_CAN
            self.send_result(answer)
            self.root.quit()
            # self.root.destroy()
        self.button2 = tk.Button(self.frame,font = ("", 20),  width = 7, height = 1, text = 'cancel', fg = "red", command = cancel)
        self.button2.grid(row = 4, column = 2, sticky = tk.W) 
        
        # self.root.mainloop()

    #----------------------------------------------------
    def messDialog(self, mess):
        self.label.configure(text=mess)

    #----------------------------------------------------
    def loopDialog(self):
        self.root.focus()
        self.root.mainloop()
    
    #----------------------------------------------------
    def destroyDialog(self):
        self.root.destroy()
    
    #----------------------------------------------------
    def run(self):
        proc_name = self.name

        # connect
        sock = socket(AF_INET, SOCK_STREAM)
        sock.bind    ((HOST, PORT))
        sock.listen  (NUM_THREAD)
        print ("receiver ready, NUM_THREAD  = " + str(NUM_THREAD))

        self.showDialog('start of scratch dialog')

        # loop
        while True:
            try:
                conn, addr = sock.accept()
                mess       = conn.recv(MAX_MESSAGE).decode('utf-8')
                conn.close()

                # exit ?
                if (mess == CHR_EOT):
                    self.destroyDialog()
                    break

                # text
                self.messDialog(mess)
                self.loopDialog()

            except:
                print ('Error:' + mess)

        # disconnect
        sock.close()
        return

#----------------------------------------------------
def proc():
    # start dialog process
    dialog = Dialog()
    dialog.start()
    
    # wait for the end of process
    dialog.join()
    
if __name__ == '__main__':
    proc()
```
起動は、dialogsrv.proc()です。

## ダイアローグへ表示を依頼するプロセス
ダイアローグへ文字列の表示要求をするプロセスは、表示する文字列を準備した後、ソケット通信を使って文字列を送信します。その後、ダイアローグからの応答を待ちます。
ここでは、簡単のため、10回のループで'message:n' （n:0～9）を表示しています。

```dialogcli.py
import multiprocessing      as mp
import tkinter              as tk
import tkinter.simpledialog as simpledialog
import tkinter.messagebox   as tkmsg
import time

from socket import socket, AF_INET, SOCK_STREAM

HOST        = 'localhost'
PORT        = 51000
MAX_MESSAGE = 2048
NUM_THREAD  = 1

CHR_CAN     = '\18'
CHR_EOT     = '\04'

class DialogIF(mp.Process):

    #----------------------------------------------------
    def connect(self):
        # connect
        self.sock = socket(AF_INET, SOCK_STREAM)
        self.sock.bind    ((HOST, PORT + 1))
        self.sock.listen  (NUM_THREAD)
        print ("receiver ready, NUM_THREAD  = " + str(NUM_THREAD))

        return

    #----------------------------------------------------
    def dequeue(self):
        try:
            conn, addr = self.sock.accept()
            mess       = conn.recv(MAX_MESSAGE).decode('utf-8')
            conn.close()

            # cancel ?
            if (mess == CHR_CAN):
                print ('cancel')

            # text
            print ('text> ' + mess)

        except:
            print ('Error:' + mess)

        return mess

    #----------------------------------------------------
    def disconnect():
        self.sock.close()
        return

    #----------------------------------------------------
    def enqueue(self, mess):
        while True:
            try:
                # connect
                sock = socket(AF_INET, SOCK_STREAM)
                sock.connect((HOST, PORT))
                
                # send message
                sock.send(mess.encode('utf-8'))

                # disconnect
                sock.close()
                break
                
            except:
                print ('retry: ' + mess)
        
    #----------------------------------------------------
    def terminate(self):
        self.enqueue(CHR_EOT)

#----------------------------------------------------
def proc():
    dif = DialogIF()
    dif.connect()
    
    for i in range(10):
        dif.enqueue('message:' + str(i))
        r = dif.dequeue()
        if r is None:
            break
        
        if (r == CHR_CAN):
            print ('cancel')
        else:
            print ('result> ' + r)
        
    dif.terminate()

if __name__ == '__main__':
    proc()
```