0

I've been stuck on this problem for a few hours. I am attempting to implement an MVVM-style Word Add-In in WPF. I am not using an MVVM toolkit. I have a WPF user control that is docked within a WinForm. While I am able to see the WPF user control within the win form and interact with it, my generic RelayCommand<T> that is bound to a WPF button won't execute when I click the button. The RelayCommand lives in ViewModel.cs and the DataContext for the view is set through the code-behind. I'm sure I'm doing something silly, but can't figure out what it is and therefore not sure why RelayCommand property's get{} won't get executed. Please see the code below. Thanks in advance for the help!

RelayCommand.cs (code snippet excludes namespace and includes statements)

/// <summary>
/// RelayCommand
/// </summary>
/// <typeparam name="T">Generic Parameter</typeparam>
public class RelayCommand<T> : ICommand where T : class
{
    #region Constructors

    /// <summary>
    /// RelayCommand constructor
    /// </summary>
    /// <param name="exec">Delegate that encapsulates a method that takes in a single parameter and returns void</param>
    /// <param name="canExec">Delegate that encapsulates a method that defines a set of criteria and returns a true if criteria is met; else false</param>
    public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
    {
        if (execute == null)
            throw new ArgumentNullException("execute is null");

        _canExecute = canExecute;
        _execute    = execute;
    }

    #endregion
    #region Members

    /// <summary>
    /// Execute method
    /// </summary>
    /// <param name="param">Parameter</param>
    public void Execute(object param)
    {
        T obj = param as T;

        if(obj != null)
        {
            _execute(obj);
        }
    }

    /// <summary>
    /// CanExec is a method that shows whether or not execution can happen
    /// </summary>
    /// <param name="param">Parameter</param>
    /// <returns>true if can execute; else false</returns>
    public bool CanExecute(object param)
    {
        if (_canExecute == null)
            return true;

        T obj = param as T;
        return obj == null || _canExecute(obj);
    }

    /// <summary>
    /// CanExec event changed
    /// </summary>
    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    #endregion

    #region Fields

    private readonly Predicate<T> _canExecute;
    private readonly Action<T> _execute;

    #endregion
}

SubmissionUserControl.xaml (only the pertinent snippet. excludes some code)

<Button Grid.Column="2" x:Name="SubmitButton" Command="{Binding Path=SubmitCommentCommand}"
                        Content="Submit" HorizontalAlignment="Right" Margin="5"/>

SubmissionUserControl.xaml.cs (contains snippet where I reference the ViewModel)

ViewModel viewModel;
public SubmissionUserControl()
{
    InitializeComponent();
    viewModel = new ViewModel();
    DataContext = viewModel;
}

ViewModel.cs (excludes some code. only shows the pertinent RelayCommand)

/// <summary>
/// SubmitCommentCommand responsible for interacting with UI to submit a comment.
/// </summary>
/// <returns>Returns a RelayCommand that executes a method to Save comments from the comment box</returns>
public ICommand SubmitCommentCommand
{
    get
    {
        return _submitCommentCommand ?? (_submitCommentCommand = new RelayCommand<object>(param => this.SaveComment()));
    }
}
Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
RudyD
  • 87
  • 1
  • 10
  • Nothing is wrong in your code.. check the Datacontext is applied correctly.. add another text property and bound it to a textblock and check that displays? – Sankarann Feb 17 '14 at 06:11
  • use Snoop (http://snoopwpf.codeplex.com/) to check your datacontext on runtime – blindmeis Feb 17 '14 at 06:13
  • it seems to me that your not sending a command parameter , and in your RelayCommand implementation you for some reason only call execute if the parameter isn't null. – eran otzap Feb 17 '14 at 07:09
  • Why are you using ICommand instead of RelayCommand in your ViewModel.cs? – menty Feb 19 '14 at 08:01

2 Answers2

3

To give you a more detailed start into MVVM and RelayCommands:

You do not have to declare your ViewModel in Xaml, this is mostly done programmatically on application root level, maybe with some DI.

When sticking to this MSDN Article your RelayCommand should look like this:

public class RelayCommand : ICommand
{
    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields

    #region Constructors

    public RelayCommand(Action<object> execute)
        : this(execute, null)
    {
    }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

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

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

    #endregion // ICommand Members
}

Additionally you can define a generic RelayCommand to handle Commandparameters like this:

public class GenericRelayCommand<T> : ICommand
{
    private readonly Action<T> _execute;
    public Predicate<T> CanExecuteFunc { get; private set; }

    public GenericRelayCommand(Action<T> execute) : this(execute, p => true)
    {}

    public GenericRelayCommand(Action<T> execute, Predicate<T> canExecuteFunc)
    {
        _execute = execute;
        CanExecuteFunc = canExecuteFunc;
    }

    public bool CanExecute(object parameter)
    {
        var canExecute = CanExecuteFunc((T)parameter);
        return canExecute;
    }

    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }

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

In your ViewModel the RelayCommands should be definied like this (I implemented INotifyPropertyChanged as well for further WPF Xaml Property handling example):

public class ViewModel : INotifyPropertyChanged
{
    private string _comment;
    public string Comment
    {
        get { return _comment; }
        set { _comment = value; OnPropertyChanged("Comment"); }
    }

    public GenericRelayCommand<string> SubmitComment1Command { get; set; }
    public RelayCommand SubmitComment2Command { get; set; }

    public ViewModel()
    {
        Comment = "Hello World!";
        SubmitComment1Command = new GenericRelayCommand<string>(OnSubmitComment1);
        SubmitComment2Command = new RelayCommand(OnSubmitComment2);
    }

    private void OnSubmitComment1(string obj)
    {
        //Save Comment Mock with CommandParameter
        MessageBox.Show(obj);    
    }

    private void OnSubmitComment2(object obj)
    {
        //Save Comment Mock with Property
        MessageBox.Show(Comment);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

I put your Button Example into a fresh WPF Application like this:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Width="525"
        Height="350">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="0"
                    Orientation="Horizontal">
            <TextBox Name="textBox"
                     Width="200"
                     Text="{Binding Comment,
                                    Mode=TwoWay,
                                    UpdateSourceTrigger=PropertyChanged}" />
            <Button x:Name="SubmitButton1"
                    Grid.Column="0"
                    Margin="5"
                    HorizontalAlignment="Right"
                    Command="{Binding Path=SubmitComment1Command}"
                    CommandParameter="{Binding ElementName=textBox,
                                               Path=Text}"
                    Content="Submit1" />
        </StackPanel>


        <Button x:Name="SubmitButton2"
                Grid.Column="1"
                Margin="5"
                HorizontalAlignment="Right"
                Command="{Binding Path=SubmitComment2Command}"
                Content="Submit2" />
    </Grid>
</Window>

And set the DataContext like this for simplicity reasons (As stated before, this is normally done through some kind of DI at root level):

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }
}

