Delphi のフォームを Python から使う方法

# はじめに
Delphi を使って Python の GUI を簡単に作成できるようなので、実際に試してみました。
以下に Jim McKeeth 氏の LearnDelphi 動画およびスライドの説明があります。今回はこれを元にインストールとサンプルを実施します。（今のところ他にこの情報は少ないようです）

https://learndelphi.org/ja/how-to-make-superb-python-gui-apps-using-delphivcl/

この LearnDelphi は part1 と part2 に分かれていて、それぞれ VCL と FMX を使った説明になってます。
基本的な操作方法は同じのため、本記事ではまとめて記載します。

・part1 DelphiVCL

https://blogs.embarcadero.com/introduction-to-python-gui-development-with-delphi-for-python-vcl-fmx/

・part2 DelphiFMX

https://blogs.embarcadero.com/python-on-android-with-delphi-fmx/


## 前提条件
予め以下の Delphi と Python の開発環境をしておきます。(カッコは実行時のバージョン)
・Delphi (11 Alexandria)
・Python (3.10.8)
・pip (22.3)
・PyScripter
（PyScripter は Python IDE の一つで、簡単に Python を実行できます。VSCode でもプラグインを入れることによって容易に実行できます。どちらでも可）
[PyScripter download](https://www.embarcadero.com/free-tools/pyscripter/free-download)

## Delphi VCL for Python / Delphi FMX for Python のインストール
#### VCL
pip 経由で delphivcl をインストールします。
[pypi.org/project/delphivcl/](https://pypi.org/project/delphivcl/)

PowerShell で実行
`
pip install delphivcl
`

サンプルコードなどを実施するため、下記コードを Local にコピーするかクローンしておきます。
[github.com/Embarcadero/DelphiVCL4Python](https://github.com/Embarcadero/DelphiVCL4Python)

#### FMX
pip 経由で delphifmx をインストールします。
[pypi.org/project/delphifmx/](https://pypi.org/project/delphifmx/)

PowerShell で実行
`
pip install delphifmx
`

サンプルコードなどを実施するため、下記コードを Local にコピーするかクローンしておきます。
[github.com/Embarcadero/DelphiFMX4Python](https://github.com/Embarcadero/DelphiFMX4Python)

## サンプル Hello World の実行
GitHub で取得したコードにサンプルがあります。

#### VCL
DelphiVCL4Python\samples\HelloWorld\
・hellodelphivcl.py
```
from delphivcl import *

class MainForm(Form):

    def __init__(self, owner):
        self.Caption = "A VCL Form..."
        self.SetBounds(10, 10, 500, 400)
        self.Position = "poScreenCenter"

        self.lblHello = Label(self)
        self.lblHello.SetProps(Parent=self,
            Caption="Hello DelphiVCL for Python")
        self.lblHello.SetBounds(10, 10, 300, 24)

        self.OnClose = self.__on_form_close

    def __on_form_close(self, sender, action):
        action.Value = caFree

def main():
    Application.Initialize()
    Application.Title = "Hello Python"
    Main = MainForm(Application)
    Main.Show()
    FreeConsole()
    Application.Run()
    Main.Destroy()

main()
```

import で delphivcl が指定されてます。
これを実行すると下記の様なフォームがでます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/a76e9e90-7bb7-98f2-0afe-f839efb4912e.png)

#### FMX
DelphiFMX4Python-main\samples\
・Simplest.py
```
from delphifmx import *

Application.Initialize()
Application.Title = "Hello Delphi FMX"
Application.MainForm = Form(Application)
Application.MainForm.SetProps(Caption = "Hello World")
msg = Label(Application.MainForm)
msg.SetProps(Parent = Application.MainForm,
    Text = "Hello Python from Delphi FMX",
    Position = Position(PointF(20, 20)),
    Width = 200)
Application.MainForm.Show()
Application.Run() # This is the main loop
Application.MainForm.Destroy()
```

import で delphivcl が指定されてます。
これを実行すると下記の様なフォームがでます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/2dfc8241-a554-7f31-4f3d-b177cdc68566.png)


## その他のサンプル
他にも幾つかのサンプルがあります。ここではファイルパスと画像のみを記載しますので、実際のコードは GitHub で取得したコードをご確認ください。

#### VCL
■ VCL.ActivityIndicator Sample
DelphiVCL4Python\samples\ActivityIndicator
・activityindicator.py
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/5fc1c58e-820b-59d2-43b1-51c76f7b8b52.png)


#### FMX
■ Hello World (An Object Oriented example)
DelphiFMX4Python-main\samples\
・HelloDelphiFMX.py
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/391150b1-ecbf-ea39-3fee-920ed53afc56.png)

■ ToDo App (Starting to get useful….)
DelphiFMX4Python-main\samples\
・ToDoList.py
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/a0d9019c-c42c-f330-26b3-e3212da21edc.png)

