顯示具有 ReactiveUI 標籤的文章。 顯示所有文章
顯示具有 ReactiveUI 標籤的文章。 顯示所有文章

2.16.2021

.Net Desktop Skeleton

.Net Desktop UI Skeleton Project

Recently I created two skeleton project for c# desktop environment,

Prism is WPF only, but ReactiveUI can use with WPF and WinForm.

The prism skeleton is based on prism template pack, but I upgrade the prism version to v8.0.

Prism Shell

ReactiveUI Shell

How To Start

Cloning the repository, either Prism or ReactiveUI

  1. Clone this repository:
    1. Prism: git clone https://github.com/liaochihung/PrismBlankApp.git your-project-name
    2. ReactiveUI git clone https://github.com/liaochihung/ReactiveUIBlankApp.git your-project-name
  2. Move to the project directory: cd your-project-name
  3. Create your own repository and cleaning the bootstrap project history:
    1. Remove previous Git history in order to do not add the bootstrap repo noise in your project: rm -rf .git
    2. Initialize your own Git repository: git init
    3. Add the bootstrap files: git add .
    4. Commit
    5. Add your remote repository: git remote add origin git@github.com:your-name/your-project-name
    6. Upload your local commits to the new remote repo: git push -u origin master
  4. Start coding!

Written with StackEdit.

7.15.2017

ReactiveUI的ReactiveCommand

RxUI的命令和其它MVVM中實作ICommand的最大不一樣的地方就是 – 它可以有回傳值(當然,是reactive式的IObservable)!

之前剛學Prism(under Xarmin.Forms)的時候,在DelegateCommand的說明那裡找不到回傳值的用法,再看了一次RxUI的API說明,才發覺到正常來說,ICommand介面的實作中基本上就是執行動作,其概念中是不管動作的執行結果的,不管是Prism中的DelegateCommand或MvvmLight中的RelayCommand都是如此。

而在Reactive的世界中,實作時思考的方式是需要改變的,如上一篇blog介紹的範例中,RetrieveWordCommand的定義如下:

public ReactiveCommand<Unit, List<WordOption>> RetrieveWordCommand

ReactiveCommand<Unit, List<WordOption>>表示此命令不需要代入參數(Unit),但會傳回一IObservable<List<WordOption>>,因此,在建立此命令的程式中:

