1

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!

Nahuel Ianni
  • 3,177
  • 4
  • 23
  • 30

1 Answers1

1

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.

McGarnagle
  • 101,349
  • 31
  • 229
  • 260
  • Thanks for the answer @McGarnagle but there is one doubt I still have: In my code, what is the difference between passing a literal and binding a resource to the WatermarkText? I cannot figure that out; I know I must be missing something simple here but believe me, I'm far away from figuring that out :/ – Nahuel Ianni Jul 03 '14 at 16:10
  • @NahuelI. wow, I'm sorry, I see now that my answer is very muddled. To be honest I find it hard to follow the flow of what you've set up there, it's kind of funky. I don't think I can answer your question without more details. Maybe you can elaborate on the "processing" that you need -- is it just converting the watermark text into something else, or what? – McGarnagle Jul 03 '14 at 16:35
  • @NahuelI. It looks like what you're really attempting is to make the view model property the *target* for the binding, with the dependency property being the *source*. In theory that would work, except that view model properties cannot be binding targets, which I think is why you're attempting the two-way binding. But that confuses everything. I think there has to be a better way, as I mentioned above, using an IValueConverter and/or the "dependency property changed" delegate. – McGarnagle Jul 03 '14 at 16:53
  • 1
    You got it right now, I'm trying to save the value of what needs to show my DP on the VM. The value converter won't work in what I'm trying to do, but I'll definetly check the "dep. prop. changed" delegate tomorrow; I'll get back to your answer once I have tried it :) – Nahuel Ianni Jul 03 '14 at 17:07