Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding WPF data binding and value converter interactions

I'm trying to understand what's actually happening behind the scenes on the simplified repro code below.

I have a single Window with a ListBox and a TextBlock that are bound together (i.e., master -> detail). I then have a ViewModel with a couple properties--a string and a date. For the date, I implemented a value converter (LongDateConverter).

I have a couple Debug.WriteLine() calls in the code that lead to the following output:

  • Launch app
    • In converter: ConverterProblem.MainWindowViewModel
    • In converter: null
  • Click on one of the two items in the list box
    • In converter: ConverterProblem.DataModel

The second and third calls to the IValueConverter method I think I understand. The second is null because the ListBox doesn't have a selected item yet. The third is for the item that I selected.

What I don't understand is:

  1. Why is the first call passed a value of type MainWindowViewModel?
  2. Why is that call even happening in the first place?

Here's my code:

MainWindow.xaml:

<Window x:Class="ConverterProblem.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:app="clr-namespace:ConverterProblem"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <app:LongDateConverter x:Key="longDateConverter"/>
    </Window.Resources>
    <StackPanel Orientation="Horizontal">
        <ListBox SelectedItem="{Binding Data}" ItemsSource="{Binding DataList}"
                 DisplayMemberPath="Name"/>
        <TextBlock Text="{Binding Converter={StaticResource longDateConverter}}" 
                   DataContext="{Binding Data}" />
    </StackPanel>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace ConverterProblem 
{
    public class LongDateConverter : IValueConverter 
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
        {
            if (value == null) {
                Debug.WriteLine("In converter: null");
                return "null";
            }

            Debug.WriteLine("In converter: " + value.GetType().ToString());

            if (value.GetType() == typeof(MainWindowViewModel))
                return "viewmodel";

            return ((DataModel)value).Date.ToLongDateString();
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }

    public class DataModel
    {
        public string Name { get; set; }
        public DateTime Date { get; set; }
    }

    public class MainWindowViewModel : INotifyPropertyChanged
    {
        private DataModel _data;
        private List<DataModel> _dataList;

        public MainWindowViewModel()
        {
            _dataList = new List<DataModel> { 
                new DataModel { Date = DateTime.Now, Name = "John" }, 
                new DataModel { Date = DateTime.Now.AddDays(50), Name = "Sue" }
            };
        }

        public DataModel Data
        {
            get { return _data; }
            set
            {
                if (_data == value) return;

                _data = value;
                RaisePropertyChanged("Data");
            }
        }

        public List<DataModel> DataList
        {
            get { return _dataList; }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null) {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public partial class MainWindow : Window
    {
        private MainWindowViewModel _viewModel;

        public MainWindow()
        {
            _viewModel = new MainWindowViewModel();
            DataContext = _viewModel;
            InitializeComponent();
        }
    }
}
like image 788
GusP Avatar asked Jul 10 '14 03:07

GusP


People also ask

What types of data can I bind to WPF?

WPF data binding supports data in the form of .NET objects, XML, and even XAML element objects. To provide some examples, your binding source may be a UIElement, any list object, an ADO.NET or Web Services object, or an XmlNode that contains your XML data. For more information, see Binding sources overview.

What is one-way data binding in WPF?

In one-way binding, data is bound from its source (that is the object that holds the data) to its target (that is the object that displays the data) Let’s take a simple example to understand one-way data binding in detail. First of all, create a new WPF project with the name WPFDataBinding.

What is data binding in Windows Presentation Foundation?

Data binding in Windows Presentation Foundation (WPF) provides a simple and consistent way for apps to present and interact with data. Elements can be bound to data from different kinds of data sources in the form of.NET objects and XML.

How do I bind to a collection in WPF?

Because WPF binds to a collection only by using a view (either a view you specify, or the collection's default view), all bindings to collections have a current item pointer. When binding to a view, the slash ("/") character in a Path value designates the current item of the view. In the following example, the data context is a collection view.


1 Answers

Issue is you have binded Text dependency prior of setting DataContext for TextBlock.

XAML files are compiled into BAML and on application run, it is loaded from BAML by XAMLLoader which parse XAML from top to bottom and set value for DP's accordingly.

Since, Text DP gets encountered first so it will try to first set it's value and DataContext is not set yet for TextBlock so it will inherit from its parent Window whose DataContext is set to MainWindowViewModel. Hence, you see MainWindowViewModel printed in your converter. And when DataContext is set all DP's binding will be re-evaluated as per new DataContext.


Replace your XAML to this and you will see MainWindowViewModel won't print any more:

<TextBlock DataContext="{Binding Data}"
           Text="{Binding Converter={StaticResource longDateConverter}}" />

Output:

In converter: null
In converter: ConverterProblem.DataModel
like image 157
Rohit Vats Avatar answered Oct 22 '22 17:10

Rohit Vats