Question

My View Contains a Button and a UserControl. The UserControl also contains a Button. Both, Button1 in my View and Button2 in my UserControl should call the same method in the ViewModel.

ViewViewModelUserControl

Therefore I need to 'inject' (don't know how to call it) the RelayCommand into the UserControl. I thought it would be easy with dependency properties but i can't get it to work.

What i tried:

The UserControl contains a dependency property which is set in the XAML Code of the View containing the UserControl:

UserControl Code:

public const string LoadDataCommandPropertyName = "LoadDataCommand";
public Action LoadDataCommand
{
    get
    {
        return (Action)GetValue(LoadDataCommandProperty);
    }
    set
    {
        SetValue(LoadDataCommandProperty, value);
    }
}

public static readonly DependencyProperty LoadDataCommandProperty = DependencyProperty.Register(
    LoadDataCommandPropertyName,
    typeof(Action),
    typeof(GridViewPersistenceController),
    new UIPropertyMetadata());

View Xaml Code: (usage of the dp in the UserControl)

<customControls:MyUserControl Grid.Column="1" 
                              x:Name="RadGridViewSettingsPersistenceControl" 
                              GridControl="{Binding ElementName=RadGridView}"
                              LoadDataCommand="{Binding ActionTest}"
                              />

The LoadDataCommand from the UserControl is bound to this property in the ViewModel

private const string ActionTestPropertyName = "ActionTest";
private Action actionTest;
public Action ActionTest
{
    get
    {
        return this.actionTest;
    }

    set
    {
        this.RaisePropertyChanging(ActionTestPropertyName);
        this.actionTest = value;
        this.RaisePropertyChanged(ActionTestPropertyName);
    }
}

The ActionTest property is initialized in the Constructor of the ViewModel

public MyViewModel()
        {
            this.ActionTest = new Action(this.LoadData);
        }

The visual studio output window also gave me a binding error (which i don't understand)

System.Windows.Data Error: 40 : BindingExpression path error: 'ActionTest' property not found on 'object' ''MyUserControl' (Name='RadGridViewSettingsPersistenceControl')'. BindingExpression:Path=ActionTest; DataItem='MyUserControl' (Name='RadGridViewSettingsPersistenceControl'); target element is 'MyUserControl' (Name='RadGridViewSettingsPersistenceControl'); target property is 'LoadDataCommand' (type 'Action')

I found a workaround which is working but i don't like it. I set the LoadDataCommand in LoadedEvent of View codebehind. It looks messy to me and i feel i missed some important concepts.

//TODO: Dirty coding, fix me
    private void MyView_OnLoaded(object sender, RoutedEventArgs e)
    {
        this.RadGridViewSettingsPersistenceControl.LoadDataCommand = new Action((this.DataContext as MyViewModel).LoadData);
    }

Questions:

  • How do i pass a Command/Action defined in the ViewModel into a UserControl using xaml code in the View?
  • Why is my current approach with bindings not working?
  • Am I missing some fundamental concepts? (Could i archieve this easier with delegates? (I tried..))
Was it helpful?

Solution

As evident from the error:

'ActionTest' property not found on 'object' ''MyUserControl' (Name='RadGridViewSettingsPersistenceControl')'

binding engine is searching for property in control and not in your ViewModel. (by default it searches for property in DataContext of control and i suspect you have set DataContext of control to itself somewhere in your code)

Use RelativeSource to get DataContext of your UserControl which will be your ViewModel.

LoadDataCommand="{Binding DataContext.ActionTest,
                   RelativeSource={RelativeSource Mode=FindAncestor,
                                           AncestorType=UserControl}}"

Also instead of creating DP of type Action, use ICommand and create ICommand in your ViewModel and bind to it.

OTHER TIPS

As an alternative, you can use a attached dependency property. Below is an example:

AttachedDependencyProperty

public static class UserControlExtension
{
    public static readonly DependencyProperty ActionProperty;

    public static void SetAction(DependencyObject DepObject, ICommand value)
    {
        DepObject.SetValue(ActionProperty, value);
    }

    public static ICommand GetAction(DependencyObject DepObject)
    {
        return (ICommand)DepObject.GetValue(ActionProperty);
    }

    static UserControlExtension()
    {
        ActionProperty = DependencyProperty.RegisterAttached("Action",
                                                            typeof(ICommand),
                                                            typeof(UserControlExtension));
    }
}

TestViewModel

public class TestViewModel
{
    private ICommand _testButtonCommand = null;

    public ICommand TestButtonCommand
    {
        get
        {
            if (_testButtonCommand == null)
            {
                _testButtonCommand = new RelayCommand(param => this.TestButton(), null);
            }

            return _testButtonCommand;
        }
    }

    private void TestButton() 
    {
        MessageBox.Show("Test command execute");
    }
}

MainWindow as View

<Window.Resources>
    <local:TestViewModel x:Key="TestVM" />
</Window.Resources>

<Grid DataContext="{StaticResource TestVM}">
    <local:TestUserControl x:Name="TestUserControl"
                           AttachedProperties:UserControlExtension.Action="{Binding TestButtonCommand}" />
</Grid>

UserControl

<Grid>
    <Button Name="TestButton"
            HorizontalAlignment="Center" 
            VerticalAlignment="Center"
            Content="TestContent"
            Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, 
                              Path=(AttachedProperties:UserControlExtension.Action)}" />        
</Grid>

Sample project is available here.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top