Nahuel Ianni
Nahuel Ianni

Reputation: 3185

Dependency property using view model values

I'm having problems when trying to implement a user control that contains a view model and some dependency properties.

Right now, the idea is to have a text box on the UC that contains a watermark property which should allow for the developer using it to pass a localized string from a resource file to it via xaml.

Given that the UC will have to process some info, I need to create a view model for it.

What I have so far is as follow:

On the user control I have one control which contains a string property named "Watermark", the value for it is binded to my VM watermark property:

<Grid x:Name="LayoutRoot" Background="Gray">    
    <controls:CustomTextBox Watermark="{Binding Path=WatermarkTextValue}"/>
</Grid>

And the view model looks like this:

private string watermarkText;
public string WatermarkTextValue
        {
            get
            {
                return watermarkText;
            }
            set
            {
                watermarkText = value;
                this.OnPropertyChanged(() => this.WatermarkTextValue);
            }
        }

The User control code behind contains the dependency property to use in order to bind the watermark of the view model against a resource file entry and in the constructor create a binding between the VM property and the dependency one:

public partial class SearchFilterUserControl : UserControl
    {
        public SearchFilterUserControl()
        {
            InitializeComponent();

            this.DataContext = new SearchFilterViewModel();
            var viewModelPropertyBinding = new Binding("WatermarkTextValue") { Mode = BindingMode.TwoWay, Source = this.DataContext };

            this.SetBinding(WatermarkTextProperty, viewModelPropertyBinding);
        }

        public string WatermarkText
        {
            get
            {
                return (string)this.GetValue(WatermarkTextProperty);
            }
            set
            {
                this.SetValue(WatermarkTextProperty, value);
            }
        }

        public static readonly DependencyProperty WatermarkTextProperty =
            DependencyProperty.Register("WatermarkText", typeof(string), typeof(SearchFilterUserControl), new PropertyMetadata(string.Empty));
    }

The main issue here is that, when using the UC from a view; I can only see values that are hardcoded in xaml, any other kind of binding won't work, so out of these two lines:

<userControls:SearchFilterUserControl WatermarkText="{Binding Path=SearchFilterUserControl_SearchWatermarkText, Source={StaticResource ResourceManagementClientResources}}"/>

<userControls:SearchFilterUserControl WatermarkText="Hardcoded text"/>

I see an empty text box and another one with the "Hardcoded text" watermark in it!

Upvotes: 1

Views: 537

Answers (1)

McGarnagle
McGarnagle

Reputation: 102763

There are a few problems here, but let's start with this:

public SearchFilterUserControl()
{
    InitializeComponent();

    this.DataContext = new SearchFilterViewModel();
}

When you do this, you are changing the data context of the control, which will break any bindings for the user of the control. That is, with this:

<controls:SearchFilterUserControl Watermark="{Binding Path=WatermarkTextValue}" />

the runtime will now look for "WatermarkTextValue" in the "SearchFilterViewModel" that is its new data context.

One way of getting around this issue is to apply the data context to a child element of your control (typically "LayoutRoot" or similar). That way the outer DataContext will be preserved. Note that you have to do this in the "OnApplyTemplate" override -- you can't do it in the constructor, as the template elements won't be loaded yet. Something like this:

public SearchFilterUserControl()
{
    InitializeComponent();

    this.DataContext = new SearchFilterViewModel();
}

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    GetTemplateChild("LayoutRoot").DataContext = new SearchFilterViewModel();
}

A second problem is that it looks like you're exposing "WatermarkTextValueProperty" as a binding target to the consumer of your control, then attempting to re-set it as a target of your internal binding to your view model. This obviously will not work.

My suggestion is to simply ditch the view model, and use a combination of an IValueConverter in your template binding, and/or the dependency property's "changed" event, to handle whatever processing you need. That will be a lot less convoluted.

If you insist on using the internal view model, then you will need to figure out a way to separate the "external" binding (which the consumer of your control sets) from the "internal" binding which your control uses to display the text.

Upvotes: 1

Related Questions