Search code examples
wpfmvvmrouted-eventsrouted-commands

Can I communicate from child viewmodels to main window with a routed command or routed event?


I'm building an MVVM WPF application with a custom dialog box control that resides in and is managed by the main window. I'd like to be able to launch this dialog from anywhere in the application (e.g. from a viewmodel belonging to some child view somewhere).

My question is: can I use a bubbling RoutedCommand or RoutedEvent somehow to get from the logic in some child view's viewmodel up to the main window and show the dialog, leveraging the element tree and WPF's routing system rather than tightly coupling things together? For example, I've got a ViewModelBase, and would like to just be able to call ViewModelBase.ShowDialog() from anywhere and trigger the dialog logic in the main window.

I get the sense this could work, but I'm not quite seeing it. As with others, all the literature on how to RoutedCommand my way to copy-paste-menu-item bliss leaves me at a loss. I've built event aggregators for similar things, and I know MVVM frameworks with message buses are out there — but I'd like to leverage WPF built-ins rather than another one-off solution if there's a natural way to do so.

Edit: To be clear, I'd like to avoid pulling in extra dependencies or frameworks such as Prism.

Edit 2: Got it working using the ideas from III's answer below. Here's the exact method I used to wire it up:

Commands.cs: Provide a static singleton command object.

// Static class exposing singleton RoutedCommand objects.
public static class Commands
{
    public static readonly ICommand ShowDialog = new RoutedCommand();
}

MainWindow.xaml: Wire it up to an event handler through Window.CommandBindings.

<Window xmlns:common="(namespace containing static Commands class)">
    <Window.CommandBindings>
        <CommandBinding Command="{x:Static common:Commands.ShowDialog}" Executed="ShowDialog_Executed" />
    </Window.CommandBindings>
</Window>

MainWindow.xaml.cs

public void ShowDialog_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e) 
{ 
    // handle command
}

ViewModelBase.cs: Code that kicks off the RoutedCommand on behalf of a caller.

protected void ShowDialog()
{
    Commands.ShowDialog.Execute(...); // Can pass dialog text through here.
}

Solution

  • You can setup a static class that can just be an Action, and handle registrations for any subscribers. Whoever is subscribed to that command can invoke the action.

    The idea is something like this..

    public static class CommandManager
    {
       List<ViewModel> _subscribers;
    
       static CommandManager()
       {
         _subscribers = new List<ViewModel>();
         ShowDialogCommand = new Action(() => window.ShowDialog()); // or do whatever you want with your child view models.
       }
    
       public ICommand ShowDialogCommand { get; private set; }
    
       public void Register(ViewModel viewModel)
       {
         _subscribers.Add(command);
       }
    
    }
    

    ChildViewModel

    public class ChildViewModel
    {
       public ChildViewModel()
       { 
          CommandManager.Register(this);
       }
    }
    

    View

    <Button Command="{x:Static CommandManager.ShowDialogCommand}"/>