見出し画像

超簡単!? TkinterフォームをVBAから変換する(Python x VBA(Excelマクロ))

紹介動画はこちら

こんにちは、Rcatです。
 今回はFormToTkツールを使ったVBAを使って楽にTkinerフォームを作る方法をご紹介します!
動画もありますのでぜひそちらもご視聴ください。

JavaScript版は以下


概要

そもそもどういうこと??

本ツールはその名の通り、VBA(Excelマクロ)で作成したフォームをPythonのTkiner用ソースに変換するというものです。
 Tkinterを手打ちしたことがある方は分かると思いますが、要素ひとつ作るのに数行必要で位置なんかはプレビューしないと分かり辛いなど、非常にやり辛いです。
ここでVBAerとして「Excelでユーザーフォーム作ってVBAでPython Tkinerソースに変換すれば簡単なのでは?」思ってしまったのです…。
そしてそれは意外と簡単に実現できました。今回は作った作品の紹介と配布案内になります。

VBAでフォームを作るとは?

 一応解説しておきますが、VBAとはエクセルマクロの正式名称です。
 そしてVBAと言えばExcelでコピペを自動化したり表を作ったり印刷したりといった用途でよく書籍なんかで紹介されています。
しかし、その程度ではExcelを自動化した"マクロ"に過ぎません。"VBA"とは本来、プログラミング言語なのです。そう、自動化に限ったことしかできないわけではなく、使い手次第で無限の可能性があります(まぁ制約もあるが)。自動化ももちろん大事ですが、「ツールが無ければVBAで作ればいい」と考えられるくらいになれると一気に無双できるようになります(個人差があります)。
 さて、今回目を付けたのがユーザーフォーム機能です。こちらはその名の通り自分でGUIを作れる機能となっています。しかもGUI上でGUIを作れるというおまけつき。
エクセルという世界だけ見ていればまず使うことのない機能ですが、アドインを作ったりパワポでVBAを使うようになるとお世話になります。
 というわけで、こんな便利なものを知っていれば自然とそれをTkinerに変換してしまえばいいと思ってくるわけです。

ユーザーフォーム編集画面

環境/条件

  • Windows10

  • マイクロソフト純正のエクセル

ツール紹介

動画抜粋

冒頭でも説明しましたが、本ツールはVBAのユーザーフォームをPythonのTkinerのソースに変換するツールで、上図のようにVBAで作ったフォームを一瞬でTkinter化します。
以下の機能があります。

機能

  • フォーム出力
    フォームの内容をTkiner形式に変換する機能です

    • ラベル

    • テキストボックス

    • ボタン

    • コンボボックス

    • チェックボックス

    • オプションボタン(ラジオボタン)

    • リストボックス

    • スクロールバー

    • イメージ

  • プロパティの引継ぎ(要素により違いあり)
    要素に設定したプロパティを使って出力を行います

    • オブジェクト名

    • サイズ

  • イベント関数の自動作成
    "ボタンを押したとき"などのイベントが自動で挿入/関連付けされます

    • ボタン押下時

    • コンボボックス

    • リストボックス

  • 値の設定/取得コードの自動生成
    値の設定や取得が地味に面倒なTkinterなので補助コードが出力されます

    • テキストボックス

    • コンボボックス

  • 便利関数出力
    まぁ使うであろうGUI系関数が追加で出力されます

    • MsgBox

    • GetOpenFileName(ファイルを開く)

  • クラスで出力
    複数ウィンドウのプログラムに使用できるようにクラスの形で出力されます

なんか色々書いてありますが、ざっとよく使う要素の変換とその要素を楽に扱うために付属のコードも一緒に出ますよという感じです。

サンプルコード

ちなみに、画像のFTPフォームは以下のようなコードとして出力されます。
※貼り付ければフォームが出ます

# coding: UTF-8
import tkinter as tk
from tkinter import messagebox
from tkinter import filedialog
from tkinter import ttk
#ファイルダイアログ、フォルダ版
def OpenDir(initialdir=''):
	return tk.filedialog.askdirectory(initialdir=initialdir)
