10

I have an UserControl with a button inside. This button needs to add some items to a Grid that's inside said UC. I'm aware I can do this with a Click event.

The issue here is I am using MVVM and altering data outside their corresponding ViewModel would break the format (So to say).

Is there a way to create an ICommand Dependency Property so I can bind said DP to the button and have the functionality of adding the item to the Grid in my ViewModel? (I already have the List in both my UC and my ViewModel and they are working as expected)

Thank you.

Xanagandr
  • 723
  • 2
  • 9
  • 19
  • support your question with code, and simple it – safi Feb 22 '15 at 07:41
  • 7
    The thing was I didn't had any code to support my question, as I didn't know where to start, and I don't know how much simpler the question can be. On the other hand, I managed to find a solution. It is already posted below. Thanks. – Xanagandr Feb 22 '15 at 15:54

3 Answers3

26

Found a way to solve it in the way I was trying to. Leaving the answer here so people may use it:

1) In your User Control's code-behind, create a Dependency Property. I choose ICommand, since in my ViewModel I set it as a DelegateCommmand:

public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register(
        "Command",
        typeof(ICommand),
        typeof(UserControl));

    public ICommand Command
    {
        get 
        { 
            return (ICommand)GetValue(CommandProperty); 
        }

        set 
        { 
            SetValue(CommandProperty, value); 
        }
    }

2) In your UserControl's XAML code, bind this Dependency Property (In this case, a button):

<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">

    <Button Command="{Binding Command}" />

</Grid>

3) Next, on your ViewModel, declare a Command property and configure accordingly:

public ICommand ViewModelCommand { get; set; }

public ViewModelConstructor()
{
    ViewModelCommand = new DelegateCommand(ViewModelCommandExecute);
}

private void ViewModelCommandExecute()
{
    // Do something
}

4) Finally, on your View where the UserControl is nested, we declare the binding:

<UserControls:UserControl Command={Binding ViewModelCommand}/>

This way, the binding will take place and you can bind Commands from the buttons of any User Control to your ViewModels without breaking MVVM.

Xanagandr
  • 723
  • 2
  • 9
  • 19
  • 3
    I've got the following error message: WPF Error 40 BindingExpression path error: property not found on 'object'. Number 2) solved my problem. I missed the DataContext. – MUG4N Jul 23 '16 at 12:23
  • I created simple application which shows working version of described code. [Sample](https://github.com/pwujczyk/ProductivityTools.Examples.WPFDependencyProperty) – Pawel Wujczyk Jan 29 '20 at 22:54
  • 1
    How do you pass a parameter out of the User Control to the Command, like the DataGridView does with its SelectedItem? – Robert Harvey Mar 29 '20 at 01:33
0

The basic way is to create an Object (ie MyCommand) which implements ICommand, and nest it inside your ViewModel. Inside MyCommand you have no access to your ViewModel. You can workaround it (ie pass a reference to the ViewModel in MyCommand constructor) but at the end it's too much code (for simple stuff like this). I think almost nobody really do this.

Most use a DelegateCommand which resolve (most of) the above issues.

Last but not least, just use event handlers.
If you code them simply like this:

void Grid_MouseMove(object sender, MouseEventArgs e)
{ viewModel.SaveMousePosition(e.GetPosition()); }

you are not breaking any MVVM rule.
And you can't handle the above event with Commands.
There is no Command for MouseMove (there is none for most events), and you can't pass event parameters in a Command.

You can handle every event using Interaction.Triggers like this
But you still miss the capability to handle event parameters (and add ugly XAML).

To me, until WPF will support databinding in event handlers, like

Grid MouseMove="{Binding SaveMousePosition(e)}"

code behind is still the most effective way to handle events.

corradolab
  • 718
  • 3
  • 16
  • Thanks for the answer. The thing was I was trying to avoid using event handlers, since I'm also using MEF. I already found the answer to my question, but thanks anyway. – Xanagandr Feb 22 '15 at 15:55
  • 2
    Just wanted to note that `DelegateCommand` is sometimes called `RelayCommand` as well for those who may want to read up more on it – Aleksandr Albert Feb 23 '16 at 07:40
0

I faced similar problem and this question/answers helped me the most; so I will post my solution here in case somebody else will google it later. Made with mvvm light.

I had a custom winforms control as a Model and a WPF control as a View. So, xaml of View (I have an usercontrol for my View, no app.xaml):

<UserControl.Resources>
    <ResourceDictionary>
        <viewModel:ViewModelLocator x:Key="Locator"  />
    </ResourceDictionary>
</UserControl.Resources>
<UserControl.DataContext>
    <Binding Path = "Main" Source="{StaticResource Locator}"></Binding>
</UserControl.DataContext>
<Grid>
      <Button Command="{Binding Zoom, ElementName=Wrapper}"></Button>

      <viewModel:ProfileWrapper x:Name="Wrapper"  >                   
      </viewModel:ProfileWrapper>
</Grid>

Click of a Button is routed to a RelayCommand Zoom in ProfileWrapper (which is where my Model implemented)

Then the xaml of ProfileWrapper is straghtforward:

<Grid>
    <WindowsFormsHost>
        <local:ManualControl x:Name="abc" ></local:ManualControl>
    </WindowsFormsHost>
</Grid>

And the codebehind of ProfileWrapper :

public partial class ProfileWrapper : UserControl
{
    public ProfileWrapper()
    {
        InitializeComponent();
        test = abc;           
        Command = new RelayCommand(() => test.bZoomIn());
    }
    public ManualControl test;
    public RelayCommand Zoom { get; set; } 
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register(
            "Zoom",
            typeof(ICommand),
            typeof(ProfileWrapper));

    public ICommand Command
    {
        get
        {
            return (ICommand)GetValue(CommandProperty);
        }
        set
        {
            SetValue(CommandProperty, value);
        }
    }

}

My MainViewModel class is empty and all fuctionality goes to ProfileWrapper class, which might be bad, but at least it works.

Michael Snytko
  • 327
  • 3
  • 13