Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting WPF Window DataContext to RelativeSource Self

Tags:

c#

wpf

xaml

If I set the Window's DataContext to this in the constructor as well as to {Binding RelativeSource={RelativeSource Self}} in XAML then I can see that the DataContext refers to the correct object instance (i.e. the MainWindow) by placing a break point in the code-behind's Loaded event. However, the Window's child element exampleButton's Command binding is null. The assertion fails.

When I remove the XAML DataContext setting (and rely on the constructor setting only) then exampleButton's Command uses the DataContext correctly.

Why is exampleButton's Command bound to null in the XAML scenario?

MainWindow.xaml

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Example"
        SizeToContent="WidthAndHeight"
        x:Name="mainWindow"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Loaded="mainWindow_Loaded">
    <Button x:Name="exampleButton" Command="{Binding Path=ExampleCommand}" Content="Click"/>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public ICommand ExampleCommand { get; }

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        ExampleCommand = new DelegateCommand(x => { throw new ApplicationException(); });
    }

    private void mainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        Debug.Assert(mainWindow == this);
        Debug.Assert(mainWindow.DataContext == this);
        Debug.Assert(exampleButton.DataContext == this);
        Debug.Assert(exampleButton.Command == ExampleCommand); //<-- FAIL
    }
}
like image 745
Jono Avatar asked Mar 10 '23 17:03

Jono


2 Answers

Why is exampleButton's Command bound to null in the XAML scenario?

Because the ExampleCommand property actually has a value of null by the time the InitializeComponent() method returns and the DataContext property is set.

If you want to set a property to a new value after this, the class must implement the INotifyPropertyChanged interface for the target property to get refreshed automatically:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();
        ExampleCommand = new RelayCommand<object>(x => { throw new ApplicationException(); });
    }

    private ICommand _exampleCommand;
    public ICommand ExampleCommand
    {
        get { return _exampleCommand; }
        set { _exampleCommand = value; NotifyPropertyChanged(); }
    }

    private void mainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        Debug.Assert(exampleButton.DataContext == this);
        Debug.Assert(exampleButton.Command == ExampleCommand); //<-- FAIL
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
like image 114
mm8 Avatar answered Mar 23 '23 23:03

mm8


Set ExampleCommand and DataContext before InitializeComponent:

 DataContext = this;
 ExampleCommand = new DelegateCommand(x => { throw new ApplicationException(); });
 InitializeComponent();

Also note that there is no difference between using DataContext="{Binding RelativeSource={RelativeSource Self}}" or DataContext = this;, if you set datacontext before initializecomponent.

like image 28
Ron Avatar answered Mar 23 '23 23:03

Ron