RetrieveWordCommand = ReactiveCommand.CreateFromTask<List<WordOption>>(async (arg) =>
{
    // ... 
    return wordResults.Select(wr => new WordOption{ //...    }
}, canRetrieve);

它回傳了一個可觀察佇列,且在接續的程式中,定義了當其命令完成後的動作:

RetrieveWordCommand
    .ObserveOn(RxApp.MainThreadScheduler)
    .Subscribe(wordOptions =>
    {
        // ...
    });

程式中,ObserveOn表示後續動作會在其指定的執行緒上被執行,此例中是UI主執行緒,因為它會修改被綁定到View上的屬性,而Subscribe中則會收到原先定義的回傳值List<WordOpton>當參數。

Written with StackEdit.

7.10.2017

在Xamarin.Forms上的MVVM

ReactiveUI

最近在看Xamarin.Forms的MVVM framework,個人徧好ReactiveUI,於是找到了這篇介紹 –
A Simple Vocabulary App Using ReactiveUI and Xamarin Forms,它實作了一個猜字的遊戲,如下圖:
enter image description here

基本上它使用的方式和在Windows平台上沒有什麼差別,仍是focus在view和viewmodel的互動,沒有其它的東西,如頁面的routing、Navigation、IoC的使用等。

此程式主要的動作在WordPickViewModel中:

public WordPickViewModel(IWordRepository wordRepository)
{
    _wordRepository = wordRepository;
    WordOptions = new ObservableCollection<WordOption>();
    CorrectPct = "0%";

    // 設定狀態的條件,型別為IObservable<bool>
    var canRetrieve = this.WhenAnyValue(x => x.CanRetrieve).Select(x => x);
    var canSelect = this.WhenAnyValue(x => x.CanRetrieve).Select(x => !x);

    // 原文使用CreateAsyncTask,目前版本v7.4,要改用如下函式
    // 定義命令,此命令完成後會回傳一List<WordOption>>
    RetrieveWordCommand = ReactiveCommand.CreateFromTask<List<WordOption>>(async (arg) =>
    {
        var wordResults = await _wordRepository.GetWords(_rangeFloor, RangeCeiling);

        return wordResults.Select(wr =>
            new WordOption
            {
                Word = wr.Name,
                Definition = wr.Definition,
                WordId = wr.Id
            }).ToList();
    }, canRetrieve);

    // 原文使用CreateAsyncTask,目前版本v7.4,要改用如下函式
    SelectAnswerCommand = ReactiveCommand.CreateFromTask(async arg =>
    {
        await HandleItemSelectedAsync(arg);
    }, canSelect);

    // ObserveOn表示後續動作會在其指定的執行緒上被執行
    // Subscribe定義命令完成後的處理
    RetrieveWordCommand
        .ObserveOn(RxApp.MainThreadScheduler)
        .Subscribe(wordOptions =>
        {
            _timerCancellationToken = new CancellationTokenSource();
            NextRange();
            CanRetrieve = false;
            WordOptions.Clear();

            // randomly determine the word to challenge user with
            var rand = new Random();
            var challengeWord = wordOptions[rand.Next(wordOptions.Count)];
            ChallengeWord = $"\"{challengeWord.Word}\"";

            foreach (var item in wordOptions)
            {
                var isAnswer = item.WordId == challengeWord.WordId;
                item.IsAnswer = isAnswer;
                item.Image = isAnswer ? "check.png" : "x.png";
                WordOptions.Add(item);
            }

            TimerCountdown = 10;
            Device.StartTimer(new TimeSpan(0, 0, 1), () =>
            {
                if (_timerCancellationToken.IsCancellationRequested)
                {
                    return false;
                }

                if (TimerCountdown == 0)
                {
                    ProcessAnswer();
                    return false;
                }
                TimerCountdown--;
                return true;
            });
        });

    //Behaviors
    this.WhenAnyValue(x => x.Begin).InvokeCommand(RetrieveWordCommand);
}

不過程式中對xaml頁面的部份,都是用code behind中的程式碼產生,View和ViewModel的綁定也是,個人比較不建議這個方式,這讓xaml的優勢不再。

xamvvm

這個輕量級的framework填補了ReactiveUI缺少的部份,因此兩個可以一起合用

Prism

Xarmin.Forms上的Prism不像WPF環境下那麼龐大,它精簡了很多,當然也比ReactiveUI完整多了,若是需要可以互相配合使用。

這邊有一個不錯的教學影片可參考.。

MVVMLight

在學Wpf時主要都是使用這一個,在Xamarin.Forms上它也滿適合的,輕量且剛剛好的功能,作者也寫了一個跨平台的範例程式,另在channel9有影片介紹

另有以此為基礎的延伸框架:

Xarch-starter

可以把它當作基楚的樣板來擴充,wiki中也提到了另一個更進階的框架 - Exrin,也可以參考看看…

這些framework都滿足了最基本的需求,不過大部份的說明文件都不甚豐富,想要應用都需要一點時間;當然也可以不依靠這些framework,之前在學wpf時就看過一系列的教學沒有應用任何framework,不過後來程式一步步擴大,不斷的重構後就出來了另一個框架了,就學習層面來說這很不錯,但還是要看個人的取舍了XD。

Written with StackEdit.

6.21.2017

ReactiveUI 的 Interactions

在使用MVVM開發方式時,ViewModel總是會遇到需要由使用者確認的情況,如由使用者確認是否執行刪除動作。這時最簡單的方式可能是直接在ViewModel中顯示訊息視窗,但會導致ViewModel本身和訊息視窗的UI架構綁定 – 這又違反了我們使用MVVM的初衷,且難以被測試。

在MVVMLight中,這種狀況通常會建議使用介面的方式來鬆綁,如使用並注入一個特定的IDialogService服務,而ReactiveUI提供了另一個方式 – Interactions,連結為其文件位置,不過範例程式不完整,可看下列提供的程式碼。

public class ViewModel : ReactiveObject
{
    // 不帶入參數,將回傳bool的一個Interaction
    public Interaction<Unit, bool> ConfirmDel { get; }
    // 實際和View綁定的命令
    public ReactiveCommand DelCommand { get; private set; }

    public ViewModel()
    {
        ConfirmDel = new Interaction<Unit, bool>();
        DelCommand = ReactiveCommand.CreateFromTask(async () =>
        {
            var delete = await Confirm.Handle(Unit.Default);
            if (delete == false)
                return;

            // del stuff...

        }, null);
    }
}

// 原文說明中未實作 IViewFor<>
public class View : IViewFor<ViewModel>
{
    public View()
    {
        this.WhenActivated(d =>
        {
            // _vm 為實作IViewFor時建立的ViewModel實體,可改為自己的命名
            d(_vm.ConfirmDel.RegisterHandler(async inct =>
            {
                var result = 
                    MessageBox.Show(
                        Properties.Resources.AreYouSureToDelThisData, 
                        Properties.Resources.ConfirmDelete, 
                        MessageBoxButton.YesNo, MessageBoxImage.Question);

                inct.SetOutput(result == MessageBoxResult.Yes);
            }));
        });
    }
}

原文中另有全域使用的方式,可供參考。

Written with StackEdit.

4.19.2017

Winform、WPF共用ViewModel

本文介紹在WPF和WinForms中使用ReactiveUI來共用ViewModel。ViewModel來自ReactiveUI說明文件中的LoginViewModel,但加上了ReactiveUI.Fody的使用。

一般來說,你找得到的大多數.Net MVVM Framework都是基於XAML環境的,以前剛看到ReactiveUI時就想測試它的共用性,如下所示。

下列程式碼基本上展示了在WinForm和WPF程式中,共用同一個ViewModel的方式,View主要是呈現一個可選用Guest或是User登入系統的畫面。

ViewModel

//[InjectValidation]
public class LoginViewModel : ReactiveObject
{
    [Reactive]
    public string User { get; set; }

    [Reactive]
    public string Password { get; set; }

    [Reactive]
    public bool IsUserLogin { get; set; }

    public ReactiveCommand<Unit, Unit> LoginCommand { get; private set; }
    public ReactiveCommand<Unit, Unit> ResetCommand { get; private set; }

    //private readonly LoginViewModelValidator _viewModelValidator;

    public LoginViewModel()
    {
        //_viewModelValidator = new LoginViewModelValidator();

        // assume a user is going to login
        IsUserLogin = true;

        // 可登入條件,當使用者及密碼皆有輸入,且密碼長度大於3,或是Guest時可登入系統
        var canLogin = this.WhenAny(
            vm => vm.User,
            vm => vm.Password,
            vm => vm.IsUserLogin,
            (user, pass, isUser) =>
                !string.IsNullOrWhiteSpace(user.Value) &&
                !string.IsNullOrWhiteSpace(pass.Value) &&
                (pass.Value.Length > 3) ||
                !isUser.Value);
        // 設定登入命令來源及其條件
        LoginCommand = ReactiveCommand.CreateFromObservable(this.LoginAsync, canLogin);

        // 當使用者或密碼欄位不為空時,可清除
        var canReset = this.WhenAny(
            vm => vm.User,
            vm => vm.Password,
            (user, pass) =>
                (!string.IsNullOrWhiteSpace(user.Value) || !string.IsNullOrWhiteSpace(pass.Value))
            );
        // 設定清除命令動作及其條件
        ResetCommand = ReactiveCommand.Create(() =>
        {
            User = string.Empty;
            Password = string.Empty;
        }, canReset);
    }

    // 模擬登入動作
    private IObservable<Unit> LoginAsync() =>
        Observable
            .Return(new Random().Next(0, 2) == 1)
            .Delay(TimeSpan.FromSeconds(1))
            .Do(
                success =>
                {
                    if (!success)
                    {
                        Debug.WriteLine("Failed to login");
                        //MessageBox.Show("Failed to login");
                        //throw new InvalidOperationException("Failed to login.");
                    }
                }
            )
            .Select(_ => Unit.Default);
}

Views

XAML

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:SharedViewModel;assembly=SharedViewModel"
        xmlns:local="clr-namespace:WpfApp1"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        WindowStartupLocation="CenterScreen"
        Title="{x:Static local:MainWindow.WindowTitle}" Height="250" Width="450">

    <Window.DataContext>
        <vm:LoginViewModel/>
    </Window.DataContext>

    <Window.Resources>
        <local:UserTypeConverter x:Key="UserTypeConverter"/>
        <Style TargetType="{x:Type Control}" x:Key="baseStyle">
            <Setter Property="FontSize" Value="20" />
        </Style>
        <Style TargetType="{x:Type Button}" BasedOn="{StaticResource baseStyle}"></Style>
        <Style TargetType="{x:Type Label}" BasedOn="{StaticResource baseStyle}"></Style>
        <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource baseStyle}"></Style>
        <Style TargetType="{x:Type ListView}" BasedOn="{StaticResource baseStyle}"></Style>
        <Style TargetType="{x:Type RadioButton}" BasedOn="{StaticResource baseStyle}"></Style>
    </Window.Resources>

    <Border Padding="10">
        <StackPanel>
            <StackPanel.Resources>
                <DataTemplate DataType="{x:Type ValidationError}">
                    <TextBlock FontStyle="Italic" Foreground="Red" HorizontalAlignment="Right" Margin="4" Text="{Binding Path=ErrorContent}" />
                </DataTemplate>
            </StackPanel.Resources>

            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <RadioButton Grid.Column="0" HorizontalAlignment="Right" GroupName="UserType" 
                         Content="Guest"
                         Margin="0 0 20 0"
                         IsChecked="{Binding IsUserLogin,  
                                     Converter={StaticResource UserTypeConverter}}"/>
                <RadioButton Grid.Column="1" HorizontalAlignment="Left" GroupName="UserType" 
                         Content="Member"
                         Margin="10 0 0 0"
                         IsChecked="{Binding IsUserLogin}"/>
            </Grid>

            <Grid Margin="0 10 0 0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="Account:" FontSize="18" VerticalAlignment="Center" Grid.Column="0" Margin="0 0 10 0" HorizontalAlignment="Center"/>
                <TextBox IsEnabled="{Binding IsUserLogin}" Text="{Binding User, UpdateSourceTrigger=LostFocus, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}" Grid.Column="1" Grid.ColumnSpan="2" Margin="0 0 20 0">
                    <Validation.ErrorTemplate>
                        <ControlTemplate>
                            <StackPanel Orientation="Horizontal">
                                <AdornedElementPlaceholder x:Name="textBox"/>
                                <TextBox Margin="5" Text="{Binding [0].ErrorContent}" Foreground="Red"></TextBox>
                            </StackPanel>
                        </ControlTemplate>
                    </Validation.ErrorTemplate>
                </TextBox>
            </Grid>

            <Grid Margin="0 10 0 0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="Password:" FontSize="18" VerticalAlignment="Center" Grid.Column="0" Margin="0 0 10 0" HorizontalAlignment="Center"/>
                <TextBox IsEnabled="{Binding IsUserLogin}" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}" Grid.Column="1" Grid.ColumnSpan="2" Margin="0 0 20 0">
                    <Validation.ErrorTemplate>
                        <ControlTemplate>
                            <StackPanel Orientation="Horizontal">
                                <AdornedElementPlaceholder x:Name="textBox"/>
                                <TextBox Margin="5" Text="{Binding [0].ErrorContent}" Foreground="Red"></TextBox>
                            </StackPanel>
                        </ControlTemplate>
                    </Validation.ErrorTemplate>
                </TextBox>
            </Grid>

            <Grid Margin="0 10">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>

                <Button Command="{Binding LoginCommand}" Margin="10 0" Grid.Column="1" Content="登入"></Button>
                <Button x:Name="btnExit" Click="BtnExit_OnClick" Margin="10 0" Grid.Column="2" Content="離開"></Button>
                <Button Command="{Binding ResetCommand}" Grid.Column="3" Content="Reset"></Button>
            </Grid>

        </StackPanel>
    </Border>