Then everything should work fine.

menty
  • 416
  • 3
  • 6
1

I solved this issue by telling the Model about data context in XAML instead of .cs file.

First: Tell Model the namespace in which you placed your view model, mine was like below:

xmlns:Local="clr-namespace:MKPL.Views.A01.S020"

Second: Add your ViewModel in XAML Resources like:

<UserControl.Resources>  
        <Local:ViewModel x:Key="dvm"/>   
</UserControl.Resources>

Third: Add DataContext to the parent container,in my case that is Grid.

<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource dvm}}">

Fourth: In your button code add the data context like:

<Button Grid.Column="2" x:Name="SubmitButton" Command="{Binding Path=SubmitCommentCommand, Source={StaticResource dvm}}"
                        Content="Submit" HorizontalAlignment="Right" Margin="5"/>

Hope it will help you

Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
Zeeshan Ajmal
  • 880
  • 1
  • 16
  • 29
  • Thanks, to all who responded. This particular solution worked for me. – RudyD Feb 18 '14 at 18:29
  • @user3317801 if this soln work for you then kindly mark it as a answer to help others noticing it. – Zeeshan Ajmal Feb 19 '14 at 05:13
  • I think I spoke too soon. I tried the solution earlier in the afternoon and thought it was successful because upon launch of the add-in, the SubmitCommentCommand was called, but tonight when I try to re-launch the app I notice that the command executes at no other time except at launch. Guess it always did so and I never noticed. Anyway, not sure what's going on here. I'm going to try downloading snoop as another person who commented on this question suggests and see what happens. – RudyD Feb 19 '14 at 06:45
  • @user3317801 well you should give GalaSoft a try, it is for MVVM pattern and i tested my solution with it. – Zeeshan Ajmal Feb 19 '14 at 07:17
  • Just to let you know, I think your solution could work, but I noticed one warning the XAML editor in Visual Studio gave me. This could be the source of my problem: Basically when I name the view model in the UserControl.Resources tag, namely , the warning says "The ViewModel does not exist in the namespace clr-...". Think there is anyway to avoid this? I know the ViewModel exists and compiles. Not sure why XAML isn't recognizing it. I declare make the local declaration by saying up top xmlns:Local="clr-namespace:AppName" since its in the top level of the directory. – RudyD Feb 19 '14 at 07:43
  • @user3317801 did you make your view model public ?? make it public and compile it again – Zeeshan Ajmal Feb 19 '14 at 08:03
  • Yes, view model was already public and compiled again. I notice in your solution, you don't set the data context and snoop says the data context is null. I removed the code-behind setting of datacontext because i thought that the source tag in the binding of the button referring to the view model key was enough. Am I wrong to have removed this? Should I have set this through the xaml tag? I've seen others set Datacontext by saying and I've seen others set the DataContext directly within other controls such as – RudyD Feb 19 '14 at 08:16
  • @user3317801 : i guess u have to do this
    – Zeeshan Ajmal Feb 19 '14 at 09:17
  • @user3317801 if this works for you, let me know i will update the answer accordingly. One more thing i assign the DataSource to my Grid because that Grid contains all the other controls so make sure to assign DataContext to the parent one. – Zeeshan Ajmal Feb 19 '14 at 09:18