0

I am trying to use the IsHighlighted dependency property on the CustomView class. It works as expected when I pass it a hard bool value, but it doesnt work when I pass it a binding. Anyone know why?

Also should I be calling OnPropertyChanged in the IsHighlighted setter?

MainWindow.xaml

<StackPanel>

    <!-- The data context is set properly -->
    <TextBlock Text="{Binding Text}" FontSize="50"/>
    
    <!-- This works! -->
    <views:CustomView IsHighlighted="true"/>

    <!-- This does not! -->
    <views:CustomView IsHighlighted="{Binding Path=One}"/>

</StackPanel>

MainWindowViewModel.cs

public class MainWindowViewModel : ViewModelBase
{

    private string _text;

    public string Text
    {
        get { return _text; }
        set { _text = value; }
    }

    private bool _one;

    public bool One
    {
        get { return _one; }
        set { _one = value; }
    }

    public MainWindowViewModel()
    {
        Text = "bound text!";
        One = true;
    }
}

CustomView.xaml

<UserControl>
    <Grid Background="{Binding Path=CurrentBackground, 
                               RelativeSource={RelativeSource FindAncestor, 
                               AncestorType={x:Type local:CustomView}, AncestorLevel=1}}">
        <TextBlock Text="{Binding IsHighlighted}" FontSize="40"/>
    </Grid>
</UserControl>

CustomView.xaml.cs

public partial class CustomView : UserControl
{
    public CustomView()
    {
        InitializeComponent();
        DataContext = this;
    }

    public static readonly DependencyProperty IsHighlightedProperty =
        DependencyProperty.Register(
          name: "IsHighlighted",
          propertyType: typeof(bool),
          ownerType: typeof(CustomView)
        );

    public bool IsHighlighted
    {
        get => (bool)GetValue(IsHighlightedProperty);
        set => SetValue(IsHighlightedProperty, value);
    }

    public Brush CurrentBackground
    {
        get 
        {
            if (IsHighlighted)
                return new SolidColorBrush(Colors.Yellow); // highlighted
            else
                return new SolidColorBrush(Colors.White); // not highlighted
        }
    }


    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
}
  • `IsHighlighted="{Binding Path=One}"` does not work because the current DataContext object - i.e. the source object of the Binding - does not expose the One property (or should it be called `On`?). This is because you have explicitly set the DataContext to hold a reference to the control, not the view model of the MainWindow. A control with bindable properties should never explicitly set its own DataContex because that breaks any standard, DataContext-based Bindings of the control's properties. – Clemens Dec 09 '21 at 10:25
  • You should have noticed a data binding error message in the Output Window in Visual Studio when you had run your application in debug mode. – Clemens Dec 09 '21 at 10:27

1 Answers1

0

First remove line DataContext = this;. User control will inherit DataContext from his parent (in this case MainWindow.xaml and context will be MainViewModel).

Another question is that you properly set DataContext to your MainWindow. Did you set DataContext of your MainWindow.xaml like

this.DataContext = new MainWindowViewModel();

If not, do it somewhere in MainWindow.cs (for example in constructor). The DataContext property is the default source of your bindings.

https://learn.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.datacontext?view=windowsdesktop-6.0

Second thing is, you need to call PropertyChanged in your MainWindowViewModel One property setter.

public bool One
{
    get { return _one; }
    set 
    { 
       _one = value; 
       OnPropertyChanged();
    }
}

I am expecting that your ViewModelBase is implementing the INotifyPropertyChanged interface. If not you will need to implement it. In your case, it will work without the INotifyPropertyChanged implementation because you set the One property in constructor. But when you want to change this property in some logic in your ViewModel, you will need to notify the view that the One property is changing/changed (you will do it with INotifyPropertyChanged` interface implementation).

https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=net-6.0

One remark to these lines of code in Xaml.

<Grid Background="{Binding Path=CurrentBackground, 
                           RelativeSource={RelativeSource FindAncestor, 
                           AncestorType={x:Type local:CustomView}, AncestorLevel=1}}">

A better way how to change the background is to create a ValueConverter like.

  public class BackgroundConverter : IValueConverter
  {
      public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
      {
         return (bool)value ? new SolidColorBrush(Colors.Yellow) : SolidColorBrush(Colors.White);
      }

      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
      {
         throw new NotImplementedException();
      }
  }

Usage:

<Grid Background="{Binding IsHighlighted,
                   RelativeSource={RelativeSource AncestorType=local:CustomView},
                   Converter={StaticResource BackgroundConverter}}">
Clemens
  • 123,504
  • 12
  • 155
  • 268
puko
  • 2,819
  • 4
  • 18
  • 27
  • Even better than a Converter would be a DataTrigger. In either case, note that the Binding of the Background property to IsHighlighted would also need `RelativeSource={RelativeSource AncestorType=local:CustomView}`. – Clemens Dec 09 '21 at 07:14
  • Why the Binding would also need `RelativeSource={RelativeSource AncestorType=local:CustomView}` ? It is a property of MainViewModel, so you can bind it directly like `Background="{Binding IsHighlighted, Converter={StaticResource BackgroundConverter}}"` . O i am missing something ? – puko Dec 09 '21 at 07:24
  • It's a property of the control. – Clemens Dec 09 '21 at 07:25
  • Control properrty is `CurrentBackground` not `IsHighlighted`. – puko Dec 09 '21 at 07:26
  • 1
    Read the question. IsHighlighted is declared right before CurrentBackground. – Clemens Dec 09 '21 at 07:26
  • Oh yes. My bad in answer .. that should be `Background="{Binding One, Converter={StaticResource BackgroundConverter}}"` – puko Dec 09 '21 at 07:29
  • 1
    That would a bad advice. A control should not know a specific view model. It should expose bindable properties like IsHighlighted. – Clemens Dec 09 '21 at 07:30
  • That's true. I will delete this advice. Thanks for correction. – puko Dec 09 '21 at 07:32
  • 1
    I already did that on your behalf. Consider showing a DataTrigger in a Grid Style for how to implement this without a Converter. – Clemens Dec 09 '21 at 07:32