Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactiveUI: Return value from a custom dialog

Tags:

I have a window that uses a ReactiveUI Interaction to open a second window as a modal dialog, then return data from a ListBox in the second window.

The problem is that when .ShowDialog() completes, the ViewModel's SelectedItem always evaluates to null. I've confirmed that the binding is working correctly, and the selected item is getting updated in the dialog window's ViewModel properly. It's only when I return to the Interaction that the property resets to its default value (null).

I've put together a minimal example of the issue here:

https://github.com/replicaJunction/ReactiveDialogTest

The main logic under test is in MainWindowViewModel.cs.

Edit: Here's a snippet from the code with the basic idea:

GetNumberFromDialog = new Interaction<Unit, int>();
GetNumberFromDialog.RegisterHandler(interaction =>
{
    var vm = new DialogWindowViewModel();

    // Get a reference to the view for this VM
    var view = Locator.Current.GetService<IViewFor<DialogWindowViewModel>>();

    // Set its VM to our current reference
    view.ViewModel = vm;

    var window = view as Window;
    var dialogResult = window.ShowDialog();

    // At this point, vm.SelectedNumber is expected be the number the user selected -
    // but instead, it always evaluates to 0.

    if (true == dialogResult)
        interaction.SetOutput(vm.SelectedNumber);
    else
        interaction.SetOutput(-1);
});

OpenDialog = ReactiveCommand.Create(() =>
{
    GetNumberFromDialog.Handle(Unit.Default)
        .Where(retVal => -1 != retVal) // If the dialog did not return true, don't update
        .Subscribe(retVal =>
        {
            this.MyNumber = retVal;
        });
});

Steps to reproduce the issue:

  1. Run the project. Note the label with the -5000 - this is the number to update.
  2. Click the "Open Dialog" button. A dialog window opens.
  3. Select a number in the ListBox. Note that the label under "Current Selection" updates - this is bound to the same value as the ListBox.SelectedItem.
  4. Click OK. The dialog closes.

Expected behavior: the label in the main window beneath "my number is" should update to the value you selected in the ListBox.

Actual behavior: the label updates to 0 (default value for int).

Why is my ViewModel resetting itself when the dialog closes?

like image 699
Freddy The Horse Avatar asked Dec 06 '17 18:12

Freddy The Horse


1 Answers

Looking at your sample on GitHub helps to reveal the issue. Your DialogWindow looks like this:

public partial class DialogWindow : Window, IViewFor<DialogWindowViewModel>
{
    public DialogWindow()
    {
        InitializeComponent();
        this.ViewModel = new DialogWindowViewModel();
        this.DataContext = this.ViewModel;

        this.ViewModel
            .WhenAnyValue(x => x.DialogResult)
            .Where(x => null != x)
            .Subscribe(val =>
            {
                this.DialogResult = val;
                this.Close();
            });
    }

    public DialogWindowViewModel ViewModel { get; set; }
    object IViewFor.ViewModel
    {
        get => ViewModel;
        set => ViewModel = (DialogWindowViewModel)value;
    }
}

In your MainWindowViewModel, you set the DialogWindow.ViewModel property to a new instance of your DialogWindowViewModel. The issue ocurrs at this point. Your problem is that setting the DialogWindow.ViewModel property does not set the view's DataContext or re-create the WhenAnyValue observable. This means the view is still binding to the SelectedNumber property on the old instance of the DialogWindowViewModel (the one created in the DialogWindow constructor). To fix your sample code above, you can simply avoid setting the ViewModel property, and use the ViewModel that's already set on the dialog:

GetNumberFromDialog.RegisterHandler(interaction =>
{
    // Get a reference to the view for this VM
    var view = Locator.Current.GetService<IViewFor<DialogWindowViewModel>>();

    var window = view as Window;
    var dialogResult = window.ShowDialog();

    // use the ViewModel here that's already set on the DialogWindow
    if (true == dialogResult)
        interaction.SetOutput(view.ViewModel.SelectedNumber);
    else
        interaction.SetOutput(-1);
});
like image 173
Eugene Pawlik Avatar answered Sep 19 '22 13:09

Eugene Pawlik