WPF UserControl doesn't work with DependencyProperty OnSomeBytePropertyChanged

38 Views Asked by At

To start, I have made an UserControl in a WPF project for setting colors in a settings window.

The Window has a ViewModel called MainWindowViewModel.cs containing:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Windows.Media;

namespace integrate.vsto.planning.settings.wpf.Viewmodels
{
    /// <summary>
    /// The Viewmodel used in the MainWindow
    /// </summary>
    public class MainWindowViewModel : INotifyPropertyChanged
    {

        #region Fields (Border color)

        /// <summary>
        /// Red value (0-255) of the border color
        /// </summary>
        private byte _borderColorR { get; set; } = 200;

        /// <summary>
        /// Green value (0-255) of the border color
        /// </summary>
        private byte _borderColorG { get; set; } = 200;

        /// <summary>
        /// Blue value (0-255) of the border color
        /// </summary>
        private byte _borderColorB { get; set; } = 200;

        #endregion

        #region Properties (Border color)

        /// <summary>
        /// Red value (0-255) of the border color
        /// </summary>
        public byte BorderColorR
        {
            get { return _borderColorR; }
            set
            {
                _borderColorR = value;
                OnPropertyChanged(nameof(BorderColorR));
                OnPropertyChanged(nameof(BorderColorBrush));
            }
        }

        /// <summary>
        /// Green value (0-255) of the border color
        /// </summary>
        public byte BorderColorG
        {
            get { return _borderColorG; }
            set
            {
                _borderColorG = value;
                OnPropertyChanged(nameof(BorderColorG));
                OnPropertyChanged(nameof(BorderColorBrush));
            }
        }

        /// <summary>
        /// Blue value (0-255) of the border color
        /// </summary>
        public byte BorderColorB
        {
            get { return _borderColorB; }
            set
            {
                _borderColorB = value;
                OnPropertyChanged(nameof(BorderColorB));
                OnPropertyChanged(nameof(BorderColorBrush));
            }
        }

        /// <summary>
        /// WPF compatible color for any element containing an implementation for a Brush
        /// </summary>
        public Brush BorderColorBrush
        {
            get
            {
                return new SolidColorBrush(Color.FromRgb(BorderColorR, BorderColorG, BorderColorB));
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// The event triggered on any Property changed
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Fires the PropertyChanged event for the given property (by name)
        /// </summary>
        /// <param name="propertyName">Given property name</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

    }
}

This Viewmodel contains a lot more than the given code, and works fine for everything containing no user control interactions, like texts and other bytes presented in normal Textboxes. So the link between my MainWindow and the Viewmodel is properly setup like this.DataContext = new MainWindowViewModel();

Then there is the UserControl called ColorSettingUserControl

using integrate.vsto.planning.settings.wpf.Viewmodels;
using System.Windows;
using System.Windows.Controls;

namespace integrate.vsto.planning.settings.wpf
{
    /// <summary>
    /// Interaction logic for ColorSettingUserControl.xaml
    /// </summary>
    public partial class ColorSettingUserControl : UserControl
    {
        /// <summary>
        /// The viewmodel for the color settings user control
        /// </summary>
        private ColorSettingViewModel _viewmodel;
    
        #region Attribute Registry

        /// <summary>
        /// The red value (0-255) for the preview control
        /// </summary>
        public static readonly DependencyProperty RedProperty =
            DependencyProperty.Register(nameof(Red), typeof(byte), typeof(ColorSettingUserControl), new FrameworkPropertyMetadata((byte)0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnRedPropertyChanged))

        /// <summary>
        /// The green value (0-255) for the preview control
        /// </summary>
        public static readonly DependencyProperty GreenProperty =
            DependencyProperty.Register(nameof(Green), typeof(byte), typeof(ColorSettingUserControl), new FrameworkPropertyMetadata((byte)0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnGreenPropertyChanged))

        /// <summary>
        /// The blue value (0-255) for the preview control
        /// </summary>
        public static readonly DependencyProperty BlueProperty =
            DependencyProperty.Register(nameof(Blue), typeof(byte), typeof(ColorSettingUserControl), new FrameworkPropertyMetadata((byte)0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBluePropertyChanged))

        #endregion

        #region Attribute Properties

        /// <summary>
        /// Color value red (0-255)
        /// </summary>
        public byte Red
        {
            get { return (byte)GetValue(RedProperty); }
            set { _viewmodel.Red = value; SetValue(RedProperty, value); }
        }

        /// <summary>
        /// Color value green (0-255)
        /// </summary>
        public byte Green
        {
            get { return (byte)GetValue(GreenProperty); }
            set { _viewmodel.Green = value; SetValue(GreenProperty, value); }
        }

        /// <summary>
        /// Color value blue (0-255)
        /// </summary>
        public byte Blue
        {
            get { return (byte)GetValue(BlueProperty); }
            set { _viewmodel.Blue = value; SetValue(BlueProperty, value); }
        }

        #endregion

    }
}

Lastly, here is the XAML lines in the UserControl that I need to work:

<UserControl x:Class="integrate.vsto.planning.settings.wpf.ColorSettingControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:local="clr-namespace:integrate.vsto.planning.settings.wpf"
    mc:Ignorable="d" 
    d:DesignHeight="100" d:DesignWidth="800">

    <TextBox x:Name="txt_R" Text="{Binding Red}" Width="46" />
    <TextBox x:Name="txt_G" Text="{Binding Green}" Width="46" />
    <TextBox x:Name="txt_B" Text="{Binding Blue}" Width="46" />

</UserControl>

And then the XAML for the MainWindow's implementation of the UserControl

<local:ColorSettingUserControl
    Red="{Binding BorderColorR}" 
    Green="{Binding BorderColorG}" 
    Blue="{Binding BorderColorB}" />

So I don't get the actual value of BorderColorR from the MainWindow's Viewmodel into the user's control Textbox. When debugging, the debugger also won't stop in the setter of public byte Red, nor the Green or Blue ones.

Not sure in what direction I need to do my research to make this work.

What I tried

I have looked at other questions related to this issue. However, I can't find the proper resource to investigate my issue. Everything works when entering a value directly in the <ColorSettingUserControl Red="20" />, and when I just make a new <Label Text={Binding Red} /> it gives the red color from the ViewModel. But when doing <ColorSettingUserControl Red={Binding Red}> it doesn't work.

I tried to use PropertyMetadata instead of FrameworkPropertyMetadata in ColorSettingUserControl.cs for the dependency properties.

When trying the value without a Binding, like <ColorSettingUserControl Red="20" />, it actually triggers the setter of the DependencyProperty.

What I expect

I expect that the <ColorSettingUserControl Red={Binding BorderColorR} /> triggers the setter of the DependencyProperty, which has a default value in it's private declaration.

1

There are 1 best solutions below

7
BionicCode On

Your bindings inside the UserControl are wrong. They use the DataContext as data source what is wrong. They must use the UserControl as data source.

You achieve this by using Bindig.RelativeSource (or alternatively Binding.ElementName which performs better as it doesn't have to traverse the element tree and uses the static lookup table of the current name scope instead):

<UserControl x:Name="Root">

  <!-- Option #1: Use Bindig.RelativeSource -->
    <TextBox x:Name="txt_R" Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=Red}" Width="46" />

  <!-- Option #2: Use Binding.ElementName -->
    <TextBox x:Name="txt_G" Text="{Binding ElementName=Root, Path=Green}" Width="46" />

    <TextBox x:Name="txt_B" Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=Blue}" Width="46" />
</UserControl>