1

I have built a dummy UserControl that has a method in its code-behind to display a message. I have used this control in my main window and want to execute its method when I click a Button using Commands and MVVM. Is this a good design, and if not, how can I improve it? My current code looks like this:

<UserControl x:Class="ControlBining.Control1"
             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"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
    </Grid>
</UserControl>


public partial class Control1 : UserControl
   {
      public Control1()
      {
         InitializeComponent();
      }

      public void ShowMessage()
      {
         MessageBox.Show("Called from other control!");
      }
   }

<Window x:Class="ControlBining.MainWindow"
        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:ControlBining"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <StackPanel Margin="0 50 0 0">
        <local:Control1 Width="100"/>
        <Button Width="100" Content="Show Message"/>
    </StackPanel>
</Window>

   public class RelayCommand : ICommand
   {
      private readonly Predicate<object> m_canExecute;
      private readonly Action<object> m_execute;

      public RelayCommand(Predicate<object> canExecute, Action<object> execute)
      {
         m_canExecute = canExecute;
         m_execute = execute;
      }

      public event EventHandler CanExecuteChanged
      {
         add => CommandManager.RequerySuggested += value;
         remove => CommandManager.RequerySuggested -= value;
      }

      public bool CanExecute(object parameter)
      {
         return m_canExecute(parameter);
      }

      public void Execute(object parameter)
      {
         m_execute(parameter);
      }
   }

Currently, I have made it work using the following code, but I am not sure if this is a good design:

  private void Control1_Loaded(object sender, RoutedEventArgs e)
  {

     ViewModel m = (ViewModel)DataContext;
     m.ShowMessage += M_ShowMessage;
  }

  private void M_ShowMessage()
  {
     ShowMessage();
  }

  public event Action ShowMessage;

  private ICommand m_showMessageCommand;
  public ICommand ShowMessageCommand
  {
     get
     {
        return m_showMessageCommand ?? (m_showMessageCommand = new RelayCommand(
                  p => true,
                  p => ShowMessage?.Invoke()));
     }
  }
Vahid
  • 5,144
  • 13
  • 70
  • 146
  • Your ViewModel does not know, or should not know anything about the View. Therefor you cannot call a method from the View – Nawed Nabi Zada Oct 11 '18 at 13:15
  • @NawedNabiZada I was hoping to do it via events or an indirect way. – Vahid Oct 11 '18 at 13:17
  • In case a code is View only you can just do it in the View layer. No need to involve VM – Nawed Nabi Zada Oct 11 '18 at 13:18
  • @Vahid: Why are you displaying the message from the code-behind of the view? Can't you inject your view model with a message service that displays the message? – mm8 Oct 11 '18 at 13:19
  • @mm8 I have oversimplified the problem here. The Control is actually a render layer and instead of ShowMessage I am actually trying to Zoom/Pan stuff like that. So these methods are actually part of the control. I want a way to fire them from outside though. It is a mix of MVVM and MVP I believe. – Vahid Oct 11 '18 at 13:20
  • @Vahid: Where and how do you set the DataContext of the UserControl to an instance of your view model where the command is defined then? – mm8 Oct 11 '18 at 13:22
  • @mm8 Currently in the XAML of MainWindow – Vahid Oct 11 '18 at 13:23
  • @Vahid: So how do you bind to the command that should call the method...? – mm8 Oct 11 '18 at 13:24
  • @mm8 Updated the code and it works but I really doubt it being a good design as I have now hard-coded the ViewModel inside Control. – Vahid Oct 11 '18 at 13:31
  • @Vahid: I don't understand your issue. Do you want to bind to a command of a view model that should call a method of the view? If so, how do you bind to the command of the view model from the view? – mm8 Oct 11 '18 at 13:32
  • @mm8 i am binding from the Button Control to a Command in my ViewModel. This ViewModel is also shared with the other control. The Command in the ViewModel executes an Event in the ViewModel and I have already subscribed to this event in the code-behind of my View. So basically now ViewModel is not aware of the View. – Vahid Oct 11 '18 at 13:36
  • Controls are not supposed to have methods that are invoked by view models. If you you want to show a MessageBox, do it in the command's execute handler in the view model. – Clemens Oct 11 '18 at 13:38
  • @Clemens This is a bit complex Control. I have oversimplified it for the question. as it renders drawings and does the zooming/panning and.. I am not sure if I can handle them outside the control itself. – Vahid Oct 11 '18 at 13:46
  • That should all be possible with bindable properties. – Clemens Oct 11 '18 at 13:47
  • @Clemens Can you please help me with that? I was thinking of defining dependency property and ICommand in Control but I was not sure how to solve it that way- – Vahid Oct 11 '18 at 13:49
  • You may take a look at the MapBase class in my [XAML Map Control](https://github.com/ClemensFischer/XAML-Map-Control) library. It has a lot of properties for pan, zoom and rotation and the like, also some `Target...` properties that start animations. A complex control without any method that must be called in code behind or by a view model command. – Clemens Oct 11 '18 at 13:51
  • @Clemens Thank you. I will look into it. – Vahid Oct 11 '18 at 13:59

1 Answers1

1

If you simply need to show a message, you should move the ShowMessage() method to the view model and use a message service to do this from the view model class.

If you really want to call some method that it only makes sense to define in the view, this can be done by implementing an interface in the view and inject the view model with this interface. For example when you invoke the command:

public interface IView
{
    void ShowMessage();
}

public partial class Control1 : UserControl, IView
{
    public Control1()
    {
        InitializeComponent();
    }

    public void ShowMessage()
    {
        MessageBox.Show("Called from other control!");
    }
}

View Model:

public ICommand ShowMessageCommand
{
    get
    {
        return m_showMessageCommand ?? (m_showMessageCommand = new RelayCommand(
                  p => true,
                  p =>
                  {
                      IView view as IView;
                      if (view != null)
                      {
                          //...
                          view.ShowMessage();
                      }
                  }));
    }
}

The view model knows nothing about the view, it only knows about an interface which of course may be called something else than IView.

The other option is to use an event aggregator or a messenger to send an event or a message from the view model to the view in a lously coupled way. Please refer to this blog post for more information about this.

Neither approach break the MVVM pattern.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • I like the first method. I will try to implement that if I do not find any other way using dependency properties. – Vahid Oct 11 '18 at 13:50