</Window>

WinForm

基本上WinForm看起來就跟WPF一樣,不是重點,就不列出了。

Code behind

WPF

/// <summary>
/// MainWindow.xaml 的互動邏輯
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new LoginViewModel();
    }

    public static string WindowTitle = "Login";
    public static string ShowText { get { return "show text"; } }

    private void BtnExit_OnClick(object sender, RoutedEventArgs e)
    {
        this.Close();
    }
}

WinForm

public partial class LoginForm : Form, IViewFor<LoginViewModel>
{
    public LoginForm()
    {
        InitializeComponent();

        ViewModel = new LoginViewModel();
        // 將ViewModel的屬性綁定至UI控制項上
        this.Bind(ViewModel, x => x.IsUserLogin, x => x.rdoUser.Checked);
        this.Bind(ViewModel, x => x.User, x => x.txtAccount.Text);
        this.Bind(ViewModel, x => x.Password, x => x.txtPassword.Text);

        // 將ViewModel的命令綁定至UI控制項
        this.BindCommand(ViewModel, x => x.LoginCommand, x => x.btnLogin);
        this.BindCommand(ViewModel, x => x.ResetCommand, x => x.btnReset);

        this.OneWayBind(ViewModel, x => x.IsUserLogin, x => x.txtAccount.Enabled);
        this.OneWayBind(ViewModel, x => x.IsUserLogin, x => x.txtPassword.Enabled);

        /*
        this.OneWayBind(ViewModel, 
            x => x.IsUserLogin, 
            x => x.txtAccount.BackColor, 
            x => x ? Color.Green : Color.BlueViolet); */
    }

