了解WPF数据绑定和值转换器的交互

本文关键字:转换器 交互 WPF 数据绑定 了解 | 更新日期: 2023-09-27 18:12:10

我试图理解下面简化的repro代码背后实际发生了什么。

我有一个Window, ListBoxTextBlock被绑定在一起(即,master -> detail)。然后我有一个ViewModel与几个属性——字符串和日期。对于日期,我实现了一个值转换器(LongDateConverter)。

我在代码中有几个Debug.WriteLine()调用,导致以下输出:

  • 启动应用程序
    • In converter: ConverterProblem.MainWindowViewModel
    • In converter: null
  • 单击列表框中的两个项目之一
    • In converter: ConverterProblem.DataModel

第二次和第三次调用IValueConverter方法,我想我明白了。第二个是null,因为ListBox还没有被选中的项目。第三个是我选择的项目。

我不明白的是:

  1. 为什么第一次调用传递的是MainWindowViewModel类型的值
  2. 为什么这个电话会发生呢?

下面是我的代码:

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数据绑定和值转换器的交互

问题是您在为TextBlock设置 DataContext 之前绑定了 Text 依赖项。

XAML文件被编译成BAML,在应用程序运行时,它通过 XAMLLoader 从BAML加载,其中从上到下解析XAML并相应地设置DP的值

因为,文本DP首先遇到,所以它会尝试首先设置它的值和DataContext还没有为TextBlock设置,所以它将继承它的父窗口,其DataContext被设置为MainWindowViewModel。因此,您将看到MainWindowViewModel打印在您的转换器中。当DataContext被设置时,所有DP的绑定将根据新的DataContext重新评估。


将你的XAML替换为这个,你会看到 MainWindowViewModel 不再打印了:

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

输出:

In converter: null
In converter: ConverterProblem.DataModel