■ With Style (And more controls)
DelphiFMX4Python-main\samples\samples\ControlsDesktop\
・controlsdemo.py
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/725297d9-e164-5b5f-bb13-323a7fc4cc52.png)


## Delphi のフォームを Python から使う
今回一番のポイントとなる機能です。
Delphi でフォームを作成し、Python で使用できるようにフォームをエクスポートします。その後の実処理は Python でコードを書きます。(Delphi では画面の器を作成するイメージです)

#### Delphi4PythonExporter の導入
以下にコード一式があり、説明もあります。

https://github.com/Embarcadero/Delphi4PythonExporter

実際の導入は Delphi IDE の GetItパッケージマネージャー から Delphi4PythonExporter をインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/d135b6cf-a809-12fd-1e2a-2f55b7dab481.png)

### Delphi4PythonExporter の使い方サンプル
GitHub で取得したコードにサンプルがあるため、それを使用します。
基本的な使い方は VCL / FMX で同じです。

#### VCL
DelphiVCL4Python\samples\DesignExport\SampleForm
のプロジェクトを開きます (SampleFormDesignExport.dproj)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/07dea72d-3fb1-8110-20d5-0c7f0ddb8981.png)

・[ツール] から [Export to Python] を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/9a6754a8-ee53-c8df-ef8d-457b8894543f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/3b8e63d9-eb47-3554-96c2-c8c6396770b3.png)

どちらかを選んで Export すると以下のファイルができます。
（ここでは Export Select Forms を選びました）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/fe1e461c-5a8c-b346-6aa4-e833a8c14764.png)

ファイルの中身（Python のコードと Delphi の .dfm の様な内容）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/29e5877d-12fc-0720-eb76-bca3b39382cf.png)

この「uMainForm.py」を実行すると下記のフォームがでます。
実際の処理は uMainForm.py に記述する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/687a8ad1-b1ed-b28a-c914-e532be2a7735.png)

#### FMX
基本的な使い方は VCL と同様です。
（ここでは Delphi4PythonExporter 内のサンプルを使用します）
Delphi4PythonExporter\Samples\TipCalculator
のプロジェクトを開きます(TipCalculator.dproj)

ボタンイベントが空で実装されてます。これは後述の Export で作成した Python のコードに実処理を追加する必要があるからです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/149f2dee-c530-0637-925f-7868be569158.png)

[ツール] から [Export to Python] で Export します。
（ここでは Export Entire Current Project を選びました）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/e5add2d8-aae2-1f2a-20e4-15190377f550.png)

ファイルの中身（Python のコードと Delphi の .dfm の様な内容）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/bf60aa45-21a3-57a4-72be-6046beb44759.png)


左側の TipMain.py 内の処理が何もないため、ボタン等を押しても機能しません。
そのため、以下の様にコードを追記してから実行します。

コードを追記（TipMain.py）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/0c4d28ae-fdf6-7a99-46fb-fa040f472cee.png)
<details><summary>追記した TipMain.py のコードです</summary>

