From e970507404fcd5ac46fc55682eb764bbb67c2fe0 Mon Sep 17 00:00:00 2001 From: Kazuki Ota <117221407+kaota_microsoft@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:25:46 +0900 Subject: [PATCH 1/2] Proofread English docs and add Japanese docs - Improve English wording across docs/docs Markdown pages. - Add mirrored Japanese Markdown translations under docs/docs-ja. - Keep VuePress configuration and npm scripts unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/docs-ja/README.md | 126 ++++ docs/docs-ja/advanced/awaitable.md | 63 ++ docs/docs-ja/advanced/r3-migration.md | 189 ++++++ docs/docs-ja/advanced/thread.md | 91 +++ .../advanced/work-with-other-mvvm-framwork.md | 141 ++++ docs/docs-ja/features/Collections.md | 511 +++++++++++++++ docs/docs-ja/features/Commanding.md | 501 ++++++++++++++ .../Event-transfer-to-ViewModel-from-View.md | 169 +++++ docs/docs-ja/features/Extension-methods.md | 364 +++++++++++ docs/docs-ja/features/Notifiers.md | 272 ++++++++ docs/docs-ja/features/ReactiveProperty.md | 614 ++++++++++++++++++ docs/docs-ja/features/ReactivePropertySlim.md | 124 ++++ ...together-with-plane-model-layer-objects.md | 251 +++++++ docs/docs-ja/getting-started/add-snippets.md | 43 ++ docs/docs-ja/getting-started/avalonia.md | 62 ++ docs/docs-ja/getting-started/blazor.md | 126 ++++ docs/docs-ja/getting-started/uno-platform.md | 134 ++++ docs/docs-ja/getting-started/uwp.md | 92 +++ docs/docs-ja/getting-started/wpf.md | 99 +++ docs/docs-ja/getting-started/xf.md | 65 ++ docs/docs-ja/samples.md | 15 + docs/docs/README.md | 38 +- docs/docs/advanced/awaitable.md | 12 +- docs/docs/advanced/r3-migration.md | 6 +- docs/docs/advanced/thread.md | 26 +- .../advanced/work-with-other-mvvm-framwork.md | 30 +- docs/docs/features/Collections.md | 54 +- docs/docs/features/Commanding.md | 82 +-- .../Event-transfer-to-ViewModel-from-View.md | 20 +- docs/docs/features/Extension-methods.md | 35 +- docs/docs/features/Notifiers.md | 46 +- docs/docs/features/ReactiveProperty.md | 138 ++-- docs/docs/features/ReactivePropertySlim.md | 18 +- ...together-with-plane-model-layer-objects.md | 32 +- docs/docs/getting-started/add-snippets.md | 4 +- docs/docs/getting-started/avalonia.md | 22 +- docs/docs/getting-started/blazor.md | 36 +- docs/docs/getting-started/uno-platform.md | 23 +- docs/docs/getting-started/uwp.md | 12 +- docs/docs/getting-started/wpf.md | 18 +- docs/docs/getting-started/xf.md | 23 +- docs/docs/samples.md | 10 +- 42 files changed, 4390 insertions(+), 347 deletions(-) create mode 100644 docs/docs-ja/README.md create mode 100644 docs/docs-ja/advanced/awaitable.md create mode 100644 docs/docs-ja/advanced/r3-migration.md create mode 100644 docs/docs-ja/advanced/thread.md create mode 100644 docs/docs-ja/advanced/work-with-other-mvvm-framwork.md create mode 100644 docs/docs-ja/features/Collections.md create mode 100644 docs/docs-ja/features/Commanding.md create mode 100644 docs/docs-ja/features/Event-transfer-to-ViewModel-from-View.md create mode 100644 docs/docs-ja/features/Extension-methods.md create mode 100644 docs/docs-ja/features/Notifiers.md create mode 100644 docs/docs-ja/features/ReactiveProperty.md create mode 100644 docs/docs-ja/features/ReactivePropertySlim.md create mode 100644 docs/docs-ja/features/Work-together-with-plane-model-layer-objects.md create mode 100644 docs/docs-ja/getting-started/add-snippets.md create mode 100644 docs/docs-ja/getting-started/avalonia.md create mode 100644 docs/docs-ja/getting-started/blazor.md create mode 100644 docs/docs-ja/getting-started/uno-platform.md create mode 100644 docs/docs-ja/getting-started/uwp.md create mode 100644 docs/docs-ja/getting-started/wpf.md create mode 100644 docs/docs-ja/getting-started/xf.md create mode 100644 docs/docs-ja/samples.md diff --git a/docs/docs-ja/README.md b/docs/docs-ja/README.md new file mode 100644 index 00000000..9b44bf8a --- /dev/null +++ b/docs/docs-ja/README.md @@ -0,0 +1,126 @@ +# ReactiveProperty とは + +ReactiveProperty は Reactive Extensions 向けに MVVM と非同期処理を支援する機能を提供します。ターゲット フレームワークは .NET Standard 2.0 です。 + +![概要](../docs/images/rpsummary.png) + +ReactiveProperty のコンセプトは 楽しいプログラミング です。 +ReactiveProperty を使うと MVVM アプリケーションを記述できます。とても楽しいですよ! + +![UWP](../docs/images/launch-uwp-app.gif) + +次のコードは、ReactiveProperty と通常のオブジェクト プロパティの間の双方向バインディングを示しています。 + +```csharp +class Model : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler PropertyChanged; + + private string _name; + public string Name + { + get => _name; + set + { + _name = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); + } + } +} +class ViewModel +{ + private readonly Model _model = new Model(); + public ReactiveProperty Name { get; } + public ViewModel() + { + // ReactiveProperty と Model#Name プロパティの双方向同期。 + Name = _model.ToReactivePropertyAsSynchronized(x => x.Name); + } +} +``` + +ReactiveProperty は `IObservable` を通じて実装されています。そうです!LINQ を使えます。 + +```csharp +var name = new ReactiveProperty(); +name.Where(x => x.StartsWith("_")) // フィルター + .Select(x => x.ToUpper()) // 変換 + .Subscribe(x => { ... 何らかの処理 ... }); +``` + +ReactiveProperty は `IObservable` から作成されます。 + +```csharp +class ViewModel +{ + public ReactiveProperty Input { get; } + public ReactiveProperty Output { get; } + + public ViewModel() + { + Input = new ReactiveProperty(""); + Output = Input + .Delay(TimeSpan.FromSeconds(1)) // Rx メソッドを使用。 + .Select(x => x.ToUpper()) // LINQ メソッドを使用。 + .ToReactiveProperty(); // ReactiveProperty に変換 + } +} +``` + +このメソッド チェーンはとてもクールです。 + +`ICommand` と `IObservable` インターフェイスを実装する `ReactiveCommand` クラスも提供しています。`ReactiveCommand` は `IObservable` から作成できます。 +次のサンプルでは、`Input` プロパティが空でないときに実行できる `ReactiveCommand` を作成します。 + +```csharp +class ViewModel +{ + public ReactiveProperty Input { get; } + public ReactiveProperty Output { get; } + + public ReactiveCommand ResetCommand { get; } + + public ViewModel() + { + Input = new ReactiveProperty(""); + // 上のサンプルと同じ + Output = Input + .Delay(TimeSpan.FromSeconds(1)) // Rx メソッドを使用。 + .Select(x => x.ToUpper()) // LINQ メソッドを使用。 + .ToReactiveProperty(); // ReactiveProperty に変換 + + ResetCommand = Input.Select(x => !string.IsNullOrWhiteSpace(x)) // ReactiveProperty を IObservable に変換 + .ToReactiveCommand() // IObservable から ReactiveCommand を作成できます。true 値が発行されると、コマンドを実行できます。 + .WithSubscribe(() => Input.Value = ""); // ResetCommand.Subscribe(() => ...) のショートカットです。 + } +} +``` + +クールです!本当に宣言的で分かりやすいです。 + +## 始めましょう! + +ReactiveProperty は次のリンクから使い始めることができます。 + +- [Windows Presentation Foundation](getting-started/wpf.md) +- [Universal Windows Platform](getting-started/uwp.md) +- [Xamarin.Forms](getting-started/xf.md) +- [Uno Platform](getting-started/uno-platform.md) + +コア機能については、次のリンクで学べます。 + +- [ReactiveProperty](features/ReactiveProperty.md) +- [コマンド](features/Commanding.md) +- [コレクション](features/Collections.md) + + +## NuGet パッケージ + +|パッケージ ID|バージョンとダウンロード数|説明| +|----|----|----| +|ReactiveProperty|![](https://img.shields.io/nuget/v/ReactiveProperty.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.svg)|すべてのコア機能が含まれ、ターゲット プラットフォームは .NET Standard 2.0 です。ほぼすべての状況に適しています。| +|ReactiveProperty.Core|![](https://img.shields.io/nuget/v/ReactiveProperty.Core.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.Core.svg)|`ReactivePropertySlim` や `ReadOnlyReactivePropertySlim` など、最小限のクラスが含まれます。System.Reactive にも依存しません。Rx 機能が不要な場合に適しています。| +|ReactiveProperty.WPF|![](https://img.shields.io/nuget/v/ReactiveProperty.WPF.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.WPF.svg)|WPF 向けの EventToReactiveProperty と EventToReactiveCommand が含まれます。.NET Core 3.0 以降および .NET Framework 4.7.2 以降向けです。| +|ReactiveProperty.UWP|![](https://img.shields.io/nuget/v/ReactiveProperty.UWP.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.UWP.svg)|UWP 向けの EventToReactiveProperty と EventToReactiveCommand が含まれます。| +|ReactiveProperty.XamarinAndroid|![](https://img.shields.io/nuget/v/ReactiveProperty.XamarinAndroid.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.XamarinAndroid.svg)|Xamarin.Android ネイティブのイベントから IObservable インスタンスを作成するための多くの拡張メソッドが含まれます。| +|ReactiveProperty.XamariniOS|![](https://img.shields.io/nuget/v/ReactiveProperty.XamariniOS.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.XamariniOS.svg)|ReactiveProperty と ReactiveCommand を Xamarin.iOS ネイティブ コントロールにバインドするための多くの拡張メソッドが含まれます。| \ No newline at end of file diff --git a/docs/docs-ja/advanced/awaitable.md b/docs/docs-ja/advanced/awaitable.md new file mode 100644 index 00000000..931098ea --- /dev/null +++ b/docs/docs-ja/advanced/awaitable.md @@ -0,0 +1,63 @@ +# Awaitable + +`ReactiveProperty`(`ReactivePropertySlim` を含む)、`ReadOnlyReactiveProperty`(`ReadOnlyReactivePropertySlim` を含む)、`ReactiveCommand` では `await` 演算子を使用できます。 +`await` 演算子を使用すると、次の値が発行されるまでプログラムは待機します。 + +## 例: + +```csharp +// CancellationTokenSource を持つ View +public partial class SampleWindow : Window +{ + CancellationTokenSource cts; + SampleViewModel viewModel; + + public SampleWindow() + { + InitializeComponent(); + cts = new CancellationTokenSource(); + viewModel = new SampleViewModel(cts.Token); + } + + protected override void OnClosed(EventArgs e) + { + // 終了時に、すべての await をキャンセルします。 + cts.Cancel(); + cts.Dispose(); + + base.OnClosed(e); + } +} + +// CancellationToken を持つ ViewModel +public class SampleViewModel +{ + public ReactiveCommand MyCommand { get; private set; } + public ReactiveProperty ClickCount { get; private set; } + + public SampleViewModel(CancellationToken closeToken) + { + MyCommand = new ReactiveCommand(); + ClickCount = new ReactiveProperty(); + + // async/await でイベントを処理します。 + SubscribeAsync(closeToken); + } + + async void SubscribeAsync(CancellationToken closeToken) + { + using (var handler = MyCommand.GetAsyncHandler(closeToken)) + { + while (true) + { + await handler; // クリックされるまで await します。 + ClickCount.Value += 1; + } + } + } +} +``` + +複数回 await する場合は、`GetAsyncHandler` から `ObservableAsyncHandler` を取得してください。これは割り当てなしで複数回 await できます。1 回だけ await する場合は、`await command.WaitUntilValueChangedAsync(token)` を使用できます。 + +> 注: `ReactiveProperty` を直接 await することもできますが、`GetAsyncHandler`(複数回の await)または `WaitUntilValueChangedAsync`(1 回限り)を使用し、`CancellationToken` を渡すことを推奨します。 diff --git a/docs/docs-ja/advanced/r3-migration.md b/docs/docs-ja/advanced/r3-migration.md new file mode 100644 index 00000000..b86d1b0a --- /dev/null +++ b/docs/docs-ja/advanced/r3-migration.md @@ -0,0 +1,189 @@ +# ReactiveProperty.R3 で R3 に移行する + +ReactiveProperty では新規アプリケーションに [R3](https://github.com/Cysharp/R3) を推奨するようになりましたが、既存アプリの多くは R3 が標準では提供しない機能にまだ依存しています。`ReactiveProperty.R3` は、そのようなケースのための永続的なブリッジ パッケージです。使い慣れた ReactiveProperty の体験を保ちながら、R3 と相性のよい Observable を公開し、`System.Reactive` のスケジューラーではなく `TimeProvider` / `SynchronizationContext` を使用します。 + +## 使用する場面 + +プロジェクトを `Reactive.Bindings` から R3 へ移行していて、R3 が意図的に含めていない上位レベルの MVVM ヘルパーがまだ必要な場合は、`ReactiveProperty.R3` を使用します。 + +このブリッジは、移行中によく現れる次の不足を補います: + +- `BooleanNotifier`、`BusyNotifier`、`CountNotifier`、`ScheduledNotifier` などの notifier とメッセージ ブローカー +- `ReactiveTimer` +- `AsyncReactiveCommand` +- `ReadOnlyReactiveCollection` とコレクション ヘルパー +- `ValidatableReactiveProperty` と検証ヘルパー +- `ToReactivePropertyAsSynchronized` + +## パッケージをインストールする + +> **ブリッジの提供状況:** `R3` と `ObservableCollections.R3` はすでに NuGet で利用できます。 +> `ReactiveProperty.R3` と `ReactiveProperty.R3.WPF` のブリッジ パッケージは **まだ公開されていません**。 +> 公開されるまでは、このリポジトリの `Source/ReactiveProperty.R3` と +> `Source/ReactiveProperty.R3.WPF` プロジェクトに `ProjectReference` を追加して、ソースからブリッジを参照してください。 +> 以下の `dotnet add package` コマンドは、ブリッジのリリース後に想定している利用手順を示しています。 + +.NET CLI または Visual Studio NuGet Package Manager を使用して、プロジェクトにパッケージを追加します。 + +.NET CLI を使用している場合は、次を実行します: + +```powershell +dotnet add package R3 +dotnet add package ReactiveProperty.R3 +``` + +Visual Studio を使用している場合は、プロジェクトを右クリックし、Manage NuGet Packages を選択して `R3` と `ReactiveProperty.R3` を検索し、そこからインストールします。 + +プロジェクトで WPF のトリガー アクションまたはコンバーターを使用している場合は、WPF ブリッジ パッケージも追加します: + +```powershell +dotnet add package ReactiveProperty.R3.WPF +``` + +Visual Studio ユーザーは、同じ方法で NuGet Package Manager から `ReactiveProperty.R3.WPF` をインストールできます。 + +## 移行の進め方 + +1. 古い ReactiveProperty パッケージを `R3` と `ReactiveProperty.R3` に置き換えます。 +2. R3 がすでに直接提供している部分は、ネイティブの R3 API に書き換えます。 +3. ブリッジ型は、上記の本当の不足部分にだけ残します。 +4. Observable を合成するファイルでは `using R3;` を追加します。ブリッジ型は R3 Observable を返すためです。 + +### 名前空間の変更 + +このブリッジは専用の名前空間を使用するため、段階的な移行中に元の ReactiveProperty パッケージと共存できます: + +- `Reactive.Bindings` -> 直接変換では `R3` +- `Reactive.Bindings.Extensions` -> `R3` と `Reactive.Bindings.R3.Extensions` +- `Reactive.Bindings.Notifiers` -> `Reactive.Bindings.R3.Notifiers` + +次の表は、もっとも一般的な移行を示しています: + +| ReactiveProperty API | R3 / ブリッジでの置き換え | +|---|---| +| `ReactivePropertySlim` | `R3.ReactiveProperty` | +| `AsyncReactiveCommand` | `Reactive.Bindings.R3.AsyncReactiveCommand` | +| `BusyNotifier` | `Reactive.Bindings.R3.Notifiers.BusyNotifier` | +| `ReadOnlyReactiveCollection` | `Reactive.Bindings.R3.ReadOnlyReactiveCollection` | +| `ValidatableReactiveProperty` | `Reactive.Bindings.R3.ValidatableReactiveProperty` | +| `ToReactivePropertyAsSynchronized` | `Reactive.Bindings.R3.Extensions.ToReactivePropertyAsSynchronized` | + +典型的な移行後のコードは次のようになります: + +```csharp +using R3; +using Reactive.Bindings.R3.Notifiers; + +public sealed class ViewModel +{ + public ReactiveProperty Count { get; } = new(0); + public BusyNotifier IsBusy { get; } = new(); +} +``` + +## 実行可能なサンプル + +このリポジトリには、完全な移行を文脈付きで並べて確認できる WPF アプリの **移行前/移行後ペア** が含まれています: + +- **移行前** — `Samples/ReactivePropertySamples.WPF`(`Samples/ReactivePropertySamples.Shared` を含む): + `Reactive.Bindings`(ReactiveProperty)を基にした元のアプリです。 +- **移行後** — `Samples/ReactivePropertySamples.R3.WPF`(`Samples/ReactivePropertySamples.R3.Shared` を含む): + 同じアプリを R3 + `ReactiveProperty.R3` に移行したものです。 + +ViewModel は `.Shared` プロジェクトにあるため、2 つの `.Shared` フォルダーを比較すると、どのシンボルがどのように変わったかを正確に確認できます。移行後の ViewModel は `Samples/ReactivePropertySamples.R3.Tests` でもカバーされており、以下で説明する動作を固定しています。 + +## GitHub Copilot CLI で移行する + +このリポジトリには、`skills/migrating-reactiveproperty-to-r3/` に移行用の **skill** が含まれています。これは上記の手順を、繰り返し実行できるエージェント駆動のフローにします。パッケージ参照を変更し、マッピング テーブルに基づいてすべての ReactiveProperty シンボルを書き換え、推測ではなく人の判断が必要な少数のケースを報告します。以下のフローは、ViewModel、DataAnnotations 検証、notifier、`ReadOnlyReactiveCollection`、XAML の `EventToReactiveCommand` を使用するサンプル WPF アプリでエンドツーエンドに検証済みです。 + +### 1. skill をインストールする + +**推奨 — CLI が自動的に読み込み、エージェントが skill のマッピング テーブルをスムーズに読めるように、あなたのプロジェクトにインストールしてください。** skill フォルダーをプロジェクトの `.agents/skills/` ディレクトリにコピーします(これはリポジトリの `skills/README.md` が最初に推奨している方法です): + +```powershell +# このリポジトリのクローンから、あなたのアプリのリポジトリ ルートで実行します +Copy-Item -Recurse /skills/migrating-reactiveproperty-to-r3 ` + ".agents/skills/migrating-reactiveproperty-to-r3" +``` + +skill がプロジェクト ツリーの **内側** にあるため、エージェントは同梱の `references/rules.json` を構造化ファイル リーダーで直接読み取れます — 読み取りスコープの回避策は不要です。 + +**代替 — 個人用 Copilot skills ディレクトリに一度だけインストールし、すべてのプロジェクトで使用します**: + +```powershell +Copy-Item -Recurse skills/migrating-reactiveproperty-to-r3 ` + "$HOME/.copilot/skills/migrating-reactiveproperty-to-r3" +``` + +macOS/Linux での個人用のコピー先は `~/.copilot/skills/migrating-reactiveproperty-to-r3` です。複数のプロジェクトを移行するときに便利ですが、その場合 skill フォルダーはプロジェクトの *外側* に置かれるため、このガイドの最後にある読み取りスコープに関する注意に該当します。どちらの方法でも、コピー後にプロジェクト内で `copilot` を起動します。skill は自動的に読み込まれ、R3 への移行を依頼したときに有効になります。(ローカル プラグインや marketplace としてインストールする方法も使えますが、フォルダーをコピーするのがもっとも簡単です。) + +### 2. コードを変更する前に計画を依頼する + +プロジェクトで CLI を開き、まず棚卸しと計画を依頼します。スコープを絞った現実的なプロンプトが最適です — エージェントに「すべてを移行して」と一度に依頼しないでください: + +```text +この WPF アプリを ReactiveProperty から R3 に移行したいです。コードを変更する前に、 +プロジェクト内のすべての ReactiveProperty シンボルを棚卸しし、それぞれについて、 +一致するルール、対象 (r3-direct / reactiveproperty-r3 / manualReview)、R3 での置き換えを +教えてください。manual-review 項目は別に一覧化してください。 +``` + +エージェントはマッピング テーブルを読み取り、シンボルごとの計画を作成します。そこには、直接の R3 置き換え(`ReactivePropertySlim` → `R3.ReactiveProperty`、`ReactiveProperty` → `R3.BindableReactiveProperty`、`ObserveProperty` → `ObservePropertyChanged`、…)、本当の不足部分に対するブリッジ置き換え(`AsyncReactiveCommand`、`BusyNotifier`、`ReadOnlyReactiveCollection`、`ValidatableReactiveProperty`、`ToReactivePropertyAsSynchronized`)、短い manual-review リストが含まれます。 + +### 3. 段階的に移行する + +小さくレビューしやすい単位で移行し、同じセッションを `--continue` で継続します: + +```text +CounterViewModel と PeopleViewModel を今 R3 に移行してください。NuGet の R3 と、 +不足する型には ReactiveProperty.R3 ブリッジを使用してください。 +``` + +```text +次に残りの ViewModel を移行し、パッケージ参照と using ディレクティブを修正し、 +XAML の EventToReactiveCommand xmlns を R3.WPF ブリッジに差し替えてください。 +``` + +XAML トリガー アクションの場合、書き換えはほぼ xmlns の差し替えです: + +```xml + +xmlns:i="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPF" + +xmlns:i="clr-namespace:Reactive.Bindings.R3.Interactivity;assembly=ReactiveProperty.R3.WPF" +``` + +### 4. ビルド、テスト、修正する + +エージェントに既存のテストのビルドと実行を依頼し、失敗があれば具体的なプロンプトで対応します: + +```text +ソリューションをビルドしてテストを実行し、ビルド状態、テスト状態、manual-review +リストを報告してください。 +``` + +```text +ビルドが次のエラーで失敗します: <エラーを貼り付ける>。修正してください。 +``` + +ブリッジの不足部分を補う型は **R3** Observable を公開するため、購読や演算子チェーン(`Subscribe`、`Where`、`Select`、…)を使うファイルには `using R3;` が存在することを確認してください。 + +> **検証タイミング:** 単一プロパティの DataAnnotations は R3 の +> `BindableReactiveProperty.EnableValidation()` に対応しますが、R3 の検証は **遅延実行** です — バインディングまたは購読者がアタッチされるまで初期値をエラーとして扱いません。ヘッドレス テスト(またはロジック)が元の即時検証を期待している場合は、代わりに `ReactiveProperty.R3` の `ValidatableReactiveProperty` を使用してください。これは即時に検証します。 + +### 5. manual-review 項目を解決する + +skill は機械的に書き換えられないケースを推測しません。ファイル、行、メモ付きでフラグを立てます。通常は次のような短いリストになります: + +- **`ReactivePropertyMode` 引数**(たとえば `ToReactivePropertyAsSynchronized` や `ReactiveProperty` コンストラクター上のもの)。R3 に対応するものはないため、エージェントはそれを削除し、意図を再現する方法を説明します。`DistinctUntilChanged` は R3 の既定の重複除外動作に対応し、`RaiseLatestValueOnSubscribe` はプロパティが現在値を再生するかどうかに対応します。実際に依存していた動作を確認してください。 +- **カスタム `IScheduler` 引数。** R3 は時間に `TimeProvider`、ディスパッチに `SynchronizationContext` を使用するため、既定以外のスケジューラーは自動変換できません。エージェントはそれを報告し、あなたがその呼び出し箇所に適した provider を選択します。 +- **`System.Reactive` に残す必要がある `System.IObservable` 境界**(公開 API、サードパーティの Rx など)。宣言を書き換えるのではなく、R3 の `ToObservable()` / `AsSystemObservable()` でブリッジしてください。 +- **即時検証のままにする必要がある DataAnnotations 検証。** skill は単一プロパティの DataAnnotations を `EnableValidation()` に書き換えますが、これは遅延検証です — 上記の **検証タイミング** の注を参照してください。即時検証に依存していた場合(たとえばヘッドレス テストなど)は、そのプロパティを `ValidatableReactiveProperty` に切り替えてください。 + +このフロー全体は、「このアプリを R3 に移行して」「ReactiveProperty を R3 に置き換えて」「この ViewModel を ReactiveProperty から移行して」のような簡単な依頼で開始できます。 + +> **注 (個人用 `~/.copilot/skills/` インストールを使用した場合のみ):** skill は、同梱のマッピング テーブル `migrating-reactiveproperty-to-r3/references/rules.json` によって動作します。CLI の構造化ファイル リーダーはプロジェクト ディレクトリにスコープされるため、skill がプロジェクトの外側にある `~/.copilot/skills/` 配下にある場合、エージェントがそのファイルを「読めない」と報告することがあります — コンテンツ/ポリシー ブロックだと誤って表示することさえあります。これは読み取りスコープ上の癖であり、実際のブロックではありません。エージェントは引き続き(たとえばシェル コマンドで)読み取ることができ、そうでない場合も skill 自体に含まれるガイダンスにフォールバックします。**step 1 で推奨しているように、skill をプロジェクトの `.agents/skills/` にインストールすれば、この問題は完全に避けられます**。マッピング テーブルが作業ツリーの内側に置かれるためです。 + +## 大規模な移行に関するメモ + +このブリッジは一時的な shim ではなく、長期的な移行パスとして意図されています。既存の MVVM パターンやすでに依存している動作を保ちながら、段階的に R3 へ移行したいプロジェクトに適しています。 diff --git a/docs/docs-ja/advanced/thread.md b/docs/docs-ja/advanced/thread.md new file mode 100644 index 00000000..c8c2a23b --- /dev/null +++ b/docs/docs-ja/advanced/thread.md @@ -0,0 +1,91 @@ +# スレッド + +ReactiveProperty は実行スレッドを制御する機能を提供します。 +ReactiveProperty は `PropertyChanged` イベントを UI スレッドで自動的に発生させます。 + +## スケジューラーを変更する + +この動作は `IScheduler` を使って変更できます。 +インスタンスを作成するときに、`raiseEventScheduler` 引数に `IScheduler` インスタンスを設定します。 + +```csharp +var rp = Observable.Interval(TimeSpan.FromSeconds(1)) + .ToReactiveProperty(raiseEventScheduler: ImmediateScheduler.Instance); +``` + +`ReactiveCollection` と `ReadOnlyReactiveCollection` は、`ReactiveProperty` と同様に UI スレッドで `CollectionChanged` イベントを発生させます。 +この動作は、コンストラクターやファクトリー メソッドの scheduler 引数を使用して変更できます。 + +```csharp +var collection = Observable.Interval(TimeSpan.FromSeconds(1)) + .ToReactiveCollection(scheduler: ImmediateScheduler.Instance); + +var readOnlyCollection = Observable.Interval(TimeSpan.FromSeconds(1)) + .ToReadOnlyReactiveProperty(scheduler: ImmediateScheduler.Instance); +``` + +## グローバル スケジューラーを変更する + +`ReactivePropertyScheduler.SetDefault` メソッドを使用すると、ReactiveProperty の既定のスケジューラーを変更できます。 + +```csharp +ReactivePropertyScheduler.SetDefault(TaskPoolScheduler.Default); +var taskPoolRp = new ReactiveProperty(); +ReactivePropertyScheduler.SetDefault(ImmediateScheduler.Instance); +var immediateRp = new ReactiveProperty(); + +taskPoolRp.Value = "changed"; // TaskPoolScheduler スレッドでイベントを発生させます。 +immediateRp.Value = "changed"; // ImmediateScheduler スレッドでイベントを発生させます。 +``` + +## グローバル スケジューラー ファクトリーを変更する + +`ReactivePropertyScheduler.SetDefaultSchedulerFactory` メソッドを使用すると、ReactiveProperty の既定のスケジューラー インスタンスを作成するファクトリー メソッドを変更できます。 + +```csharp +using System.Reactive.Concurrency; +using System.Windows; +using System.Windows.Threading; +using Reactive.Bindings; + +namespace MultiUIThreadApp +{ + public partial class App : Application + { + private void Application_Startup(object sender, StartupEventArgs e) + { + // 各インスタンスの作成時に DispatcherScheduler インスタンスを作成するように設定します + // 対象は ReactiveProperty、ReadOnlyReactiveProperty、ReactiveCollection、ReadOnlyReactiveProperty です。 + ReactivePropertyScheduler.SetDefaultSchedulerFactory(() => + new DispatcherScheduler(Dispatcher.CurrentDispatcher)); + } + } +} +``` + +## Rx オペレーター + +もちろん、`ObserveOn` 拡張メソッドを使用できます。 + +```csharp +var rp = Observable.Interval(TimeSpan.FromSeconds(1)) + .ObserveOn(someScheduler) + .ToReactiveProperty(); +``` + +`ObserveOnUIDispatcher` 拡張メソッドも提供しています。 +これは `ObserveOn(ReactivePropertyScheduler.Default)` のショートカットです。 + +```csharp +var rp = Observable.Interval(TimeSpan.FromSeconds(1)) + .ObserveOnUIDispatcher() + .ToReactiveProperty(); +``` + +## 注意 + +既定では、ReactiveProperty は単一 UI スレッドのプラットフォーム向けに設計されています。 +つまり、UWP などの複数 UI スレッドを持つプラットフォームでは、一部の機能が動作しません。 + +UWP では、複数のウィンドウを作成すると、単一プロセス内に複数の UI スレッドが存在します。 +UWP で複数のウィンドウを作成する場合は、`ReactivePropertyScheduler.SetDefault` メソッドで `ImmediateScheduler` を設定して UI スレッドへの自動イベント ディスパッチを無効にするか、`ReactivePropertyScheduler.SetDefaultSchedulerFactory` メソッドを使用して UI スレッドごとに異なるスケジューラー インスタンスを作成してください。または、ReactiveProperty / ReadOnlyReactiveProperty クラスの代わりに `ReactivePropertySlim` / `ReadOnlyReactivePropertySlim` クラスを使用してください。 diff --git a/docs/docs-ja/advanced/work-with-other-mvvm-framwork.md b/docs/docs-ja/advanced/work-with-other-mvvm-framwork.md new file mode 100644 index 00000000..fcd88796 --- /dev/null +++ b/docs/docs-ja/advanced/work-with-other-mvvm-framwork.md @@ -0,0 +1,141 @@ +# 他の MVVM フレームワークと連携する + +ReactiveProperty は ViewModel やその他のレイヤー向けの基底クラスを提供しません。 +つまり、Prism、MVVM Light Toolkit などの他の MVVM フレームワークと一緒に ReactiveProperty を使用できます。 + +このセクションでは、ReactiveProperty を Prism と一緒に使う方法を説明します。 + +始めましょう! + +## Prism プロジェクトを作成する + +Prism は Visual Studio 向けに Prism Template Pack 拡張機能を提供しています。 +拡張機能をインストールすると、プロジェクト テンプレートからアプリを作成できます。 + +![](../../docs/advanced/images/create-project.png) + +ReactiveProperty を Prism と一緒に使用する場合、`DelegateCommand` を `ReactiveCommand` に置き換えられます。また、その他すべての ReactiveProperty 機能も Prism と一緒に使用できます。 + +この例では、PrismSampleApp という名前の Prism Blank App (WPF) と、PrismSampleModule という名前の Prism Module (WPF) を作成します。 +PrismSampleApp に PrismSampleModule への参照を追加し、次に以下のように App.xaml.cs を編集してモジュールを追加します: + +```csharp +public partial class App +{ + protected override Window CreateShell() + { + return Container.Resolve(); + } + + protected override void RegisterTypes(IContainerRegistry containerRegistry) + { + + } + + protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) + { + moduleCatalog.AddModule(); + } +} +``` + +次に、PrismSampleModuleModule.cs を編集してナビゲーション用のビューを追加し、ViewA をシェルに登録します。 + +```csharp +using PrismSampleModule.Views; +using Prism.Ioc; +using Prism.Modularity; +using Prism.Regions; + +namespace PrismSampleModule +{ + public class PrismSampleModuleModule : IModule + { + private readonly IRegionManager _regionManager; + + public PrismSampleModuleModule(IRegionManager regionManager) + { + _regionManager = regionManager; + } + + public void OnInitialized(IContainerProvider containerProvider) + { + _regionManager.RequestNavigate("ContentRegion", "ViewA"); + } + + public void RegisterTypes(IContainerRegistry containerRegistry) + { + containerRegistry.RegisterForNavigation(); + } + } +} +``` + +## ReactiveProperty を使用する + +NuGet を使用して、すべてのプロジェクトに ReactiveProperty 参照を追加します。ReactiveProperty の任意のクラスを自由に使用できます。 + +この例では、以下に示すように ViewAViewModel.cs で ReactiveProperty の機能を使用します: + +```csharp +using Prism.Mvvm; +using Reactive.Bindings; +using System; +using System.Linq; +using System.Reactive.Linq; + +namespace PrismSampleModule.ViewModels +{ + public class ViewAViewModel : BindableBase + { + public ReactiveProperty Input { get; } + public ReadOnlyReactiveProperty Output { get; } + + public ReactiveCommand ResetCommand { get; } + + public ViewAViewModel() + { + Input = new ReactiveProperty(""); + Output = Input.Delay(TimeSpan.FromSeconds(1)) + .Select(x => x.ToUpper()) + .ToReadOnlyReactiveProperty(); + + ResetCommand = Input.Select(x => !string.IsNullOrWhiteSpace(x)) + .ToReactiveCommand() + .WithSubscribe(() => Input.Value = ""); + } + } +} +``` + +次に、`ViewA.xaml` を編集します。 + +```xml + + + + + + + + +``` + +```csharp +using Reactive.Bindings; +using Windows.UI.Xaml.Controls; + +namespace App1 +{ + public sealed partial class MainPage : Page + { + public MainPageViewModel ViewModel { get; } = new MainPageViewModel(); + + public MainPage() + { + this.InitializeComponent(); + } + } + + public class MainPageViewModel + { + public ReactiveCommand SelectFileCommand { get; } + public ReadOnlyReactiveProperty FileName { get; } + + public MainPageViewModel() + { + this.SelectFileCommand = new ReactiveCommand(); + this.FileName = this.SelectFileCommand.ToReadOnlyReactiveProperty(); + } + } + +} +``` + +![EventToReactiveCommand と EventToReactiveProperty](../../docs/features/images/event-to-reactivexxx.gif) + + +`EventToReactiveProperty` は、`ReactiveConverter` で変換された値を `ReactiveProperty` に設定します。 + +```xml + + + + + + +``` + +```csharp +using Reactive.Bindings; +using Windows.UI.Xaml.Controls; + +namespace App1 +{ + public sealed partial class MainPage : Page + { + public MainPageViewModel ViewModel { get; } = new MainPageViewModel(); + + public MainPage() + { + this.InitializeComponent(); + } + } + + public class MainPageViewModel + { + public ReactiveProperty FileName { get; } = new ReactiveProperty(); + } + +} +``` + +## EventToReactiveCommand のカスタマイズ + +### CallExecuteOnScheduler プロパティ + +既定の動作では、`ReactivePropertyScheduler.Default` に設定された `IScheduler` 上でコマンドの Execute メソッドを呼び出します。この動作を無効にするには、このプロパティを false に設定します。 + +### AutoEnable プロパティ + +既定の動作では、AssociatedObject.IsEnabled とコマンドの CanExecute が自動的に同期されます。 +この動作を無効にするには、このプロパティを false に設定します。 diff --git a/docs/docs-ja/features/Extension-methods.md b/docs/docs-ja/features/Extension-methods.md new file mode 100644 index 00000000..3b15224a --- /dev/null +++ b/docs/docs-ja/features/Extension-methods.md @@ -0,0 +1,364 @@ +# 拡張メソッド + +`Reactive.Bindings.Extensions` 名前空間は便利な拡張メソッドを提供します。 + +## `AddTo` + +この名前空間の中でも、とても便利な拡張メソッドです。 +メソッド チェーンの中で `IDisposable` インスタンスを集約できます。 + +このメソッドがない場合、インスタンス作成用と `IDisposable` インスタンス追加用の 2 つの文が必要です。 + +```csharp +// 初期化 +var d = new CompositeDisposable(); + +Name = model.ObserveProperty(x => x.Name) + .ToReadOnlyReactiveProperty(); +d.Add(Name); + +Age = model.ObserveProperty(x => x.Age) + .ToReadOnlyReactiveProperty(); +d.Add(Age); + +// すべて破棄 +d.Dispose(); +``` + +`AddTo` 拡張メソッドのサンプル コードです。 + +```csharp +// 初期化 +var d = new CompositeDisposable(); + +Name = model.ObserveProperty(x => x.Name) + .ToReadOnlyReactiveProperty() + .AddTo(d); + +Age = model.ObserveProperty(x => x.Age) + .ToReadOnlyReactiveProperty() + .AddTo(d); + +// すべて破棄 +d.Dispose(); +``` + +とても便利です。 + +## `CatchIgnore` + +この拡張メソッドは例外を捕捉し、`Observable.Empty` を返します。 + +```csharp +source.CatchIgnore((Exception ex) => { ... error action ... }) + .Subscribe(); +``` + +## `CombineLatestValuesAreAllXXXX` + +2 つのメソッドを提供します。 + +- `CombineLatestValuesAreAllTrue` +- `CombineLatestValuesAreAllFalse` + +これらは単なるショートカットです。 + +```csharp +/// +/// 各シーケンスの最新値がすべて true です。 +/// +public static IObservable CombineLatestValuesAreAllTrue( + this IEnumerable> sources) => + sources.CombineLatest(xs => xs.All(x => x)); + + +/// +/// 各シーケンスの最新値がすべて false です。 +/// +public static IObservable CombineLatestValuesAreAllFalse( + this IEnumerable> sources) => + sources.CombineLatest(xs => xs.All(x => !x)); +``` + +## DisposePreviousValue + +この拡張メソッドは、`IObservable` シーケンスの前の値に対して `Dispose` メソッドを呼び出します。 + +```csharp +var source = new Subject(); +var rrp = source.Select(x => new SomeDisposableClass(x)) + .DisposePreviousValue() + .ToReadOnlyReactivePropertySlim(); + +source.OnNext("first"); // first の SomeDisposableClass が作成されます。 +source.OnNext("second"); // second の SomeDisposableClass が作成され、first は破棄されます。 +source.OnCompleted(); // second も破棄されます。 +``` + +## `CanExecuteChangedAsObservable` + +これは `ICommand` インターフェイスの拡張メソッドです。 +`Observable.FromEvent` のショートカットです。 + +```csharp +/// CanExecuteChanged を Observable シーケンスに変換します。 +public static IObservable CanExecuteChangedAsObservable(this T source) + where T : ICommand => + Observable.FromEvent( + h => (sender, e) => h(e), + h => source.CanExecuteChanged += h, + h => source.CanExecuteChanged -= h); +``` + +## `INotifyCollectionChanged` 拡張メソッド + +`CollectionChanged` イベントを `IObservable` に変換します。 + +```csharp +/// CollectionChanged:Remove を監視し、単一の項目を取り出します。 +public static IObservable ObserveRemoveChanged(this INotifyCollectionChanged source) => + source.CollectionChangedAsObservable() + .Where(e => e.Action == NotifyCollectionChangedAction.Remove) + .Select(e => (T)e.OldItems[0]); + +/// CollectionChanged:Remove を監視します。 +public static IObservable ObserveRemoveChangedItems(this INotifyCollectionChanged source) => + source.CollectionChangedAsObservable() + .Where(e => e.Action == NotifyCollectionChangedAction.Remove) + .Select(e => e.OldItems.Cast().ToArray()); + +/// CollectionChanged:Move を監視し、単一の項目を取り出します。 +public static IObservable> ObserveMoveChanged(this INotifyCollectionChanged source) => + source.CollectionChangedAsObservable() + .Where(e => e.Action == NotifyCollectionChangedAction.Move) + .Select(e => new OldNewPair((T)e.OldItems[0], (T)e.NewItems[0])); + +/// CollectionChanged:Move を監視します。 +public static IObservable> ObserveMoveChangedItems(this INotifyCollectionChanged source) => + source.CollectionChangedAsObservable() + .Where(e => e.Action == NotifyCollectionChangedAction.Move) + .Select(e => new OldNewPair(e.OldItems.Cast().ToArray(), e.NewItems.Cast().ToArray())); + +/// CollectionChanged:Replace を監視し、単一の項目を取り出します。 +public static IObservable> ObserveReplaceChanged(this INotifyCollectionChanged source) => + source.CollectionChangedAsObservable() + .Where(e => e.Action == NotifyCollectionChangedAction.Replace) + .Select(e => new OldNewPair((T)e.OldItems[0], (T)e.NewItems[0])); + +/// CollectionChanged:Replace を監視します。 +public static IObservable> ObserveReplaceChangedItems(this INotifyCollectionChanged source) => + source.CollectionChangedAsObservable() + .Where(e => e.Action == NotifyCollectionChangedAction.Replace) + .Select(e => new OldNewPair(e.OldItems.Cast().ToArray(), e.NewItems.Cast().ToArray())); + +/// CollectionChanged:Reset を監視します。 +public static IObservable ObserveResetChanged(this INotifyCollectionChanged source) => + source.CollectionChangedAsObservable() + .Where(e => e.Action == NotifyCollectionChangedAction.Reset) + .Select(_ => new Unit()); +``` + +## `ObservableCollection` 拡張メソッド + +これは `INotifyPropertyChanged` 拡張メソッドの型安全なバージョンです。 + +```csharp +/// CollectionChanged:Add を監視し、単一の項目を取り出します。 +public static IObservable ObserveAddChanged(this ObservableCollection source) => + ((INotifyCollectionChanged)source).ObserveAddChanged(); + +/// CollectionChanged:Add を監視します。 +public static IObservable ObserveAddChangedItems(this ObservableCollection source) => + ((INotifyCollectionChanged)source).ObserveAddChangedItems(); + +/// CollectionChanged:Remove を監視し、単一の項目を取り出します。 +public static IObservable ObserveRemoveChanged(this ObservableCollection source) => + ((INotifyCollectionChanged)source).ObserveRemoveChanged(); + +/// CollectionChanged:Remove を監視します。 +public static IObservable ObserveRemoveChangedItems(this ObservableCollection source) => + ((INotifyCollectionChanged)source).ObserveRemoveChangedItems(); + +/// CollectionChanged:Move を監視し、単一の項目を取り出します。 +public static IObservable> ObserveMoveChanged(this ObservableCollection source) => + ((INotifyCollectionChanged)source).ObserveMoveChanged(); + +/// CollectionChanged:Move を監視します。 +public static IObservable> ObserveMoveChangedItems(this ObservableCollection source) => + ((INotifyCollectionChanged)source).ObserveMoveChangedItems(); + +/// CollectionChanged:Replace を監視し、単一の項目を取り出します。 +public static IObservable> ObserveReplaceChanged(this ObservableCollection source) => + ((INotifyCollectionChanged)source).ObserveReplaceChanged(); + +/// CollectionChanged:Replace を監視します。 +public static IObservable> ObserveReplaceChangedItems(this ObservableCollection source) => + ((INotifyCollectionChanged)source).ObserveReplaceChangedItems(); + +/// CollectionChanged:Reset を監視します。 +public static IObservable ObserveResetChanged(this ObservableCollection source) => + ((INotifyCollectionChanged)source).ObserveResetChanged(); +``` + +## `ObservableCollection` と `IFilteredReadOnlyObservableCollection` の要素の `PropertyChanged` イベントを監視する + +`ObservableCollection` と `IFilteredReadOnlyObservableCollection` 内の要素の `PropertyChanged` イベントを監視します。 +`ObserveElementProperty` 拡張メソッドでは、特定のプロパティの `PropertyChanged` イベントを監視できます。 + +```csharp +using Reactive.Bindings.Extensions; +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; + +namespace ReactivePropertyEduApp +{ + public class Person : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + private string _name; + public string Name + { + get => _name; + set + { + _name = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); + } + } + } + class Program + { + static void Main(string[] args) + { + var c = new ObservableCollection(); + c.ObserveElementProperty(x => x.Name) + .Subscribe(x => Console.WriteLine($"Subscribe: {x.Instance}, {x.Property.Name}, {x.Value}")); + + var neuecc = new Person { Name = "neuecc" }; + var xin9le = new Person { Name = "xin9le" }; + var okazuki = new Person { Name = "okazuki" }; + + Console.WriteLine("Add items"); + c.Add(neuecc); + c.Add(xin9le); + c.Add(okazuki); + + Console.WriteLine("Change okazuki name to Kazuki Ota"); + okazuki.Name = "Kazuki Ota"; + + Console.WriteLine("Remove okazuki from collection"); + c.Remove(okazuki); + + Console.WriteLine("Change okazuki name to okazuki"); + okazuki.Name = "okazuki"; + } + } +} +``` + +``` +Add items +Subscribe: ReactivePropertyEduApp.Person, Name, neuecc +Subscribe: ReactivePropertyEduApp.Person, Name, xin9le +Subscribe: ReactivePropertyEduApp.Person, Name, okazuki +Change okazuki name to Kazuki Ota +Subscribe: ReactivePropertyEduApp.Person, Name, Kazuki Ota +Remove okazuki from collection +Change okazuki name to okazuki +``` + +対象オブジェクトのプロパティ型が `ReactiveProperty` の場合は、`ObserveElementObservableProperty` 拡張メソッドを使います。 + +```csharp +using Reactive.Bindings; +using Reactive.Bindings.Extensions; +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; + +namespace ReactivePropertyEduApp +{ + public class Person + { + public ReactiveProperty Name { get; } + + public Person(string name) + { + Name = new ReactiveProperty(name); + } + } + class Program + { + static void Main(string[] args) + { + var c = new ObservableCollection(); + c.ObserveElementObservableProperty(x => x.Name) + .Subscribe(x => Console.WriteLine($"Subscribe: {x.Instance}, {x.Property.Name}, {x.Value}")); + + var neuecc = new Person("neuecc"); + var xin9le = new Person("xin9le"); + var okazuki = new Person("okazuki"); + + Console.WriteLine("Add items"); + c.Add(neuecc); + c.Add(xin9le); + c.Add(okazuki); + + Console.WriteLine("Change okazuki name to Kazuki Ota"); + okazuki.Name.Value = "Kazuki Ota"; + + Console.WriteLine("Remove okazuki from collection"); + c.Remove(okazuki); + + Console.WriteLine("Change okazuki name to okazuki"); + okazuki.Name.Value = "okazuki"; + } + } +} +``` + +``` +Add items +Subscribe: ReactivePropertyEduApp.Person, Name, neuecc +Subscribe: ReactivePropertyEduApp.Person, Name, xin9le +Subscribe: ReactivePropertyEduApp.Person, Name, okazuki +Change okazuki name to Kazuki Ota +Subscribe: ReactivePropertyEduApp.Person, Name, Kazuki Ota +Remove okazuki from collection +Change okazuki name to okazuki +``` + +## `INotifyDataErrorInfo` 拡張メソッド + +`ErrorsChanged` イベントを `IObservable` に変換します。 +`FromEvent` メソッドのショートカットです。 + +```csharp +/// ErrorsChanged を Observable シーケンスに変換します。 +public static IObservable ErrorsChangedAsObservable(this T subject) + where T : INotifyDataErrorInfo => + Observable.FromEvent, DataErrorsChangedEventArgs>( + h => (sender, e) => h(e), + h => subject.ErrorsChanged += h, + h => subject.ErrorsChanged -= h); +``` + +`ObserveErrorInfo` 拡張メソッドは、`ErrorsChanged` イベントが発生したときにプロパティ値を発行します。 + +## `Inverse` + +`IObservable` シーケンスの bool 値を反転します。 + +```csharp +IObservable boolSequence = ...; +IObservable inversedBoolSequence = boolSequence.Inverse(); +``` + +これは次のコードと同じです。 + +```csharp +IObservable boolSequence = ...; +IObservable inversedBoolSequence = boolSequence.Select(x => !x); +``` + diff --git a/docs/docs-ja/features/Notifiers.md b/docs/docs-ja/features/Notifiers.md new file mode 100644 index 00000000..7b06f6a6 --- /dev/null +++ b/docs/docs-ja/features/Notifiers.md @@ -0,0 +1,272 @@ +# 通知クラス + +`Reactive.Bindings.Notifiers` 名前空間は、`IObservable` インターフェイスを実装する多くの便利なクラスを提供します。 + +## `BooleanNotifier` + +`BooleanNotifier` クラスは `IObservable` インターフェイスを実装しています。 +いくつかのメソッドとプロパティを持っています。 + +- `TurnOn` メソッド + - 状態を true に変更します。 +- `TurnOff` メソッド + - 状態を false に変更します。 +- `SwitchValue` メソッド + - 状態を切り替えます。 +- `Value` プロパティ + - 状態を設定します。 + +初期状態はコンストラクターで設定できます。既定値は false です。 + + +```csharp +var n = new BooleanNotifier(); +n.Subscribe(x => Debug.WriteLine(x)); + +n.TurnOn(); // true +n.TurnOff(); // false +n.Value = true; // true +n.Value = false; // false +``` + +次のように `ReactiveCommand` のソースとして使えます。 + +```csharp +var n = new BooleanNotifier(); // 既定値は false です。 + +// ReactiveCommand の CanExecute メソッドは既定で true を返すため、initialValue に `n.Value` を明示的に設定します。 +var command = n.ToReactiveCommand(initialValue: n.Value); + +// または、ToReactiveCommand を呼び出す前に Select などの演算子で何かに変換したい場合は、StartWith を使えます。 +var command2 = n.StartWith(n.Value).Select(x => Something(x)).ToReactiveCommand(); +``` + +## `CountNotifier` + +`CountNotifier` クラスは `IObservable` インターフェイスを実装しています。インクリメントとデクリメント機能を提供し、状態が変わると `CountChangedStatus` 値を発行します。 + +CountChangedStatus enum は次のように定義されています。 + +```csharp +/// CountNotifier のイベント種別です。 +public enum CountChangedStatus +{ + /// カウントがインクリメントされました。 + Increment, + /// カウントがデクリメントされました。 + Decrement, + /// カウントは 0 です。 + Empty, + /// カウントが最大値に達しました。 + Max +} +``` + +`CountNotifier` の最大値はコンストラクター引数から設定できます。 + +```csharp +var c = new CountNotifier(); // 既定の最大値は int.MaxValue です +// 状態を出力します。 +c.Subscribe(x => Debug.WriteLine(x)); +// 現在の値を出力します。 +c.Select(_ => c.Count).Subscribe(x => Debug.WriteLine(x)); +// インクリメント +var d = c.Increment(10); +// インクリメントを元に戻します +d.Dispose(); +// インクリメントとデクリメント +c.Increment(10); +c.Decrement(5); +// 現在の値を出力します。 +Debug.WriteLine(c.Count); +``` + +出力は次のとおりです。 + +``` +Increment +10 +Decrement +0 +Empty +0 +Increment +10 +Decrement +5 +5 +``` + +## `ScheduledNotifier` + +このクラスはスケジューラー上で値を発行します。既定のスケジューラーは `Scheduler.Immediate` です。コンストラクター引数を使ってスケジューラーを設定します。 + +```csharp +var n = new ScheduledNotifier(); +n.Subscribe(x => Debug.WriteLine(x)); +// 値をすぐに出力します +n.Report("Hello world"); +// 2 秒後に値を出力します。 +n.Report("After 2 seconds.", TimeSpan.FromSeconds(2)); +``` + +## `BusyNotifier` + +このクラスは `IObservable` インターフェイスを実装しています。 +処理の実行中は `true` を発行し、すべての処理が終了すると `false` を発行します。 + +`ProcessStart` メソッドは `IDisposable` インスタンスを返します。処理が終了したら、Dispose メソッドを呼び出します。 + + +```csharp +using Reactive.Bindings.Notifiers; +using System; +using System.Threading.Tasks; + +namespace ReactivePropertyEduApp +{ + class Program + { + static void Main(string[] args) + { + MainAsync(args).Wait(); + } + + static async Task MainAsync(string[] args) + { + var b = new BusyNotifier(); + b.Subscribe(x => Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")}: OnNext: {x}")); + + await Task.WhenAll( + Task.Run(async () => + { + using (b.ProcessStart()) + { + Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")}: Process1 started."); + await Task.Delay(TimeSpan.FromSeconds(1)); + Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")}: Process1 finished."); + } + }), + Task.Run(async () => + { + using (b.ProcessStart()) + { + Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")}: Process2 started."); + await Task.Delay(TimeSpan.FromSeconds(2)); + Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")}: Process2 finished."); + } + })); + } + } +} +``` + +出力は次のとおりです。 + +``` +15:07:45: OnNext: False +15:07:45: OnNext: True +15:07:45: Process1 started. +15:07:45: Process2 started. +15:07:46: Process1 finished. +15:07:47: Process2 finished. +15:07:47: OnNext: False +``` + + +## `MessageBroker` + +`MessageBroker` はインメモリの pub-sub notifier です。`EventAggregator` や `MessageBus` に似ており、Rx と async に適しています。messenger パターンに利用できます。 + +```csharp +using Reactive.Bindings.Notifiers; +using System; +using System.Reactive.Linq; +using System.Threading.Tasks; + +public class MyClass +{ + public int MyProperty { get; set; } + + public override string ToString() + { + return "MP:" + MyProperty; + } +} +class Program +{ + static void RunMessageBroker() + { + // グローバル スコープの pub-sub メッセージング + MessageBroker.Default.Subscribe(x => + { + Console.WriteLine("A:" + x); + }); + + var d = MessageBroker.Default.Subscribe(x => + { + Console.WriteLine("B:" + x); + }); + + // IObservable への変換をサポート + MessageBroker.Default.ToObservable().Subscribe(x => + { + Console.WriteLine("C:" + x); + }); + + MessageBroker.Default.Publish(new MyClass { MyProperty = 100 }); + MessageBroker.Default.Publish(new MyClass { MyProperty = 200 }); + MessageBroker.Default.Publish(new MyClass { MyProperty = 300 }); + + d.Dispose(); // 購読解除 + MessageBroker.Default.Publish(new MyClass { MyProperty = 400 }); + } + + static async Task RunAsyncMessageBroker() + { + // 非同期の message pub-sub + AsyncMessageBroker.Default.Subscribe(async x => + { + Console.WriteLine("A:" + x); + await Task.Delay(TimeSpan.FromSeconds(1)); + }); + + var d = AsyncMessageBroker.Default.Subscribe(async x => + { + Console.WriteLine("B:" + x); + await Task.Delay(TimeSpan.FromSeconds(2)); + }); + + // すべての subscriber の完了を待ちます + await AsyncMessageBroker.Default.PublishAsync(new MyClass { MyProperty = 100 }); + await AsyncMessageBroker.Default.PublishAsync(new MyClass { MyProperty = 200 }); + await AsyncMessageBroker.Default.PublishAsync(new MyClass { MyProperty = 300 }); + + d.Dispose(); // 購読解除 + await AsyncMessageBroker.Default.PublishAsync(new MyClass { MyProperty = 400 }); + } + + static void Main(string[] args) + { + Console.WriteLine("MessageBroker"); + RunMessageBroker(); + + Console.WriteLine("AsyncMessageBroker"); + RunAsyncMessageBroker().Wait(); + } +} +``` + +messenger パターンのマルチスレッド ディスパッチは、Rx で簡単に扱えます。 + +```csharp +MessageBroker.Default.ToObservable() + .ObserveOn(Dispatcher) // Rx の魔法! + .Subscribe(x => + { + Console.WriteLine(x); + }); +``` + + + diff --git a/docs/docs-ja/features/ReactiveProperty.md b/docs/docs-ja/features/ReactiveProperty.md new file mode 100644 index 00000000..caf70b0f --- /dev/null +++ b/docs/docs-ja/features/ReactiveProperty.md @@ -0,0 +1,614 @@ +# ReactiveProperty + +`ReactiveProperty` はこのライブラリの中核となるクラスです。 +次の機能を備えています。 + +- `INotifyPropertyChanged` インターフェイスを実装しています。 + - Value プロパティは `PropertyChanged` イベントを発生させます。 +- `IObservable` インターフェイスを実装しています。 + +はい、Value プロパティは XAML コントロールのプロパティへバインディングできます。 +また、このクラスは値が設定されたときに `IObserver` の `OnNext` メソッドも呼び出します。 + +サンプル コードを次に示します。 + +```csharp +using Reactive.Bindings; +using System; + +namespace ReactivePropertyEduApp +{ + class Program + { + static void Main(string[] args) + { + // 既定のコンストラクターから作成します(既定値は null)。 + var name = new ReactiveProperty(); + // イベント ハンドラーと OnNext コールバックを設定します。 + name.PropertyChanged += (_, e) => Console.WriteLine($"PropertyChanged: {e.PropertyName}"); + name.Subscribe(x => Console.WriteLine($"OnNext: {x}")); + + // Value プロパティを更新します。 + name.Value = "neuecc"; + name.Value = "xin9le"; + name.Value = "okazuki"; + } + } +} +``` + +このプログラムの出力は次のとおりです。 + +``` +OnNext: +OnNext: neuecc +PropertyChanged: Value +OnNext: xin9le +PropertyChanged: Value +OnNext: okazuki +PropertyChanged: Value +``` + +`PropertyChanged` コールバックと `OnNext` コールバックの違いは何でしょうか。 +`OnNext` コールバックは購読時に呼び出されます。`PropertyChanged` はイベント ハンドラーが追加されても呼び出されません。また、`OnNext` コールバックの引数はプロパティ値ですが、`PropertyChanged` の引数にはプロパティ値がありません。 + +`PropertyChanged` イベントはデータ バインディングのために提供されています。通常は Reactive Extensions のメソッドを使うべきです。 + +## XAML プラットフォームで使う + +`ReactiveProperty` クラスは、WPF、UWP、Xamarin.Forms などの XAML プラットフォーム向けに設計されています。 +このクラスは ViewModel レイヤーで使用できます。 + +`ReactiveProperty` を使わない場合、ViewModel クラスは次のようになります。 + +```csharp +public class MainPageViewModel : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler PropertyChanged; + + private string _name; + public string Name + { + get => _name; + set + { + _name = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); + } + } + + // 他のプロパティも同様のコードで定義します。 +} +``` + +これらのプロパティは XAML コードでバインディングします。 + +```xml + + + + + + + + + + + +``` + +`ReactiveProperty` を使うと、ViewModel のコードはとてもシンプルになります。 + +```csharp +// WPF で使用する場合は、INotifyPropertyChanged インターフェイスを実装する必要があります。 +// そうしないとメモリ リークが発生する可能性があります。 +public class MainPageViewModel +{ + public ReactiveProperty Name { get; } = new ReactiveProperty(); + + // 他のプロパティも同様のコードで定義します。 +} +``` + +XAML コードでバインディングするときは、バインディング パスに `.Value` を追加する必要があります。 +これがこのライブラリの唯一の制限です。 + +```xml + + + + + + + + + + + +``` + +> `.Value` を忘れてしまうことがあります。ReSharper ライセンスをお持ちの場合は、このプラグインを利用できます。 +> [ReactiveProperty XAML Binding Corrector](https://resharper-plugins.jetbrains.com/packages/ReSharper.RpCorrector/) +> XAML で ReactiveProperty の ".Value" バインディングが不足している箇所を強調表示します。 + +## `ReactiveProperty` インスタンスの作成方法 + +`ReactiveProperty` クラスはさまざまな方法で作成できます。 + +### コンストラクターから作成する + +最も簡単な方法は、コンストラクターを使うことです。 + +```csharp +// 既定値で作成します。 +var name = new ReactiveProperty(); +Console.WriteLine(name.Value); // -> 空の出力 + +// 初期値を指定して作成します。 +var name = new ReactiveProperty("okazuki"); +Console.WriteLine(name.Value); // -> okazuki +``` + +### `IObservable` から作成する + +`IObservable` から作成できます。 +`ToReactiveProperty` メソッドを呼び出すだけです。 + +```csharp +IObservable observableInstance = Observable.Interval(TimeSpan.FromSeconds(1)); + +// IObservable から ReactiveProperty に変換します。 +ReactiveProperty counter = observableInstance.ToReactiveProperty(); +``` + +#### `ReactiveProperty` から作成する + +`ReactiveProperty` は `IObservable` インターフェイスを実装しています。 +つまり、`ReactiveProperty` から `ReactiveProperty` を作成できます。 + +```csharp +var name = new ReactiveProperty(""); + +var formalName = name.Select(x => $"Dear {x}") + .ToReactiveProperty(); +``` + +すべての `IObservable` インスタンスは `ReactiveProperty` になれます。 + +## 検証 + +`ReactiveProperty` クラスは `INotifyDataErrorInfo` インターフェイスを実装しています。 + +### カスタム検証ロジックを設定する + +`SetValidateNotifyError` メソッドを使ってカスタム検証ロジックを設定できます。 + +```csharp +var name = new ReactiveProperty() + .SetValidateNotifyError(x => string.IsNullOrWhiteSpace(x) ? "Error message" : null); +``` + +値が正しい場合、検証ロジックは null を返す必要があります。 +値が不正な場合、検証ロジックはエラー メッセージを返す必要があります。 + +### DataAnnotations と連携する + +このクラスは DataAnnotations と連携できます。 +`SetValidateAttribute` メソッドを使って検証属性を設定できます。 + +```csharp +class ViewModel +{ + // 検証属性を設定します + [Required(ErrorMessage = "The name is required.")] + [StringLength(100, ErrorMessage = "The name length should be lower than 30.")] + public ReactiveProperty Name { get; } + + public ViewModel() + { + Name = new ReactiveProperty() + // 検証属性を ReactiveProperty に設定します。 + .SetValidateAttribute(() => Name); + } +} +``` + +WPF は `INotifyDataErrorInfo` インターフェイスと統合されています。次の例を参照してください。 + +![WPF 検証](../../docs/features/images/wpf-validation.png) + +### 検証エラーの処理 + +他のプラットフォームでは、`INotifyDataErrorInfo` インターフェイスからのエラー メッセージを表示できません。 +`ReactiveProperty` クラスには、検証エラーを処理するためのプロパティがいくつかあります。 + +最初のプロパティは `ObserveErrorChanged` です。 +この型は `IObservable` です。`IEnumerable` をエラー メッセージに変換できます。次の例を参照してください。 + +```csharp +class ViewModel +{ + // 検証属性を設定します + [Required(ErrorMessage = "The name is required.")] + [StringLength(100, ErrorMessage = "The name length should be lower than 30.")] + public ReactiveProperty Name { get; } + + public ReadOnlyReactiveProperty NameErrorMessage { get; } + + public ViewModel() + { + Name = new ReactiveProperty() + // 検証属性を ReactiveProperty に設定します。 + .SetValidateAttribute(() => Name); + + // エラー メッセージの処理 + NameErrorMessage = Name.ObserveErrorChanged + .Select(x => x?.OfType()?.FirstOrDefault()) + .ToReadOnlyReactiveProperty(); + } +} +``` + +`NameErrorMessage.Value` プロパティをテキスト コントロールにバインディングします。エラー メッセージを表示できます。 + +UWP の場合は、次の例を参照してください。 + +```csharp +public sealed partial class MainPage : Page +{ + private ViewModel ViewModel { get; } = new ViewModel(); + public MainPage() + { + this.InitializeComponent(); + } +} +``` + +```xml + + + + + + + +``` + +![検証エラー メッセージ](../../docs/features/images/validation-errormessage.png) + +ReactiveProperty v7.0.0 以降では、`ObserveErrorChanged.Select(x => x?.OfType()?.FirstOrDefault())` の代わりに `ObserveValidationErrorMessage` 拡張メソッドを使います。上のコードは次のようになります。 + +```csharp +class ViewModel +{ + // 検証属性を設定します + [Required(ErrorMessage = "The name is required.")] + [StringLength(100, ErrorMessage = "The name length should be lower than 30.")] + public ReactiveProperty Name { get; } + + public ReadOnlyReactiveProperty NameErrorMessage { get; } + + public ViewModel() + { + Name = new ReactiveProperty() + // 検証属性を ReactiveProperty に設定します。 + .SetValidateAttribute(() => Name); + + // エラー メッセージの処理 + NameErrorMessage = Name.ObserveValidationErrorMessage() + .ToReadOnlyReactiveProperty(); + } +} +``` + +次のプロパティは `ObserveHasErrors` です。`ObserveHasErrors` プロパティの型は `IObservable` です。 +一般的な入力フォームでは、`ObserveHasErrors` プロパティの値を組み合わせると非常に便利です。 + +このサンプル プログラムは、2 つの `ReactiveProperty` の `ObserveHasErrors` プロパティを組み合わせて、`ReactiveProperty` 型の `HasErrors` プロパティを作成します。 + +```csharp +public class ViewModel +{ + // 検証属性を設定します + [Required(ErrorMessage = "The name is required.")] + [StringLength(100, ErrorMessage = "The name length should be lower than 30.")] + public ReactiveProperty Name { get; } + + [Required(ErrorMessage = "The memo is required.")] + public ReactiveProperty Memo { get; } + + public ReadOnlyReactiveProperty HasErrors { get; } + + public ViewModel() + { + Name = new ReactiveProperty() + .SetValidateAttribute(() => Name); + + Memo = new ReactiveProperty() + .SetValidateAttribute(() => Memo); + + // 複数の ObserveHasErrors 値を組み合わせられます。 + HasErrors = new[] + { + Name.ObserveHasErrors, + Memo.ObserveHasErrors, + }.CombineLatest(x => x.Any(y => y)) + .ToReadOnlyReactiveProperty(); + } +} +``` + +```xml + + + + + + + + + + +``` + +![HasErrors](../../docs/features/images/haserrors-handling.png) + +![HasErrors2](../../docs/features/images/haserrors-handling2.png) + +最後のプロパティは `HasErrors` です。これは単なる `bool` プロパティです。 + +```csharp +public class ViewModel +{ + // 検証属性を設定します + [Required(ErrorMessage = "The name is required.")] + [StringLength(100, ErrorMessage = "The name length should be lower than 30.")] + public ReactiveProperty Name { get; } + + public ViewModel() + { + Name = new ReactiveProperty() + .SetValidateAttribute(() => Name); + } + + public void DoSomething() + { + if (Name.HasErrors) + { + // 値が不正な場合 + } + else + { + // 値が正しい場合 + } + } +} +``` + +### 初期検証エラーが不要な場合 + +既定の動作では、`ReactiveProperty` は検証ロジックが設定されたときにエラーを通知します。 +初期検証エラーが不要な場合は、そのエラーをスキップできます。 +`Skip` メソッドを呼び出すだけです。 + +```csharp +class ViewModel +{ + // 検証属性を設定します + [Required(ErrorMessage = "The name is required.")] + [StringLength(100, ErrorMessage = "The name length should be lower than 30.")] + public ReactiveProperty Name { get; } + + public ReadOnlyReactiveProperty NameErrorMessage { get; } + + public ViewModel() + { + Name = new ReactiveProperty() + .SetValidateAttribute(() => Name); + + // エラー メッセージの処理 + NameErrorMessage = Name.ObserveErrorChanged + .Skip(1) // 最初のエラーをスキップします。 + .Select(x => x?.OfType()?.FirstOrDefault()) + .ToReadOnlyReactiveProperty(); + } +} +``` + +または、コンストラクターで `IgnoreInitialValidationError` フラグを設定します。 + +```csharp +class ViewModel +{ + // 検証属性を設定します + [Required(ErrorMessage = "The name is required.")] + [StringLength(100, ErrorMessage = "The name length should be lower than 30.")] + public ReactiveProperty Name { get; } + + public ReadOnlyReactiveProperty NameErrorMessage { get; } + + public ViewModel() + { + // IgnoreInitialValidationError フラグを追加します + Name = new ReactiveProperty(mode: ReactivePropertyMode.Default | ReactivePropertyMode.IgnoreInitialValidationError) + .SetValidateAttribute(() => Name); + + // エラー メッセージの処理 + NameErrorMessage = Name.ObserveErrorChanged + .Select(x => x?.OfType()?.FirstOrDefault()) + .ToReadOnlyReactiveProperty(); + } +} +``` + +`Skip` と `IgnoreInitialValidationError` の違いは何でしょうか。 +`IgnoreInitialValidationError` の場合、`ReactiveProperty` クラスは初期値のエラーを通知しません。 +`Skip` の場合は、エラー イベントを無視するだけです。 + +この違いは、WPF など `INotifyDataErrorInfo` をサポートするプラットフォームで重要です。 +`Skip` のアプローチでは、赤い枠として UI に反映されます。 +`IgnoreInitialValidationError` のアプローチでは、UI に反映されません。 + +## `ReactiveProperty` のモード + +`ReactiveProperty` クラスは、`Subscribe` メソッドが呼び出されたときに `OnNext` コールバックを呼び出します。 + +```csharp +var x = new ReactiveProperty("initial value"); +x.Subscribe(x => Console.WriteLine(x)); // -> initial value +``` + +この動作は、`ReactiveProperty` インスタンスの作成時に変更できます。 +コンストラクターと `ToReactiveProperty` メソッドには `ReactivePropertyMode` 引数があります。 +次の値を設定できます。 + +- `ReactivePropertyMode.None` + - ReactiveProperty は、`Subscribe` メソッドが呼び出されても `OnNext` コールバックを呼び出しません。同じ値が設定された場合は `OnNext` コールバックを呼び出します。 +- `ReactivePropertyMode.DistinctUntilChanged` + - 同じ値が設定された場合、`OnNext` コールバックを呼び出しません。 +- `ReactivePropertyMode.RaiseLatestValueOnSubscribe` + - `Subscribe` メソッドが呼び出されたときに `OnNext` コールバックを呼び出します。 +- `ReactivePropertyMode.Default` + - 既定値です。`ReactivePropertyMode.DistinctUntilChanged | ReactivePropertyMode.RaiseLatestValueOnSubscribe` と同じです。 +- `ReactivePropertyMode.IgnoreInitialValidationError` + - 初期検証エラーを無視します。 + +この動作が不要な場合は、`ReactivePropertyMode.None` 値を設定できます。 + +```csharp +var rp = new ReactiveProperty("initial value", mode: ReactivePropertyMode.None); +rp.Subscribe(x => Console.WriteLine(x)); // -> 値を出力しません +rp.Value = "initial value"; // -> initial value +``` + +## `ForceNotify` + +値を強制的に通知したい場合は、`ForceNotify` メソッドを使えます。 +このメソッドは値を subscriber に通知し、`PropertyChanged` イベントを発生させます。 + +```csharp +var rp = new ReactiveProperty("value"); +rp.Subscribe(x => Console.WriteLine(x)); +rp.PropertyChanged += (_, e) => Console.WriteLine($"{e.PropertyName} changed"); + +rp.ForceNotify(); +``` + +出力は次のとおりです。 + +``` +value # 最初の subscribe +value # ForceNotify メソッドによる +Value changed # ForceNotify メソッドによる +``` + +## 比較ロジックを変更する + +コンストラクターとファクトリ メソッドの equalityComparer 引数を使って、比較ロジックを変更できます。 + +たとえば、大文字と小文字を区別しない comparer は次のとおりです。 + +```csharp +class IgnoreCaseComparer : EqualityComparer +{ + public override bool Equals(string x, string y) + => x?.ToLower() == y?.ToLower(); + + public override int GetHashCode(string obj) + => (obj?.ToLower()).GetHashCode(); +} + +// コンストラクター +var rp = new ReactiveProperty(equalityComparer: new IgnoreCaseComparer()); +rp.Value = "Hello world"; // null から "Hello world" に変更 +rp.Value = "HELLO WORLD"; // 変更しません +rp.Value = "Hello japan"; // "Hello world" から "Hello japan" に変更 + +// ファクトリ メソッド +var source = new Subject(); +var rp = source.ToReactiveProperty(equalityComparer: new IgnoreCaseComparer()); +source.OnNext("Hello world"); // null から "Hello world" に変更 +source.OnNext("HELLO WORLD"); // 変更しません +source.OnNext("Hello japan"); // "Hello world" から "Hello japan" に変更 +``` + + +## `ReadOnlyReactiveProperty` クラス + +`Value` プロパティを一切設定しない場合は、`ReadOnlyReactiveProperty` クラスを使えます。 +このクラスではプロパティを設定できず、それ以外の動作は ReactiveProperty クラスと同じです。 +`ReadOnlyReactiveProperty` クラスは `ToReadOnlyReactiveProperty` 拡張メソッドから作成されます。 + +次の例を参照してください。 + +```csharp +public class ViewModel +{ + public ReactiveProperty Input { get; } + + // Output は値を設定しません。 + public ReadOnlyReactiveProperty Output { get; } + + public ViewModel() + { + Input = new ReactiveProperty(""); + Output = Input + .Delay(TimeSpan.FromSeconds(1)) + .Select(x => x.ToUpper()) + .ToReadOnlyReactiveProperty(); // ReadOnlyReactiveProperty に変換します + } +} +``` + +## `Unsubscribe` + +`ReactiveProperty` クラスは `IDisposable` インターフェイスを実装しています。 +`Dispose` メソッドが呼び出されると、`ReactiveProperty` クラスはすべての購読を解放します。 +別のインスタンスのイベントを購読する場合は、ViewModel のライフサイクルの最後に `Dispose` メソッドを呼び出してください。 + +```csharp +public class ViewModel : IDisposable +{ + public ReadOnlyReactiveProperty Time { get; } + + public ViewModel() + { + Time = Observable.Interval(TimeSpan.FromSeconds(1)) + .Select(_ => DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")) + .ToReadOnlyReactiveProperty(); + } + + public void Dispose() + { + // 購読解除 + Time.Dispose(); + } +} +``` diff --git a/docs/docs-ja/features/ReactivePropertySlim.md b/docs/docs-ja/features/ReactivePropertySlim.md new file mode 100644 index 00000000..f1ffdb8d --- /dev/null +++ b/docs/docs-ja/features/ReactivePropertySlim.md @@ -0,0 +1,124 @@ +# ReactivePropertySlim + +`ReactivePropertySlim` は `ReactiveProperty` の軽量版です。 +`ReactivePropertySlim` は `ReactiveProperty` より 5 倍高速です。 + +`ReactivePropertySlim` は次の機能を提供します。 + +- `INotifyPropertyChanged` インターフェイスを実装しています。 +- `IObservable` インターフェイスを実装しています。 +- `Value` プロパティを提供します。 +- `ForceNotify` メソッドを提供します。 + +`ReactivePropertySlim` は高性能です。 +次の表は、`ReactiveProperty` と `ReactivePropertySlim` のベンチマーク結果を示しています。 +ReactivePropertySlim は、インスタンス作成で 16 倍、主要なユースケースで 36 倍高速です。 + +``` +| メソッド | 平均 | エラー | 標準偏差 | +|----------------------------------- |-------------:|----------:|----------:| +| CreateReactivePropertyInstance | 87.146 ns | 0.8331 ns | 0.7385 ns | +| CreateReactivePropertySlimInstance | 5.460 ns | 0.0537 ns | 0.0502 ns | +| BasicForReactiveProperty | 2,470.957 ns | 9.1934 ns | 8.1497 ns | +| BasicForReactivePropertySlim | 68.773 ns | 1.3841 ns | 1.8478 ns | +``` + +このクラスは `ReactiveProperty` のように使用できます。 + +```csharp +var rp = new ReactivePropertySlim("neuecc"); +rp.Select(x => $"{x}-san").Subscribe(x => Console.WriteLine(x)); +rp.Value = "xin9le"; +rp.Value = "okazuki"; +``` + +出力は次のとおりです。 + +``` +neuecc-san +xin9le-san +okazuki-san +``` + +`ReactiveProperty` との違いの 1 つは、`ReactivePropertySlim` は `IObservable` から作成できないことです。 + +```csharp +// 有効なコードではありません。 +var rp = Observable.Interval(TimeSpan.FromSeconds(1)).ToReactivePropertySlim(); +``` + +`IObservable` から Slim クラスのインスタンスを作成したい場合は、`ToReadOnlyReactivePropertySlim` 拡張メソッドを使います。 + +```csharp +var rp = Observable.Interval(TimeSpan.FromSeconds(1)).ToReadOnlyReactivePropertySlim(); +``` + +## UI スレッドへのディスパッチ + +`ReactivePropertySlim` クラスは UI スレッドへ自動的にディスパッチしません。 +必要な場合は、`ReactiveProperty` を使うか、明示的に UI スレッドへディスパッチしてください。 + +```csharp +var rp = Observable.Interval(TimeSpan.FromSeconds(1)) + .ObserveOnUIDispatcher() // UI スレッドへディスパッチします + .ToReadOnlyReactivePropertySlim(); +``` + +## 検証 + +`ValidatableReactiveProperty` は、検証機能を備えた `IReactiveProperty` の軽量実装です。高性能を維持しながら検証機能を提供するように設計されています。 + +#### 例 + +簡単な検証ロジックで `ValidatableReactiveProperty` を使う例を次に示します。 + +```csharp +var validatableProperty = new ValidatableReactiveProperty( + initialValue: "", + validate: value => string.IsNullOrEmpty(value) ? "Value cannot be empty" : null +); + +validatableProperty.Value = "valid"; // 検証エラーなし +validatableProperty.Value = ""; // 検証エラー: "Value cannot be empty" +``` + +より複雑な検証シナリオでは、`ValidatableReactiveProperty` を `DataAnnotations` と一緒に使うこともできます。 + +```csharp +public class Person +{ + [Required(ErrorMessage = "Name is required")] + public string Name { get; set; } +} + +public class PersonViewModel : IDisposable +{ + private Person _person = new Person(); + + public ValidatableReactiveProperty Name { get; } + + public PersonViewModel() + { + Name = _person.ToReactivePropertySlimAsSynchronized(x => x.Name) + .ToValidatableReactiveProperty(() => Name, disposeSource: true); + } + + public void Dispose() + { + Name.Dispose(); + } +} +``` + +この例では、`Person` クラスの `Name` プロパティを `DataAnnotations` で検証しています。`PersonViewModel` クラスは `Name` プロパティを `ValidatableReactiveProperty` インスタンスと同期し、検証ルールが適用されるようにします。 + +#### パフォーマンス + +`ValidatableReactiveProperty` は、従来の検証付き `ReactiveProperty` に比べて大幅な性能向上を提供します。次のベンチマーク結果は、その性能上の利点を示しています。 + +| メソッド | 平均 | エラー | 標準偏差 | +|---------------------------------------------- |-------------:|------------:|------------:| +| ReactivePropertyValidation | 4,954.138 ns | 93.2171 ns | 107.3490 ns | +| ValidatableReactivePropertyValidation | 704.852 ns | 12.8322 ns | 10.7155 ns | + +`ValidatableReactiveProperty` を使うことで、アプリケーション内の堅牢な検証ロジックを維持しながら高性能を実現できます。 diff --git a/docs/docs-ja/features/Work-together-with-plane-model-layer-objects.md b/docs/docs-ja/features/Work-together-with-plane-model-layer-objects.md new file mode 100644 index 00000000..b35c2ad7 --- /dev/null +++ b/docs/docs-ja/features/Work-together-with-plane-model-layer-objects.md @@ -0,0 +1,251 @@ +# POCO と連携する + +このライブラリのクラスは POCO クラスと連携できます。 + +## `INotifyPropertyChanged` を実装するクラスと接続する + +ReactiveProperty は、POCO クラスのインスタンスと同期するための多くの機能を提供します。 + +### 一方向同期 + +`INotifyPropertyChanged` インターフェイスの `ObserveProperty` 拡張メソッドは、`INotifyPropertyChanged` を `IObservable` に変換します。 +`IObservable` は `ReactiveProperty` に変換できます。つまり、`INotifyPropertyChanged` から `ReactiveProperty` への一方向同期を実現できます。 + +例を示します。 + +```csharp +public class BindableBase : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler PropertyChanged; + + protected void RaisePropertyChanged([CallerMemberName]string propertyName = null) => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + protected void SetProperty(ref T field, T value, [CallerMemberName]string propertyName = null) + { + if (Comparer.Default.Compare(field, value) == 0) + { + return; + } + + field = value; + RaisePropertyChanged(propertyName); + } +} + +public class Person : BindableBase +{ + private string _name; + public string Name + { + get { return _name; } + set { SetProperty(ref _name, value); } + } + + private int _age; + public int Age + { + get { return _age; } + set { SetProperty(ref _age, value); } + } +} +``` + +一方向同期は次のように記述します。 + +```csharp +// using Reactive.Bindings.Extensions; +public class ViewModel +{ + private Person Person { get; } = new Person(); + + public ReadOnlyReactiveProperty Name { get; } + + public ReactiveCommand UpdatePersonCommand { get; } + + public ViewModel() + { + Name = Person + // Name の PropertyChanged イベントを IObservable に変換します + .ObserveProperty(x => x.Name) + // ReadOnlyReactiveProperty に変換します + .ToReadOnlyReactiveProperty(); + + UpdatePersonCommand = new ReactiveCommand() + .WithSubscribe(() => + { + // name プロパティを更新します。 + Person.Name = "Tanaka"; + }); + } +} +``` + +### 双方向同期 + +`ToReactivePropertyAsSynchronized` 拡張メソッドは双方向同期を提供します。 + +```csharp +// using Reactive.Bindings.Extensions; +public class ViewModel +{ + public Person Person { get; } = new Person(); + + public ReactiveProperty Name { get; } + + public ViewModel() + { + Name = Person.ToReactivePropertyAsSynchronized(x => x.Name); + } +} +``` + +UWP の例を次に示します。 + +MainPage.xaml.cs + +```csharp +public sealed partial class MainPage : Page +{ + private ViewModel ViewModel { get; } = new ViewModel(); + public MainPage() + { + this.InitializeComponent(); + } +} +``` + +MainPage.xaml + +```xml + + + + + + + + +``` + +![双方向同期](../../docs/features/images/work-together-with-poco-two-way-synchronization.gif) + +`ToReactivePropertyAsSynchronized` 拡張メソッドには、変換ロジックと逆変換ロジックを追加できます。 + +```csharp +public class ViewModel +{ + public Person Person { get; } = new Person(); + + public ReactiveProperty Name { get; } + + public ViewModel() + { + Name = Person.ToReactivePropertyAsSynchronized(x => x.Name, + convert: x => string.IsNullOrWhiteSpace(x) ? "" : $"{x}-san", + convertBack: x => Regex.Replace(x, "-san$", "")); + } +} +``` + +![変換と逆変換](../../docs/features/images/work-together-with-poco-two-way-synchronization-and-convert.gif) + +`ignoreValidationErrorValue` 引数を true に設定すると、検証エラーが発生した場合に同期を停止します。 + +```csharp +public class ViewModel +{ + public Person Person { get; } = new Person(); + + [StringLength(10)] + public ReactiveProperty Name { get; } + + public ViewModel() + { + Name = Person.ToReactivePropertyAsSynchronized(x => x.Name, + convert: x => string.IsNullOrWhiteSpace(x) ? "" : $"{x}-san", + convertBack: x => Regex.Replace(x, "-san$", ""), + ignoreValidationErrorValue: true) // この動作を有効にします + .SetValidateAttribute(() => Name); // 検証ロジックを設定します + } +} +``` + +![検証エラー値の無視](../../docs/features/images/work-together-with-poco-two-way-synchronization-and-ignoreValidationError.gif) + +次のように LINQ を使って値を変換することもできます。 + +```csharp +public class ViewModel +{ + public Person Person { get; } = new Person(); + + public ReactiveProperty Name { get; } + + public ViewModel() + { + Name = Person.ToReactivePropertyAsSynchronized(x => x.Name, + // ox は IObservable です。string は Name プロパティの型です。 + convert: ox => Observable.Merge( + ox.Where(x => string.IsNullOrEmpty(x)).Select(_ => ""), + ox.Where(x => !string.IsNullOrEmpty(x)).Select(x => $"{x}-san") + ), + // ox は IObservable です。string は変換ロジックの結果型です。 + convertBack: ox => ox + .Where(x => x.Length <= 10) // このようにすべての LINQ メソッドを使用できます。 + .Select(x => x.Replace("-san", ""))); + } +} +``` + +`ReactivePropertySlim` を使いたい場合は、`ToReactivePropertySlimAsSynchronized` 拡張メソッドを使えます。 +これは `ToReactivePropertyAsSynchronized` に似ています。`ignoreValidationErrorValue` 引数と `scheduler` 引数は利用できませんが、それ以外は同じです。 + +### ソースへの一方向同期 + +`FromObject` メソッドは、POCO から `ReactiveProperty` インスタンスを作成します。 +このメソッドは、`ReactiveProperty` インスタンスが作成されたときに POCO から `Value` プロパティを設定します。`Value` プロパティが更新されると、ソース値を更新します。 + +```csharp +using Reactive.Bindings; +using System; + +namespace ReactivePropertyEduApp +{ + class Sample + { + public string Property1 { get; set; } + } + class Program + { + static void Main(string[] args) + { + var sample = new Sample { Property1 = "xxx" }; + + var rp = ReactiveProperty.FromObject(sample, x => x.Property1); + Console.WriteLine(rp.Value); // -> xxx + sample.Property1 = "updated"; + Console.WriteLine(rp.Value); // -> xxx + } + } +} +``` + +### ネストされたプロパティ パス + +`ObserveProperty`、`ToReactivePropertyAsSynchronized`、`ToReactivePropertySlimAsSynchronized`、`FromObject` は、`x => x.Child.Name` のようなネストされたプロパティ パスをサポートしています。 +パス内のいずれかのプロパティの値が null の場合、ソース プロパティから ReactiveProperty へ同期するときは ReactiveProperty が Value プロパティに `default(T)` を設定し、ReactiveProperty からソース プロパティへ同期するときはソース プロパティへの同期を停止します。 + +値が null 以外の値に更新された後、ReactiveProperty は同期を再開します。 diff --git a/docs/docs-ja/getting-started/add-snippets.md b/docs/docs-ja/getting-started/add-snippets.md new file mode 100644 index 00000000..14e81f4e --- /dev/null +++ b/docs/docs-ja/getting-started/add-snippets.md @@ -0,0 +1,43 @@ +# コード スニペットの追加 + +スニペット ファイルは [こちら](https://github.com/runceel/ReactiveProperty/tree/master/Snippet) で提供しています。 +使用する場合は、スニペットを手動でインストールしてください。 + +次のドキュメントに、Visual Studio にスニペットを追加する手順が記載されています。 + +- [Visual Studio にコード スニペットを追加する](https://docs.microsoft.com/en-us/visualstudio/ide/walkthrough-creating-a-code-snippet?view=vs-2019#add-a-code-snippet-to-visual-studio) + +## 提供されているスニペット + +- `rprop` + ```csharp + public ReactiveProperty PropertyName { get; } + ``` +- `rrprop` + ```csharp + public ReadOnlyReactiveProperty PropertyName { get; } + ``` +- `rcom` + ```csharp + public ReactiveCommand CommandName { get; } + ``` +- `rcomg` + ```csharp + public ReactiveCommand CommandName { get; } + ``` +- `arcom` + ```csharp + public AsyncReactiveCommand CommandName { get; } + ``` +- `arcomg` + ```csharp + public AsyncReactiveCommand CommandName { get; } + ``` +- `rcoll` + ```csharp + public ReactiveCollection CollectionName { get; } + ``` +- `rrcoll` + ```csharp + public ReadOnlyReactiveCollection CollectionName { get; } + ``` diff --git a/docs/docs-ja/getting-started/avalonia.md b/docs/docs-ja/getting-started/avalonia.md new file mode 100644 index 00000000..6281f8d7 --- /dev/null +++ b/docs/docs-ja/getting-started/avalonia.md @@ -0,0 +1,62 @@ +# Avalonia UI をはじめる + +Avalonia はクロスプラットフォームの .NET UI フレームワークです! + +次を参照してください: + +[Avalonia UI フレームワーク](http://avaloniaui.net/) + +## プロジェクトの作成 +- Avalonia .NET Core Application プロジェクトを作成します。(もちろん、Avalonia アプリケーション プロジェクトでも .NET Core プロジェクトと同じ ReactiveProperty を使用できます。) +- NuGet から ReactiveProperty をインストールします。 + +## コードの編集 +- MainWindowViewModel.cs ファイルを作成します。 +- 次のようにファイルを編集します。 + +MainWindowViewModel.cs +```csharp +using Reactive.Bindings; +using System; +using System.Reactive.Linq; + +namespace AvaloniaApp +{ + public class MainWindowViewModel + { + public ReactiveProperty Input { get; } + public ReadOnlyReactiveProperty Output { get; } + public MainWindowViewModel() + { + Input = new ReactiveProperty(""); + Output = Input + .Delay(TimeSpan.FromSeconds(1)) + .Select(x => x.ToUpper()) + .ToReadOnlyReactiveProperty(); + } + } +} +``` + +MainWindow.xaml +```xml + + + + + + + + + +``` + +## アプリケーションの起動 + +アプリを起動すると、下のウィンドウが表示されます。 +入力してから 1 秒後に、出力値が大文字で表示されます。 + +![アプリの起動](../../docs/getting-started/images/launch-avalonia-app.gif) diff --git a/docs/docs-ja/getting-started/blazor.md b/docs/docs-ja/getting-started/blazor.md new file mode 100644 index 00000000..01120100 --- /dev/null +++ b/docs/docs-ja/getting-started/blazor.md @@ -0,0 +1,126 @@ +# Blazor をはじめる + +Blazor は C# を使用したシングルページ アプリケーション用の開発フレームワークです。 + +次を参照してください: + +[ASP.NET Core Blazor](https://docs.microsoft.com/en-us/aspnet/core/blazor/) + +ReactiveProperty は Blazor Server と Blazor WebAssembly の両方で動作しますが、Blazor WebAssembly は Reactive Extensions のすべての操作をサポートしているわけではありません。たとえば、`Delay` 拡張メソッドは Blazor WASM では動作しません。 +Blazor WASM で ReactiveProperty を使用する場合は、サポートされていない機能を使用しないように注意してください。 + +## プロジェクトの作成 + +- Blazor Server または WebAssembly プロジェクトを作成します。 +- NuGet から ReactiveProperty.Blazor パッケージをインストールします。 + +## コードの編集 + +- `IndexViewModel` という名前のクラスを作成します。 +- 次のようにクラスを編集します: + +```csharp +using Reactive.Bindings; +using Reactive.Bindings.Extensions; +using System.Reactive.Disposables; +using System.Reactive.Linq; + +namespace BlazorApp1; + +public class IndexViewModel : IDisposable +{ + private CompositeDisposable _disposable = new(); + + public ReactivePropertySlim Input { get; } + public ReadOnlyReactivePropertySlim Output { get; } + + public IndexViewModel() + { + Input = new ReactivePropertySlim("") + .AddTo(_disposable); + Output = Input + .Delay(TimeSpan.FromSeconds(2)) // 重要! Blazor WASM では Delay メソッドは動作しません。WASM で作業している場合は、この行を削除してください。 + .Select(x => x.ToUpperInvariant()) + .ToReadOnlyReactivePropertySlim("") + .AddTo(_disposable); + } + + public void Dispose() => _disposable.Dispose(); +} +``` + +- Index.razor を次のように編集します: + +```csharp +@page "/" +@using System.Reactive.Disposables +@using Reactive.Bindings.Extensions +@implements IDisposable + +Index + +