#GetOpenFileName的な
def OpenFile(FType,initialdir=''): #FType [('Pythonファイル','*.py')]
	return tk.filedialog.askopenfilename(filetypes=FType,initialdir=initialdir)
#MsgBox関数 VBA似せ
def MsgBox(Msg,Mode='Information',Title='MsgBox',root=None):
	if root is None:
		root = tk.Tk()
		root.withdraw()
	if Mode == 'Information':
		return messagebox.showinfo(Title,Msg) #ok
	elif Mode == 'Exclamation':
		return messagebox.showwarning(Title,Msg) #ok
	elif Mode == 'Critical':
		return messagebox.showerror(Title,Msg) #ok
	elif Mode == 'OKCancel':
		return messagebox.askokcancel(Title,Msg) #True/False
	elif Mode == 'YesNo':
		return messagebox.askyesno(Title,Msg) #True/False
	elif Mode == 'RetryCancel':
		return messagebox.askretrycancel(Title,Msg) #True/False
#テキストボックスの内容を入力する関数
def TextBox_SetText(Box,Text):
	Box.delete(0,tk.END)
	Box.insert(tk.END,Text)
#+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
class FTPServer:
	def __init__(self,Master):
		self.MASTER = Master
		self.MASTER.resizable(0,0)
		self.MASTER.title("Rcat FTP Server")
		self.MASTER.geometry("249x315")
		self.MASTER_BGColor = "#F0F0F0" #背景色フォーム及びラベルに有効
		self.MASTER['bg'] = self.MASTER_BGColor
		self.Style = ttk.Style(self.MASTER)
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#Label:Label1#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.Label1 = tk.Label(self.MASTER,font=('',15),justify=tk.LEFT)
		self.Label1['text'] = "Rcat FTP Server"
		self.Label1['bg'] = self.MASTER_BGColor
		self.Label1.place(x=8,y=11)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#Label:Label2#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.Label2 = tk.Label(self.MASTER,font=('',11),justify=tk.LEFT)
		self.Label2['text'] = "ユーザー名"
		self.Label2['bg'] = self.MASTER_BGColor
		self.Label2.place(x=16,y=59)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#Label:Label3#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.Label3 = tk.Label(self.MASTER,font=('',11),justify=tk.LEFT)
		self.Label3['text'] = "パスワード"
		self.Label3['bg'] = self.MASTER_BGColor
		self.Label3.place(x=16,y=83)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#Label:Label4#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.Label4 = tk.Label(self.MASTER,font=('',11),justify=tk.LEFT)
		self.Label4['text'] = "ディレクトリ"
		self.Label4['bg'] = self.MASTER_BGColor
		self.Label4.place(x=16,y=131)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#Label:Label5#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.Label5 = tk.Label(self.MASTER,font=('',11),justify=tk.LEFT)
		self.Label5['text'] = "ポート"
		self.Label5['bg'] = self.MASTER_BGColor
		self.Label5.place(x=16,y=107)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#TextBox:TextBox_USER#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.TextBox_USER = tk.Entry(self.MASTER,width=19)
		self.TextBox_USER.place(x=96,y=64)
		#TextBox_SetText(self.TextBox_USER,ここに文字を入力)
		#self.TextBox_USER.get()
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#TextBox:TextBox_PASSWD#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.TextBox_PASSWD = tk.Entry(self.MASTER,width=19)
		self.TextBox_PASSWD.place(x=96,y=88)
		#TextBox_SetText(self.TextBox_PASSWD,ここに文字を入力)
		#self.TextBox_PASSWD.get()
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#TextBox:TextBox_RootDIR#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.TextBox_RootDIR = tk.Entry(self.MASTER,width=19)
		self.TextBox_RootDIR.place(x=96,y=136)
		#TextBox_SetText(self.TextBox_RootDIR,ここに文字を入力)
		#self.TextBox_RootDIR.get()
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#TextBox:TextBoxPort#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.TextBoxPort = tk.Entry(self.MASTER,width=5)
		self.TextBoxPort.place(x=96,y=112)
		#TextBox_SetText(self.TextBoxPort,ここに文字を入力)
		#self.TextBoxPort.get()
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#CommandButton:CommandButton_Open#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.CommandButton_Open = tk.Button(self.MASTER,command=self.CommandButton_Open_Click,height=2,width=7,font=('',9))
		self.CommandButton_Open['text'] = "開始"
		self.CommandButton_Open['bg'] = "#C0FFC0"
		#self.CommandButton_Open['state'] = 'disable' #normal
		self.CommandButton_Open.place(x=176,y=8)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#CommandButton:CommandButton_DirOpen#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.CommandButton_DirOpen = tk.Button(self.MASTER,command=self.CommandButton_DirOpen_Click,height=1,width=5,font=('',9))
		self.CommandButton_DirOpen['text'] = "開く"
		self.CommandButton_DirOpen['bg'] = "#FFE0C0"
		#self.CommandButton_DirOpen['state'] = 'disable' #normal
		self.CommandButton_DirOpen.place(x=168,y=112)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#CheckBox:CheckBox_Perm_Write#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.CheckBox_Perm_Write_Value = tk.BooleanVar()
		self.CheckBox_Perm_Write_Value.set(False) #初期値設定
		#self.CheckBox_Perm_Write_Value.get() #取得時
		self.CheckBox_Perm_Write = tk.Checkbutton(self.MASTER,variable=self.CheckBox_Perm_Write_Value,text='書き込み禁止')
		self.CheckBox_Perm_Write['bg'] = self.MASTER_BGColor
		self.CheckBox_Perm_Write.place(x=16,y=224)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#Label:Label6#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.Label6 = tk.Label(self.MASTER,font=('',11),justify=tk.LEFT)
		self.Label6['text'] = "権限設定"
		self.Label6['bg'] = self.MASTER_BGColor
		self.Label6.place(x=16,y=195)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#CheckBox:CheckBox_Perm_rm#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.CheckBox_Perm_rm_Value = tk.BooleanVar()
		self.CheckBox_Perm_rm_Value.set(False) #初期値設定
		#self.CheckBox_Perm_rm_Value.get() #取得時
		self.CheckBox_Perm_rm = tk.Checkbutton(self.MASTER,variable=self.CheckBox_Perm_rm_Value,text='削除禁止')
		self.CheckBox_Perm_rm['bg'] = self.MASTER_BGColor
		self.CheckBox_Perm_rm.place(x=16,y=248)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#CheckBox:CheckBox_Perm_Mkdir#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.CheckBox_Perm_Mkdir_Value = tk.BooleanVar()
		self.CheckBox_Perm_Mkdir_Value.set(False) #初期値設定
		#self.CheckBox_Perm_Mkdir_Value.get() #取得時
		self.CheckBox_Perm_Mkdir = tk.Checkbutton(self.MASTER,variable=self.CheckBox_Perm_Mkdir_Value,text='フォルダ作成禁止')
		self.CheckBox_Perm_Mkdir['bg'] = self.MASTER_BGColor
		self.CheckBox_Perm_Mkdir.place(x=16,y=272)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#CommandButton:CommandButton_PortOpen#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.CommandButton_PortOpen = tk.Button(self.MASTER,command=self.CommandButton_PortOpen_Click,height=1,width=9,font=('',9))
		self.CommandButton_PortOpen['text'] = "ポート開放"
		self.CommandButton_PortOpen['bg'] = "#FFC0FF"
		#self.CommandButton_PortOpen['state'] = 'disable' #normal
		self.CommandButton_PortOpen.place(x=128,y=216)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#Label:Label7#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.Label7 = tk.Label(self.MASTER,font=('',11),justify=tk.LEFT)
		self.Label7['text'] = "機能"
		self.Label7['bg'] = self.MASTER_BGColor
		self.Label7.place(x=128,y=195)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#TextBox:TextBox_Network#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.TextBox_Network = tk.Entry(self.MASTER,width=14)
		self.TextBox_Network.place(x=128,y=168)
		#TextBox_SetText(self.TextBox_Network,ここに文字を入力)
		#self.TextBox_Network.get()
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#Label:Label8#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.Label8 = tk.Label(self.MASTER,font=('',11),justify=tk.LEFT)
		self.Label8['text'] = "デバイス名(Linux)"
		self.Label8['bg'] = self.MASTER_BGColor
		self.Label8.place(x=16,y=163)
		
		#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#Label:Log#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
		self.Log = tk.Label(self.MASTER,font=('',11),justify=tk.LEFT)
		self.Log['text'] = ""
		self.Log['bg'] = self.MASTER_BGColor
		self.Log.place(x=128,y=243)
		
	#------------------------------------------------
	def Show(self):
		self.MASTER.mainloop()
	#------------------------------------------------
	def Unload(self):
		self.MASTER.destroy()
	#------------------------------------------------
	def Iconset(self,Icon_Base64):
		self.MASTER.iconphoto(True, tk.PhotoImage(data=Icon_Base64))
	#------------------------------------------------
	def GetWindowSize(self):
		return self.MASTER.geometry().split('+')[0].split('x')
	#------------------------------------------------
	def GetWindowpos(self):
		return self.MASTER.geometry().split('+')[1:]
	#+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
	#------------------------------------------------
	def CommandButton_Open_Click(self):
		pass
	#------------------------------------------------
	def CommandButton_DirOpen_Click(self):
		pass
	#------------------------------------------------
	def CommandButton_PortOpen_Click(self):
		pass
