Search code examples
c#wpfmvvmprismreactiveui

ReactiveUI and PRISM.MVVM


I'm new to ReactiveUI. I'm trying to combine PRISM.MVVM with ReactiveUI. In particular, I'd like to use PRISM for its navigation utilities and ReactiveUI for creating the UI. I'm not sure about using code in view code-behind even if I use the MVVM pattern: is there a way to bind the UI control to the ViewModel property without creating binding in code-behind?
Here is an example:

ViewModel:

public class MainWindowViewModel : ReactiveObject
{
    private string _inputValue;
    public string InputValue
    {
        get => _inputValue;
        set => this.RaiseAndSetIfChanged(ref _inputValue, value);
    }

    private string _outputValue;
    public string OutputValue
    {
        get => _outputValue;
        set => this.RaiseAndSetIfChanged(ref _outputValue, value);
    }

    public MainWindowViewModel()
    {
        this.WhenAnyValue(x => x.InputValue).Subscribe(x => OutputValue = x);
    }
}

View:

<rxui:ReactiveWindow 
xmlns:rxui="http://reactiveui.net"
x:Class="WPFReactiveUI.Views.MainWindow"
x:TypeArguments="viewModels:MainWindowViewModel"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:WPFReactiveUI.ViewModels"
    Title="ReactiveUI and PRISM sample" Height="350" Width="525" >
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"></ColumnDefinition>
        <ColumnDefinition Width="*"></ColumnDefinition>
    </Grid.ColumnDefinitions>

    <TextBox Grid.Row="0" Grid.Column="0" x:Name="InputText" Text="{Binding InputValue}" />
    <Label Grid.Row="0" Grid.Column="1" x:Name="OutputLabel" Content="{Binding OutputValue}"/>
</Grid>
</rxui:ReactiveWindow>

View code-behind:

public partial class MainWindow 
{
    public MainWindow()
    {
        InitializeComponent();
        ViewModel = new AppViewModel();

        this.WhenActivated(disposable =>
        {
            this.Bind(ViewModel, x => x.Input, x => x.InputText.Text)
                .DisposeWith(disposable);
            this.Bind(ViewModel, x => x.Output, x => x.OutputLabel.Content)
                .DisposeWith(disposable);
        });
    }
}

This example is working fine, but I don't understand if there is a way not to write code in the code-behind or if I'm obliged to do it because of ReactiveUI. Thanks in advance


Solution

  • There's a way. Not sure you're going to like it but there is a way.

    You can minimise the boiler plate code you have to write.

    You have to write some code AND you complicate each property in your viewmodel though.

    You can now use code generators which generate code files in the build. These can read classes and code you have, add code to your project.

    You could write one of these.

    Usually, this is attribute driven. But you can write code which examines your source code and emits more source code to your specification. You write the code generates the strings.

    An example is clearly explained here:

    https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/overview

    As it says you have

    [ObservableProperty]
    private string? name;
    

    And the source generator creates the public property Name with setproperty code which raises property changed.

    Your generated code goes into a file there on disk to be compiled into your app. You can find and look at it.

    You would need convention or another attribute specifies where a viewmodel is used. ( Yep. Bit smelly that ). You would need a string in your own attribute as a parameter tells it what control name you want to bind to. ( Yep. Also a bit smelly ).

    That file would be a partial class for mainwindow with just your generated reactiveui not-binding code in it.

    You'd arrange for that to be in a method which takes that disposable parameter and run that as well as any code you write. Roughly:

     public MainWindow()
    {
        InitializeComponent();
        ViewModel = new AppViewModel();
    
        this.WhenActivated(disposable =>
        {
            this.Bind(ViewModel, x => x.Input, x => x.InputText.Text)
                .DisposeWith(disposable);
            this.Bind(ViewModel, x => x.Output, x => x.OutputLabel.Content)
                .DisposeWith(disposable);
            GeneratedBindingCodeMethod(disposable);
        });
    }
    

    Here, GeneratedBindingCodeMethod is in a partial mainwindow class which is generated by your source generator.

    This cannot generate all your binding code. Or hopefully not anyhow. If there is no translation then you rather wasted your time with all those lambdas

    Or... you know.. you could maybe just use viewmodel first "navigation" and ditch prism. Just use someone else's code generator and go with the community toolkit.

    Personally, I prefer that toolkit to reactiveui and or prism.