Question

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()));
    }
}
Était-ce utile?

La solution

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.

Autres conseils

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top