web-dev-qa-db-ja.com

WPFデータバインディングと値コンバーターの相互作用を理解する

以下の簡略化された再現コードの舞台裏で実際に何が起こっているのかを理解しようとしています。

単一のWindowListBoxおよびTextBlockがバインドされています(つまり、マスター->詳細)。次に、文字列と日付の2つのプロパティを持つViewModelがあります。日付については、値コンバーター(LongDateConverter)を実装しました。

次の出力につながるコード内にいくつかのDebug.WriteLine()呼び出しがあります。

  • アプリを起動します
    • In converter: ConverterProblem.MainWindowViewModel
    • In converter: null
  • リストボックスの2つの項目のいずれかをクリックします
    • In converter: ConverterProblem.DataModel

IValueConverterメソッドの2回目と3回目の呼び出しは理解できると思います。 2番目はnullです。これは、ListBoxにまだ選択されたアイテムがないためです。 3つ目は私が選んだアイテムです。

私が理解していないのは:

  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();
        }
    }
}
9
GusP

問題は、TextBlockの設定の前にText依存関係をバインドしたことですDataContext

XAMLファイルはBAMLにコンパイルされ、アプリケーションの実行時に、XAMLLoaderによってBAMLからロードされます。XAMLを上から下に解析します。それに応じてDPの値を設定します

Text 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
15
Rohit Vats