```rb
import os
from delphifmx import *

class Main_Window(Form):

    def __init__(self, owner):
        self.styleRuby = None
        self.styleLight = None
        self.ListBox1 = None
        self.ListBoxItem1 = None
        self.editTotal = None
        self.Label6 = None
        self.ListBoxItem2 = None
        self.Label7 = None
        self.editTip = None
        self.ListBoxItem3 = None
        self.trackTip = None
        self.ListBoxItem4 = None
        self.editPeople = None
        self.Label3 = None
        self.ListBoxItem5 = None
        self.trackPeople = None
        self.ListBoxItem6 = None
        self.Layout2 = None
        self.ListBoxItem7 = None
        self.per_person_share = None
        self.Label1 = None
        self.ListBoxItem8 = None
        self.bill_plus_tip = None
        self.Label5 = None
        self.ListBoxItem9 = None
        self.gold_style_btn = None
        self.ruby_style_btn = None
        self.light_style_btn = None
        self.default_style = None
        self.styleGold = None
        self.LoadProps(os.path.join(os.path.dirname(os.path.abspath(__file__)), "TipMain.pyfmx"))
        self.editTotal.OnChange = self.editTotalChange
        self.editTotal.Value = 100
        self.editTip.Value = 20
        self.editPeople.Value = 4

    def calc_bill_plus_tip(self):
        total = self.editTotal.Value
        tip_percent = self.editTip.Value

        if total != 0:
            self.bill_plus_tip.Text = str(round(total + (tip_percent*total)/100, 2))
            print(round(total + (tip_percent/total)*100, 2))
        else:
            self.bill_plus_tip.Text = str(0)

    def calc_per_person_share(self):
        persons = self.editPeople.Value

        self.per_person_share.Text = str(round(float(self.bill_plus_tip.Text) / persons, 2))

    def editTotalChange(self, Sender):
        self.calc_bill_plus_tip()
        self.calc_per_person_share()

    def editTipChange(self, Sender):
        self.trackTip.Value = self.editTip.Value
        self.calc_bill_plus_tip()
        self.calc_per_person_share()

    def trackTipChange(self, Sender):
        self.editTip.Value = self.trackTip.Value
        self.calc_bill_plus_tip()
        self.calc_per_person_share()

    def editPeopleChange(self, Sender):
        self.trackPeople.Value = self.editPeople.Value
        self.calc_bill_plus_tip()
        self.calc_per_person_share()

    def trackPeopleChange(self, Sender):
        self.editPeople.Value = self.trackPeople.Value
        self.calc_bill_plus_tip()
        self.calc_per_person_share()

    def gold_style_btnClick(self, Sender):
        self.styleBook = self.styleGold

    def ruby_style_btnClick(self, Sender):
        self.styleBook = self.styleRuby

    def light_style_btnClick(self, Sender):
        self.styleBook = self.styleLight

    def default_styleClick(self, Sender):
        self.styleBook = None
```
</details>

実行（下方のボタン押下でスタイルが変わります）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/d517b889-1c8b-2f70-2b57-1ec275eb16d2.png)

### その他のサンプル
今までに出てきた GitHub で取得したコード内の Samples フォルダに幾つかサンプルがありますので、必要の応じて確認してください。

## まとめ
以上のように、「簡単なフォーム」については Delphi IDE でコントロールをドラッグアンドドロップで追加して画面を作成し、Python 用に出力することにより、容易に Python UI を作成できました。

しかし、今回使用した LearnDelphi で出てきた Delphi プロジェクトは単純なフォーム(一つのUnit)でしたので、実際に作られるような複数の Form や Frame, Unit などがあるアプリについて、どこまで Python UI にできるかは、更なる検証が必要かと思いました。

今回はあまり情報の少ない Delphi for Python について、「使えるかも」という確認でした。


## 補足
①ドキュメントについて
参考サイトのスライドについては、取得した GitHub 内の tutorials フォルダに PDF ファイルがあります。
DelphiVCL4Python\tutorials\Introduction to Python GUI Development with Delphi for Python Part 1 - Delphi VCL for Python.pdf
DelphiFMX4Python\tutorials\Python-on-Android_with-DelphiFMX.pdf

②使用可能なコントロールについて
コマンドで DelphiVCL, DelphiFMX の利用可能なコントロールが確認できます。
**VCL**
```
> python
>>> from delphivcl import *
>>> dir()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/5eda59e5-04e5-736b-351b-3b2bd2316ef3.png)

**FMX**
```
> python
>>> from delphifmx import *
>>> dir()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543326/25ea7456-91a7-92d3-90d9-b176e5c96e2c.png)

現段階で例えば「TSaveDialog」を用いた Delphi を Export すると Python の実行でエラーになる様子。
`
NameError: name 'TSaveDialog' is not defined
`