lolveley
lolveley

Reputation: 1709

how to access to viewModel dependency properties from inherited user control?

I am struggled in a demo WPF project. I have to recognize that I have many constraints which are complicating the code.

The core element is a control which is inherited from UserControl. I would like to keep its code-behind as light as possible. Also, I would like to have its XAML in a ControlTemplate. Its C# code should be in a dedicated ViewModel (this example is for a huge project and having a dedicated viewModel could help by having all viewmodel grouped. but anyway, say it's mandatory). Last, but not least, I would like to bind 2 properties of this control to external properties.

Here is my MainWindow.xaml file:

<Window x:Class="ViewModel_defined_in_ControlTemplate.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:ViewModel_defined_in_ControlTemplate"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="MyDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <local:MyUserControl Template="{StaticResource TextBoxTemplate}"
                                 NomPersonne="sg"/>
            <Button Content="Click me!" Command="{Binding ElementName=MyViewModel,Path=ChangeTextBoxContent}" Width="100" HorizontalAlignment="Left"/>
        </StackPanel>
    </Grid>
</Window>

The button simply changes the value of the NomPersonne dependency property(see below). MyDictionary.xaml contains the ControlTemplate:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:ViewModel_defined_in_ControlTemplate">

    <ControlTemplate x:Key="TextBoxTemplate" TargetType="{x:Type local:MyUserControl}">
        <Grid>
            <Grid.DataContext>
                <local:MyViewModel/>
            </Grid.DataContext>
            <TextBox Width="50" HorizontalAlignment="Left" Text="{TemplateBinding NomPersonne}"/>
        </Grid>


    </ControlTemplate>    
</ResourceDictionary>

I don't know where to put my dependency property, and how to access to it. I tried to put it in MyUserControl:

namespace ViewModel_defined_in_ControlTemplate
{
    public partial class MyUserControl : UserControl
    {



        public string NomPersonne
        {
            get { return (string)GetValue(NomPersonneProperty); }
            set { SetValue(NomPersonneProperty, value); }
        }

        public static readonly DependencyProperty NomPersonneProperty =
            DependencyProperty.Register("NomPersonne", typeof(string), typeof(MyUserControl), new PropertyMetadata(""));

    }
}

and now it's accessible from the XAML of MyUserCOntrol, but then I don't know how to access to it in order to have the button's command change the property:

namespace ViewModel_defined_in_ControlTemplate
{
    public class MyViewModel : ViewModelBase
    {

        public RelayCommand ChangeTextBoxContent = new RelayCommand(() => 
        { 
            //...
        }, () => true);

    }

}

I would rather having the dependency property in the viewmodel, but in this case how can I access to in in the XAML of MyUserControl, in MainWindow?

thank you.

Upvotes: 0

Views: 723

Answers (2)

Adam Štafa
Adam Štafa

Reputation: 368

It's a really bad idea to give a UserControl a ViewModel. You've just stumbled upon one of many problems of this approach. There's no way to define the DependecyProperty in ViewModel, code-behind is the only way.

In order to synchronize data between code-behind and ViewModel, you would have to subscribe to PropertyChanged of ViewModel in code-behind and each time a value changes in the ViewModel, update the respective DependencyProperties in code-behind. This has to work the other way around too. When a DependencyProperty changes, you have to update it in the ViewModel. It's not impossible to achieve this, but it's really ugly (trust me, I've done it; never will again).

Another problem is setting the DataContext of the UserControl (either in code-behind or XAML) to the ViewModel. If you set it on the UserControl directly, bindings won't work. Workaround is to set the DataContext of the first child of the UserControl (again, don't do it).

UserControl with a ViewModel is a really bad idea. But that's not to say your code-behind should contain all the code. You can always extract methods doing some advanced logic into their separate classes. A static method can be called anywhere, even code-behind.

Upvotes: 0

mm8
mm8

Reputation: 169200

You should add a source property to the view model, bind the target property of the UserControl to this one and update the source property in the view model:

<local:MyUserControl Template="{StaticResource TextBoxTemplate}" 
                     NomPersonne="{Binding Name}"/>

View Model:

public class MyViewModel : ViewModelBase
{
    public RelayCommand ChangeTextBoxContent = new RelayCommand(() =>
    {
        Name = "...":
    }, () => true);

    private string _name;

    public string Name
    {
        get { return _name; }
        set { _name = value; OnPropertyChanged(); }
    }

    ...
}

You should also set the DataContext in the window rather than in the ResourceDictionary:

<Window x:Class="ViewModel_defined_in_ControlTemplate.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:ViewModel_defined_in_ControlTemplate"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="MyDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Window.DataContext>
        <local:MyViewModel/>
    </Window.DataContext>
    <Grid>
        <StackPanel>
            <local:MyUserControl Template="{StaticResource TextBoxTemplate}"
                                 NomPersonne="{Binding Name}"/>
            <Button Content="Click me!" Command="{Binding ChangeTextBoxContent}" Width="100" HorizontalAlignment="Left"/>
        </StackPanel>
    </Grid>
</Window>

The ResourceDictionary should only define the template:

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:ViewModel_defined_in_ControlTemplate">

        <ControlTemplate x:Key="TextBoxTemplate" TargetType="{x:Type local:MyUserControl}">
            <Grid>
                <TextBox Width="50" HorizontalAlignment="Left" 
                         Text="{TemplateBinding NomPersonne}"/>
            </Grid>
        </ControlTemplate>
    </ResourceDictionary>

Upvotes: 1

Related Questions