Hello, world!

+ + +
+@_viewModel.Output.Value +
+ +@code { + private readonly CompositeDisposable _disposable = new(); + private IndexViewModel _viewModel = default!; + + protected override void OnInitialized() + { + _viewModel = new IndexViewModel() + .AddTo(_disposable); + + // Output プロパティの変更を監視し、UI スレッドで StateHasChanged を呼び出します。 + _viewModel.Output + .Subscribe(x => InvokeAsync(StateHasChanged)) + .AddTo(_disposable); + } + + public void Dispose() => _disposable.Dispose(); +} +``` + +- アプリを起動します。 + +次の結果を確認できます: + +![アプリの起動](../../docs/getting-started/images/blazor-helloworld.png) + +## Blazor に関するその他のトピック + +### 依存関係の挿入 + +ViewModel をページに注入したい場合は、Program.cs で DI コンテナーに次のように登録します: + +```csharp +builder.Services.AddTransient(); +``` + +その後、`@inject IndexViewModel _viewModel` を使用して ViewModel をページに注入します。 + + +### 検証機能との統合 + +Blazor の EditForm コンポーネントで ReactiveProperty の検証機能を使用したい場合は、`Reactive.Bindings.Components.ReactivePropertiesValidator` コンポーネントを使用できます。 + +`ReactivePropertiesValidator` は、以下に示すように `DataAnnotationsValidator` コンポーネントと同じ方法で使用できます: + +```csharp + + @* Reactive.Bindings.Components の @using が必要です *@ + + + +
+ + + +
+``` + +詳細は Samples/Blazor フォルダーの Blazor サンプル アプリを参照してください。これを使用しているページは、BlazorSample.Shared プロジェクトの Pages/Index.razor です。 diff --git a/docs/docs-ja/getting-started/uno-platform.md b/docs/docs-ja/getting-started/uno-platform.md new file mode 100644 index 00000000..6fd192ff --- /dev/null +++ b/docs/docs-ja/getting-started/uno-platform.md @@ -0,0 +1,134 @@ +# Uno Platform をはじめる + +Uno Platform はクロスプラットフォーム アプリ用の開発プラットフォームです。 +Uno は UWP アプリ プロジェクトを Android、iOS、WebAssembly、Linux、macOS アプリとしてビルドします。 + +このはじめにガイドを開始する前に、Visual Studio 用の Uno Platform 拡張機能をインストールしてください。 + +## プロジェクトの作成 +- Cross-Platform App (Uno Platform) プロジェクトを作成します。 +- NuGet からすべてのプロジェクトに ReactiveProperty パッケージをインストールします。 +- YourProjectName.Wasm プロジェクトに Reactive.Wasm パッケージをインストールします。 + +## コードの編集 +- Reactive Extensions をサポートするために、Wasm プロジェクトの Program.cs を編集します。 + +```csharp +using System; +using Windows.UI.Xaml; +using System.Reactive.PlatformServices; // 追加 + +namespace GettingStartedUno.Wasm +{ + public class Program + { + private static App _app; + + static int Main(string[] args) + { +#pragma warning disable CS0618 // 型またはメンバーは廃止されています + PlatformEnlightenmentProvider.Current.EnableWasm(); // 追加 +#pragma warning restore CS0618 // 型またはメンバーは廃止されています + Windows.UI.Xaml.Application.Start(_ => _app = new App()); + + return 0; + } + } +} +``` + +- Shared プロジェクトに MainPageViewModel.cs ファイルを作成します。 +- 次のようにファイルを編集します。 + +MainPageViewModel.cs +```csharp +using Reactive.Bindings; +using System; +using System.Linq; +using System.Reactive.Linq; + +namespace GettingStartedUno +{ + public class MainPageViewModel + { + public ReactiveProperty Input { get; } + public ReadOnlyReactiveProperty Output { get; } + + public MainPageViewModel() + { + Input = new ReactiveProperty(""); + Output = Input + .Delay(TimeSpan.FromSeconds(1)) + .Select(x => x.ToUpper()) + .ToReadOnlyReactiveProperty(); + } + } +} +``` + +MainPage.xaml.cs +```csharp +using Windows.UI.Xaml.Controls; + +// Blank Page 項目テンプレートについては、https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 を参照してください。 + +namespace GettingStartedUno +{ + /// + /// 単体で使用することも、Frame 内で移動先として使用することもできる空のページです。 + /// + public sealed partial class MainPage : Page + { + private MainPageViewModel ViewModel { get; } = new MainPageViewModel(); + public MainPage() + { + this.InitializeComponent(); + } + } +} +``` + +MainPage.xaml +```xml + + + + + + + + + +``` + +## アプリケーションの起動 + +アプリを起動すると、各プラットフォームで以下のアプリを確認できます。 +入力してから 1 秒後に、出力値が大文字で表示されます。 + +### WebAssembly + +![アプリの起動](../../docs/getting-started/images/wasm-getting-started.gif) + +### UWP + +![アプリの起動](../../docs/getting-started/images/unouwp-getting-started.gif) + +### Android + +![アプリの起動](../../docs/getting-started/images/unoandroid-getting-started.gif) + +### iOS + +未定 diff --git a/docs/docs-ja/getting-started/uwp.md b/docs/docs-ja/getting-started/uwp.md new file mode 100644 index 00000000..3bc8774a --- /dev/null +++ b/docs/docs-ja/getting-started/uwp.md @@ -0,0 +1,92 @@ +# UWP をはじめる + +## プロジェクトの作成 +- Blank App (Universal Windows) プロジェクトを作成します。 + - `Minimum version` 項目を Windows 10 Fall Creators Update (10.0; Build 16299) に設定します。 + + ![ターゲット バージョン](../../docs/getting-started/images/uwp-target-version.png) + +- NuGet から ReactiveProperty パッケージをインストールします。 + +## コードの編集 +- MainPageViewModel.cs ファイルを作成します。 +- 次のようにファイルを編集します。 + +MainPageViewModel.cs +```csharp +using Reactive.Bindings; +using System; +using System.Linq; +using System.Reactive.Linq; + +namespace GettingStartedUWP +{ + public class MainPageViewModel + { + public ReactiveProperty Input { get; } + public ReadOnlyReactiveProperty Output { get; } + + public MainPageViewModel() + { + Input = new ReactiveProperty(""); + Output = Input + .Delay(TimeSpan.FromSeconds(1)) + .Select(x => x.ToUpper()) + .ToReadOnlyReactiveProperty(); + } + } +} +``` + +MainPage.xaml.cs +```csharp +using Windows.UI.Xaml.Controls; + +// Blank Page 項目テンプレートについては、https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 を参照してください。 + +namespace GettingStartedUWP +{ + /// + /// 単体で使用することも、Frame 内で移動先として使用することもできる空のページです。 + /// + public sealed partial class MainPage : Page + { + private MainPageViewModel ViewModel { get; } = new MainPageViewModel(); + public MainPage() + { + this.InitializeComponent(); + } + } +} +``` + +MainPage.xaml +```xml + + + + + + + + + +``` + +## アプリケーションの起動 + +アプリを起動すると、下のウィンドウが表示されます。 +入力してから 1 秒後に、出力値が大文字で表示されます。 + +![アプリの起動](../../docs/getting-started/images/launch-uwp-app.gif) diff --git a/docs/docs-ja/getting-started/wpf.md b/docs/docs-ja/getting-started/wpf.md new file mode 100644 index 00000000..f53f45b6 --- /dev/null +++ b/docs/docs-ja/getting-started/wpf.md @@ -0,0 +1,99 @@ +# WPF をはじめる + +## プロジェクトの作成 +- WPF App (.NET Framework) プロジェクトを作成します。 + - .NET Framework 4.7.2 以降、または .NET Core 3.0 以降を使用する必要があります。 +- NuGet から ReactiveProperty.WPF パッケージをインストールします。 + +## コードの編集 + +- App クラスに Startup イベント ハンドラーを追加します。 +- ReactiveProperty クラス用のスケジューラーを初期化するコードを追加します。 + +```csharp +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using Reactive.Bindings; +using Reactive.Bindings.Schedulers; + +namespace WpfApp1 +{ + public partial class App : Application + { + private void Application_Startup(object sender, StartupEventArgs e) + { + ReactivePropertyScheduler.SetDefault(new ReactivePropertyWpfScheduler(Dispatcher)); + } + } +} +``` + +- MainWindowViewModel.cs ファイルを作成します。 +- 次のようにファイルを編集します。 + +MainWindowViewModel.cs +```csharp +using Reactive.Bindings; +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; + +namespace WpfApp1 +{ + class MainWindowViewModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + public ReactiveProperty Input { get; } + public ReadOnlyReactiveProperty Output { get; } + + public MainWindowViewModel() + { + Input = new ReactiveProperty(""); + Output = Input + .Delay(TimeSpan.FromSeconds(1)) + .Select(x => x.ToUpper()) + .ToReadOnlyReactiveProperty(); + } + } +} +``` + +MainWindow.xaml +```xml + + + + + + + +``` + +## アプリケーションの起動 + +アプリを起動すると、下のウィンドウが表示されます。 +入力してから 1 秒後に、出力値が大文字で表示されます。 + +![アプリの起動](../../docs/getting-started/images/launch-wpf-app.gif) diff --git a/docs/docs-ja/getting-started/xf.md b/docs/docs-ja/getting-started/xf.md new file mode 100644 index 00000000..e7c5bd6c --- /dev/null +++ b/docs/docs-ja/getting-started/xf.md @@ -0,0 +1,65 @@ +# Xamarin.Forms をはじめる + +## プロジェクトの作成 + +- Cross-Platform app (Xamarin.Forms) プロジェクトを作成します。 +- `New Cross Platform App` ダイアログを次のように設定します。 + .NET Standard プロジェクトを選択します。もちろん、共有プロジェクトを選択することもできます。 + ![New Cross Platform App ダイアログ](../../docs/getting-started/images/xf-create-project.png) +- NuGet からすべてのプロジェクトに ReactiveProperty をインストールします。 + +## コードの編集 + +- .NET Standard プロジェクトに MainPageViewModel.cs を作成します。 +- 次のようにファイルを編集します。 + +MainPageViewModel.cs +```csharp +using Reactive.Bindings; +using System; +using System.Reactive.Linq; + +namespace GettingStartedXF +{ + public class MainPageViewModel + { + public ReactiveProperty Input { get; } + public ReadOnlyReactiveProperty Output { get; } + + public MainPageViewModel() + { + Input = new ReactiveProperty(""); + Output = Input + .Delay(TimeSpan.FromSeconds(1)) + .Select(x => x.ToUpper()) + .ToReadOnlyReactiveProperty(); + } + } +} +``` + +MainPage.xaml +```xml + + + + + + + + + +``` + +## アプリケーションの起動 + +アプリを起動すると、下のウィンドウが表示されます。 +入力してから 1 秒後に、出力値が大文字で表示されます。 + +![アプリの起動](../../docs/getting-started/images/launch-xf-app-android.gif) + +![アプリの起動](../../docs/getting-started/images/launch-xf-app-uwp.gif) diff --git a/docs/docs-ja/samples.md b/docs/docs-ja/samples.md new file mode 100644 index 00000000..3d10678a --- /dev/null +++ b/docs/docs-ja/samples.md @@ -0,0 +1,15 @@ +サンプル プログラム: + +ReactiveProperty-Samples.sln を開くとサンプル プログラムを確認できます。含まれているものは次のとおりです: +- ReactivePropertySamples
+ 基本的な機能を説明するサンプル ページがあります。`BasicSample/ReactivePropertySamples.WPF` をスタートアップ プロジェクトに設定して、アプリを起動してください。次の画面が表示されます: + ![サンプル アプリ](../docs/images/sampleapp.gif) + + ViewModel クラスと Model クラスは ReactivePropertySamples.Shared プロジェクトにあります。 + +- Reactive.Todo
+ このサンプル アプリは ReactiveProperty と [Prism](https://prismlibrary.com/index.html) で作成されています。[TodoMVC](http://todomvc.com/) に似た小さなサンプル アプリです。 + + ![サンプル アプリ](../docs/images/todoapp.gif) + + `WithPrism/Reactive.Todo` プロジェクトからアプリを起動できます。 \ No newline at end of file diff --git a/docs/docs/README.md b/docs/docs/README.md index 342ce890..d19317cc 100644 --- a/docs/docs/README.md +++ b/docs/docs/README.md @@ -1,15 +1,15 @@ # What is ReactiveProperty -ReactiveProperty provides MVVM and asynchronous support features under Reactive Extensions. Target framework is .NET Standard 2.0. +ReactiveProperty provides MVVM and asynchronous support features for Reactive Extensions. The target framework is .NET Standard 2.0. ![Summary](./images/rpsummary.png) -Concept of ReactiveProperty is Fun programing. -You can write MVVM pattern programs using ReactiveProperty. It's very fun! +The concept of ReactiveProperty is Fun programming. +You can write MVVM applications with ReactiveProperty. It's a lot of fun! ![UWP](./images/launch-uwp-app.gif) -Following code is two way binding between ReactiveProperty and plain object property. +The following code shows two-way binding between a ReactiveProperty and a plain object property. ```csharp class Model : INotifyPropertyChanged @@ -33,7 +33,7 @@ class ViewModel public ReactiveProperty Name { get; } public ViewModel() { - // TwoWay synchronize to ReactiveProperty and Model#Name property. + // Two-way synchronization between ReactiveProperty and the Model#Name property. Name = _model.ToReactivePropertyAsSynchronized(x => x.Name); } } @@ -48,7 +48,7 @@ name.Where(x => x.StartsWith("_")) // filter .Subscribe(x => { ... some action ... }); ``` -ReactiveProperty is created from `IObservable`. +ReactiveProperty is created from `IObservable`. ```csharp class ViewModel @@ -60,7 +60,7 @@ class ViewModel { Input = new ReactiveProperty(""); Output = Input - .Delay(TimeSpan.FromSecond(1)) // Using a Rx method. + .Delay(TimeSpan.FromSeconds(1)) // Using an Rx method. .Select(x => x.ToUpper()) // Using a LINQ method. .ToReactiveProperty(); // Convert to ReactiveProperty } @@ -69,8 +69,8 @@ class ViewModel This method chain is very cool. -And we provide `ReactiveCommand` class which implements `ICommand` and `IObservable` interfaces. `ReactiveCommand` can be created from an `IObservable`. -Following sample creates a `ReactiveCommand` that is able to be executed when the `Input` property is not empty. +We also provide the `ReactiveCommand` class, which implements the `ICommand` and `IObservable` interfaces. `ReactiveCommand` can be created from an `IObservable`. +The following sample creates a `ReactiveCommand` that can execute when the `Input` property is not empty. ```csharp class ViewModel @@ -83,20 +83,20 @@ class ViewModel public ViewModel() { Input = new ReactiveProperty(""); - // Same as above sample + // Same as the sample above Output = Input - .Delay(TimeSpan.FromSecond(1)) // Using a Rx method. + .Delay(TimeSpan.FromSeconds(1)) // Using an Rx method. .Select(x => x.ToUpper()) // Using a LINQ method. .ToReactiveProperty(); // Convert to ReactiveProperty - - ResetCommand = Input.Select(x => !string.IsNullOrWhitespace(x)) // Convert ReactiveProperty to IObservable - .ToReactiveCommand() // You can create ReactiveCommand from IObservable (When true value was published, then the command would be able to execute.) - .WithSubscribe(() => Input.Value = ""); // This is a shortcut of ResetCommand.Subscribe(() => ...) + + ResetCommand = Input.Select(x => !string.IsNullOrWhiteSpace(x)) // Convert ReactiveProperty to IObservable + .ToReactiveCommand() // You can create ReactiveCommand from IObservable. When a true value is published, the command can execute. + .WithSubscribe(() => Input.Value = ""); // This is a shortcut for ResetCommand.Subscribe(() => ...) } } ``` -Cool!! It is really declarative, really clear. +Cool! It is really declarative and clear. ## Let's start! @@ -107,7 +107,7 @@ You can start using ReactiveProperty from the following links. - [Xamarin.Forms](getting-started/xf.md) - [Uno Platform](getting-started/uno-platform.md) -And learn the core features on following links. +Learn the core features from the following links. - [ReactiveProperty](features/ReactiveProperty.md) - [Commanding](features/Commanding.md) @@ -119,8 +119,8 @@ And learn the core features on following links. |Package Id|Version and downloads|Description| |----|----|----| |ReactiveProperty|![](https://img.shields.io/nuget/v/ReactiveProperty.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.svg)|The package includes all core features, and the target platform is .NET Standard 2.0. It fits almost all situations.| -|ReactiveProperty.Core|![](https://img.shields.io/nuget/v/ReactiveProperty.Core.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.Core.svg)|The package includes minimum classes such as `ReactivePropertySlim` and `ReadOnlyReactivePropertySlim`. And this doesn't have any dependency even System.Reactive. If you don't need Rx features, then it fits.| +|ReactiveProperty.Core|![](https://img.shields.io/nuget/v/ReactiveProperty.Core.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.Core.svg)|The package includes minimal classes such as `ReactivePropertySlim` and `ReadOnlyReactivePropertySlim`. It has no dependencies, not even System.Reactive. If you don't need Rx features, it fits.| |ReactiveProperty.WPF|![](https://img.shields.io/nuget/v/ReactiveProperty.WPF.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.WPF.svg)|The package includes EventToReactiveProperty and EventToReactiveCommand for WPF. This is for .NET Core 3.0 or later and .NET Framework 4.7.2 or later.| |ReactiveProperty.UWP|![](https://img.shields.io/nuget/v/ReactiveProperty.UWP.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.UWP.svg)|The package includes EventToReactiveProperty and EventToReactiveCommand for UWP.| -|ReactiveProperty.XamarinAndroid|![](https://img.shields.io/nuget/v/ReactiveProperty.XamarinAndroid.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.XamarinAndroid.svg)|The package includes many extension methods to create IObservable from events for Xamarin.Android native.| +|ReactiveProperty.XamarinAndroid|![](https://img.shields.io/nuget/v/ReactiveProperty.XamarinAndroid.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.XamarinAndroid.svg)|The package includes many extension methods to create IObservable instances from events for Xamarin.Android native.| |ReactiveProperty.XamariniOS|![](https://img.shields.io/nuget/v/ReactiveProperty.XamariniOS.svg)![](https://img.shields.io/nuget/dt/ReactiveProperty.XamariniOS.svg)|The package includes many extension methods to bind ReactiveProperty and ReactiveCommand to Xamarin.iOS native controls.| diff --git a/docs/docs/advanced/awaitable.md b/docs/docs/advanced/awaitable.md index d376bde2..3c70306a 100644 --- a/docs/docs/advanced/awaitable.md +++ b/docs/docs/advanced/awaitable.md @@ -1,9 +1,9 @@ # Awaitable -You can use `await` operator on `ReactiveProperty`(includes `ReactivePropertySlim`), `ReadOnlyReactiveProperty`(includes `ReadOnlyReactivePropertySlim`), and `ReactiveCommand`. +You can use the `await` operator on `ReactiveProperty` (including `ReactivePropertySlim`), `ReadOnlyReactiveProperty` (including `ReadOnlyReactivePropertySlim`), and `ReactiveCommand`. When using the `await` operator, the program will wait until the next value is published. -## For example: +## Example: ```csharp // View with CancellationTokenSource @@ -21,7 +21,7 @@ public partial class SampleWindow : Window protected override void OnClosed(EventArgs e) { - // on finish, cancel all await. + // On finish, cancel all awaits. cts.Cancel(); cts.Dispose(); @@ -40,7 +40,7 @@ public class SampleViewModel MyCommand = new ReactiveCommand(); ClickCount = new ReactiveProperty(); - // handling event by async/await. + // Handle events with async/await. SubscribeAsync(closeToken); } @@ -58,6 +58,6 @@ public class SampleViewModel } ``` -If you await multiple times, you should get `ObservableAsyncHandler` from `GetAsyncHandler`. it can await multiple times on zero allocation. If you await single time, you can use `await command.WaitUntilValueChangedAsync(token)`. +If you await multiple times, get `ObservableAsyncHandler` from `GetAsyncHandler`. It can await multiple times with zero allocation. If you await only once, you can use `await command.WaitUntilValueChangedAsync(token)`. -> Note: you can await `ReactiveProperty` directly but we recommend use `GetAsyncHandler`(multiple) or `WaitUntilValueChangedAsync`(one shot) with pass over `CancellationToken`. \ No newline at end of file +> Note: you can await `ReactiveProperty` directly, but we recommend using `GetAsyncHandler` (multiple awaits) or `WaitUntilValueChangedAsync` (one shot) and passing a `CancellationToken`. diff --git a/docs/docs/advanced/r3-migration.md b/docs/docs/advanced/r3-migration.md index 5f1e8c96..1f610869 100644 --- a/docs/docs/advanced/r3-migration.md +++ b/docs/docs/advanced/r3-migration.md @@ -8,7 +8,7 @@ Use `ReactiveProperty.R3` when you are moving a project from `Reactive.Bindings` The bridge covers the common gaps that appear during migration: -- notifiers such as `BooleanNotifier`, `BusyNotifier`, `CountNotifier`, `ScheduledNotifier` and message brokers +- notifiers such as `BooleanNotifier`, `BusyNotifier`, `CountNotifier`, `ScheduledNotifier`, and message brokers - `ReactiveTimer` - `AsyncReactiveCommand` - `ReadOnlyReactiveCollection` and collection helpers @@ -40,7 +40,7 @@ If the project uses WPF trigger actions or converters, also add the WPF bridge p dotnet add package ReactiveProperty.R3.WPF ``` -Visual Studio users can install `ReactiveProperty.R3.WPF` the same way from the NuGet package manager. +Visual Studio users can install `ReactiveProperty.R3.WPF` the same way from NuGet Package Manager. ## Migration approach @@ -128,7 +128,7 @@ Copy-Item -Recurse skills/migrating-reactiveproperty-to-r3 ` On macOS/Linux the personal destination is `~/.copilot/skills/migrating-reactiveproperty-to-r3`. This is handy when you migrate several projects, but the skill folder is then *outside* your project, -which triggers the read-scoping note at the end of this guide. Either way, after the copy start +which triggers the read-scoping note at the end of this guide. Either way, after copying, start `copilot` in your project; the skill loads on its own and activates when you ask to migrate to R3. (Installing it as a local plugin/marketplace also works, but the folder copy is simplest.) diff --git a/docs/docs/advanced/thread.md b/docs/docs/advanced/thread.md index c8fecbf6..0ec4d58e 100644 --- a/docs/docs/advanced/thread.md +++ b/docs/docs/advanced/thread.md @@ -1,19 +1,19 @@ # Threading -ReactiveProperty provides execution thread control feature. -ReactiveProperty raises `PropertyChanged` event on UI thread automatically. +ReactiveProperty provides execution thread control features. +ReactiveProperty raises the `PropertyChanged` event on the UI thread automatically. ## Change the scheduler You can change this behavior using `IScheduler`. -When the instance is created, set `IScheduler` instance to `raiseEventScheduler` argument. +When the instance is created, set an `IScheduler` instance to the `raiseEventScheduler` argument. ```csharp var rp = Observable.Interval(TimeSpan.FromSeconds(1)) .ToReactiveProperty(raiseEventScheduler: ImmediateScheduler.Instance); ``` -`ReactiveCollection` and `ReadOnlyReactiveCollection` raise `CollectionChanged` event on UI thread same as `ReactiveProperty`. +`ReactiveCollection` and `ReadOnlyReactiveCollection` raise the `CollectionChanged` event on the UI thread, same as `ReactiveProperty`. This behavior can be changed using the scheduler constructor and factory method argument. ```csharp @@ -26,7 +26,7 @@ var readOnlyCollection = Observable.Interval(TimeSpan.FromSeconds(1)) ## Change the global scheduler -You can change the ReactiveProperty's default scheduler using `ReactivePropertyScheduler.SetDefault` method. +You can change ReactiveProperty's default scheduler using the `ReactivePropertyScheduler.SetDefault` method. ```csharp ReactivePropertyScheduler.SetDefault(TaskPoolScheduler.Default); @@ -40,7 +40,7 @@ immediateRp.Value = "changed"; // raise event on the ImmediateScheduler thread. ## Change the global scheduler factory -Using the `ReactivePropertyScheduler.SetDefaultSchedulerFactory` method, you can change a factory method to create the ReactiveProperty's default scheduler instance. +Using the `ReactivePropertyScheduler.SetDefaultSchedulerFactory` method, you can change the factory method that creates ReactiveProperty's default scheduler instance. ```csharp using System.Reactive.Concurrency; @@ -54,7 +54,7 @@ namespace MultiUIThreadApp { private void Application_Startup(object sender, StartupEventArgs e) { - // Set to create a DispatcherScheduler instance when every instance is created + // Set to create a DispatcherScheduler instance when each instance is created // for ReactiveProperty, ReadOnlyReactiveProperty, ReactiveCollection, and ReadOnlyReactiveProperty. ReactivePropertyScheduler.SetDefaultSchedulerFactory(() => new DispatcherScheduler(Dispatcher.CurrentDispatcher)); @@ -73,8 +73,8 @@ var rp = Observable.Interval(TimeSpan.FromSeconds(1)) .ToReactiveProperty(); ``` -And we provide the `ObserveOnUIDispatcher` extension method. -This is a shortcut of `ObserveOn(ReactiveProeprtyScheduler.Default)`. +We also provide the `ObserveOnUIDispatcher` extension method. +This is a shortcut for `ObserveOn(ReactivePropertyScheduler.Default)`. ```csharp var rp = Observable.Interval(TimeSpan.FromSeconds(1)) @@ -84,8 +84,8 @@ var rp = Observable.Interval(TimeSpan.FromSeconds(1)) ## Caution -As a default, ReactiveProperty was designed for a single UI thread platform. -It means a few features don't work on multi UI thread platforms such as UWP. +By default, ReactiveProperty was designed for single-UI-thread platforms. +This means a few features don't work on multi-UI-thread platforms such as UWP. -UWP has multi UI threads in the single process when multiple Windows are created. -So, in case of creating multi-windows on UWP, then you should set `ImmediateScheduler` using the `ReactivePropertyScheduler.SetDefault` method to disable a feature of auto dispatch events to UI thread or change to create different scheduler instances for each UI thread using the `ReactivePropertyScheduler.SetDefaultSchedulerFactory` method. Or please use `ReactivePropertySlim` / `ReadOnlyReactivePropertySlim` classes instead of ReactiveProperty/ReadOnlyReactiveProperty classes. +UWP has multiple UI threads in a single process when multiple windows are created. +When creating multiple windows on UWP, set `ImmediateScheduler` using the `ReactivePropertyScheduler.SetDefault` method to disable automatic event dispatch to the UI thread, or create different scheduler instances for each UI thread using the `ReactivePropertyScheduler.SetDefaultSchedulerFactory` method. Alternatively, use the `ReactivePropertySlim` / `ReadOnlyReactivePropertySlim` classes instead of the ReactiveProperty / ReadOnlyReactiveProperty classes. diff --git a/docs/docs/advanced/work-with-other-mvvm-framwork.md b/docs/docs/advanced/work-with-other-mvvm-framwork.md index f2be26d8..9d2efeb7 100644 --- a/docs/docs/advanced/work-with-other-mvvm-framwork.md +++ b/docs/docs/advanced/work-with-other-mvvm-framwork.md @@ -1,23 +1,23 @@ -# Work with other MVVM Framework +# Work with other MVVM frameworks -ReactiveProperty doesn't provide base classes for ViewModel and other layers. -This means you can use ReactiveProperty with other MVVM Frameworks like Prism, MVVM Light Toolkit and others. +ReactiveProperty doesn't provide base classes for ViewModels or other layers. +This means you can use ReactiveProperty with other MVVM frameworks like Prism, MVVM Light Toolkit, and others. In this section, we explain how to use ReactiveProperty with Prism. -Let's start! +Let's get started! ## Create a Prism project -Prism provides Prism Template Pack extension for Visual Studio. -After installing the extension, you can create an app from Project Templates. +Prism provides the Prism Template Pack extension for Visual Studio. +After installing the extension, you can create an app from the project templates. ![](./images/create-project.png) When using ReactiveProperty with Prism, you can replace `DelegateCommand` with `ReactiveCommand`, and you can also use all other ReactiveProperty features with Prism. -I created a Prism Blank App(WPF) as PrismSampleApp and Prism Module (WPF) as PrismSampleModule. -And add PrismSampleModule reference to PrismSampleApp, then edit App.xaml.cs to add the module, like below: +This example creates a Prism Blank App (WPF) named PrismSampleApp and a Prism Module (WPF) named PrismSampleModule. +Add a PrismSampleModule reference to PrismSampleApp, and then edit App.xaml.cs to add the module, as shown below: ```csharp public partial class App @@ -39,7 +39,7 @@ public partial class App } ``` -Next, edit PrismSampleModuleModule.cs file to add a view for Navigation, and to show ViewA to Shell. +Next, edit PrismSampleModuleModule.cs to add a view for navigation and register ViewA with the shell. ```csharp using PrismSampleModule.Views; @@ -73,9 +73,9 @@ namespace PrismSampleModule ## Using ReactiveProperty -Add ReactiveProperty reference to all projects using NuGet. And please feel free use classes of ReactiveProperty. +Add a ReactiveProperty reference to all projects using NuGet. Feel free to use any ReactiveProperty classes. -In this case, I used ReactiveProperty features at ViewAViewModel.cs like below: +In this example, ReactiveProperty features are used in ViewAViewModel.cs as shown below: ```csharp using Prism.Mvvm; @@ -108,17 +108,17 @@ namespace PrismSampleModule.ViewModels } ``` -And next, edit `ViewA.xaml`. +Next, edit `ViewA.xaml`. ```xml `ToUnit` extension method is defined in the Reactive.Bindings.Extensions namespace. -> This extension method is same as `.Select(_ => Unide.Default)`. +> This extension method is the same as `.Select(_ => Unit.Default)`. -Example of UWP platform. +Example for UWP. MainPage.xaml.cs ```csharp @@ -80,8 +80,8 @@ MainPage.xaml ## Collection operations -`ReactiveCollection` class has `XxxxOnScheduler` methods. For example, `AddOnScheduler`, `RemoveOnScheduler`, `ClearOnScheduler`, `GetOnScheduler`, etc... -Those methods run on the `IScheduler` and can be called from outside of the UI thread. +The `ReactiveCollection` class has `XxxxOnScheduler` methods, such as `AddOnScheduler`, `RemoveOnScheduler`, `ClearOnScheduler`, and `GetOnScheduler`. +These methods run on the `IScheduler` and can be called from outside the UI thread. ```csharp public class ViewModel @@ -104,7 +104,7 @@ public class ViewModel .ToReactiveCollection(); ClearCommand = new ReactiveCommand(); - ClearCommand.ObserveOn(TaskPoolScheduler.Default) // run on the another thread + ClearCommand.ObserveOn(TaskPoolScheduler.Default) // run on another thread .Subscribe(_ => Records.ClearOnScheduler()); } } @@ -139,13 +139,13 @@ public class ViewModel ![Collection operations](./images/collections-reactivecollection-collection-operations.gif) -When `ReactiveCollection` class is `Dispose`d, it unsubscribes from the source IObservable instance. +When a `ReactiveCollection` instance is disposed, it unsubscribes from the source IObservable instance. ## ReadOnlyReactiveCollection -`ReadOnlyReactiveCollection` class provides one-way synchronization from `ObservableCollection`. Can set converting logic, and dispatch `CollectionChanged` event raise on the `IScheduler`. The default `IScheduler` dispatches to the UI thread. +The `ReadOnlyReactiveCollection` class provides one-way synchronization from `ObservableCollection`. You can set conversion logic and dispatch the `CollectionChanged` event on the `IScheduler`. The default `IScheduler` dispatches to the UI thread. -At first, create a POCO classes. +First, create POCO classes. ```csharp public class BindableBase : INotifyPropertyChanged @@ -191,9 +191,9 @@ public class TimerObject : BindableBase, IDisposable } ``` -This is a simple class that counts up the `Count` property per second. +This is a simple class that increments the `Count` property once per second. -Wrap the class to ViewModel layer using ReactiveProperty. +Wrap the class in the ViewModel layer using ReactiveProperty. ```csharp public class TimerObjectViewModel : IDisposable @@ -218,8 +218,8 @@ public class TimerObjectViewModel : IDisposable ``` Manage `TimerObject` instances using the `ObservableCollection`. -We should provide `TimerObjectViewModel` instances to View layer, can use `ReadOnlyReactiveCollection` class. -`ReadOnlyReactiveCollection` instance is created using `ToReadOnlyReactiveCollection` extension method. +To provide `TimerObjectViewModel` instances to the View layer, use the `ReadOnlyReactiveCollection` class. +A `ReadOnlyReactiveCollection` instance is created using the `ToReadOnlyReactiveCollection` extension method. ```csharp public class ViewModel @@ -249,7 +249,7 @@ public class ViewModel } ``` -Test view is below. +The test view is below. ```xml `. -When this argument raises a value, then the collection is cleared. +When this argument publishes a value, the collection is cleared. ```csharp public class ViewModel @@ -349,13 +349,13 @@ public class ViewModel ``` -When the `ResetCommand` is executed, clear the Messages. +When the `ResetCommand` is executed, the Messages collection is cleared. ![Reset](./images/collections-reactivecollection-readonly-collection-reset.gif) ## IFilteredReadOnlyObservableCollection -A collection which filters in realtime from `ObservableCollection`. +A collection that filters in real time from `ObservableCollection`. `IFilteredReadOnlyObservableCollection` watches the `PropertyChanged` event of the source collection item and the `CollectionChanged` event. ```csharp @@ -462,16 +462,16 @@ public class ViewModel ![IFilteredReadOnlyObservableCollection](./images/collections-filtered-collection.gif) -When the Value property is greater than 7, then display the value in the Filtered Values ListView (right side). +When the Value property is greater than 7, display the value in the Filtered Values ListView (right side). ### Customize how to observe collection elements -If you want to change update elements trigger from CollectionChanged event to other, then you can customize it using another overload method that has `IObservable sourceElementStatusChanged` argument. +If you want to change the trigger that updates elements from the CollectionChanged event to another trigger, you can customize it using another overload that has an `IObservable sourceElementStatusChanged` argument. -For example, you want to filter nested property of elements on a collection. +For example, you may want to filter by a nested property of elements in a collection. ```csharp -// An object that has nested object property +// An object that has a nested object property public class NestedPropertyObject : INotifyPropertyChanged { // omit INPC impl @@ -490,20 +490,20 @@ var sourceCollection = new ObservableCollection }; var filteredCollection = sourceCollection.ToFilteredReadOnlyObservableCollection( - // a lambda expression for filter condition + // a lambda expression for the filter condition x => x.NestedObject.Value, - // create a IObservable instance for update trigger of collection elements + // create an IObservable instance for the collection element update trigger x => x.ObserveProperty(y => NestedObject.Value) ); Console.WriteLine(filteredCollection.Count); // 3 -// filteredCollection is observing NextedObject.Value property path. -// Then the following line triggers re-eval for filter condition +// filteredCollection is observing the NestedObject.Value property path. +// Then the following line triggers re-evaluation of the filter condition sourceCollection[1].NestedObject.Value = false; Console.WriteLine(filteredCollection.Count); // 2 ``` -The following two lines are same: +The following two lines are the same: ```csharp collection.ToFilteredReadOnlyObservableCollection(x => x.SomeProperty); diff --git a/docs/docs/features/Commanding.md b/docs/docs/features/Commanding.md index 15f01dfe..2b98831f 100644 --- a/docs/docs/features/Commanding.md +++ b/docs/docs/features/Commanding.md @@ -7,21 +7,21 @@ ## Basic usage -This class can be created using `ToReactiveCommand` extension method from `IObservable` instance. -When the `IObservable` instance is updated, the `CanExecuteChanged` event is raised. +This class can be created using the `ToReactiveCommand` extension method from an `IObservable` instance. +When the `IObservable` instance publishes a value, the `CanExecuteChanged` event is raised. -If you always want an executable command, then you can create a `ReactiveCommand` instance using the default constructor. +If you always want an executable command, you can create a `ReactiveCommand` instance using the default constructor. ```csharp IObservable canExecuteSource = ...; -ReactiveCommand someCommand = canExecuteSource.ToReactiveCommand(); // non command parameter version. -ReactiveCommand hasCommandParameterCommand = canExecuteSource.ToReactiveCommand(); // has command parameter version -ReactiveCommand alwaysExecutableCommand = new ReactiveCommand(); // non command parameter and always can execute version. -ReactiveCommand alwaysExecutableAndHasCommandParameterCommand = new ReactiveCommand(); // has command parameter and always can execute version. +ReactiveCommand someCommand = canExecuteSource.ToReactiveCommand(); // version without a command parameter. +ReactiveCommand hasCommandParameterCommand = canExecuteSource.ToReactiveCommand(); // version with a command parameter +ReactiveCommand alwaysExecutableCommand = new ReactiveCommand(); // version without a command parameter that can always execute. +ReactiveCommand alwaysExecutableAndHasCommandParameterCommand = new ReactiveCommand(); // version with a command parameter that can always execute. ``` -You can set the initial return value of `CanExecute` method using the factory extension method's `initalValue` argument. +You can set the initial return value of the `CanExecute` method using the factory extension method's `initialValue` argument. The default value is `true`. ```csharp @@ -43,7 +43,7 @@ someCommand.Execute(); // OnNext callback is called. ## Using in ViewModel class -The first example, just use `ReactiveCommand` class. +The first example just uses the `ReactiveCommand` class. ```csharp public class ViewModel @@ -61,7 +61,7 @@ public class ViewModel } ``` -UWP platform example. +Example for UWP. ```csharp public sealed partial class MainPage : Page @@ -97,8 +97,8 @@ public sealed partial class MainPage : Page ## Work with LINQ -`ReactiveCommand` class implements the `IObservable` interface. -Can use LINQ methods, and `ReactiveProperty` class can be created from `IObservable`. +`ReactiveCommand` class implements the `IObservable` interface. +You can use LINQ methods, and the `ReactiveProperty` class can be created from `IObservable`. The previous example code can be changed to this: ```csharp @@ -106,7 +106,7 @@ public class ViewModel { public ReactiveCommand UpdateTimeCommand { get; } - // Don't need that set Value property. So can change to ReadOnlyReactiveProperty. + // You don't need to set the Value property, so this can change to ReadOnlyReactiveProperty. public ReadOnlyReactiveProperty Time { get; } public ViewModel() @@ -121,7 +121,7 @@ public class ViewModel ## Create from `IObservable` -Change so that the `UpdateTimeCommand` doesn't invoke for 5 secs after the command is invoked. +Change this so that the `UpdateTimeCommand` can't execute for 5 seconds after the command is invoked. ```csharp public class ViewModel @@ -149,7 +149,7 @@ public class ViewModel ## Create command and subscribe, in one statement -In the case that you aren't using LINQ methods, you can create a command and subscribe in one statement. +If you aren't using LINQ methods, you can create a command and subscribe in one statement. `WithSubscribe` extension method subscribes and returns the `ReactiveCommand` instance: ```csharp @@ -176,27 +176,27 @@ public class ViewModel `WithSubscribe` method is just a shortcut: ```csharp -// No use the WithSubscribe +// Without WithSubscribe var command = new ReactiveCommand(); command.Subscribe(_ => { ... some actions ... }); -// Use the WithSubscribe +// With WithSubscribe var command = new ReactiveCommand() .WithSubscribe(() => { ... some actions ... }); ``` -If you use LINQ methods, then separate statements create an instance and subscribe. +If you use LINQ methods, create separate statements to instantiate and subscribe. ## Unsubscribe actions -If need to unsubscribe actions, then use the `Dispose` method of `IDisposable` which the `Subscribe` method returned. +If you need to unsubscribe actions, use the `Dispose` method of the `IDisposable` instance returned by the `Subscribe` method. ```csharp var command = new ReactiveCommand(); var subscription1 = command.Subscribe(_ => { ... some actions ... }); var subscription2 = command.Subscribe(_ => { ... some actions ... }); -// Unsubscribe per Subscribe method. +// Unsubscribe each Subscribe method. subscription1.Dispose(); subscription2.Dispose(); @@ -204,7 +204,7 @@ subscription2.Dispose(); command.Dispose(); ``` -`WithSubscribe` extension method has override methods which have an `IDisposable` argument. +The `WithSubscribe` extension method has overloads with an `IDisposable` argument. ```csharp IDisposable subscription = null; @@ -214,8 +214,8 @@ var command = new ReactiveCommand().WithSubscribe(() => { ... some actions ... } subscription.Dispose(); ``` -And has another override of `Action` argument. -It is used together with `CompositeDisposable` class. +It also has another overload with an `Action` argument. +It is used together with the `CompositeDisposable` class. ```csharp var subscriptions = new CompositeDisposable(); @@ -227,20 +227,20 @@ var command = new ReactiveCommand() subscription.Dispose(); ``` -In other instance's events subscribe, then you should call the `Dispose` method of `ReactiveCommand` class at the end of the ViewModel lifecycle. +When subscribing to another instance's events, you should call the `Dispose` method of the `ReactiveCommand` class at the end of the ViewModel lifecycle. -## Async version ReactiveCommand +## Async version of ReactiveCommand -`AsyncReactiveCommand` class is an `async` version `ReactiveCommand` class. -This class can subscribe using `async` methods, and when executing an async method then `CanExecute` method returns `false`. -So, this class can't re-execute while the async method is running. +The `AsyncReactiveCommand` class is an `async` version of the `ReactiveCommand` class. +This class can subscribe to `async` methods, and while an async method is executing, the `CanExecute` method returns `false`. +Therefore, this class can't re-execute while the async method is running. -And `ExecuteAsync` method is an async version `Execute` method. The method is able to wait finishing all async proccesses are added to the command. This method is useful for unit testing and call commands on C#. +The `ExecuteAsync` method is an async version of the `Execute` method. It can wait for all async processes added to the command to finish. This method is useful for unit testing and for calling commands from C#. ### Basic usage -Nearly the same as a `ReactiveCommand` class. -The only difference is that it accepts an `async` method in `Subscribe` method argument, and doesn't implement the `IObservable` interface. +It is nearly the same as the `ReactiveCommand` class. +The only difference is that it accepts an `async` method as a `Subscribe` method argument and doesn't implement the `IObservable` interface. ```csharp public class ViewModel @@ -282,7 +282,7 @@ public class ViewModel ![HeavyCommand](./images/asyncreactivecommand-heavyprocess.gif) -Of course, `AsyncReactiveCommand` is created from `IObservable`. +Of course, `AsyncReactiveCommand` can be created from `IObservable`. ```csharp public class ViewModel @@ -308,14 +308,14 @@ public class ViewModel ![From IObservable](./images/asyncreactivecommand-from-iobool.gif) -And `AsyncReactiveCommand` implements the `IDisposable` interface. -You should call the `Dispose` method when the another instance's event subscribe. +The `AsyncReactiveCommand` class also implements the `IDisposable` interface. +You should call the `Dispose` method when subscribing to another instance's events. ### Share `CanExecute` state -Sometimes you want only one async method is to be executing in a page. -In this case, you can share `CanExecute` state between `AsyncReactiveCommand` instances. -When it's created from a same `IReactiveProperty` instance, you can synchronize the `CanExecute` state. +Sometimes you want only one async method to execute on a page. +In this case, you can share the `CanExecute` state between `AsyncReactiveCommand` instances. +When they are created from the same `IReactiveProperty` instance, you can synchronize the `CanExecute` state. ```csharp public class ViewModel @@ -371,7 +371,7 @@ public class ViewModel ![Share state](./images/asyncreactivecommand-share-state.gif) -Of course, you can combine `IObservable` and `IReactiveProperty`. `IObservable` for source of `AsyncReactiveCommand`, `IReactiveProperty` is for sharing state across `AsyncReactiveCommand`s. +Of course, you can combine `IObservable` and `IReactiveProperty`. Use `IObservable` as the source of `AsyncReactiveCommand`, and use `IReactiveProperty` to share state across `AsyncReactiveCommand`s. You can use `ToAsyncReactiveCommand(this IObservable source, IReactiveProperty sharedCanExecute = null)` method, like this: ```csharp @@ -398,7 +398,7 @@ namespace RPSample { Input = new ReactiveProperty().SetValidateAttribute(() => Input); - // create AsyncReactiveCommands from same source and same IReactiveProperty for sharing CanExecute status. + // create AsyncReactiveCommands from the same source and the same IReactiveProperty to share CanExecute status. SharedCanExecute = new ReactivePropertySlim(true); CommandA = Input.ObserveHasErrors .Inverse() @@ -413,7 +413,7 @@ namespace RPSample } ``` -After binding the ViewModel class to view: +After binding the ViewModel class to the view: ```csharp // code behind @@ -465,7 +465,7 @@ It works like this: ### `ReactiveCommand` class -When using the `ReactiveCommand` class, the class raises the `CanExecute` event on a scheduler(default is UI thread scheduler.) If you want to change the behaviour then use the overload method of `ToReactiveCommand` which has an `IScheduler` argument. +When using the `ReactiveCommand` class, the class raises the `CanExecute` event on a scheduler (the default is the UI thread scheduler). If you want to change the behavior, use the overload of `ToReactiveCommand` that has an `IScheduler` argument. See below: diff --git a/docs/docs/features/Event-transfer-to-ViewModel-from-View.md b/docs/docs/features/Event-transfer-to-ViewModel-from-View.md index 7a76243a..ffa1a8d8 100644 --- a/docs/docs/features/Event-transfer-to-ViewModel-from-View.md +++ b/docs/docs/features/Event-transfer-to-ViewModel-from-View.md @@ -1,14 +1,14 @@ # Transfer events to ViewModels from Views -`EventToReactiveProperty` and `EventToReactiveCommand` classes transfer events to a `ReactiveProperty` and `ReactiveCommand` from the View layer. -Those classes extend `TriggerAction`. Those are designed that uses together with `EventTrigger`. +`EventToReactiveProperty` and `EventToReactiveCommand` classes transfer events from the View layer to a `ReactiveProperty` or `ReactiveCommand`. +These classes extend `TriggerAction` and are designed to be used with `EventTrigger`. -Note: -> This feature available only WPF and UWP. Xamarin.Forms can't use this. If you would like to use it, then please add `ReactiveProperty.WPF` package for WPF or `ReactiveProperty.UWP` package for UWP to your project. +Note: +> This feature is available only for WPF and UWP. Xamarin.Forms can't use it. If you want to use it, add the `ReactiveProperty.WPF` package for WPF or the `ReactiveProperty.UWP` package for UWP to your project. -Those classes can convert `EventArgs` to any types object using `ReactiveConverter`. +These classes can convert `EventArgs` to any object type using `ReactiveConverter`. -`ReactiveConverter` class can use Rx method chain. It's very powerful. +The `ReactiveConverter` class can use an Rx method chain. It's very powerful. UWP sample: @@ -43,7 +43,7 @@ namespace App1 It converts the `RoutedEventArgs` to the file path. -XAML and Code behind are below. +XAML and code-behind are below. ```xml { ... error action ... }) .Subscribe(); ``` -## `CombineLatestsValuesAreAllXXXX` +## `CombineLatestValuesAreAllXXXX` Provides two methods. @@ -65,15 +65,15 @@ These are just shortcuts: ```csharp /// -/// Lastest values of each sequence are all true. +/// Latest values of each sequence are all true. /// public static IObservable CombineLatestValuesAreAllTrue( - this IEnumerable> sources) => + this IEnumerable> sources) => sources.CombineLatest(xs => xs.All(x => x)); /// -/// Lastest values of each sequence are all false. +/// Latest values of each sequence are all false. /// public static IObservable CombineLatestValuesAreAllFalse( this IEnumerable> sources) => @@ -82,7 +82,7 @@ public static IObservable CombineLatestValuesAreAllFalse( ## DisposePreviousValue -This is an extension method to call `Dispose` method for previous values of a `IObservable` sequence. +This extension method calls the `Dispose` method for previous values of an `IObservable` sequence. ```csharp var source = new Subject(); @@ -92,13 +92,13 @@ var rrp = source.Select(x => new SomeDisposableClass(x)) source.OnNext("first"); // first SomeDisposableClass is created. source.OnNext("second"); // second SomeDisposableClass is created, and first one is disposed. -source.OnComplete(); // second one is also disposed. +source.OnCompleted(); // second one is also disposed. ``` ## `CanExecuteChangedAsObservable` This is an extension method of the `ICommand` interface. -It is a shortcut for the `Observable.FromEvent`. +It is a shortcut for `Observable.FromEvent`. ```csharp /// Converts CanExecuteChanged to an observable sequence. @@ -112,7 +112,7 @@ public static IObservable CanExecuteChangedAsObservable(this T sou ## `INotifyCollectionChanged` extension methods -Convert `CollectionChanged` event to `IObservable`. +Convert the `CollectionChanged` event to `IObservable`. ```csharp /// Observe CollectionChanged:Remove and take single item. @@ -160,7 +160,7 @@ public static IObservable ObserveResetChanged(this INotifyCollectionCha ## `ObservableCollection` extension methods -It is a typesafe version of `INotifyPropertyChanged` extension methods. +It is a type-safe version of the `INotifyPropertyChanged` extension methods. ```csharp /// Observe CollectionChanged:Add and take single item. @@ -202,8 +202,8 @@ public static IObservable ObserveResetChanged(this ObservableCollection ## Observe `PropertyChanged` events of elements of `ObservableCollection` and `IFilteredReadOnlyObservableCollection` -Watch `PropertyChanged` events of elements of `ObservableCollection` and `IFilteredReadOnlyObservableCollection`. -`ObserveElementProperty` extension method can observe specific property's `PropertyChanged` events. +Watch `PropertyChanged` events of elements in `ObservableCollection` and `IFilteredReadOnlyObservableCollection`. +The `ObserveElementProperty` extension method can observe a specific property's `PropertyChanged` events. ```csharp using Reactive.Bindings.Extensions; @@ -268,7 +268,7 @@ Remove okazuki from collection Change okazuki name to okazuki ``` -If the target object's property type is `ReactiveProperty`, then use the `ObserveElementPropertyChanged` extension method. +If the target object's property type is `ReactiveProperty`, use the `ObserveElementObservableProperty` extension method. ```csharp using Reactive.Bindings; @@ -331,7 +331,7 @@ Change okazuki name to okazuki ## `INotifyDataErrorInfo` extension methods -Convert `ErrorChanged` event to an `IObservable`. +Convert the `ErrorsChanged` event to an `IObservable`. It is a shortcut of `FromEvent` method. ```csharp @@ -344,7 +344,7 @@ public static IObservable ErrorsChangedAsObservable< h => subject.ErrorsChanged -= h); ``` -`ObserveErrorInfo` extension method is the version that raises the property value when the `ErrorChanged` event is raised. +The `ObserveErrorInfo` extension method raises the property value when the `ErrorsChanged` event is raised. ## `Inverse` @@ -355,13 +355,10 @@ IObservable boolSequence = ...; IObservable inversedBoolSequence = boolSequence.Inverse(); ``` -It is a same as the below code: +It is the same as the code below: ```csharp IObservable boolSequence = ...; IObservable inversedBoolSequence = boolSequence.Select(x => !x); ``` - - - diff --git a/docs/docs/features/Notifiers.md b/docs/docs/features/Notifiers.md index f9420ed1..924baddc 100644 --- a/docs/docs/features/Notifiers.md +++ b/docs/docs/features/Notifiers.md @@ -1,22 +1,22 @@ # Notifiers -`Reactive.Bindings.Notifiers` namespace provides many useful classes which implement `IObservable` interface. +The `Reactive.Bindings.Notifiers` namespace provides many useful classes that implement the `IObservable` interface. ## `BooleanNotifier` `BooleanNotifier` class implements the `IObservable` interface. -And has some methods and property. +It has some methods and a property. - `TurnOn` method - - Change state to true. + - Changes the state to true. - `TurnOff` method - - Change state to false. + - Changes the state to false. - `SwitchValue` method - - Switch state. + - Switches the state. - `Value` property - - Set state + - Sets the state. -The initial state can be set the constructor. The default value is false. +The initial state can be set in the constructor. The default value is false. ```csharp @@ -29,23 +29,23 @@ n.Value = true; // true n.Value = false; // false ``` -It can use as source of `ReactiveCommand` like below: +It can be used as the source of `ReactiveCommand` as shown below: ```csharp var n = new BooleanNotifier(); // the default value is false. -// CanExecute method of ReactiveCommand returns true as default. So, you set initialValue explicitly to `n.Value`. +// The CanExecute method of ReactiveCommand returns true by default, so set initialValue explicitly to `n.Value`. var command = n.ToReactiveCommand(initialValue: n.Value); -// Or if you would like to convert to something using Select and others before calling ToReactiveCommand, you can use StartWith. +// Or, if you would like to convert to something using Select and other operators before calling ToReactiveCommand, you can use StartWith. var command2 = n.StartWith(n.Value).Select(x => Something(x)).ToReactiveCommand(); ``` ## `CountNotifier` -`CountNotifier` class implements the `IObservable` interface. It provides increment and decrement features, and raise a `CountChangedStates` value when the state changes. +The `CountNotifier` class implements the `IObservable` interface. It provides increment and decrement features, and raises a `CountChangedStatus` value when the state changes. -CountChangedStates enum is defined as below. +The CountChangedStatus enum is defined below. ```csharp /// Event kind of CountNotifier. @@ -62,7 +62,7 @@ public enum CountChangedStatus } ``` -`CountNotifier`'s max value can be set from constructor argument: +`CountNotifier`'s max value can be set from a constructor argument: ```csharp var c = new CountNotifier(); // default max value is int.MaxValue @@ -81,7 +81,7 @@ c.Decrement(5); Debug.WriteLine(c.Count); ``` -Output is below. +The output is below. ``` Increment @@ -113,9 +113,9 @@ n.Report("After 2 seconds.", TimeSpan.FromSeconds(2)); ## `BusyNotifier` This class implements the `IObservable` interface. -It raises `true` during running the process, raises `false` when all processes end. +It raises `true` while a process is running and raises `false` when all processes end. -The `StartProcess` method returns an `IDisposable` instance. When the process finishes, call the Dispose method. +The `ProcessStart` method returns an `IDisposable` instance. When the process finishes, call the Dispose method. ```csharp @@ -161,7 +161,7 @@ namespace ReactivePropertyEduApp } ``` -Output is below. +The output is below. ``` 15:07:45: OnNext: False @@ -176,8 +176,7 @@ Output is below. ## `MessageBroker` -I suggest creating a new notifier called `MessageBroker`: an in-memory pubsub. This is an Rx and async friendly `EventAggregator` or `MessageBus`, etc. We can use this for the messenger pattern. -If reviewer accept this code, please add to all platforms. +`MessageBroker` is an in-memory pub-sub notifier. It is Rx- and async-friendly, similar to an `EventAggregator` or `MessageBus`, and can be used for the messenger pattern. ```csharp using Reactive.Bindings.Notifiers; @@ -209,7 +208,7 @@ class Program Console.WriteLine("B:" + x); }); - // support convert to IObservable + // support conversion to IObservable MessageBroker.Default.ToObservable().Subscribe(x => { Console.WriteLine("C:" + x); @@ -238,7 +237,7 @@ class Program await Task.Delay(TimeSpan.FromSeconds(2)); }); - // await all subscriber complete + // await all subscribers to complete await AsyncMessageBroker.Default.PublishAsync(new MyClass { MyProperty = 100 }); await AsyncMessageBroker.Default.PublishAsync(new MyClass { MyProperty = 200 }); await AsyncMessageBroker.Default.PublishAsync(new MyClass { MyProperty = 300 }); @@ -258,7 +257,7 @@ class Program } ``` -Messenger pattern's multi thread dispatch can be handled easily by Rx. +Multi-thread dispatch for the messenger pattern can be handled easily with Rx. ```csharp MessageBroker.Default.ToObservable() @@ -272,6 +271,3 @@ MessageBroker.Default.ToObservable() - - - diff --git a/docs/docs/features/ReactiveProperty.md b/docs/docs/features/ReactiveProperty.md index 75584aa8..c2697ea1 100644 --- a/docs/docs/features/ReactiveProperty.md +++ b/docs/docs/features/ReactiveProperty.md @@ -1,16 +1,16 @@ # ReactiveProperty `ReactiveProperty` is the core class of this library. -This has following features. +It has the following features. - Implements the `INotifyPropertyChanged` interface. - - The value property raise the `PropertyChanged` event. + - The Value property raises the `PropertyChanged` event. - Implements the `IObservable` interface. -Yes, The value property can bind to XAML control's property. -And the class call the `IObserver`#OnNext method when the value is set. +Yes, the Value property can bind to a XAML control's property. +The class also calls the `IObserver`#OnNext method when the value is set. -A sample code is as below. +Sample code is shown below. ```csharp using Reactive.Bindings; @@ -22,13 +22,13 @@ namespace ReactivePropertyEduApp { static void Main(string[] args) { - // create from defualt constructor(default value is null) + // Create from the default constructor (default value is null). var name = new ReactiveProperty(); - // setup the event handler and the onNext callback. + // Set up the event handler and the OnNext callback. name.PropertyChanged += (_, e) => Console.WriteLine($"PropertyChanged: {e.PropertyName}"); name.Subscribe(x => Console.WriteLine($"OnNext: {x}")); - // update the value property. + // Update the Value property. name.Value = "neuecc"; name.Value = "xin9le"; name.Value = "okazuki"; @@ -49,17 +49,17 @@ OnNext: okazuki PropertyChanged: Value ``` -What's different between `PropertyChanged` and `onNext` callback? -The `onNext` callback is called when subscribed. The `PropertyChanged` isn't called when the event handler is added. And the `onNext` callback's argument is the property value, the `PropertyChanged` argument doesn't have the property value. +What's the difference between the `PropertyChanged` and `OnNext` callbacks? +The `OnNext` callback is called when subscribed. `PropertyChanged` isn't called when the event handler is added. Also, the `OnNext` callback's argument is the property value, while the `PropertyChanged` argument doesn't have the property value. -The `PropertyChanged` event was provided for data binding. In the normal case, you should use Reactive Extensions methods. +The `PropertyChanged` event is provided for data binding. In normal cases, you should use Reactive Extensions methods. -## Use with XAML platform +## Use with XAML platforms -The `ReactiveProperty` class is designed for XAML platform which is like WPF, UWP, and Xamarin.Forms. -This class can be used a ViewModel layer. +The `ReactiveProperty` class is designed for XAML platforms such as WPF, UWP, and Xamarin.Forms. +This class can be used in the ViewModel layer. -In the case that you don't use the `ReactiveProperty`, a ViewModel class wrote below. +If you don't use `ReactiveProperty`, a ViewModel class looks like this. ```csharp public class MainPageViewModel : INotifyPropertyChanged @@ -77,11 +77,11 @@ public class MainPageViewModel : INotifyPropertyChanged } } - // Other properties are defined similar codes. + // Other properties are defined with similar code. } ``` -And those properties bind in the XAML code. +Those properties are bound in XAML code. ```xml @@ -97,20 +97,20 @@ And those properties bind in the XAML code. ``` -In the case that you use the `ReactiveProperty`, a ViewModel code becomes very simple! +If you use `ReactiveProperty`, the ViewModel code becomes very simple! ```csharp -// The INotifyPropertyChanged interface must implement when using the WPF. -// Because, if you don't implement this, then memory leak occurred. +// The INotifyPropertyChanged interface must be implemented when using WPF. +// Otherwise, a memory leak can occur. public class MainPageViewModel { public ReactiveProperty Name { get; } = new ReactiveProperty(); - // Other properties are defined similar codes. + // Other properties are defined with similar code. } ``` -When binding in the XAML code, you must add the `.Value` in binding path. +When binding in XAML code, you must add `.Value` to the binding path. This is the only limitation of this library. ```xml @@ -127,32 +127,32 @@ This is the only limitation of this library. ``` -> We forget the `.Value` sometimes. If you have a ReSharper license, then you can use this plugin. +> We sometimes forget the `.Value`. If you have a ReSharper license, you can use this plugin. > [ReactiveProperty XAML Binding Corrector](https://resharper-plugins.jetbrains.com/packages/ReSharper.RpCorrector/) -> Highlight the missing of ReactiveProperty ".Value" in XAML. +> It highlights missing ReactiveProperty ".Value" bindings in XAML. ## How to create a `ReactiveProperty` instance -The `ReactiveProperty` class can create from many methods. +The `ReactiveProperty` class can be created in many ways. ### Create from the constructor -The simplest way is using the constructor. +The simplest way is to use the constructor. ```csharp -// create with the default value. +// Create with the default value. var name = new ReactiveProperty(); Console.WriteLine(name.Value); // -> empty output -// create with the initial value. +// Create with the initial value. var name = new ReactiveProperty("okazuki"); Console.WriteLine(name.Value); // -> okazuki ``` ### Create from `IObservable` -This can created from `IObservable`. -Just calls `ToReactiveProperty` method. +It can be created from `IObservable`. +Just call the `ToReactiveProperty` method. ```csharp IObservable observableInstance = Observable.Interval(TimeSpan.FromSeconds(1)); @@ -173,11 +173,11 @@ var formalName = name.Select(x => $"Dear {x}") .ToReactiveProperty(); ``` -All `IObservable` instances can become `ReactiveProperty`. +All `IObservable` instances can become a `ReactiveProperty`. ## Validation -The `ReactiveProperty` class implements `INotifyDataErrorInfo` interface. +The `ReactiveProperty` class implements the `INotifyDataErrorInfo` interface. ### Set custom validation logic @@ -193,8 +193,8 @@ In invalid value case, logic should return an error message. ### Work with DataAnnotations -This class can work together with the DataAnnotations. -You can set validation attribute using the `SetValidateAttribute` method. +This class can work with DataAnnotations. +You can set validation attributes using the `SetValidateAttribute` method. ```csharp class ViewModel @@ -213,17 +213,17 @@ class ViewModel } ``` -WPF is integrated with `INotifyDataErrorInfo` interface. See below. +WPF is integrated with the `INotifyDataErrorInfo` interface. See below. ![WPF Validation](./images/wpf-validation.png) ### Handling validation errors -Another platform can't display error messages from the `INofityDataErrorInfo` interface. -`ReactiveProperty` class have some properties for handling validation errors. +Other platforms can't display error messages from the `INotifyDataErrorInfo` interface. +The `ReactiveProperty` class has some properties for handling validation errors. -A first property is `ObserveErrorChanged`. -This type is `IObservable`. You can convert to an error message from `IEnumerable`. See below. +The first property is `ObserveErrorChanged`. +This type is `IObservable`. You can convert an `IEnumerable` to an error message. See below. ```csharp class ViewModel @@ -251,7 +251,7 @@ class ViewModel Bind `NameErrorMessage.Value` property to a text control. An error message can be displayed. -In the case of UWP, see below. +For UWP, see below. ```csharp public sealed partial class MainPage : Page @@ -287,7 +287,7 @@ public sealed partial class MainPage : Page ![A validation error message](./images/validation-errormessage.png) -ReactiveProperty v7.0.0 or later, there is `ObserveValidationErrorMessage` extension method instead of `ObserveErrorChanged.Select(x => x?.OfType()?.FirstOrDefault())`. The above code is as below: +In ReactiveProperty v7.0.0 or later, use the `ObserveValidationErrorMessage` extension method instead of `ObserveErrorChanged.Select(x => x?.OfType()?.FirstOrDefault())`. The code above is shown below: ```csharp class ViewModel @@ -312,10 +312,10 @@ class ViewModel } ``` -Next property is `ObserveHasErrors`. `ObserveHasErrors` property type is `IObservable`. -In the popular input form case, combining `ObserveHasErrors` property values is very useful. +The next property is `ObserveHasErrors`. The `ObserveHasErrors` property type is `IObservable`. +In a typical input form, combining `ObserveHasErrors` property values is very useful. -This sample program creates the `HasErrors` property that is of type `ReactiveProperty` that combine two `ReactiveProperty`'s `ObserveHasErrors` properties. +This sample program creates a `HasErrors` property of type `ReactiveProperty` that combines two `ReactiveProperty` `ObserveHasErrors` properties. ```csharp public class ViewModel @@ -359,7 +359,7 @@ public class ViewModel mc:Ignorable="d"> @@ -382,7 +382,7 @@ public class ViewModel ![HasErrors2](./images/haserrors-handling2.png) -The last property is `HasErrors`. It is a just `bool` property. +The last property is `HasErrors`. It is just a `bool` property. ```csharp public class ViewModel @@ -414,8 +414,8 @@ public class ViewModel ### Don't need an initial validation error -In default behavior, `ReactiveProperty` reports errors when validation logic is set. -If you don't need initial validation errors, then you can skip the error. +In the default behavior, `ReactiveProperty` reports errors when validation logic is set. +If you don't need initial validation errors, you can skip the error. Just call the `Skip` method. ```csharp @@ -468,17 +468,17 @@ class ViewModel } ``` -What's different between `Skip` and `IgnoreInitialValidationError`? +What's the difference between `Skip` and `IgnoreInitialValidationError`? In the `IgnoreInitialValidationError` case, the `ReactiveProperty` class doesn't report an error of initial value. In the `Skip` case, it just ignores the error event. -This difference is important on the supported platform of `INotifyDataErrorInfo` like WPF. -`Skip` approach will be fed back to the UI by a red border. -`IgnoreInitialValidationError` approach does not feed back to the UI. +This difference is important on platforms that support `INotifyDataErrorInfo`, such as WPF. +The `Skip` approach is still reflected in the UI as a red border. +The `IgnoreInitialValidationError` approach is not reflected in the UI. ## The mode of `ReactiveProperty` -`ReactiveProperty` class calls the `OnNext` callback when `Subscribe` method called. +The `ReactiveProperty` class calls the `OnNext` callback when the `Subscribe` method is called. ```csharp var x = new ReactiveProperty("initial value"); @@ -486,21 +486,21 @@ x.Subscribe(x => Console.WriteLine(x)); // -> initial value ``` You can change this behavior when a `ReactiveProperty` instance is created. -the constructor and `ToReactiveProperty` methods have `ReactivePropertyMode` arguments. +The constructor and `ToReactiveProperty` methods have `ReactivePropertyMode` arguments. They can be set to the following values. - `ReactivePropertyMode.None` - - ReactiveProperty doesn't call the `OnNext` callback when `Subscribe` method is call. And calls the `OnNext` callback if the same value is set. + - ReactiveProperty doesn't call the `OnNext` callback when the `Subscribe` method is called. It calls the `OnNext` callback if the same value is set. - `ReactivePropertyMode.DistinctUntilChanged` - - This doesn't call `OnNext` callback if same value set. + - This doesn't call the `OnNext` callback if the same value is set. - `ReactivePropertyMode.RaiseLatestValueOnSubscribe` - - This calls `OnNext` callback when `Subscribe` method call. + - This calls the `OnNext` callback when the `Subscribe` method is called. - `ReactivePropertyMode.Default` - - It is default value. It is same as `ReactivePropertyMode.DistinctUntilChanged | ReactivePropertyMode.RaiseLatestValueOnSubscribe`. + - It is the default value. It is the same as `ReactivePropertyMode.DistinctUntilChanged | ReactivePropertyMode.RaiseLatestValueOnSubscribe`. - `ReactivePropertyMode.IgnoreInitialValidationError` - Ignore initial validation error. -If you don't need this behavior, then you can set `ReactivePropertyMode.None` value. +If you don't need this behavior, you can set the `ReactivePropertyMode.None` value. ```csharp var rp = new ReactiveProperty("initial value", mode: ReactivePropertyMode.None); @@ -510,8 +510,8 @@ rp.Value = "initial value"; // -> initial value ## `ForceNotify` -If want to push the value forcibly, then can use the `ForceNotify` method. -This method pushes the value to subscribers, and raise a `PropertyChanged` event. +If you want to push the value forcibly, you can use the `ForceNotify` method. +This method pushes the value to subscribers and raises a `PropertyChanged` event. ```csharp var rp = new ReactiveProperty("value"); @@ -521,7 +521,7 @@ rp.PropertyChanged += (_, e) => Console.WriteLine($"{e.PropertyName} changed"); rp.ForceNotify(); ``` -Output is as below. +The output is below. ``` value # first subscribe @@ -531,7 +531,7 @@ Value changed # by the ForceNotify method ## Change comparer logic -Can change comparer logic by the equalityComparer argument of constructor and factory methods. +You can change comparer logic by using the equalityComparer argument of constructors and factory methods. For example, ignore case comparer: @@ -563,8 +563,8 @@ source.OnNext("Hello japan"); // change to "Hello japan" from "Hello world" ## `ReadOnlyReactiveProperty` class If you never set `Value` property, then you can use `ReadOnlyReactiveProperty` class. -This class can't set the property, and other behavior is same ReactiveProperty class. -`ReadOnlyReactiveProperty` class is created from `ToReadOnlyReactiveProperty` extension method. +This class can't set the property, and other behavior is the same as the ReactiveProperty class. +The `ReadOnlyReactiveProperty` class is created from the `ToReadOnlyReactiveProperty` extension method. See below. @@ -573,7 +573,7 @@ public class ViewModel { public ReactiveProperty Input { get; } - // Output never set value. + // Output never sets the value. public ReadOnlyReactiveProperty Output { get; } public ViewModel() @@ -590,8 +590,8 @@ public class ViewModel ## `Unsubscribe` `ReactiveProperty` class implements the `IDisposable` interface. -When the `Dispose` method called, `ReactiveProperty` class releases all subscriptions. -In other instance's events subscribe, you should call the `Dispose` method at the end of the ViewModel lifecycle. +When the `Dispose` method is called, the `ReactiveProperty` class releases all subscriptions. +When subscribing to another instance's events, you should call the `Dispose` method at the end of the ViewModel lifecycle. ```csharp public class ViewModel : IDisposable @@ -607,7 +607,7 @@ public class ViewModel : IDisposable public void Dispose() { - // Unsbscribe + // Unsubscribe Time.Dispose(); } } diff --git a/docs/docs/features/ReactivePropertySlim.md b/docs/docs/features/ReactivePropertySlim.md index 490b902d..7e90aa20 100644 --- a/docs/docs/features/ReactivePropertySlim.md +++ b/docs/docs/features/ReactivePropertySlim.md @@ -1,9 +1,9 @@ # ReactivePropertySlim -`ReactivePropertySlim` is a lightweight version `ReactiveProperty`. +`ReactivePropertySlim` is a lightweight version of `ReactiveProperty`. `ReactivePropertySlim` is five times faster than `ReactiveProperty`. -`ReactivePropertySlim` provides following features: +`ReactivePropertySlim` provides the following features: - Implements `INotifyPropertyChanged` interface. - Implements `IObservable` interface. @@ -11,8 +11,8 @@ - Provides a `ForceNotify` method. `ReactivePropertySlim` is high performance. -The following table is a result of the benchmark test between `ReactiveProperty` and `ReactivePropertySlim`. -ReactivePropertySlim is 16 times performance to create an instance, 36 times performance on the primary use case. +The following table shows benchmark results between `ReactiveProperty` and `ReactivePropertySlim`. +ReactivePropertySlim is 16 times faster to create an instance and 36 times faster in the primary use case. ``` | Method | Mean | Error | StdDev | @@ -23,7 +23,7 @@ ReactivePropertySlim is 16 times performance to create an instance, 36 times per | BasicForReactivePropertySlim | 68.773 ns | 1.3841 ns | 1.8478 ns | ``` -This class can be used like a `ReactiveProperty`. +This class can be used like `ReactiveProperty`. ```csharp var rp = new ReactivePropertySlim("neuecc"); @@ -32,7 +32,7 @@ rp.Value = "xin9le"; rp.Value = "okazuki"; ``` -Output is as below. +The output is below. ``` neuecc-san @@ -40,14 +40,14 @@ xin9le-san okazuki-san ``` -One difference to `ReactiveProperty` is that `ReactivePropertySlim` can't be created from `IObservable`. +One difference from `ReactiveProperty` is that `ReactivePropertySlim` can't be created from `IObservable`. ```csharp // It isn't valid code. var rp = Observable.Interval(TimeSpan.FromSeconds(1)).ToReactivePropertySlim(); ``` -If you want to create Slim class's instance from `IObservable`, then use the `ToReadOnlyReactivePropertySlim` extension method. +If you want to create an instance of a Slim class from `IObservable`, use the `ToReadOnlyReactivePropertySlim` extension method. ```csharp var rp = Observable.Interval(TimeSpan.FromSeconds(1)).ToReadOnlyReactivePropertySlim(); @@ -56,7 +56,7 @@ var rp = Observable.Interval(TimeSpan.FromSeconds(1)).ToReadOnlyReactiveProperty ## Dispatch to UI thread `ReactivePropertySlim` class doesn't dispatch to the UI thread automatically. -If you need this, then use the `ReactiveProperty` or dispatch to the UI thread explicitly. +If you need this, use `ReactiveProperty` or dispatch to the UI thread explicitly. ```csharp var rp = Observable.Interval(TimeSpan.FromSeconds(1)) diff --git a/docs/docs/features/Work-together-with-plane-model-layer-objects.md b/docs/docs/features/Work-together-with-plane-model-layer-objects.md index bf563528..9645bd23 100644 --- a/docs/docs/features/Work-together-with-plane-model-layer-objects.md +++ b/docs/docs/features/Work-together-with-plane-model-layer-objects.md @@ -1,15 +1,15 @@ -# Work together with POCO +# Work with POCOs -The classes of this library can work together with POCO classes. +The classes in this library can work with POCO classes. ## Connect to classes that implement `INotifyPropertyChanged` -ReactiveProperty provides many features that synchronize to POCO class instance. +ReactiveProperty provides many features that synchronize with a POCO class instance. ### One-way synchronization -`ToObserveProperty` extension method of `INotifyPropertyChanged` interface convert `INotifyPropertyChanged` to `IObservable`. -`IObservable` can be converted to `ReactiveProperty`. It means that you can have one-way synchronization to `ReactiveProperty` from `INotifyPropertyChanged`. +The `ObserveProperty` extension method of the `INotifyPropertyChanged` interface converts `INotifyPropertyChanged` to `IObservable`. +`IObservable` can be converted to `ReactiveProperty`. This means that you can have one-way synchronization from `INotifyPropertyChanged` to `ReactiveProperty`. For example: @@ -51,7 +51,7 @@ public class Person : BindableBase } ``` -One-way synchronization is the following code. +One-way synchronization is written as follows. ```csharp // using Reactive.Bindings.Extensions; @@ -100,7 +100,7 @@ public class ViewModel } ``` -UWP platfrom example is below. +Example for UWP is below. MainPage.xaml.cs @@ -142,7 +142,7 @@ MainPage.xaml ![Two-way synchronization](./images/work-together-with-poco-two-way-synchronization.gif) -`ToSynchronizedReactiveProperty` extension method can add convert logic and convert-back logic. +The `ToReactivePropertyAsSynchronized` extension method can add conversion and convert-back logic. ```csharp public class ViewModel @@ -162,7 +162,7 @@ public class ViewModel ![Convert and convert-back](./images/work-together-with-poco-two-way-synchronization-and-convert.gif) -When an `ignoreValidationErrorValue` argument set to true, it stops the synchronization if a validation error occurred. +When the `ignoreValidationErrorValue` argument is set to true, it stops synchronization if a validation error occurs. ```csharp public class ViewModel @@ -185,7 +185,7 @@ public class ViewModel ![Ignore validation error value](./images/work-together-with-poco-two-way-synchronization-and-ignoreValidationError.gif) -You can also use LINQ to convert, like below: +You can also use LINQ to convert values, as shown below: ```csharp public class ViewModel @@ -210,13 +210,13 @@ public class ViewModel } ``` -If you would like to use `ReactivePropertySlim`, then you can use `ToReactivePropertySlimAsSynchronized` extension method. -It is similer as `ToReactivePropertyAsSynchronized`. There isn't `ignoreValidationErrorValue` and `scheduler` arguments, otherwise are same. +If you want to use `ReactivePropertySlim`, you can use the `ToReactivePropertySlimAsSynchronized` extension method. +It is similar to `ToReactivePropertyAsSynchronized`. The `ignoreValidationErrorValue` and `scheduler` arguments are not available; otherwise, they are the same. ### One-way synchronization to source The `FromObject` method creates a `ReactiveProperty` instance from a POCO. -This method sets the `Value` property from the POCO when the `ReactiveProperty` instance is created, when the `Value` property updated, then update the source value. +This method sets the `Value` property from the POCO when the `ReactiveProperty` instance is created. When the `Value` property is updated, it updates the source value. ```csharp using Reactive.Bindings; @@ -245,7 +245,7 @@ namespace ReactivePropertyEduApp ### Nested property path -`ObserveProperty`, `ToReactivePropertyAsSynchronized`, `ToReactivePropertySlimAsSynchronized` and `FromObject` support to nested property path like `x => x.Child.Name`. -Suppose the value of any property in the path is null. In that case, ReactiveProperty is set `default(T)` to Value property (this is a case of to ReactiveProperty from source property), and ReactiveProperty stops synchronization to the source property (this is a case of to source property from ReactiveProperty). +`ObserveProperty`, `ToReactivePropertyAsSynchronized`, `ToReactivePropertySlimAsSynchronized`, and `FromObject` support nested property paths like `x => x.Child.Name`. +If the value of any property in the path is null, ReactiveProperty sets `default(T)` to the Value property when synchronizing from the source property to ReactiveProperty, and ReactiveProperty stops synchronization to the source property when synchronizing from ReactiveProperty to the source property. -After the value is updated to not null value, ReactiveProperty re-start synchronization. +After the value is updated to a non-null value, ReactiveProperty restarts synchronization. diff --git a/docs/docs/getting-started/add-snippets.md b/docs/docs/getting-started/add-snippets.md index 753e0faf..6248be60 100644 --- a/docs/docs/getting-started/add-snippets.md +++ b/docs/docs/getting-started/add-snippets.md @@ -3,11 +3,11 @@ We provide snippet files [here](https://github.com/runceel/ReactiveProperty/tree/master/Snippet). If you want to use them, please install the snippets manually. -The following document lists steps to add a snippet to Visual Studio. +The following document lists the steps to add a snippet to Visual Studio. - [Add a code snippet to Visual Studio](https://docs.microsoft.com/en-us/visualstudio/ide/walkthrough-creating-a-code-snippet?view=vs-2019#add-a-code-snippet-to-visual-studio) -## Provide snippets +## Provided snippets - `rprop` ```csharp diff --git a/docs/docs/getting-started/avalonia.md b/docs/docs/getting-started/avalonia.md index 9432704a..d255b1ed 100644 --- a/docs/docs/getting-started/avalonia.md +++ b/docs/docs/getting-started/avalonia.md @@ -1,18 +1,18 @@ -# Getting start for Avalonia UI +# Getting started with Avalonia UI -Avalonia is a cross platform .NET UI Framework! +Avalonia is a cross-platform .NET UI framework! -See below: +See the following: [Avalonia UI Framework](http://avaloniaui.net/) ## Create a project -- Create an Avalonia .NET Core Application project.(Of course! An Avalonia application project can use the same ReactiveProperty as .NET Core projects.) +- Create an Avalonia .NET Core Application project. (Of course, an Avalonia application project can use the same ReactiveProperty as .NET Core projects.) - Install ReactiveProperty from NuGet. -## Edit codes +## Edit the code - Create a MainWindowViewModel.cs file. -- Edit files like following. +- Edit the files as follows. MainWindowViewModel.cs ```csharp @@ -40,8 +40,8 @@ namespace AvaloniaApp MainWindow.xaml ```xml - @@ -54,9 +54,9 @@ MainWindow.xaml ``` -## Launch the application. +## Launch the application -After launching the app, you can see the below window. -The output value was displayed to upper case, after 1sec from the input. +After launching the app, you can see the window below. +The output value is displayed in uppercase 1 second after the input. ![Launch the app](./images/launch-avalonia-app.gif) diff --git a/docs/docs/getting-started/blazor.md b/docs/docs/getting-started/blazor.md index e3633113..0e56c36b 100644 --- a/docs/docs/getting-started/blazor.md +++ b/docs/docs/getting-started/blazor.md @@ -1,23 +1,23 @@ -# Getting start for Blazor +# Getting started with Blazor -Blazor is for development framework for Single Page Application using C#. +Blazor is a development framework for single-page applications using C#. -See below: +See the following: [ASP.NET Core Blazor](https://docs.microsoft.com/en-us/aspnet/core/blazor/) -ReactiveProperty also works both of Blazor Server and Blazor WebAssembly. But Blazor WebAssembly doen't support all operations of Reactive Extensions. For example, `Delay` extension method doesn't work on Blazor WASM. -If you want to use ReactiveProperty on Blazor WASM, then please care to not use unsupported features. +ReactiveProperty works with both Blazor Server and Blazor WebAssembly, but Blazor WebAssembly doesn't support all Reactive Extensions operations. For example, the `Delay` extension method doesn't work on Blazor WASM. +If you want to use ReactiveProperty on Blazor WASM, be careful not to use unsupported features. ## Create a project - Create a Blazor Server or WebAssembly project. - Install ReactiveProperty.Blazor package from NuGet. -## Edit codes +## Edit the code -- Create a class what name is `IndexViewModel`. -- Edit the class like fillowing: +- Create a class named `IndexViewModel`. +- Edit the class as follows: ```csharp using Reactive.Bindings; @@ -39,7 +39,7 @@ public class IndexViewModel : IDisposable Input = new ReactivePropertySlim("") .AddTo(_disposable); Output = Input - .Delay(TimeSpan.FromSeconds(2)) // Important! Delay method doesn't work on Blazor WASM. If you are working on WASM, then please remove this line. + .Delay(TimeSpan.FromSeconds(2)) // Important! The Delay method doesn't work on Blazor WASM. If you are working on WASM, remove this line. .Select(x => x.ToUpperInvariant()) .ToReadOnlyReactivePropertySlim("") .AddTo(_disposable); @@ -49,7 +49,7 @@ public class IndexViewModel : IDisposable } ``` -- Edit Index.razor like below: +- Edit Index.razor as follows: ```csharp @page "/" @@ -75,7 +75,7 @@ public class IndexViewModel : IDisposable _viewModel = new IndexViewModel() .AddTo(_disposable); - // Observe changing Output property, and call StateHasChanged on UI thread. + // Observe changes to the Output property, and call StateHasChanged on the UI thread. _viewModel.Output .Subscribe(x => InvokeAsync(StateHasChanged)) .AddTo(_disposable); @@ -85,9 +85,9 @@ public class IndexViewModel : IDisposable } ``` -- Launch this app +- Launch the app. -You can see below result: +You can see the following result: ![Launch the app](./images/blazor-helloworld.png) @@ -95,20 +95,20 @@ You can see below result: ### Dependency Injection -If you want to inject ViewModels to page, then please register ViewModels to DI container on Program.cs like below: +If you want to inject ViewModels into a page, register them in the DI container in Program.cs as follows: ```csharp builder.Services.AddTransient(); ``` -And inject to page like `@inject IndexViewModel _viewModel`. +Then inject the ViewModel into the page with `@inject IndexViewModel _viewModel`. ### Integrate validation feature -If you want to use validation feature of ReactiveProperty with Blazor's EditForm component, then you can use `Reactive.Bindings.Components.ReactivePropertiesValidator` component. +If you want to use ReactiveProperty validation features with Blazor's EditForm component, you can use the `Reactive.Bindings.Components.ReactivePropertiesValidator` component. -`ReactivePropertiesValidator` can be used same as `DataAnnotationsValidator` component like below: +`ReactivePropertiesValidator` can be used in the same way as the `DataAnnotationsValidator` component, as shown below: ```csharp @@ -123,4 +123,4 @@ If you want to use validation feature of ReactiveProperty with Blazor's EditForm ``` -Please see Blazor's sample app under Samples/Blazor folder to know more details. The page using it is Pages/Index.razor page of BlazorSample.Shared project. +See the Blazor sample app under the Samples/Blazor folder for more details. The page that uses it is Pages/Index.razor in the BlazorSample.Shared project. diff --git a/docs/docs/getting-started/uno-platform.md b/docs/docs/getting-started/uno-platform.md index 0d764b01..353ddb97 100644 --- a/docs/docs/getting-started/uno-platform.md +++ b/docs/docs/getting-started/uno-platform.md @@ -1,16 +1,16 @@ -# Getting start for Uno Platform +# Getting started with Uno Platform -Uno Platform is a developing platform for cross-platform apps. -Uno builds UWP apps projects as Android apps, iOS apps, WebAssembly apps, Linux apps and macOS apps. +Uno Platform is a development platform for cross-platform apps. +Uno builds UWP app projects as Android, iOS, WebAssembly, Linux, and macOS apps. -Please install Uno Platform extension to Visual Studio, before this getting start. +Install the Uno Platform extension for Visual Studio before you start this getting-started guide. ## Create a project - Create a Cross-Platform App (Uno Platform) project. -- Install ReactiveProperty package to all projects from NuGet. -- Install Reactive.Wasm package to YourProjectName.Wasm project. +- Install the ReactiveProperty package in all projects from NuGet. +- Install the Reactive.Wasm package in the YourProjectName.Wasm project. -## Edit codes +## Edit the code - Edit Program.cs in the Wasm project to support Reactive Extensions. ```csharp @@ -37,8 +37,8 @@ namespace GettingStartedUno.Wasm } ``` -- Create a MainPageViewModel.cs file to the Shared project. -- Edit files like following. +- Create a MainPageViewModel.cs file in the Shared project. +- Edit the files as follows. MainPageViewModel.cs ```csharp @@ -114,8 +114,8 @@ MainPage.xaml ## Launch the application -After launching the app, you can see the below apps on each platforms. -The output value was displayed to upper case, after 1sec from the input. +After launching the app, you can see the apps below on each platform. +The output value is displayed in uppercase 1 second after the input. ### WebAssembly @@ -132,4 +132,3 @@ The output value was displayed to upper case, after 1sec from the input. ### iOS TBD - diff --git a/docs/docs/getting-started/uwp.md b/docs/docs/getting-started/uwp.md index 327be679..0741034c 100644 --- a/docs/docs/getting-started/uwp.md +++ b/docs/docs/getting-started/uwp.md @@ -1,16 +1,16 @@ -# Getting start for UWP +# Getting started with UWP ## Create a project - Create a Blank App (Universal Windows) project. - - Set the `Minimum version` item to Windows 10 Fall Creators Update (1.0.: Build 16299). + - Set the `Minimum version` item to Windows 10 Fall Creators Update (10.0; Build 16299). ![Target version](./images/uwp-target-version.png) - Install ReactiveProperty package from NuGet. -## Edit codes +## Edit the code - Create a MainPageViewModel.cs file. -- Edit files like following. +- Edit the files as follows. MainPageViewModel.cs ```csharp @@ -86,7 +86,7 @@ MainPage.xaml ## Launch the application -After launching the app, you can see the below window. -The output value was displayed to upper case, after 1sec from the input. +After launching the app, you can see the window below. +The output value is displayed in uppercase 1 second after the input. ![Launch the app](./images/launch-uwp-app.gif) diff --git a/docs/docs/getting-started/wpf.md b/docs/docs/getting-started/wpf.md index a1b7c3bd..5ecfdab8 100644 --- a/docs/docs/getting-started/wpf.md +++ b/docs/docs/getting-started/wpf.md @@ -1,14 +1,14 @@ -# Getting start for WPF +# Getting started with WPF ## Create a project - Create a WPF App (.NET Framework) project. - - Have to use .NET Framework 4.7.2 or later, or .NET Core 3.0 or later. + - You must use .NET Framework 4.7.2 or later, or .NET Core 3.0 or later. - Install ReactiveProperty.WPF package from NuGet. -## Edit codes +## Edit the code -- Add Startup event handler on App class. -- Add code to initialize scheduler for ReactiveProperty classes. +- Add the Startup event handler to the App class. +- Add code to initialize the scheduler for ReactiveProperty classes. ```csharp using System; @@ -34,7 +34,7 @@ namespace WpfApp1 ``` - Create a MainWindowViewModel.cs file. -- Edit files like following. +- Edit the files as follows. MainWindowViewModel.cs ```csharp @@ -91,9 +91,9 @@ MainWindow.xaml ``` -## Launch the application. +## Launch the application -After launching the app, you can see the below window. -The output value was displayed to upper case, after 1sec from the input. +After launching the app, you can see the window below. +The output value is displayed in uppercase 1 second after the input. ![Launch the app](./images/launch-wpf-app.gif) diff --git a/docs/docs/getting-started/xf.md b/docs/docs/getting-started/xf.md index e38fd1c5..345c9c26 100644 --- a/docs/docs/getting-started/xf.md +++ b/docs/docs/getting-started/xf.md @@ -1,17 +1,17 @@ -# Getting start for Xamarin.Forms +# Getting started with Xamarin.Forms ## Create a project -- Create a Cross Platform app (Xamarin.Forms) project. -- Setting the `New Cross Platform App` dialog like following. - Choose the .NET Standard project. Of course you can select shared project. +- Create a Cross-Platform app (Xamarin.Forms) project. +- Configure the `New Cross Platform App` dialog as follows. + Choose the .NET Standard project. Of course, you can select a shared project. ![New Cross Platform App dialog](./images/xf-create-project.png) -- Install ReactiveProperty to all projects from NuGet. +- Install ReactiveProperty in all projects from NuGet. -### Edit codes +## Edit the code -- Create MainPageViewModel.cs to .NET Standard project. -- Edit file like following. +- Create MainPageViewModel.cs in the .NET Standard project. +- Edit the file as follows. MainPageViewModel.cs ```csharp @@ -55,12 +55,11 @@ MainPage.xaml ``` -# Launch the application. +## Launch the application -After launching the app, you can see below window. -The output value was displayed to upper case, after 1sec from the input. +After launching the app, you can see the window below. +The output value is displayed in uppercase 1 second after the input. ![Launch the app](./images/launch-xf-app-android.gif) ![Launch the app](./images/launch-xf-app-uwp.gif) - diff --git a/docs/docs/samples.md b/docs/docs/samples.md index 52ce1c8e..3e6bb45a 100644 --- a/docs/docs/samples.md +++ b/docs/docs/samples.md @@ -1,15 +1,15 @@ Sample programs: -You can see sample programs opening ReactiveProperty-Samples.sln. There are: +You can see sample programs by opening ReactiveProperty-Samples.sln. There are: - ReactivePropertySamples
- There are sample pages for explaining basic features. Please set as a startup project to `BasicSample/ReactivePropertySamples.WPF`, then launch the app. You will see the following: + There are sample pages that explain basic features. Set `BasicSample/ReactivePropertySamples.WPF` as the startup project, and then launch the app. You will see the following: ![The sample app](./images/sampleapp.gif) - The ViewModel and Model classes are ReactivePropertySamples.Shared project. + The ViewModel and Model classes are in the ReactivePropertySamples.Shared project. - Reactive.Todo
- This sample app is created by ReactiveProperty and [Prism](https://prismlibrary.com/index.html). This is a small sample app like [TodoMVC](http://todomvc.com/). + This sample app is created with ReactiveProperty and [Prism](https://prismlibrary.com/index.html). It is a small sample app similar to [TodoMVC](http://todomvc.com/). ![The sample app](./images/todoapp.gif) - You can launch the app via `WithPrism/Reactive.Todo` project. + You can launch the app via the `WithPrism/Reactive.Todo` project. From 1be7a24e4bbf2aa88cd99db1662b06dd62c673bc Mon Sep 17 00:00:00 2001 From: Kazuki Ota <117221407+kaota_microsoft@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:31:27 +0900 Subject: [PATCH 2/2] Link README to repository docs - Replace the old Qiita Japanese link with docs/docs-ja. - Point documentation links to Markdown files in this repository. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 128742ec..899284ec 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[Japanese](https://qiita.com/okazuki/items/7572f46848d0e93516b1) +[日本語ドキュメント](docs/docs-ja/README.md) # ReactiveProperty @@ -43,7 +43,7 @@ public class MainPageViewModel It is really simple and understandable (I think!). Because there are NOT any base classes and interfaces. Just has declarative code between Input property and Output property. -All steps are written in the "Getting Started" section in the [ReactiveProperty documentation](https://github.com/runceel/ReactiveProperty/tree/main/docs). +All steps are written in the "Getting Started" section in the [ReactiveProperty documentation](docs/docs/README.md). The concept of ReactiveProperty is simple that is a core class what name is `ReactiveProperty[Slim]`, it is just a wrap class what has a value, and implements `IObservable` and `INotifyPropertyChanged`, `IObservable` is for connect change event of the property value to Rx LINQ method chane, `INotifyPropertyChanged` is for data binding system such as WPF, WinUI and MAUI. @@ -142,7 +142,8 @@ ReactiveProperty doesn't provide base class by ViewModel, which means that React ## Documentation -[ReactiveProperty documentation](https://github.com/runceel/ReactiveProperty/tree/main/docs) +- [ReactiveProperty documentation](docs/docs/README.md) +- [日本語ドキュメント](docs/docs-ja/README.md) ## NuGet packages