    object IViewFor.ViewModel
    {
        get { return ViewModel; }
        set { ViewModel = (LoginViewModel)value; }
    }

    public LoginViewModel ViewModel { get; set; }

    private void btnExit_Click(object sender, EventArgs e)
    {
        this.Close();
    }
}

基本上,ReactiveUI在這兩種GUI架構上的差別僅在WinForm要有一個綁定的動作,而資料驅動導向的WPF基本上在XAML中就直接完成綁定的動作。不過在WinForm上到是帶來了很多的好處,在資料綁定的部份不再產生”MagicString”(以前在處理類似功能時,還特別找其它的函式庫來應用);更可以直接綁定控制項和命令,減少了很多View端的CodeBehind程式碼。

而在面對多個控制項的不同狀態的互動時,一般可能會採用類Mediator模式來處理,現在也可以透過自訂狀態類別來對映至各個不同控制項,讓程式更簡潔。

Written with StackEdit.

11.24.2016

WinForm表單控制項狀態管理 - MVVM(with ReactiveUI) way

用物件導向工具開發程式1

最早最早,所有code都寫在Form中時,狀態的變更,落在各個被觸發的事件中,假設目前我們有一個TextBox,一個Button,程式需求TextBox中至少要n個字元才可以觸發Button的Click事件,於是我們在TextBox的TextChanged事件中加上判斷,再設定按鈕Enabled,搞定。

