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:
In converter: ConverterProblem.MainWindowViewModel
In converter: null
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:
MainWindowViewModel
?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();
}
}
}
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