Why does binding the MainWindow datacontext in XAML fail to act the same as binding in the codebehind with this.datacontext=this?
Asked Answered
A

1

14

I am trying to use Data binding to bind an ObservableCollection to the ItemsSource of a DataGrid, as I learn about WPF and stuff.

In the code-behind I can set the DataContext with this.DataContext = this; or bloopDataGrid.DataContext = this;. That's fine and dandy.

I thought I could try something like

<Window.DataContext>
    <local:MainWindow/>
</Window.DataContext>

in my main window, but this causes a Stack Overflow Exception as explained in this question. Fine, that makes some sense.

After reading this and other questions/answers that say to try DataContext="{Binding RelativeSource={RelativeSource Self}}" in the window's XAML code, I thought I could actually do this. Apparently I cannot. Or at least, the IDE lets me and it's syntactically correct, but does not do what I want (ie, exactly what this.DataContext = this; does).

Then I read this about using "{Binding ElementName=, Path=}" and tried to use it like so:

<DataGrid
    Name="bloopDataGrid"
    Grid.Row="1"
    ItemsSource="{Binding ElementName=testWin, Path=OutputCollection}">
</DataGrid>

Which also doesn't work. Maybe not for the same reason, but I can't figure out the problem with it.

Oddly, I can't replicate the rebinding example shown in Rachel Lim's blog post.

XAML:

<Window
    x:Class="DataBinding.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow"
    Height="350"
    Width="525"
    x:Name="testWin">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Label Grid.Row="0" Content="{Binding text}">   
        </Label>

        <DataGrid
            Name="bloopDataGrid"
            Grid.Row="1"
            ItemsSource="{Binding Path=OutputCollection}">
        </DataGrid>
    </Grid>
</Window>

C#:

using System;
using System.Collections.ObjectModel; //For ObservableCollection<T>
using System.Windows;

namespace DataBinding
{
    public partial class MainWindow : Window
    {
        public String text { get; set; }
        public ObservableCollection<testStruct> OutputCollection { get; set; }

        public struct testStruct
        {
            public testStruct(String x, String y) : this()
            {
                Col1 = x;
                Col2 = y;
            }
            public String Col1 { get; set; }
            public String Col2 { get; set; }
        }

        public MainWindow()
        {
            InitializeComponent();

            testA t1 = new testA();
            this.DataContext = this;
            //this.DataContext = t1;
            //bloopDataGrid.DataContext = this;
            text = "bound \"this\"";
            t1.text = "bound a class";

            OutputCollection = new ObservableCollection<testStruct>();
            OutputCollection.Add(new testStruct("1", "2"));
            OutputCollection.Add(new testStruct("3", "4"));
        }

        public class testA
        {
            public String text { get; set; }
        }

    }
}

The above code is what I'm using to test this, and is currently using the code-behind version which correctly gives me

In codebehind

What am I doing wrong, which is preventing me from getting the same results as the above picture but by using XAML for the DataContext handling? Am I not connecting the dots properly? ...am I missing some dots?

Assent answered 4/3, 2013 at 15:40 Comment(0)
K
35
<Window.DataContext>
    <local:MainWindow/>
</Window.DataContext>

is not the same as

this.DataContext = this;

The first one is creating a new instance of the MainWindow class and assigning that to the DataContext property of the Window, while the second is assigning the very same instance of the Window to its DataContext property.

In order to achieve that in XAML, you need to use a RelativeSource Binding:

<Window DataContext="{Binding RelativeSource={RelativeSource Self}}">
</Window>

Edit:

The difference in behavior between defining the DataContext in XAML and in code behind is caused by the fact that the XAML is actually parsed when the constructor finishes executing, because the Dispatcher waits for the user code (in the constructor of the Window) to finish before executing its pending operations.

This causes the actual property values to be different in these different moments, and since there is no INotifyPropertyChanged, WPF has no way of updating the UI to reflect the new values.

You could implement INotifyPropertyChanged in the Window itself, but I suggest creating a ViewModel for this, as I don't like the fact of mixing INotifyPropertyChanged (which is more of a ViewModel concept) with DependencyObject-derived classes (UI elements).

Koppel answered 4/3, 2013 at 15:44 Comment(7)
I mentioned that this did not work for me, and that I figured out what the problem with MainWindow is by looking at the other question about it.Assent
Your problem is that you're mashing up together your data elements inside the MainWindow class itself. You should create a ViewModel and Implement INotifyPropertyChanged.Koppel
I'm not sure I understand. Am I restricted to following the MVVM pattern for all my programs in WPF? How will making a new class fix the problem I have? I'd rather fix it or figure out why it doesn't work, not strictly avoid it just because I don't understand it.Assent
The problem is that you're not implementing INotifyPropertyChanged, and thus the UI is never notified when these properties change. You could also implement that in the Window itself, but I don't like that approach, because Window is a DependencyObject, and I don't like to mix both, because I think INotifyProeprtyChanged is more a ViewModel concept.Koppel
So everything I'm doing is actually correct, it just isn't displaying because my DataContext is MainWindow, but MainWindow doesn't implement INotifyPropertyChanged? If that's the case, why does this.DataContext = this work?Assent
because it happens in a different moment, when the window constructor has not yet been executed. Thus, the dispatcher has to wait to "draw" or better, "create" the visual tree until your code has completed.Koppel
You were correct about INotifyPropertyChanged. I implemented it on the MainWindow directly to see if this was the case, and it now works when DataContext="{Binding RelativeSource={RelativeSource Self}}" is used and I fire the event. If you change your answer to reflect this (and other things mentioned in these comments) I'll accept it, otherwise I'll answer it myself.Assent

© 2022 - 2024 — McMap. All rights reserved.