但事情發生在多個控制項,多個狀態時,複雜度隨之而來。

開發物件導向程式

於是,換個方向,使用狀態Property,在它的setter中寫判斷邏輯,看起來很好,但還是跟Form綁太緊了。尋找進階版,Mediator浮現,嗯嗯,符合SRP原則,可是好像不夠OCP,沒關係,再抽象一次…
發現有人寫好了(UI State Synchronization of WinForm Controls)

_stateManager
    .AddCommand(CMD_INDEX_CHECKING, UIObject.CreateObject(this.btnRemTarget));
_stateManager
    .AddCommand(CMD_INDEX_CHECKING, UIObject.CreateObject(this.btnSpiralTest));
_stateManager
    .AddCommand(CMD_INDEX_CHECKING, UIObject.CreateObject(this.btnIndexCheck));

_stateManager
    .AddCommand(CMD_SPIRAL_CHECKING, UIObject.CreateObject(this.btnRemTarget));
_stateManager
    .AddCommand(CMD_SPIRAL_CHECKING, UIObject.CreateObject(this.btnSpiralTest));
_stateManager
    .AddCommand(CMD_SPIRAL_CHECKING, UIObject.CreateObject(this.btnIndexCheck));

UI層的狀態維護,交給Mediator負責,而這個可重用的Mediator中透過字典記錄使用者定義的命令及相對應的控制項,並依需要設定控制項的狀態,這種方式下再配合Model-View-Presenter模式,UI和Presenter不再耦合,Presenter中也不用再出現WinForm相關的參考,perfect!

