Difference between setting DataContext=this in constructor and Binding to {RelativeSource Self} in WPF?

11.8k views Asked by At

Next code works as expected:

AskWindow.xaml:

<Window
    x:Class='AskWPF.AskWindow'
    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
    xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
    >

<DataGrid ItemsSource="{Binding SimpleItems}" />

</Window>

AskWindow.xaml.cs:

namespace AskWPF {

public class SimpleRow {
    private string firstColumn;
    private string secondColumn;

    public SimpleRow(string first, string second) {
        firstColumn = first;
        secondColumn = second;
    }

    public string FirstColumn {
        get { return firstColumn; }
        set { firstColumn = value; }
    }

    public string SecondColumn {
        get { return secondColumn; }
        set { secondColumn = value; }
    }
}

public partial class AskWindow : Window {

    private ObservableCollection<SimpleRow> simpleItems;

    public AskWindow() {
        InitializeComponent();
        DataContext = this;

        simpleItems = new ObservableCollection<SimpleRow>();
        simpleItems.Add(new SimpleRow("row 0, column 0", "row 0, column 1"));
        simpleItems.Add(new SimpleRow("row 1, column 0", "row 1, column 1"));
    }

    public ObservableCollection<SimpleRow> SimpleItems {
        get { return simpleItems; }
    }
}

}

But if set DataContext='{Binding RelativeSource={RelativeSource Self}}' in Window tag and comment line DataContext=this we get an empty window. Why?

AskWindow.xaml:

<Window .... DataContext='{Binding RelativeSource={RelativeSource Self}}'>

    <DataGrid ItemsSource="{Binding SimpleItems}" />

</Window>

AskWindow.xaml.cs:

...
public AskWindow() {
    InitializeComponent();
    // DataContext = this;

    simpleItems = new ObservableCollection<SimpleRow>();
    simpleItems.Add(new SimpleRow("row 0, column 0", "row 0, column 1"));
    simpleItems.Add(new SimpleRow("row 1, column 0", "row 1, column 1"));
}
...
3

There are 3 answers

1
dowhilefor On BEST ANSWER

Here is my guess. In both cases at one point your Collection is null. To be precise right after InitializeComponent. At this point the initial databinding got the data, but no datacontext. Now by setting the DataContext your property gets raised and every binding related to it, gets invalidated and refreshed. Here is my guessing part, the reason it works is that the binding to the ItemsSource is deferred therefore it works to just set the collection in the next line.

So in short: Setting the Datacontext will retrigger the binding. But in your RelativeSource example your binding worked from the beginning but the collection was null and you never told wpf to refetch the binding. If you directly initialize your collection it should work fine.

0
Chevul Ervin On

Actually the binding is correct and it works also. In order the screen to be updated the binding has to receive notifications that something changed. A binding first evaluates and then listens for notifications. In your second version the binding first evaluates when InitializeComponent is run, but there are no values at that moment so you see nothing. After that the values are created but the binding does not reevaluate because no notifications are sent.

So yes one solutuion would be to initialize the collection prior to InitializeComponent.

...
private ObservableCollection<SimpleRow> simpleItems = new ObservableCollection<SimpleRow>();
...

Another solution would be stupid and an overkill to notify the binding that something was changed.

Just a note, probably this is for learning purposes because the UI should not be mixed up with the model.

0
brunnerh On

I suspect it has to do with how and when certain kinds of bindings are evaluated. In the latter case i think that the binding may retrieve the value of the collection property while it still is null, then you change the property (by setting the field) without firing any change notification for the affected property.

Would recommend to move the InitializeComponent call to the end of the constructor or to at least set the field beforehand.

Usually i use a readonly field and just initialize it right away:

private readonly ObservableCollection<Data> collection =
    new ObservableCollection<Data>();
public ObservableCollection<Data> Collection { get { return collection ; } }