Reputation: 21
I'm trying to work with ReactiveUI framework in wpf project. In detail I'm trying to use the MessageBus pattern to comunicate between two different viewmodel. In the simplified example below I try to update MainWindowView Label when I click on the UserControl Button insert into MainWindowView. The Button click is managed by ReactiveUI BindCommand that call UserControlViewModel method. This method send a message by MessageBus ReactiveUI mechanism. The MainWindowViewModel listen message and update the Label caption on MainWindowView that show the title of the window. I don't know why this doesn't work.
Below there are the the code:
MainWindowView (Xaml):
<rxui:ReactiveWindow x:Class="TestMessageBus.MainWindowView"
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:local="clr-namespace:TestMessageBus"
xmlns:rxui="http://reactiveui.net"
x:TypeArguments="local:MainWindowViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<Label x:Name="TitleLabel" HorizontalAlignment="Center"/>
<local:UserControlView/>
</StackPanel>
</rxui:ReactiveWindow>
MainWindowView (Code Behind):
namespace TestMessageBus
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindowView : ReactiveWindow<MainWindowViewModel>
{
public MainWindowView()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
this.WhenActivated(disposable =>
{
this.OneWayBind(ViewModel, vm => vm.Title, v => v.TitleLabel.Content)
.DisposeWith(disposable);
});
}
}
}
MainWindowViewModel:
namespace TestMessageBus
{
public class MainWindowViewModel : ReactiveObject
{
public string Title { get; set; }
public MainWindowViewModel()
{
MessageBus.Current.Listen<string>("Contract1").Subscribe(x => MessageSend(x));
}
private void MessageSend(string title)
{
Title = title;
}
}
}
UserControlView (Xaml):
<rxui:ReactiveUserControl x:Class="TestMessageBus.UserControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestMessageBus"
xmlns:rxui="http://reactiveui.net"
x:TypeArguments="local:UserControlViewModel"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="200">
<Grid>
<Button x:Name="ButtonTest">TEST</Button>
</Grid>
</rxui:ReactiveUserControl>
UserControlView (Code behind):
namespace TestMessageBus
{
/// <summary>
/// Interaction logic for UserControlView.xaml
/// </summary>
public partial class UserControlView : ReactiveUserControl<UserControlViewModel>
{
public UserControlView()
{
InitializeComponent();
ViewModel = new UserControlViewModel();
this.WhenActivated(disposable =>
{
this.BindCommand(ViewModel, vm => vm.ClickCommand, v => v.ButtonTest).DisposeWith(disposable);
});
}
}
}
UserControlViewModel:
namespace TestMessageBus
{
public class UserControlViewModel: ReactiveObject
{
public ReactiveCommand<Unit, Unit> ClickCommand { get; set; }
public UserControlViewModel()
{
ClickCommand = ReactiveCommand.Create(ClickButton);
}
private void ClickButton()
{
MessageBus.Current.SendMessage("TITOLO", "Contract1");
}
}
}
I would expect to see the "TITOLO" string in the MainWindow Label but this doesn't happen. Someone can explain me why? Thanks.
Upvotes: 0
Views: 1673
Reputation: 7692
This is one potential approach, but I'm not sure if it fits with Reactive UI best practice. The issue for me is that Subject
smashes the IObservable
and the IObserver
together.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Diagnostics;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
namespace UnitTestProject1
{
[TestClass]
public class UnitTest1
{
private static string GetSomeData() => "Hi";
[TestMethod]
public async Task TestMethod8()
{
var subject = new Subject<string>();
//Create a class and inject the subject as IObserver
new Publisher(subject);
//Create a class and inject the subject as IObservable
new Subscriber(subject);
new Subscriber(subject);
new Subscriber(subject);
//Run the loop for 10 seconds
await Task.Delay(10000);
}
class Publisher
{
public Publisher(IObserver<string> observer)
{
Task.Run(async () =>
{
//Loop forever
while (true)
{
//Get some data, publish it with OnNext and wait 500 milliseconds
observer.OnNext(GetSomeData());
await Task.Delay(500);
}
});
}
}
class Subscriber
{
//Listen for OnNext and write to the debug window when it happens
public Subscriber(IObservable<string> observable) => observable.Subscribe((s) => Debug.WriteLine(s));
}
}
}
Upvotes: 0
Reputation: 564
First I want to note, that even though the framework provides a MessageBus implementation, we don't recommend it's use.
I don't see you Registering a message source anywhere in your example.
You should have a call somewhere to RegisterMessageSource
which takes an IObservable
and in this case I would gather it should be your button click.
something like:
MessageBus.Current.RegisterMessageSource(ButtonTest.Events().Clicked)
Where Events().Clicked
is just an Observable wrapper around the button click event.
There is an example on the ReactiveUI Website
Upvotes: 1