FTPServerObj = FTPServer(tk.Tk())
FTPServerObj.Show()

手打ちしてたらどれだけ時間がかかることやら…

変換機能の仕組み

変換機能の仕組みと仕様を抜粋ですが紹介します

フォーム本体

動画抜粋

フォーム本体の変換は、フォームのサイズ、色、名前、タイトルが引き継がれてTkコードの変換されます。
例えばオブジェクト名がクラス名に、Captionがタイトルになっていたりと、VBAのプロパティをTkの対応する部分に割り当てていくことで変換を行っています。

テキストボックス

動画抜粋

テキストボックスの場合はオブジェクト名、位置とサイズ、そして入力内容があればそのテキストを引き継ぎます。
例題はオブジェクト名が適当なので"TextBox1"とかになっていますが、名前を入れておけばちゃんとその名前で出ます。
右上のコードを見ればわかる通りテキストボックスに入力するのに必要な関数が入っており、コメントアウトされているかどうかで差異が出ています。
ちなみに値を拾うコードは最下部にコメントで出力済みなので、これをそのまま利用することでテキストを取得できます。

ボタン

動画抜粋

 ボタンの場合は"押す"ことがあるので、押したときのイベントプロシージャが自動で作成されます。
初期は空欄なのでそこに処理を記述することでフォームを使ったツールを作ることができるようになります。
なお、VBAとTkinerでサイズの指定方法が異なるのでVBA側の標準文字サイズ以外のサイズだと結構ズレます…。

