現象
WPF アプリケーションでは、DataGrid コントロールに入力された値の検証を行うことが可能です。
入力された値で検証エラーが発生すると、エラーを示す赤枠が DataGrid 上に表示されます。
この時、全てのセルに対して BindingExpression.UpdateSource を実行して、一括で検証処理を行った場合に、検証エラーを示す赤枠がずれて表示されることがあります。
この現象は、以下のような場合に発生することがあります。
-
検証エラーの表示後に DataGrid コントロール内のデータをスクロールさせた場合
-
144 箇所以上の検証エラーが見つかった場合
原因
DataGrid コントロールで検証エラーが発生すると、赤枠を表示するために AdornerLayer コントロールが作成されます。
しかし、AdornerLayer コントロール、および、それらを管理する AdornerDecorator コントロールの動作に問題があり、スクロール時に赤枠がずれて表示されることがあります。
また、144 個以上の AdornerLayer コントロールが作成されると、AdornerDecorator コントロールの管理情報に問題が発生する場合があり、赤枠がずれて表示されることがあります。
状況
マイクロソフトでは、この問題をこの資料の対象製品として記載されているマイクロソフト製品の問題として認識しています。
詳細
この問題を再現する手順
-
Visual Studio から新しいWPFアプリケーションを作成します。
-
MainWindow.xaml に次のコードを追加します。
<Window x:Class="WindowsAppliation1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WindowsAppliation1"
Title="MainWindow" Height="450" Width="600">
<Grid>
<DataGrid Name="dataGrid1" AutoGenerateColumns="False" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,30,0,0">
<DataGrid.Columns>
<DataGridTextColumn Header="ID">
<DataGridTextColumn.Binding>
<Binding Path="id" Mode="TwoWay">
<Binding.ValidationRules>
<local:NullCheckValidationRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTextColumn Header="NAME">
<DataGridTextColumn.Binding>
<Binding Path="name" UpdateSourceTrigger="Explicit" Mode="TwoWay" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:NullCheckValidationRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTextColumn Header="ADDRESS">
<DataGridTextColumn.Binding>
<Binding Path="address" UpdateSourceTrigger="Explicit" Mode="TwoWay" ValidatesOnDataErrors="True">
<Binding.ValidationRules >
<local:NullCheckValidationRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<Button Name="button1" Content="TEST" HorizontalAlignment="Left" VerticalAlignment="Top" Click="button1_Click" />
</Grid>
</Window> -
MainWindow.xaml.cs に次のコードを追加します。
namespace WindowsAppliation1
{
public partial class MainWindow : Window
{
private DataTable dt;
public MainWindow()
{
InitializeComponent();
dt = new DataTable();
dt.Columns.Add(new DataColumn("id"));
dt.Columns.Add(new DataColumn("name"));
dt.Columns.Add(new DataColumn("address"));
for (int i = 0; i <= 100; i++)
{
if (i % 5 == 0)
dt.Rows.Add(i.ToString(), "name" + i.ToString(), "address" + i.ToString());
else
dt.Rows.Add(i.ToString(), "name" + i.ToString(), DBNull.Value);
}
dt.AcceptChanges();
dataGrid1.ItemsSource = dt.DefaultView;
dataGrid1.EnableRowVirtualization = false;
dataGrid1.EnableColumnVirtualization = false;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
for (int rowIndex = 0; rowIndex < dataGrid1.Items.Count; rowIndex++)
{
DataGridRow rowContainer = (DataGridRow)dataGrid1.ItemContainerGenerator.ContainerFromIndex(rowIndex);
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);
for (int columnIndex = 0; columnIndex < dataGrid1.Columns.Count; columnIndex++)
{
DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
BindingExpression expression = BindingOperations.GetBindingExpression(GetVisualChild<TextBlock>(cell), TextBlock.TextProperty);
expression.UpdateSource();
}
}
}
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
child = GetVisualChild<T>(v);
else
break;
}
return child;
}
}
public class NullCheckValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string str = value as string;
if (String.IsNullOrEmpty(str))
return new ValidationResult(false, "error");
else
return new ValidationResult(true, null);
}
}
} -
アプリケーションを実行します。
-
Button コントロールをクリックします
結果
Button コントロールをクリックすると DataGrid コントロールの値の検証が行われ、検証エラーの位置に赤枠が表示されます。
この後 DataGrid コントロールをスクロールすると、赤枠がずれる現象が発生します。
なお、本問題が発生したとしても、実際のデータには影響はありません。
回避策
問題が発生した場合には、Alt キーを押下することにより表示が正常になります。
これは、Alt キーが押下された際の WPF の内部処理で AdornerLayer コントロールが再作成されるためです。