I am attempting to bind a command between a View and associated ViewModel via the code-behind file of the view:
public partial class MyView : ReactiveUserControl<MyViewModel>
{
public MyView()
{
this.WhenActivated(disposables =>
{
this.BindCommand(
this.ViewModel,
vm => vm.MyCommand,
v => v.MyButton);
});
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
This is the corresponding view xaml:
<UserControl xmlns="https://github.com/avaloniaui"
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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MyProject.Views.MyView"
xmlns:vm="using:MyProject.ViewModels"
x:DataType="vm:MyViewModel"
x:CompileBindings="True"
>
<StackPanel>
<Button Name="MyButton" Content="Next" HorizontalAlignment="Center" />
</StackPanel>
</UserControl>
And this is the View Model which has the command which should be binded to a Button control on the View:
public class MyViewModel : ViewModelBase, IRoutableViewModel, IActivatableViewModel
{
public ViewModelActivator Activator { get; }
public string UrlPathSegment { get; } = "my-url";
public IScreen HostScreen { get; }
public ReactiveCommand<Unit, IRoutableViewModel> MyCommand { get; }
public MyViewModel(IScreen screen)
{
Activator = new ViewModelActivator();
HostScreen = screen;
MyCommand =
ReactiveCommand.CreateFromObservable( () =>
HostScreen.Router.Navigate.Execute(new MyViewModel(HostScreen)));
this.WhenActivated((CompositeDisposable disposables) =>
{
/* handle activation */
Disposable
.Create(() => { /* handle deactivation */ })
.DisposeWith(disposables);
});
}
}
However, this results in the following error being raised:
Unhandled exception. System.InvalidOperationException: Operation is not valid due to the current state of the object.
at ReactiveUI.Reflection.<>c__DisplayClass4_0.<GetValueFetcherForProperty>b__0(Object obj, Object[] _) in D:\a\ReactiveUI\ReactiveUI\src\ReactiveUI\Expression\Reflection.cs:line 123
at ReactiveUI.Reflection.TryGetValueForPropertyChain[TValue](TValue& changeValue, Object current, IEnumerable`1 expressionChain) in D:\a\ReactiveUI\ReactiveUI\src\ReactiveUI\Expression\Reflection.cs:line 226
at ReactiveUI.ObservedChangedMixin.TryGetValue[TSender,TValue](IObservedChange`2 item, TValue& changeValue) in D:\a\ReactiveUI\ReactiveUI\src\ReactiveUI\Mixins\ObservedChangedMixin.cs:line 105
at ReactiveUI.ObservedChangedMixin.GetValueOrDefault[TSender,TValue](IObservedChange`2 item) in D:\a\ReactiveUI\ReactiveUI\src\ReactiveUI\Mixins\ObservedChangedMixin.cs:line 63
at ReactiveUI.ReactiveNotifyPropertyChangedMixin.<>c.<NestedObservedChanges>b__5_0(IObservedChange`2 x) in D:\a\ReactiveUI\ReactiveUI\src\ReactiveUI\Mixins\ReactiveNotifyPropertyChangedMixin.cs:line 181
at System.Reactive.Linq.ObservableImpl.Select`2.Selector._.OnNext(TSource value) in /_/Rx.NET/Source/src/System.Reactive/Linq/Observable/Select.cs:line 39
--- End of stack trace from previous location ---
... (the stacktrace is quite lengthy so I will omit the rest, please let me know if posting the full trace would be helpful)
When I bind the command directly in the Xaml file using the Command={Binding MyCommand} syntax, everything works as expected.
After playing around a bit, I determined the problem seems to be related to referencing the ViewModel property on MyView.
I have ReactiveUI routing configured on my MainWindowViewModel, and I cannot tell whether this is related:
public class MainWindowViewModel : ViewModelBase, IScreen, IActivatableViewModel
{
public ViewModelActivator Activator { get; }
public RoutingState Router { get; } = new RoutingState();
readonly ObservableAsPropertyHelper<IRoutableViewModel> _currentViewModel;
private IRoutableViewModel? CurrentViewModel => _currentViewModel.Value;
public string? CurrentViewUrl() => CurrentViewModel?.UrlPathSegment;
public MainWindowViewModel()
{
Activator = new ViewModelActivator();
_currentViewModel = Router.CurrentViewModel.ToProperty(this, x => x.CurrentViewModel);
this.WhenActivated((CompositeDisposable disposables) =>
{
Router.Navigate.Execute(new MyViewModel(this));
/* handle activation */
Disposable
.Create(() => { /* handle deactivation */ })
.DisposeWith(disposables);
});
}
}
And the corresponding MainWindow.xaml for the view (I have not modified the code-behind file for this view)
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyProject.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveUi="http://reactiveui.net"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MyProject.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
x:CompileBindings="True"
Icon="/Assets/avalonia-logo.ico"
Title="MyProject"
Background="{DynamicResource Zinc800}">
<reactiveUi:RoutedViewHost Router="{Binding Router}" PageTransition="{x:Null}">
<reactiveUi:RoutedViewHost.DefaultContent>
<TextBlock Text="Requested view not found"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</reactiveUi:RoutedViewHost.DefaultContent>
</reactiveUi:RoutedViewHost>
</Window>
I am going in circles trying to figure out why this is happening, when I pan through the various examples implementing bindings in code-behind, nothing stands out to me as different than what I have done here, unless it has something to do with the way I have implemented routing.
Please forgive me if the cause is obvious, I am new to ReactiveUI, MVVM and desktop development.