diff --git a/Images/vswizard_en.png b/Images/vswizard_en.png deleted file mode 100644 index 371dcfa..0000000 Binary files a/Images/vswizard_en.png and /dev/null differ diff --git a/Images/vswizard_ja.png b/Images/vswizard_ja.png deleted file mode 100644 index 833bdc1..0000000 Binary files a/Images/vswizard_ja.png and /dev/null differ diff --git a/README.ja.md b/README.ja.md index 2aaebe8..3fd198c 100644 --- a/README.ja.md +++ b/README.ja.md @@ -14,7 +14,6 @@ |Epoxy.Avalonia11|[![NuGet Epoxy.Avalonia11](https://img.shields.io/nuget/v/Epoxy.Avalonia11.svg?style=flat)](https://www.nuget.org/packages/Epoxy.Avalonia11)|Avalonia version 11| |Epoxy.Avalonia|[![NuGet Epoxy.Avalonia](https://img.shields.io/nuget/v/Epoxy.Avalonia.svg?style=flat)](https://www.nuget.org/packages/Epoxy.Avalonia)|Avalonia version| |Epoxy.OpenSilver|[![NuGet Epoxy.OpenSilver](https://img.shields.io/nuget/v/Epoxy.OpenSilver.svg?style=flat)](https://www.nuget.org/packages/Epoxy.OpenSilver)|OpenSilver version| -|Epoxy.Xamarin.Forms|[![NuGet Epoxy.Xamarin.Forms](https://img.shields.io/nuget/v/Epoxy.Xamarin.Forms.svg?style=flat)](https://www.nuget.org/packages/Epoxy.Xamarin.Forms)|Xamarin Forms version| |Epoxy.Maui|[![NuGet Epoxy.Maui](https://img.shields.io/nuget/v/Epoxy.Maui.svg?style=flat)](https://www.nuget.org/packages/Epoxy.Maui)|.NET MAUI version| ## NuGetパッケージ F#専用 @@ -25,12 +24,6 @@ |FSharp.Epoxy.Avalonia11|[![NuGet FSharp.Epoxy.Avalonia11](https://img.shields.io/nuget/v/FSharp.Epoxy.Avalonia11.svg?style=flat)](https://www.nuget.org/packages/FSharp.Epoxy.Avalonia11)|Avalonia version 11| |FSharp.Epoxy.Avalonia|[![NuGet FSharp.Epoxy.Avalonia](https://img.shields.io/nuget/v/FSharp.Epoxy.Avalonia.svg?style=flat)](https://www.nuget.org/packages/FSharp.Epoxy.Avalonia)|Avalonia version| -## dotnet CLIテンプレート - -|Package|main|Description| -|:--|:--|:--| -|Epoxy.Templates|[![NuGet Epoxy.Templates](https://img.shields.io/nuget/v/Epoxy.Templates.svg?style=flat)](https://www.nuget.org/packages/Epoxy.Templates)|dotnet CLI template package| - ## これは何? * Epoxyは、.NET XAML環境で使える、Model-View-ViewModel (MVVM) アーキテクチャ向けの、独立した柔軟性のあるライブラリです。 @@ -39,7 +32,6 @@ * WPF: .NET 8.0/7.0/6.0/5.0, .NET Core 3.0/3.1, .NET Framework 4.5/4.8 * Avalonia: [Avalonia](https://avaloniaui.net/) (New v11 or 0.10 series) * OpenSilver: [OpenSilver](https://opensilver.net/) (1.0.0 or higher) - * Xamarin Forms: [Xamarin Forms](https://github.com/xamarin/Xamarin.Forms) (5.0.0.1874 or higher) * .NET MAUI: 7.0 or higher * 非同期処理 (async-await) を安全に書くことが出来るように配慮しています。 * C# 8.0でサポートされた、null許容参照型を使えます。 @@ -53,85 +45,46 @@ * それぞれの機能が、相互に関係「しません」。独立しているので、自由に組み合わせることが出来ます。 * ほかのフレームワークライブラリ(例: ReactiveProperty)と組み合わせて使えるように、余計な操作や暗黙の前提を排除しています。 -### 解説動画があります (YouTube, 日本語のみ): - -[![Epoxyで C# MVVMアーキテクチャを簡単に実装する話 - 作ってみた 第一回](https://img.youtube.com/vi/LkyrgJbuiQs/0.jpg)](https://www.youtube.com/watch?v=LkyrgJbuiQs) - -[(再生出来ない場合はこちら)](https://www.youtube.com/watch?v=LkyrgJbuiQs) - - ## サンプルコード 様々な環境の実働サンプルがあります。 -このサンプルは、The Cat APIから、最新の投稿記事と画像を非同期でダウンロードしながら、 -リスト形式で表示するものです。 - -サンプルコードプロジェクトは、[playgroundディレクトリ](playground/) 、又はF#のサンプルコードは [playground.FSharpディレクトリ](playground.FSharp/) -にあります。 - -### ビルドテンプレートとビルド方法 - -注意: 将来のバージョンで、テンプレートプロジェクトは廃止されます。 -現在のバージョンにおいても、単にあなたのプロジェクトに対応するパッケージを追加するだけで、Epoxyを使用することが出来ます。 - -.NET 7 SDKのCLIテンプレートに対応しています。以下のようなコマンドで、簡単にテンプレートコードをクリーンな状態で試すことができます: - -```bash -# テンプレートパッケージをインストール(初回又はバージョンアップ時のみ) -dotnet new -i Epoxy.Templates - -# 現在のディレクトリにWPFサンプルコードを展開 -dotnet new epoxy-wpf - -# ビルド -dotnet build -``` - -テンプレートコードには、以下のコードが含まれています: - -* MVVMのモデルコードを格納する、`Core`プロジェクト。但し中身は空です。 -* "Hello Epoxy!"と表示するだけの、非常にシンプルなコードを含むプロジェクト。 -注意: テンプレートは .NET 7 を想定しているため、.NET 7 SDKをあらかじめインストールして下さい。 -他のバージョンのみの環境(例えば.NET 8/6/5 SDK)では、`TargetFramework`を修正しないと、ビルドに失敗します。 - -### 現在サポートしているテンプレート一覧 +起動後にボタンをクリックすると、The Cat APIから、最新の投稿記事と画像を非同期でダウンロードしながら、 +リスト形式で表示するものです。 -|`dotnet new`引数|言語|対象| -|:--|:--|:--| -|`epoxy-wpf`|C#, F#|WPFのテンプレートコード| -|`epoxy-avalonia11`|C#, F#|Avalonia 11のテンプレートコード (xplat相当)| -|`epoxy-avalonia`|C#, F#|旧Avaloniaのテンプレートコード| -|`epoxy-opensilver`|C#|OpenSilverのテンプレートコード| -|`epoxy-xamarin-forms`|C#|Xamarin Formsのテンプレートコード| +![EpoxyHello.Wpf](https://github.com/kekyo/Epoxy/raw/main/Images/sample.Wpf.png) -* デフォルトではC#のテンプレートコードが展開されます。F#にする場合は、`dotnet new epoxy-wpf -lang F#`のように、オプションをコマンドラインに加えます。 -* Xamarin Formsは、古い形式のMSBuildプロジェクトを使用しています。 - * ビルド・実行する場合は、`dotnet build` ではなく、Visual Studioでソリューションを開く必要があります。 -* OpenSilverのテンプレートコードは、.NET Frameworkベースのシミュレータプロジェクトが含まれています。 - * ビルド・実行する場合は、`dotnet build` ではなく、Visual Studioでソリューションを開く必要があります。 - * WebAssemblyとしてChromeやFirefoxなどでホストする場合は、別途プロジェクトが必要です。 -* develブランチパッケージを使用できます。dotnet CLI公式には説明されていませんが、`--nuget-source`オプションを使用します: `dotnet new -i Epoxy.Templates:: --nuget-source http://nuget.kekyo.online:59103/repository/nuget/index.json --force` +![EpoxyHello.Xamarin.Forms](https://github.com/kekyo/Epoxy/raw/main/Images/sample.Xamarin.Forms.png) -### Visual Studioのウィザードから選択 +サンプルコードプロジェクトは、[playgroundディレクトリ](playground/) 、又はF#のサンプルコードは [playground.FSharpディレクトリ](playground.FSharp/) +にあります。 -上記テンプレートのインストールを行っておけば、Visual Studioの新規プロジェクト生成でも選択する事が出来ます。 +フルスクラッチでEpoxyを導入したい、あるいは既存のプロジェクトにEpoxyを導入したい場合は、 +[ステップバイステップでコミットを作成した、Avalonia 11のサンプルリポジトリ](https://github.com/kekyo/Epoxy.Avalonia11.SampleProject) が役に立つかもしれません。 -![Template selection dialog](Images/vswizard_ja.png) +### 解説動画があります (YouTube, 日本語のみ): ----- +[![Epoxyで C# MVVMアーキテクチャを簡単に実装する話 - 作ってみた 第一回](https://img.youtube.com/vi/LkyrgJbuiQs/0.jpg)](https://www.youtube.com/watch?v=LkyrgJbuiQs) -### サンプルコードの解説 +[(再生出来ない場合はこちら)](https://www.youtube.com/watch?v=LkyrgJbuiQs) -起動後にボタンをクリックすると、完全に非同期でダウンロードしながら、リストに結果を追加していきます。 +### 導入方法 -![EpoxyHello.Wpf](https://github.com/kekyo/Epoxy/raw/main/Images/sample.Wpf.png) +ターゲットとなるGUIフレームワークに対応したNuGetパッケージを導入してください。 +Epoxyパッケージは沢山公開されていますが、必要なのは: -![EpoxyHello.Xamarin.Forms](https://github.com/kekyo/Epoxy/raw/main/Images/sample.Xamarin.Forms.png) +* `Epoxy.Avalonia11` +* `Epoxy.Avalonia` +* `Epoxy.WPF` +* `Epoxy.OpenSilver` +* `Epoxy.MAUI` -フルスクラッチでEpoxyを導入したい、あるいは既存のプロジェクトにEpoxyを導入したい場合は、 -[ステップバイステップでコミットを作成した、Avalonia 11のサンプルリポジトリ](https://github.com/kekyo/Epoxy.Avalonia11.SampleProject) が役に立つかもしれません。 +などのパッケージのみです。 +他に `Epoxy.Core.WPF`や`Epoxy.Build`と言ったパッケージが見つかるかもしれませんが、 +これらは上記のパッケージから依存して自動的に使用されます。 +注意: `Epoxy.Templates`には、テンプレートプロジェクト定義が含まれていましたが、1.15.0から廃止されました。 +これに伴い、Visual Studioのテンプレートウィザードも廃止されています。 ---- @@ -242,7 +195,7 @@ public sealed class MainWindowViewModel ### Modelの実装例 The Cat APIにアクセスする共通コードは、`EpoxyHello.Core` プロジェクトで実装しています。 -このプロジェクトは、WPF・Xamarin Forms・Avalonia・OpenSilverのいずれにも依存せず、完全に独立しています。 +このプロジェクトは、WPF・Avalonia・OpenSilver・MAUIのいずれにも依存せず、完全に独立しています。 このように、依存性を排除することで、マルチプラットフォーム対応の共通化を行うことが出来ますが、 小規模な開発であれば、`Model`の実装を`ViewModel`と同じプロジェクトに配置してもかまいません @@ -285,6 +238,7 @@ Modelの実装は、直接ユーザーインターフェイスを操作する事 |ViewModelインジェクタ|ViewModelに必要なPropertyChangedイベントなどを、ビルド時に自動的に実装出来る機能です。対象のクラスに属性を適用するだけで、煩雑なコードの実装を省略出来ます。| |ViewModel基底クラス|ViewModelに必要なPropertyChangedイベントなどを、オーソドックスな基底クラスとして提供します。ViewModelインジェクタが適さないシナリオで、使用することが出来ます。| |Command factory|任意の非同期デリゲートを、ICommandとして利用できるようにします。非同期処理を安全にICommandとして実装出来ます。| +|Fountain/Well|任意のXAMLコントロールのイベントを、バインディング可能にする添付プロパティです。イベントハンドリングを簡単かつ安全にバインディング出来ます。| |EventBinder|任意のXAMLコントロールのCLRイベントを、ICommandとしてバインディング可能にする添付プロパティです。Commandプロパティが提供されていない任意のイベントを、安全にバインディング出来ます。| |Anchor/Pile|任意のXAMLコントロールを、一時的かつ安全にViewModelから参照出来るようにします。Anchor/Pileを使用すると、全てのコードビハインドを排除出来るため、MVVMを使用する場合の実装の見通しが良くなります。Messengerパターンとして知られたテクニックも、Anchor/PileでViewModelに集約することが出来ます。| |ValueConverter|XAMLの値コンバーターの基底クラスを提供します。事前に型判定が行われ、型制約がある状態で実装することが出来ます。| @@ -342,8 +296,93 @@ csprojの`PropertyGroup`の`EpoxyBuildEnable`に`False`を指定して下さい ---- +### Fountain/Well + +`Fountain`/`Well` は、後述の `EventBinder` に代わる、新しいイベントハンドリング機能です。 + +バインディング出来ないCLRイベントが公開されている場合に、 +コードビハインドを一切記述しないで、ViewModel側で簡単にフック出来るようにします。 +`RoutedEvent` にも対応しているため、あらゆるイベント処理を全く同じように記述できます。 + +また、コントロールが表示から切り離されると、イベントのアンフックも自動的に行われるため、 +メモリリークを防ぐことが出来ます。 + +* `Fountain` とは、コントロールのイベントの発生源です。 +* `Well` とは、 `Fountain` で発生したイベントを受信する場所で、 `Fountain` とはデータバインディングで結合します。 +* イベントハンドラは、 `Well` に対して追加・削除します。 + +例えば、以下のように、WPFの `Window.Loaded` CLRイベントをバインディング出来ます。 +イベントをフックしたいコントロールに、 `Fountain` 添付プロパティを配置してバインディングします: + +```xml + + + + + +``` + +`ViewModel` 側は、 `Well` を配置しておきます。 +`Well` に対してイベント名を指定してハンドラを追加します: + +```csharp +// Windowからのイベントを受信するWellを定義する +public Well MainWindowWell { get; } = Well.Factory.Create(); + +// ... + +// Loadedイベントが発生した場合のハンドラをWellに追加 +this.MainWindowWell.Add("Loaded", async () => +{ + // リストに表示する情報をModelから非同期で取得 + foreach (var item in await Model.FetchInitialItemsAsync()) + { + this.Items.Add(item); + } +}); +``` + +`Well.Add()`メソッドで、イベントに対応するハンドラデリゲートを登録します。 +もちろんこのハンドラは、非同期処理対応です。 + +このメソッドには、イベント名を文字列で指定するオーバーロードと、`RoutedEvent`を指定するオーバーロードがあります。 +`RoutedEvent`を指定するオーバーロードを使えば、いわゆる「添付イベント」を受信することも可能です: + +```csharp +// ドラッグイベントが発生した場合のハンドラを追加 +// (Avaloniaの場合、イベント引数eの型は自動的に決定されます) +this.MainWindowWell.Add(DragDrop.DragEnterEvent, async e => +{ + // (ドラッグイベントの処理) +}); +``` + +Avaloniaでは、 `RoutedEvent` からイベントの引数 `EventArgs` の型が供給されるため、 +最も記述量が少なく、タイプセーフ性が維持されます。 + +* MAUIには、`RoutedEvent`が存在しないため、Epoxyにもこの機能はありません。 + +一つの `Well` に対して、異なるイベントを同時に追加出来ます。 +同じイベント名のハンドラは、同時に一つのみ追加可能です。 + +#### EventBinderからの移行 + +`EventBinder` も引き続きサポートされますが、`Fountain`/`Well`に移行すべきかどうか判断したい場合は、以下の表を参考にしてください。 + +|要素|利点|考慮事項| +|:----|:----|:----| +|XAML|記述量が圧倒的に少ない|いわゆるBehaviorとは構造が異なる| +|バインディング|`Well`をバインディング出来る|`ICommand`がバインディング出来ない| +|ViewModel|CLRイベントだけではなく、`RoutedEvent`もフック出来る。Avaloniaではハンドラ引数型が自動的に決定される|`ICommand`を使用していない| +|リフレクション|将来的にリフレクションフリーを実現する構造|現在はリフレクションを使用。`EventBinder`は常にリフレクションを使用。| + +---- + ### EventBinder +注意:より便利な `Fountain`/`Well` 機能が追加されました。新規に使用する場合はそちらをお勧めします。 + `EventBinder`は、バインディング出来ないイベントが公開されている場合に、`Command`としてバインディング可能にします。 この機能により、イベントハンドラを記述するために、やむを得ずコードビハインドを書くと言う手法を回避できます。 @@ -388,11 +427,11 @@ this.Ready = Command.Factory.Create(async _ => `Command.Factory.Create`のジェネリック引数には、イベントの第二引数(通常EventArgsを継承したクラス)を指定します。 イベントの引数が必要でない場合は、非ジェネリックメソッドを使う事も出来ます。 -補足1: WPFやXamarin Formsでは、`Behavior`や`Trigger`で同じことを実現できますが、 +補足1: WPFやMAUIでは、`Behavior`や`Trigger`で同じことを実現できますが、 追加のパッケージが必要になることと、汎用的に設計されているため、やや複雑です。 `EventBinder`を使うことで、同じ記法でシンプルに記述できる利点があります。 -補足2: UWP環境(Xamarin FormsのUWPビルドを含む)では、対象のイベントは以下のようなシグネチャである必要があります: +補足2: UWP環境(MAUIのUWPビルドを含む)では、対象のイベントは以下のようなシグネチャである必要があります: ```csharp // EventBinderでバインディング可能なイベント @@ -787,6 +826,11 @@ Apache-v2 ## History +* 1.15.0: + * 新しいイベントハンドリング機能として、 `Fountain/Well` を追加。 + * Xamarin Formsを廃止。 + * テンプレートプロジェクトを廃止。 + * まだしばらくは新規プロジェクト生成として機能すると思いますが、更新されません。 * 1.14.0: * Avalonia 11で、XAMLからEpoxyを参照する際の名前空間にURLを指定可能にしました。 `xmlns:epoxy="https://github.com/kekyo/Epoxy"` のように指定できます。 diff --git a/README.md b/README.md index 8b37af9..a7d9e4d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ |Epoxy.Avalonia11|[![NuGet Epoxy.Avalonia11](https://img.shields.io/nuget/v/Epoxy.Avalonia11.svg?style=flat)](https://www.nuget.org/packages/Epoxy.Avalonia11)|Avalonia version 11| |Epoxy.Avalonia|[![NuGet Epoxy.Avalonia](https://img.shields.io/nuget/v/Epoxy.Avalonia.svg?style=flat)](https://www.nuget.org/packages/Epoxy.Avalonia)|Avalonia version| |Epoxy.OpenSilver|[![NuGet Epoxy.OpenSilver](https://img.shields.io/nuget/v/Epoxy.OpenSilver.svg?style=flat)](https://www.nuget.org/packages/Epoxy.OpenSilver)|OpenSilver version| -|Epoxy.Xamarin.Forms|[![NuGet Epoxy.Xamarin.Forms](https://img.shields.io/nuget/v/Epoxy.Xamarin.Forms.svg?style=flat)](https://www.nuget.org/packages/Epoxy.Xamarin.Forms)|Xamarin Forms version| |Epoxy.Maui|[![NuGet Epoxy.Maui](https://img.shields.io/nuget/v/Epoxy.Maui.svg?style=flat)](https://www.nuget.org/packages/Epoxy.Maui)|.NET MAUI version| ## NuGet for F# specialized @@ -25,12 +24,6 @@ |FSharp.Epoxy.Avalonia11|[![NuGet FSharp.Epoxy.Avalonia11](https://img.shields.io/nuget/v/FSharp.Epoxy.Avalonia11.svg?style=flat)](https://www.nuget.org/packages/FSharp.Epoxy.Avalonia11)|Avalonia version 11| |FSharp.Epoxy.Avalonia|[![NuGet FSharp.Epoxy.Avalonia](https://img.shields.io/nuget/v/FSharp.Epoxy.Avalonia.svg?style=flat)](https://www.nuget.org/packages/FSharp.Epoxy.Avalonia)|Avalonia version| -## dotnet CLI template - -|Package|main|Description| -|:--|:--|:--| -|Epoxy.Templates|[![NuGet Epoxy.Templates](https://img.shields.io/nuget/v/Epoxy.Templates.svg?style=flat)](https://www.nuget.org/packages/Epoxy.Templates)|dotnet CLI template package| - ## What is this ? * Epoxy is a .NET XAML Model-View-ViewModel data-bindable infrastructure library, independent flexible API sets. @@ -39,7 +32,6 @@ * WPF: .NET 8.0/7.0/6.0/5.0, .NET Core 3.0/3.1, .NET Framework 4.5/4.8 * Avalonia: [Avalonia](https://avaloniaui.net/) (New v11 or 0.10 series) * OpenSilver: [OpenSilver](https://opensilver.net/) (1.0.0 or higher) - * Xamarin Forms: [Xamarin Forms](https://github.com/xamarin/Xamarin.Forms) (5.0.0.1874 or higher) * .NET MAUI: 7.0 or higher * Safe asynchronous operation (async-await) ready. * C# 8.0 nullable reference types ready. @@ -56,7 +48,15 @@ ## Sample code You can refer multi-platform application sample code variation in. -This sample displays a list of the latest posts and images from The Cat API, downloading them asynchronously and displays them in a list format. + +Clicking the button after launching, +while downloading the latest posted articles and images asynchronously from The Cat API, +The Cat API asynchronously downloads the latest posts and images, +and displays them in a list format. + +![EpoxyHello.Wpf](https://github.com/kekyo/Epoxy/raw/main/Images/sample.Wpf.png) + +![EpoxyHello.Xamarin.Forms](https://github.com/kekyo/Epoxy/raw/main/Images/sample.Xamarin.Forms.png) Sample code projects are located in the [playground directory](playground/) or F# sample code in the [playground FSharp directory](playground.FSharp/). @@ -64,66 +64,22 @@ or F# sample code in the [playground FSharp directory](playground.FSharp/). If you want to apply Epoxy in a full-scratch or to apply Epoxy into an existing project, [Avalonia 11 sample repository with step-by-step commits](https://github.com/kekyo/Epoxy.Avalonia11.SampleProject) may be helpful. +### Introduction -### How to get and build the template code - -Note: Template projects will be discontinued in future versions. -You can still use Epoxy in the current version by simply adding the corresponding package to your project. - -The .NET 7 SDK CLI template is supported. You can easily try the template code in a clean state with the following command: - -```bash -# Install the template package (Only at the first time or version update) -dotnet new -i Epoxy.Templates - -# Extract the WPF sample code to the current directory. -dotnet new epoxy-wpf - -# Build -dotnet build -``` - -The template code includes the following code: - -* A `Core` project that contains the MVVM model code. However the contents are empty. -* A project containing very simple code that just displays "Hello Epoxy!". - -Caution: .NET 7 SDK must be installed beforehand because the template assumes .NET 7. -For other versions only (e.g. .NET 8/6/5 SDK), the build will fail if you do not modify `TargetFramework` property. +Install the NuGet package that corresponds to your target GUI framework. +There are many Epoxy packages available, but all you need is: -### List of currently supported templates +* `Epoxy.Avalonia11` +* `Epoxy.Avalonia` +* `Epoxy.WPF` +* `Epoxy.OpenSilver` +* `Epoxy.MAUI` -|`dotnet new` parameter|Language|Target| -|:--|:--|:--| -|`epoxy-wpf`|C#, F#|Template code for WPF| -|`epoxy-avalonia11`|C#, F#|Template code for Avalonia 11 (xplat)| -|`epoxy-avalonia`|C#, F#|Template code for older Avalonia| -|`epoxy-opensilver`|C#|Template code for OpenSilver| -|`epoxy-xamarin-forms`|C#|Template code for Xamarin Forms| - -* By default, the C# sample code is extracted; to change to F#, add option into command line like: `dotnet new epoxy-wpf -lang F#`. -* Xamarin Forms is required old style MSBuild project. - * If you want to build and run, you need to open the solution in Visual Studio instead of `dotnet build`. -* OpenSilver template code is contained only .NET Framework based simulator project. - * If you want to build and run, you need to open the solution in Visual Studio instead of `dotnet build`. - * You need to add a web hosting project when need to host onto Chrome and Firefox by WebAssembly. -* You can use devel branch package, describes below: `dotnet new -i Epoxy.Templates:: --nuget-source http://nuget.kekyo.online:59103/repository/nuget/index.json --force` - -### Choose by Visual Studio wizard dialog - -When you install templates with above step, Visual Studio project creation wizard will show up Epoxy's templates in the dialog: - -![Template selection dialog](Images/vswizard_en.png) - ----- - -### Detail for sample code - -Full asynchronous fetching and updating into ListBox when you click a button. +You may find other packages like `Epoxy.Core.WPF` or `Epoxy.Build`, though, +these are automatically used as dependencies from the above packages. -![EpoxyHello.Wpf](https://github.com/kekyo/Epoxy/raw/main/Images/sample.Wpf.png) - -![EpoxyHello.Xamarin.Forms](https://github.com/kekyo/Epoxy/raw/main/Images/sample.Xamarin.Forms.png) +Note: `Epoxy.Templates` used to contain template project definitions, but is discontinued as of 1.15.0. +With this change, the Template Wizard in Visual Studio has also been discontinued too. ---- @@ -187,6 +143,7 @@ Completed separately xaml based view declarations. ### Example of ViewModel (WPF) implementation Completed separately `ViewModel` implementation. +Completely, that is, without any code-behind in the View class. ```csharp // Step 1: Create a ViewModel class. Then add the ViewModel attribute. @@ -232,7 +189,7 @@ public sealed class MainWindowViewModel ### Example of Model implementation The common code to access The Cat API is implemented in the `EpoxyHello.Core` project. -It does not depend on either WPF, Xamarin Forms, Avalonia and OpenSilver assemblies and is completely independent. +It does not depend on either WPF, Avalonia, OpenSilver and MAUI assemblies and is completely independent. By eliminating dependencies in this way, we can achieve commonality for multi-platform support. However, for small-scale development, you can place the `Model` implementation in the same project as the `ViewModel` implementation @@ -274,6 +231,7 @@ Since each function is independent, it can be used in any combination. |ViewModel Injector|This function allows you to automatically implement the PropertyChanged event and other events required for ViewModel at build time. Simply apply the attributes to the target class, and you can skip the complicated code implementation.| |ViewModel base class|The ViewModel Injector provides an orthodox base class for the ViewModel's PropertyChanged events, etc. It can be used in scenarios where the ViewModel Injector is not suitable.| |Command factory|Enables arbitrary asynchronous delegates to be used as ICommand. You can safely implement asynchronous processing as an ICommand. | +|Fountain/Well|This is an attachment property that allows binding of any XAML control event. This makes simpler event handing and allows for safe binding.| |EventBinder|An attached property that allows you to bind CLR events of any XAML control as ICommand.| |Anchor/Pile|Enables any XAML control to be temporarily and safely referenced from the ViewModel,eliminating all code binding and improving implementation visibility when using MVVM. The technique known as the Messenger pattern can also be integrated into the ViewModel with Anchor/Pile.| |ValueConverter|Provides a base class for the XAML value converter. It provides a base class for XAML value converters, and can be implemented with type constraints in place.| @@ -319,21 +277,94 @@ private ValueTask TitleChangedAsync(string value) You can also derive and implement the `ViewModel` base class as before without using the `ViewModel injector`. -`ViewModel` base class provides an implementation of the `GetValue`/`SetValue` methods. -These methods automatically notify to the XAML control by property changes event `PropertyChanging`/`PropertyChanged`. -For example, when a property is changed upon a button click in `ViewModel`, the change will be notified to the XAML control and reflected to the user interface. +`ViewModel` base class provides an implementation of the `GetValue`/`SetValue` methods. These methods automatically notify to the XAML control by property changes event `PropertyChanging`/`PropertyChanged`. For example, when a property is changed upon a button click in `ViewModel`, the change will be notified to the XAML control and reflected to the user interface. + +In addition, `GetValue` defines the default value, and `SetValue` defines an overload that can perform additional operations when the value is changed. + +If you don't use the ViewModel injector at entire in your project, you can disable it to stop parsing code automatically and speed up the build. Specify `False` for` EpoxyBuildEnable` of `PropertyGroup` of csproj. + +---- + +### Fountain/Well + +`Fountain`/`Well` is a new event handling feature that is an alternative to `EventBinder` (see below). -In addition, `GetValue` defines the default value, -and `SetValue` defines an overload that can perform additional operations when the value is changed. +A XAML control CLR event that cannot be bound, it allows for easy hooking on the ViewModel side, without writing any code-behind. Since `RoutedEvent` is also supported, you can write any event handling in exactly the same way. -If you don't use the ViewModel injector at entire in your project, -you can disable it to stop parsing code automatically and speed up the build. -Specify `False` for` EpoxyBuildEnable` of `PropertyGroup` of csproj. +Also, when a control is detached from the display, the event is automatically unhooked, this prevents memory leaks. + +* `Fountain` is the source of the control's events. +* `Well` is the place to receive events generated by `Fountain`, which is combined with `Fountain` by data binding. +* Event handlers are added and removed from `Well`. + +For example, you can bind WPF's `Window.Loaded` CLR event as follows. Place the `Fountain` attachment property on the control where you want to hook the event, and bind it: + +```xml + + + + + +``` + +On the `ViewModel` side, place a `Well`. Add a handler to the `Well`, specifying the event name: + +```csharp +// Define a Well that receives events from the Window +public Well MainWindowWell { get; } = Well.Factory.Create(); + +// ... + +// Added a handler to Well when the Loaded CLR event occurs +this.MainWindowWell.Add("Loaded", async () => +{ + // Asynchronously retrieve information to be displayed in the list from Model + foreach (var item in await Model.FetchInitialItemsAsync()) + { + this.Items.Add(item); + } +}); +``` + +In the `Well.Add()` method, register a handler delegate corresponding to the event. Of course, this handler supports asynchronous processing. + +This method has two overloads: one is to specify the CLR event name as a string, and the other is to specify `RoutedEvent`. + +The overload specifying `RoutedEvent` can also be used to receive so-called "XAML Attached events": + +```csharp +// Added handler for when a drag event occurs +// (In the case of Avalonia, the event type `e` is automatically determined) +this.MainWindowWell.Add(DragDrop.DragEnterEvent, async e => +{ + // (Handling of drag events...) +}); +``` + +In Avalonia, because the type of the event argument `EventArgs` is supplied by `RoutedEvent`, This is the least descriptive and maintains type-safety. + +* Since `RoutedEvent` does not exist in MAUI, Epoxy does not have this feature either. + +Different events can be added to a `Well` at the same time. Only one handler with the same event name may be added at a time. + +#### Migration from EventBinder + +`EventBinder` will also continue to be supported, but if you want to decide if you should migrate to `Fountain`/`Well`, please refer to the table below. + +|Insight|Advantages|Disadvantages| +|:----|:----|:----|:----| +|XAML|One of the least amount of code.|Different architecture from `Behavior` based.| +|Binding|Can bind `Well`|Cannot bind `ICommand`| +|ViewModel|Not only CLR events but also `RoutedEvents` can be hooked. Avalonia automatically determines handler argument types.|Not using `ICommand`.| +|Reflection|Structure to be reflection-free in the future.|Currently uses reflection. `EventBinder` always uses reflection.| ---- ### EventBinder +Note: A more convenient `Fountain/Well` feature has been added. New users are encouraged to use it. + `EventBinder` allows binding of unbindable events as `Command` when they are exposed. This feature avoids the practice of writing a code-behind for the sake of writing an event handler. @@ -378,12 +409,12 @@ this.Ready = Command.Factory.Create(async _ => The generic argument of `Command.Factory.Create` is the second argument of the event (usually a class that inherits from EventArgs). Non-generic methods can also be used when event arguments are not required. -TIP 1: In WPF and Xamarin Forms, you can use `Behavior` and `Trigger` to achieve the same thing. +TIP 1: In WPF and MAUI, you can use `Behavior` and `Trigger` to achieve the same thing. However, they require additional packages and are designed to be generic, so they are a bit more complex. Using `EventBinder` has the advantage of being simple and using the same notation. -TIP 2: In a UWP environment (including UWP builds of Xamarin Forms), the target event should have the following signature: +TIP 2: In a UWP environment (including UWP builds of MAUI), the target event should have the following signature: ```csharp // Events that can be bound by EventBinder @@ -789,6 +820,11 @@ Apache-v2 ## History +* 1.15.0: + * Added `Fountain/Well` as a new event handling feature. + * Deprecated Xamarin Forms. + * Deprecated Template Projects. + * Will still function as a new project generation for a while, but will not be updated. * 1.14.0: * In Avalonia 11, allow URLs to be specified in the namespace when referencing Epoxy from XAML. It can be specified as `xmlns:epoxy="https://github.com/kekyo/Epoxy"`.