Order of setting the DataContext in the default constructor in WPF

Viewed 1355

I experiment with the order of setting the DataContext property in the default constructor in WPF.

<StackPanel>
        <ListBox ItemsSource="{Binding MyItems, PresentationTraceSources.TraceLevel=High}"></ListBox>
        <TextBlock Text="{Binding SomeText}"></TextBlock>
        <TextBlock Text="{Binding SomeNum}"></TextBlock>
        <TextBlock Text="{Binding Path=Person.Name}"></TextBlock>
        <ListBox ItemsSource="{Binding Path=PersonList}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"></TextBlock>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>

1) With DataContext set before the InitializeComponent method

public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private string someText = "Default text";

        public List<string> MyItems { get; set; }
        public List<Person> PersonList { get; set; }

        public Person Person { get; set; }
        public int SomeNum { get; set; }

        public string SomeText
        {
            get
            {
                return someText;
            }

            set
            {
                someText = value;
                OnPropertyChanged("SomeText");
            }
        }

        public MainWindow()
        {
            this.DataContext = this;

            MyItems = new List<string>();
            PersonList = new List<Person>();
            Person = new Person();

            InitializeComponent();

            /*These changes are not reflected in the UI*/
            SomeNum = 7;
            Person.Name = "Andy";

            /*Changes reflected with a help of INotifyPropertyChanged*/
            SomeText = "Modified Text";

            /* Changes to the Lists are reflected in the UI */
            MyItems.Add("Red");
            MyItems.Add("Blue");
            MyItems.Add("Green");
            MyItems[0] = "Golden";

            PersonList.Add(new Person() { Name = "Xavier" });
            PersonList.Add(new Person() { Name = "Scott" });
            PersonList[0].Name = "Jean";
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }

public class Person
    {
        public string Name { get; set; } = "Default Name";
    }

After the call to the InitializeComponent method changes to the values of properties are not reflected in the UI except for those properties which use INotifyPropertyChanged. Everything is clear so far.

However I noticed that changes to the list items are also reflected in the UI. How come?

I always thought that in order to reflect adding/removing from the collection I need ObservableCollection and to implement INotifyPropertyChanged on list object to detect modifications of these objects. What is the meaning of this?

2) With DataContext set after the InitializeComponent method

Why setting a DataContext property after the InitializeComponent is a bad practice with MVVM? Could you describe it more thoroughly or give a simple code example?

2 Answers

I always thought that in order to reflect adding/removing from the collection I need ObservableCollection<T> and to implement INotifyPropertyChanged on list object to detect modifications of these objects.

You do, if you want reliable updating of the UI during changes in the view model.

What is the meaning of this?

The "meaning" is that in your particular scenario, you are making assumptions that aren't valid. WPF components go through a variety of initialization steps, only some of which occur as part of the InitializeComponent() method.

If, for example, you were to move the code for your value updates into a handler for the Loaded event, you'd find some of the updates reflected in the UI, but not all.

If you move that same code into a method invoked via Dispatcher.InvokeAsync() using a priority of DispatcherPriority.SystemIdle, you'd find that none of the updates would be observed, except for the one backed by INotifyPropertyChanged. In that case, you're explicitly waiting until every aspect of initialization has completed, and there are no longer opportunities for the initialization code to observe your updated values.

It's all about timing. Any code that sets a value before the UI winds up observing it, can do so successfully without INotifyPropertyChanged or equivalent. But you're entirely at the mercy of the current implementation of the framework in that case. Different parts of the initialization happen at different times, and these are not all documented, so you're relying on undocumented behavior. It probably won't change, but you have no way to know for sure.

Why setting a DataContext property after the InitializeComponent is a bad practice with MVVM?

It's not. Don't believe everything you read, even (or especially!) on the Internet.

If you want to forego implementation of INotifyPropertyChanged, then it will be important that you initialize all of your view model data before assigning the DataContext. But, even if you assign the DataContext after calling InitializeComponent, that assignment will be observed (because DataContext is a dependency property and so provides property changed notification to the framework), and the UI will retrieve all of the bound data from your view model data.

What's important is that the view model data be initialized before the assignment of DataContext. Where that happens relative to InitializeComponent() is not important.

When a view model property does not fire the PropertyChanged event, its value must of course be set before assigning the view model instance to the view's DataContext.

It does however not matter if you assign the DataContext before or after calling InitializeComponent:

Given a Binding like

<TextBlock Text="{Binding SomeText}"/>

these two sequence will both result in showing the property value in the view:

DataContext = new { SomeText = "Hello, World." };
InitializeComponent();

and

InitializeComponent();
DataContext = new { SomeText = "Hello, World." };
Related