かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPF4のDataGridで表示が崩れる条件

ネタ元:http://d.hatena.ne.jp/okazuki/20100308/1268061755

なんとなく条件がわかってきた。

  1. DataGridにバインドするクラスがプロパティで値のバリデーションをしていて例外を投げることがある。
  2. DataGridのセルのバインドにValidatesOnException=Trueが指定してある
  3. DataGridの行エラーを表示するアイコン?が表示されるタイミングでDataGridの高さが小さい

要は

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace DataGridBug
{
	public partial class PersonViewModel : INotifyPropertyChanged, IEditableObject
	{
		#region INotifyPropertyChanged
		public event PropertyChangedEventHandler PropertyChanged;
		protected virtual void OnPropertyChanged(string name)
		{
			var h = PropertyChanged;
			if (h == null) return;
			h(this, new PropertyChangedEventArgs(name));
		}
		#endregion

		#region Name Property
		private string _Name;
		private string _NameOriginal;
		[Required(ErrorMessage="Error")]
		public string Name
		{
			get 
			{
				return _Name; 
			}
			set
			{
				Validator.ValidateProperty(value,
					new ValidationContext(this, null, null) { MemberName = "Name" });
				_Name = value;
				OnPropertyChanged("Name");
			}
		}
		#endregion
		#region Age Property
		private int _Age;
		private int _AgeOriginal;
		[Range(0, 120, ErrorMessage="please input 0-120")]
		public int Age
		{
			get 
			{
				return _Age; 
			}
			set
			{
				Validator.ValidateProperty(value,
					new ValidationContext(this, null, null) { MemberName = "Age" });
				_Age = value;
				OnPropertyChanged("Age");
			}
		}
		#endregion
		#region Memo Property
		private string _Memo;
		private string _MemoOriginal;
		[Required(ErrorMessage="Memo is required")]
		public string Memo
		{
			get 
			{
				return _Memo; 
			}
			set
			{
				Validator.ValidateProperty(value,
					new ValidationContext(this, null, null) { MemberName = "Memo" });
				_Memo = value;
				OnPropertyChanged("Memo");
			}
		}
		#endregion
		#region IEditableObject
		// IEditableObject
		private bool _isEditing;
		public void BeginEdit()
		{
			if (_isEditing) return;
			_isEditing = true;
			_NameOriginal = _Name;
			_AgeOriginal = _Age;
			_MemoOriginal = _Memo;
		}
		public void CancelEdit()
		{
			if (!_isEditing) return;
			_isEditing = false;
			_Name = _NameOriginal;
			_Age = _AgeOriginal;
			_Memo = _MemoOriginal;
		}
		public void EndEdit()
		{
			if (!_isEditing) return;
			_isEditing = false;
			_NameOriginal = default(string);
			_AgeOriginal = default(int);
			_MemoOriginal = default(string);
		}
		#endregion
	}
}

↑のようなクラスを
こんな風にバインドして

<Window x:Class="DataGridBug.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:my="clr-namespace:DataGridBug" Loaded="Window_Loaded">
    <Window.Resources>
        <CollectionViewSource x:Key="personViewSource" d:DesignSource="{d:DesignInstance my:PersonViewModel, CreateList=True}" />
    </Window.Resources>
    <Grid DataContext="{StaticResource personViewSource}">
        <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding}" Margin="12" Name="personDataGrid">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Path=Name, ValidatesOnExceptions=True}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

適当にデータを流し込む

using System.Linq;
using System.Windows;

namespace DataGridBug
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            System.Windows.Data.CollectionViewSource personViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("personViewSource")));
            personViewSource.Source = Enumerable.Range(1, 1000).Select(i =>
                new PersonViewModel
                { 
                    Name = "test person " + i,
                    Memo = "memomemo" + i
                }).ToList();
        }
    }
}

実行して画面が表示されたら、ウィンドウを小さくしてDataGridに4行くらいしかデータが表示されない状態にする。
どこかのName列の値を編集して空文字にして、ValidationErrorを発生させて、DataGridのRowHeaderに赤いマークを出させる。
たてにスクロールしてみると、DataGridの表示が崩れてる。

これで再現するかな・・・?