コンボボックス

動画抜粋
動画抜粋

コンボボックスはExcelシートとリンクした"RowSource"プロパティを読み込んでリストの初期値を引き継ぐことができます。固定の値であれば先に設定しておくとフォームを作り直しても適用されるので楽です。
また、選択変更時のイベントプロシージャも出力されます。初期は空ですが、コメントで値の取得コードが入っているので変更時に何に値が変わったのかも簡単に取得できます。

まとめ

今回はFormToTkを紹介しました。
複数のプログラミング言語の特徴を生かして組み合わせれば、ツールを作るツールになったり連携したりとより幅が広がります。
Tkinerフォームで苦しんでいる方はぜひ一度使ってみてください。

ツールの配布について

入手方法

  1. リンクを確認する
    配布のリンクは最上部の動画の説明欄よりリンクがあります。

  2. 権限レベル1パスワードを入手する
    システムの都合上、権限レベル1パスワードが必要です。
    こちらはチャンネル概要より取得してください。

  3. キーワードを入手する
    記事を購入することで、有料区間記載のキーワードを入手できます。
    事前に利用規約を読んで同意の上購入してください。
    特に、"有料作品について"重要事項です。

  4. ダウンロードページに必要情報を記入して入手
    配布パスワード及びキーワードを使用してダウンロードいただけま

ここから先は

57字

¥ 300

情報が役に立ったと思えば、僅かでも投げ銭していただけるとありがたいです。