TheAquam
TheAquam

Reputation: 43

Trouble with showing a Mahapps.Metro Dialog with a ReactiveUi Command

I am trying to use the Mahapps dialog boxes in my project but have been unable to get them to work when trigger from a ReactivUI command in my ViewModel.In the view's XAML, I have registered the dialog.

 xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
    dialogs:DialogParticipation.Register="{Binding}"

I also have a button which is bound to the ShowDialog command.

this.BindCommand(viewModel, vm => vm.ShowDialog, x => x.button);

Finally, in my ViewModel, I have the RxUI command and the dialogcoordinator instance set.

 public MainWindowViewModel(IDialogCoordinator dialogCoordinator)
    {

        _dialogCoordinator = dialogCoordinator;

        ShowDialog = ReactiveCommand.CreateFromTask(async () =>
        {
            await _dialogCoordinator.ShowMessageAsync(this, "Message from VM", "MVVM based dialogs!");
        });

        ShowDialog.ThrownExceptions.Subscribe(ex => Console.WriteLine(ex.ToString()));

    }

No matter what I have tried it always throw the same error which is

System.InvalidOperationException: Context is not registered. Consider using DialogParticipation.Register in XAML to bind in the DataContext.

I am not sure if there is anything else needed to get the dialog to work or if I am just using the commands in RxUI incorrectly

Upvotes: 4

Views: 7032

Answers (4)

N1ko
N1ko

Reputation: 1

i used _dialogCoordinator.ShowModalMessageExternal(context, title, message); and it works fine!

Upvotes: 0

somethingRandom
somethingRandom

Reputation: 1157

For me, using the ContentRendered event did not do anything; I still got the exception InvalidOperationException: Context is not registered. Consider using DialogParticipation.Register in XAML to bind in the DataContext.

I based my solution on Kent Boogaart's book samples.

View:

using System.Reactive;
using System.Reactive.Disposables;
using System.Windows;
using MahApps.Metro.Controls;
using MahApps.Metro.Controls.Dialogs;
using ReactiveUI;
using Splat;

namespace ReactiveUIMahAppsDialog
{
    public partial class ChildView
    {
        public ChildView()
        {
            InitializeComponent();

            this.WhenActivated(disposables =>
            {
                ViewModel ??= Locator.Current.GetService<ChildViewModel>() ?? new ChildViewModel();

                this.BindCommand(ViewModel,
                        viewModel => viewModel.LogInUser,
                        view => view.Test)
                    .DisposeWith(disposables);

                this.ViewModel.Message
                    .RegisterHandler(async interaction =>
                    {
                        MetroWindow window = (MetroWindow) Window.GetWindow(this);

                        await window.ShowMessageAsync("test", "test");

                        interaction.SetOutput(Unit.Default);
                    })
                    .DisposeWith(disposables);
            });
        }
    }
}

<reactiveUi:ReactiveUserControl
    x:Class="ReactiveUIMahAppsDialog.ChildView"
    x:TypeArguments="viewModels:ChildViewModel"
    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:reactiveUi="http://reactiveui.net"
    xmlns:viewModels="clr-namespace:ReactiveUIMahAppsDialog"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Button x:Name="Test" Content="Show Dialog" Width="100" Height="100"/>
    </Grid>
</reactiveUi:ReactiveUserControl>

Host the reactive user control in a MetroWindow.

ViewModel:

using System.Reactive;
using System.Reactive.Linq;
using ReactiveUI;

namespace ReactiveUIMahAppsDialog
{
    public class ChildViewModel : ReactiveObject
    {
        public ChildViewModel()
        {
            Message = new Interaction<Unit, Unit>();

            LogInUser = ReactiveCommand.CreateFromTask(async _ => await Message.Handle(Unit.Default));
        }

        public ReactiveCommand<Unit, Unit> LogInUser { get; }

        public Interaction<Unit, Unit> Message { get; }
    }
}

Full code can be found here.

Upvotes: 0

Dave Warry
Dave Warry

Reputation: 91

It does seem to be a timing issue - when I first started playing with this, I got it to work by setting the VM's DialogCoordinator instance from the View's Loaded event, rather than it's constructor.

I've put together a minimal working application demonstrating this at https://github.com/dwarry/ReactiveUiMahAppsDialog. It uses ReactiveUi's normal Interaction mechanism to trigger displaying the dialog. In keeping with that, I've put this in the View, but I don't see why it couldn't be adapted to do it from the ViewModel if you'd rather. One other thing to be aware of is that you need to wait for the dialog to close before leaving the Interaction Handler, or ReactiveUI will throw an UnhandledInteractionException.

Upvotes: 4

Hoarst
Hoarst

Reputation: 191

I had the same problem with another MVVM framework (MVVM Light Toolkit). But the framework doesn't seem the problem. It's a matter of timing. The DialogCoordinator isn't accessable from the constructor. I personally moved all the code from the constructor to RelayCommand that is being fired

<controls:MetroWindow
x:Class="UI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dialog="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
dialog:DialogParticipation.Register="{Binding}"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
d:DataContext="{d:DesignInstance vm:MainViewModel}"
mc:Ignorable="d">
<i:Interaction.Triggers>
    <i:EventTrigger EventName="ContentRendered">
        <i:InvokeCommandAction Command="{Binding StartupCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

I tried different events like Loaded, which worked in some cases but "ContentRendered" always worked in the VM I have the following code:

    public ICommand StartupCommand
    {
        get
        {
            return new RelayCommand(async ()=>
            {
                this.AccountName = await
                        dialogCoordinator.ShowInputAsync(this, "Welcome", "Please insert your account name");                    
            });
        }
    }

So basically it seems to be not yet registered directly but after the content is loaded it is. (In this example i used ShowInputAsync but it should work the same with ShowMessageAsync)

Upvotes: 5

Related Questions