Sunday, March 13, 2011

WPF DataGrid tips: Per cell commiting behavior

Несколько месяцев назад проект над которым я тогда работал благополучно переехал на .Net 4.0. Одним из результатов этого перехода было то, что мы решили отказаться от грида Infragistics в пользу WPF DataGrid. Производительность нового грида оказалась выше всяких похвал. Там где Infragistics надолго задумывался, WPF DataGrid стал отображать данные буквально без задержки. Сам процесс замены гридов прошел достаточно безболезненно, однако были несколько моментов, над которыми пришлось поломать голову. В этом и нескольких последующих постах я хочу привести проблемы с которыми я столкнулся и решения на которых остановился.

Итак, проблема первая. При редактировании ячеек WPF DataGrid’a данные в исходной коллекции обновляются не после того как вы закончите редактировать непосредственно ячейку, а только после того как закончите редактировать всю строку целиком.

Для того чтобы получить нужное поведение грида оказалось достаточным в обработчике события CellEditEnding заставлять грид сохранять все имеющиеся изменения.

private void Grid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
    if (_manualEditCommit)
        return;

    if (e.EditAction != DataGridEditAction.Commit)
        return;

    _manualEditCommit = true;
    var grid = (DataGrid)sender;
    grid.CommitEdit(DataGridEditingUnit.Row, true);
    _manualEditCommit = false;
}

Однако тут возник другой вопрос: где именно это реализовать? В code behind? Однозначно нет. Сделать производный от грида PerCellCommitDataGrid? Тоже не то.

Мне нравятся два варианта. Вариант первый: Сделать Attached property PerCellCommit, которое будет подписывать нужный обработчик на событие CellEditEnding.

public static class DataGridHelper
{
    public static readonly DependencyProperty PerCellCommitProperty =
        DependencyProperty.RegisterAttached(
            "PerCellCommit",
            typeof(bool),
            typeof(DataGridHelper),
            new PropertyMetadata(false, PerCellCommitChanged));

    public static bool GetPerCellCommit(DependencyObject obj)
    {
        return (bool)obj.GetValue(PerCellCommitProperty);
    }

    public static void SetPerCellCommit(DependencyObject obj, bool value)
    {
        obj.SetValue(PerCellCommitProperty, value);
    }

    public static void PerCellCommitChanged(
        DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var grid = obj as DataGrid;
        if (grid == null)
            return;

        if ((bool)e.NewValue)
        {
            grid.CellEditEnding += Grid_CellEditEnding;
        }
        else
        {
            grid.CellEditEnding -= Grid_CellEditEnding;
        }
    }

    private static bool _isManualEditCommit;

    private static void Grid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
    {
        if (_isManualEditCommit)
            return;

        if (e.EditAction != DataGridEditAction.Commit)
            return;

        _isManualEditCommit = true;
        var grid = (DataGrid) sender;
        grid.CommitEdit(DataGridEditingUnit.Row, true);
        _isManualEditCommit = false;
    }
}

В этом случае в XAML новую функциональность гриду можно подключить следующим образом:

<DataGrid
    ItemsSource="{Binding Tags}"
    presentation:DataGridHelper.PerCellCommit="True">
    <DataGrid.Columns>
        ...
    </DataGrid.Columns>
</DataGrid>

Вариант второй: Реализовать для грида кастомный behavior. Что такое behavior’ы и чем они полезны рекомендую почитать вот тут.

public class DataGridPerCellCommitBehavior : Behavior<DataGrid>
{
    private static bool _manualEditCommit;

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.CellEditEnding += Grid_CellEditEnding;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.CellEditEnding -= Grid_CellEditEnding;
    }

    private void Grid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
    {
        if (_manualEditCommit)
            return;

        if (e.EditAction != DataGridEditAction.Commit)
            return;

        _manualEditCommit = true;
        var grid = (DataGrid)sender;
        grid.CommitEdit(DataGridEditingUnit.Row, true);
        _manualEditCommit = false;
    }
}

В этом случае грид будет выглядеть примерно так:

<DataGrid
    ItemsSource="{Binding Tags}">
    <DataGrid.Columns>
        ...
    </DataGrid.Columns>
    <i:Interaction.Behaviors>
        <bhvr:DataGridPerCellCommitBehavior />
    </i:Interaction.Behaviors>
</DataGrid>

Исходники демки с реализованным behavior’ом можно скачать тут.

No comments:

Post a Comment