那…為什麼要ReactiveUI?

因為它支援.Net目前大多數的Presentation,in MVVM way.
GitHub測試

View

var context = SynchronizationContext.Current;
VM = new ViewModel2(
    context,
    this.WhenAnyValue(x => x.textBox1.Text),
    this.WhenAnyValue(x => x.textBox2.Text)));

// bind ViewModel's property to control
this.Bind(VM, x => x.UserName, x => x.textBox1.Text);
this.Bind(VM, x => x.Password, x => x.textBox2.Text);

// extra bind, let a property in ViewModel determinate the state of a control
VM.CanUserLogin.BindTo(this, x => x.btnLogin.Visible);

ReactiveUI提供了一個額外的綁定功能,如上將CanUserLogin綁定至控制項的屬性中。而其它的Bind動作,若是在xaml系的表單上,可以直接指定。

ViewModel, 實作商業邏輯

name.ToProperty(this, x => x.UserName, out _userName);
password.ToProperty(this, x => x.Password, out _password);

CanUserLogin = this.WhenAnyValue(
    x => x.UserName, x => x.Password,
    (user, pass) =>
        !string.IsNullOrWhiteSpace(user) &&
        !string.IsNullOrWhiteSpace(pass) &&
        user.Length >= 2 && pass.Length >= 3)
    .DistinctUntilChanged();

Written with StackEdit.

11.17.2016

ReactiveUI-MessageBus

MessageBus

這篇文章介紹了ReactiveUI Message Bus的基本概念,它的最簡用法大致如下程式碼所示─

var cur = MessageBus.Current;
cur.Listen<int>().Subscribe(i=>Console.WriteLine("value is {0}", i));

cur.SendMessage(1);
cur.SendMessage(2);

基本上,這其實類似設計模式中的Mediator模式,或者是所謂的Event-Broker方式,根據之前的經驗,應能有效解耦各類別之間的關系,不過有位仁兄有不同的意見MVVM anti-pattern: Using UI Message Bus communicate between ViewModels & Services,不過我目前的經驗還沒辦法理解他的說明,也許從之後的實作再來看。

而在ReactiveUI-Sample中,提供的測試碼為─

   public class MainViewModel : ReactiveObject
    {
        public MainViewModel()
        {
            Publisher = new PublisherViewModel();
            Subscriber = new SubscriberViewModel();
        }
        public PublisherViewModel Publisher { get; set; }
        public SubscriberViewModel Subscriber { get; set; }

    }

    public class PublisherViewModel : ReactiveObject
    {
        public PublisherViewModel()
        {
            PublishCommand = ReactiveCommand.Create();
            MessageBus.Current.RegisterMessageSource(PublishCommand);
        }

        public IReactiveCommand<object> PublishCommand { get; protected set; }
    }

    public class SubscriberViewModel : ReactiveObject
    {
        public SubscriberViewModel()
        {
            MessageBus.Current.Listen<object>().Subscribe(_ =>
            {
                Value++;
            });
        }


        private int _Value;

        public int Value
        {
            get { return _Value; }
            set { this.RaiseAndSetIfChanged(ref _Value, value); }
        }

    }

主要是這兩個類別
-PublisherViewModel,出版/發行者
-SubscriberViewModel,訂閱者

由出版者註冊命令,訂閱者訂閱當命令觸發時預執行的動作,雙方互相沒有關係,僅透過MessageBus作實際上的溝通。

Written with StackEdit.

11.07.2016

ReactiveUI.Fody

在試用ReactiveUI時,對RaiseAndSetIfChanged的方式覺得很麻煩,雖然可以用snippet的方式減少輸入量,但仍沒有{get;set;}那樣簡潔,後來想到Fody有沒有支援,結果還真的有

ReactiveUI.Fody

private string _searchId;

public string SearchId 
{
    get { return _searchId; }
    set { this.RaiseAndSetIfChanged(ref _searchId, value); }
}

to

[Reactive]public string SearchId { get; set; }

神奇的Weave!