かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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の表示が崩れてる。

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