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();
}
}
}
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.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With