Bind View Model Property To User Control Dependency Property

Rauter Martin 0 Reputation points
2023-11-29T13:43:49.99+00:00

Hello Guys!

I am fairly new to the WPF eco system, but i already made some functioning dependency properties inside user controls, which can be bound successfully, and which updates, when the bound property inside the view model changes.

For now i just used simple object types (string, bool, int, bitmapimage, etc), but now i want to bind a Observeable Collection <CustomButton>. I want to use the list for a custom tool bar.

Here is the problem. I cant get this to work. I added two dependency properties test, and the edit button list:

public static readonly DependencyProperty ButtonsProperty =
DependencyProperty.Register("Buttons", typeof(ObservableCollection<EditButton>), typeof(EditToolBar), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnEditButtonsChanged)));

public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(string), typeof(EditToolBar), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnTestChanged)));
public ObservableCollection<EditButton> Buttons
{
    get => (ObservableCollection<EditButton>)GetValue(ButtonsProperty);
    set { SetValue(ButtonsProperty, value); OnPropertyChanged(); }
}

public string Test
{
    get => (string)GetValue(TestProperty);
    set { SetValue(TestProperty, value); OnPropertyChanged(); }
}

The Test property is for testing. When i set the value like that with a literal value:

<customControls:EditToolBar Grid.Row="1" Test="abc"/>

I get the value inside my user control, it fires the OnTestChanged method, and it reaches the breakpoint / prints the value. When i set it to x:Null, it wont get fired (maybe bc the initial value is also null, which i didnt set). Now, if i bind the Test property to a property in my view model:

 <customControls:EditToolBar Grid.Row="0" Test="{Binding Test, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>

The Xaml editor finds the property, when i hover over it i get the type of it, i cant see any binding errors in my xaml / output window, i even subscribed to the own property changed event in my view model, and it prints the expected property name. I also added a timer, which changes the value of test, so the changed call back should get invoked every period:

private string test = "Test";

public string Test
{
    get => test;
    set { test = value; OnPropertyChanged(); }
}
private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
    string text = Random.Shared.Next().ToString();
    Test = text;
}

But now, the Callback wont get fired, the OnPropertyChanged event does, and i dont even get the initial value set, nor any updates of this value. The same is for the button list, which is the reason why i want a property. I also watched the values before the OnPropertyChanged event, and they are set as expected.

I looked through the web, everywhere its said that this is simple, but apparently not simple enough for me. I already made some user controls, which works with this scenario, even tough with simplier types (besides the string which also wont work here), it didnt make click to me, i always got it through luck / trail and error. The debugging for xaml files is not very straight forward (for me at least).

Can someone help me please, maybe i am missing something, even chat gpt could not help me, tough it didnt even tell me to set the data context of the user control to the user controls cs file, which apparently also isnt automatically set (correct me on that if i am wrong). I also tried to set intial values inside the dependency property field. When i create and fill the list inside my user control, everything gets displayed correct, but that isnt what i wanted.

Greetings,

Martin!

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,783 questions
Visual Studio
Visual Studio
A family of Microsoft suites of integrated development tools for building applications for Windows, the web and mobile devices.
5,173 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,995 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
814 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,326 Reputation points
    2023-12-05T05:31:22.8933333+00:00

    Hi Martin,
    I think you are having trouble setting DataContext correctly. Try following demo based an your code:

    XAML Mainwindow:

    <Window x:Class="WpfApp120.Window120"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp120"
            xmlns:customControls="clr-namespace:WpfApp120"
            mc:Ignorable="d"
            Title="Rauter_Martin_231129" Height="450" Width="800">
      <Grid>
        <customControls:EditToolBar Grid.Row="0" 
                                    Test="{Binding Test, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
                                    Width="300" Height="100" />
      </Grid>
    </Window>
    

    CodeBehind MainWindow:

    using System;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using System.Windows;
    
    namespace WpfApp120
    {
    	/// <summary>
    	/// Interaction logic for Window120.xaml
    	/// </summary>
    	public partial class Window120 : Window, INotifyPropertyChanged
    	{
    		public Window120()
    		{
    			InitializeComponent();
    			DataContext = this;
    			var timer = new System.Timers.Timer();
    			timer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed);
    			timer.Interval = 200;
    			timer.Start();
    		}
    
    		private string test = "Test";
    		public string Test
    		{
    			get => test;
    			set { test = value; OnPropertyChanged(); }
    		}
    
    		Random rnd = new Random();
    		private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    		{
    			string text = rnd.Next().ToString();
    			Test = text;
    		}
    
    		SynchronizationContext sc = SynchronizationContext.Current;
    		public event PropertyChangedEventHandler PropertyChanged;
    		public event EventHandler CanExecuteChanged;
    		private void OnPropertyChanged([CallerMemberName] string propName = "") =>
    			sc.Post(new SendOrPostCallback((p) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName))), null);
    
    	}
    }
    

    XAML UserControl:

    <UserControl x:Class="WpfApp120.EditToolBar"
                 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:WpfApp1"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
      <Border BorderBrush="Red" BorderThickness="2">
        <TextBox x:Name="tb" Text="{Binding Test}" Width="200" Height="50"/>
      </Border>
    </UserControl>
    

    CodeBehind UserControl:

    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace WpfApp120
    {
    	/// <summary>
    	/// Interaction logic for Window120UC1.xaml
    	/// </summary>
    	public partial class EditToolBar : UserControl, INotifyPropertyChanged
    	{
    		public EditToolBar()
    		{
    			InitializeComponent();
    			this.tb.DataContext= this;
    		}
    
    		public static readonly DependencyProperty TestProperty =
    			DependencyProperty.RegisterAttached("Test", typeof(string), typeof(EditToolBar), 
    				new FrameworkPropertyMetadata(new PropertyChangedCallback(OnTestChanged)));
    
    		public string Test
    		{
    			get => (string)GetValue(TestProperty);
    			set { SetValue(TestProperty, value); OnPropertyChanged(); }
    		}
    
    		private static void OnTestChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    		{
    			if (e.NewValue is string testString) Debug.WriteLine("new value: " + testString);
    		}
    
    		SynchronizationContext sc = SynchronizationContext.Current;
    		public event PropertyChangedEventHandler PropertyChanged;
    		public event EventHandler CanExecuteChanged;
    		private void OnPropertyChanged([CallerMemberName] string propName = "") =>
    			sc.Post(new SendOrPostCallback((p) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName))), null);
    
    	}
    }
    
    

    Result:

    x

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.