Question

So far all my binding have worked well, as long as they were done with existing control properties of, say, Button, Label, and so on. For a single purpose I need a custom canvas (that inherits from Canvas) with one additional property. Yet the binding doesn't seem to work there. I then created a simpler example using a custom Label that can reproduce my problem. The custom label is defined in code as follows:

namespace CustomCanvasTest
{
  class CustomLabel : Label
  {
    public string Str
    {
      get { return GetValue(StrProperty) as string; }
      set
      {
        SetValue(StrProperty, value);
      }
    }

    public static readonly DependencyProperty StrProperty =
      DependencyProperty.Register("Str", typeof(string), typeof(CustomLabel));
  }
}

The XAML of the main window:

<Window x:Class="CustomCanvasTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CustomCanvasTest"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <Grid>
        <Button Content="Button" HorizontalAlignment="Left" Margin="313,10,0,0" VerticalAlignment="Top" Width="75" Command="{Binding ButtonClickCmd}"/>
        <local:CustomLabel Content="{Binding SomeString}" Str="{Binding SomeString}" HorizontalAlignment="Left" Height="25" Margin="291,288,0,0" VerticalAlignment="Top" Width="176"/>
    </Grid>
</Window>

And the ViewModel has the following code (the MainWindow.xaml.cs is untouched):

using System.ComponentModel;
using System.Windows.Input;
using WpfHelperClasses;

namespace CustomCanvasTest
{
  class ViewModel : INotifyPropertyChanged
  {
    public ViewModel()
    {
      SomeString = "init";
    }

    private string someString;
    public string SomeString
    {
      get { return someString; }
      set
      {
        someString = value;
        RaisePropertyChanged("SomeString");
      }
    }

    #region INPC

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string name)
    {
      var handler = PropertyChanged;
      if (handler != null)
      {
        handler(this, new PropertyChangedEventArgs(name));
      }
    }

    #endregion

    public ICommand ButtonClickCmd
    {
      get
      {
        return new DelegateCommand(param =>
        {
          SomeString = "blub";
        });
      }
    }
  }
}

When I click on the button, the label content changes visibly, but the breakpoint I set inside the setter for Str is never reached. I'm surely making some newbie mistake here, but even looking around the other answers here regarding similar problems I can't quite figure it out alone. Thanks for any help in advance!

Was it helpful?

Solution

From the XAML Loading and Dependency Properties article on MSDN:

For implementation reasons, it is computationally less expensive to identify a property as a dependency property and access the property system SetValue method to set it, rather than using the property wrapper and its setter. This is because a XAML processor must infer the entire object model of the backing code based only on knowing the type and member relationships that are indicated by the structure of the markup and various strings.

...

Because the current WPF implementation of the XAML processor behavior for property setting bypasses the wrappers entirely, you should not put any additional logic into the set definitions of the wrapper for your custom dependency property. If you put such logic in the set definition, then the logic will not be executed when the property is set in XAML rather than in code.

So the answer to your question is: No, the setter is not necessarily called.


In order to get notified about value changes of a dependency property, you will have to register a PropertyChangedCallback via dependency property metadata:

public static readonly DependencyProperty StrProperty =
    DependencyProperty.Register(
        "Str", typeof(string), typeof(CustomLabel),
        new PropertyMetadata(StrPropertyChanged));

private static void StrPropertyChanged(
    DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    var label = obj as CustomLabel;
    var str = e.NewValue as string;
    ...
}

OTHER TIPS

Use UIPropertyMetadata for getting values, which are assigned to your Dependency property:

Provides property metadata for non-framework properties that do have rendering/user interface impact at the core level.

Example:

public class CustomLabel : Label
{
    public static readonly DependencyProperty StrProperty = 
                           DependencyProperty.Register("Str",
                                                       typeof(string),
                                                       typeof(CustomLabel), 
                                                       new UIPropertyMetadata(String.Empty, IsStrTurn));

    public string Str
    {
        get
        {
            return GetValue(StrProperty) as string; 
        }

        set
        {
            SetValue(StrProperty, value);
        }
    }

    private static void IsStrTurn(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {    
        string strOld = e.OldValue as string;
        string strNew = e.NewValue as string;

        System.Diagnostics.Debug.WriteLine("The old value is " + strOld); // The old value is "init"
        System.Diagnostics.Debug.WriteLine("The new value is " + strNew); // The new value is "blub"
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top