Как написать универсальный стиль для DataGridCell из WPF?

Отличный вопрос! Создание универсального стиля для DataGridCell в WPF — это фундаментальная задача для кастомизации внешнего вида таблиц. Давайте разберем эту тему максимально подробно.

Базовое понимание

DataGridCell — это контейнер для содержимого ячейки в DataGrid. Универсальный стиль позволяет единообразно оформлять все ячейки таблицы, переопределяя стандартное поведение.

1. Минимальный универсальный стиль

Самый простой способ — определить стиль в ресурсах:

<Window.Resources>
    <Style TargetType="DataGridCell">
        <Setter Property="Background" Value="White"/>
        <Setter Property="Foreground" Value="Black"/>
        <Setter Property="BorderBrush" Value="LightGray"/>
        <Setter Property="BorderThickness" Value="0,0,1,1"/>
        <Setter Property="Padding" Value="8,4"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="HorizontalContentAlignment" Value="Left"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="DataGridCell">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            Padding="{TemplateBinding Padding}">
                        <ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

2. Расширенный стиль с обработкой состояний

Для полноценного стиля нужно учитывать различные состояния ячейки:

<Style TargetType="DataGridCell">
    <!-- Базовые свойства -->
    <Setter Property="Background" Value="White"/>
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="BorderBrush" Value="#FFC9CACA"/>
    <Setter Property="BorderThickness" Value="0,0,1,1"/>
    <Setter Property="Padding" Value="8,4"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="HorizontalContentAlignment" Value="Left"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="DataGridCell">
                <Border x:Name="border"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        SnapsToDevicePixels="True">
                    <ContentPresenter x:Name="contentPresenter"
                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                </Border>
                <ControlTemplate.Triggers>
                    <!-- Выделенная ячейка -->
                    <Trigger Property="IsSelected" Value="True">
                        <Setter TargetName="border" Property="Background" Value="#FFBADDE9"/>
                        <Setter TargetName="border" Property="BorderBrush" Value="#FF3C7FB1"/>
                    </Trigger>
                    
                    <!-- Ячейка в фокусе -->
                    <Trigger Property="IsKeyboardFocusWithin" Value="True">
                        <Setter TargetName="border" Property="BorderBrush" Value="Orange"/>
                        <Setter TargetName="border" Property="BorderThickness" Value="2"/>
                    </Trigger>
                    
                    <!-- Наведение курсора -->
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="border" Property="Background" Value="#FFE8F5FE"/>
                    </Trigger>
                    
                    <!-- Read-only ячейка -->
                    <Trigger Property="IsReadOnly" Value="True">
                        <Setter Property="Foreground" Value="Gray"/>
                        <Setter TargetName="border" Property="Background" Value="#FFF5F5F5"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

3. Стиль с VisualStateManager (рекомендуемый подход)

Для более современных и гибких анимаций используйте VisualStateManager:

<Style TargetType="DataGridCell">
    <Setter Property="Background" Value="White"/>
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="BorderBrush" Value="#FFC9CACA"/>
    <Setter Property="BorderThickness" Value="0,0,1,1"/>
    <Setter Property="Padding" Value="8,4"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="DataGridCell">
                <Border x:Name="border"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        SnapsToDevicePixels="True">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ColorAnimation Duration="0:0:0.2"
                                                    Storyboard.TargetName="border"
                                                    Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
                                                    To="#FFE8F5FE"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        
                        <VisualStateGroup x:Name="SelectionStates">
                            <VisualState x:Name="Unselected"/>
                            <VisualState x:Name="Selected">
                                <Storyboard>
                                    <ColorAnimation Duration="0:0:0.2"
                                                    Storyboard.TargetName="border"
                                                    Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
                                                    To="#FFBADDE9"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="SelectedUnfocused">
                                <Storyboard>
                                    <ColorAnimation Duration="0:0:0.2"
                                                    Storyboard.TargetName="border"
                                                    Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
                                                    To="#FFE1F5FE"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <ThicknessAnimation Duration="0:0:0.1"
                                                       Storyboard.TargetName="border"
                                                       Storyboard.TargetProperty="BorderThickness"
                                                       To="2"/>
                                    <ColorAnimation Duration="0:0:0.1"
                                                    Storyboard.TargetName="border"
                                                    Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)"
                                                    To="Orange"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unfocused"/>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    
                    <ContentPresenter x:Name="contentPresenter"
                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

4. Стиль с условным форматированием

Добавим возможность менять стиль в зависимости от данных:

<Style TargetType="DataGridCell">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="DataGridCell">
                <Border x:Name="border"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Left"/>
                </Border>
                <ControlTemplate.Triggers>
                    <!-- Пример: красный фон для отрицательных чисел -->
                    <DataTrigger Binding="{Binding Content.Text, RelativeSource={RelativeSource Self}}"
                                 Value="{x:Static sys:String.Empty}">
                        <Setter TargetName="border" Property="Background" Value="#FFFFF0F0"/>
                    </DataTrigger>
                    
                    <!-- Более сложная логика через Converter -->
                    <DataTrigger Value="True">
                        <DataTrigger.Binding>
                            <MultiBinding Converter="{StaticResource CellStyleConverter}">
                                <Binding Path="DataContext.SomeProperty" RelativeSource="{RelativeSource Self}"/>
                                <Binding Path="IsSelected" RelativeSource="{RelativeSource Self}"/>
                            </MultiBinding>
                        </DataTrigger.Binding>
                        <Setter TargetName="border" Property="Background" Value="LightYellow"/>
                    </DataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    
    <Style.Triggers>
        <!-- Стиль для определенных колонок -->
        <Trigger Property="DataGridCell.Column" 
                 Value="{Binding Columns[0], ElementName=myDataGrid}">
            <Setter Property="Background" Value="#FFF0F8FF"/>
        </Trigger>
    </Style.Triggers>
</Style>

5. Стиль с поддержкой редактирования

<Style TargetType="DataGridCell">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="DataGridCell">
                <Border x:Name="border"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    
                    <Grid>
                        <ContentPresenter x:Name="contentPresenter"
                                          VerticalAlignment="Center"
                                          HorizontalAlignment="Left"
                                          Visibility="Visible"/>
                        
                        <!-- Редактор по умолчанию -->
                        <TextBox x:Name="editTextBox"
                                 VerticalAlignment="Center"
                                 HorizontalAlignment="Stretch"
                                 Visibility="Collapsed"
                                 Background="Transparent"
                                 BorderThickness="0"
                                 Padding="{TemplateBinding Padding}"/>
                    </Grid>
                </Border>
                
                <ControlTemplate.Triggers>
                    <!-- Переключение в режим редактирования -->
                    <Trigger Property="DataGridCell.IsEditing" Value="True">
                        <Setter TargetName="contentPresenter" Property="Visibility" Value="Collapsed"/>
                        <Setter TargetName="editTextBox" Property="Visibility" Value="Visible"/>
                        <Setter TargetName="editTextBox" Property="Text" 
                                Value="{Binding Path=Content.Text, RelativeSource={RelativeSource TemplatedParent}}"/>
                    </Trigger>
                    
                    <!-- Специфичные редакторы для разных типов данных -->
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="DataGridCell.IsEditing" Value="True"/>
                            <Condition Property="Content" Value="{x:Null}"/>
                        </MultiTrigger.Conditions>
                        <Setter TargetName="editTextBox" Property="Background" Value="LightYellow"/>
                    </MultiTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

6. Применение стиля

В ресурсах окна/приложения:

<Application.Resources>
    <Style x:Key="UniversalDataGridCellStyle" TargetType="DataGridCell">
        <!-- Ваш стиль здесь -->
    </Style>
</Application.Resources>

Применение к конкретному DataGrid:

<DataGrid CellStyle="{StaticResource UniversalDataGridCellStyle}"
          <!-- остальные свойства -->>
</DataGrid>

7. Лучшие практики и рекомендации

Производительность:

  • Используйте SnapsToDevicePixels="True" для четкого отображения границ
  • Минимизируйте сложные анимации для больших таблиц
  • Используйте легковесные Brush'ы (SolidColorBrush вместо LinearGradientBrush)

Совместимость:

  • Всегда тестируйте с различными темами ОС
  • Учитывайте high DPI displays
  • Проверяйте accessibility (контрастность, размеры)

Расширяемость:

  • Используйте DynamicResource для цветов, чтобы поддерживать смену темы
  • Создавайте базовый стиль и наследуйте от него специализированные стили

8. Полный пример с комментариями

<Style x:Key="ModernDataGridCellStyle" TargetType="DataGridCell">
    <!-- Базовые настройки -->
    <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
    <Setter Property="BorderBrush" Value="#FFC9CACA"/>
    <Setter Property="BorderThickness" Value="0,0,1,1"/>
    <Setter Property="Padding" Value="8,4"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="HorizontalContentAlignment" Value="Left"/>
    <Setter Property="MinHeight" Value="32"/>
    
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="DataGridCell">
                <Border x:Name="border"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        SnapsToDevicePixels="True">
                    
                    <Grid>
                        <!-- Основное содержимое -->
                        <ContentPresenter x:Name="contentPresenter"
                                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                          SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        
                        <!-- Индикатор редактирования -->
                        <Border x:Name="editIndicator"
                                Width="3" 
                                HorizontalAlignment="Left" 
                                VerticalAlignment="Stretch"
                                Background="Transparent"
                                Visibility="Collapsed"/>
                    </Grid>
                </Border>
                
                <ControlTemplate.Triggers>
                    <!-- Состояния выделения -->
                    <Trigger Property="IsSelected" Value="True">
                        <Setter TargetName="border" Property="Background" 
                                Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                        <Setter Property="Foreground" 
                                Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                    </Trigger>
                    
                    <!-- Режим редактирования -->
                    <Trigger Property="IsEditing" Value="True">
                        <Setter TargetName="editIndicator" Property="Background" Value="#FF007ACC"/>
                        <Setter TargetName="editIndicator" Property="Visibility" Value="Visible"/>
                    </Trigger>
                    
                    <!-- Наведение -->
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="border" Property="Background" Value="#FFF0F8FF"/>
                    </Trigger>
                    
                    <!-- Фокус -->
                    <Trigger Property="IsKeyboardFocusWithin" Value="True">
                        <Setter TargetName="border" Property="BorderBrush" Value="#FF007ACC"/>
                        <Setter TargetName="border" Property="BorderThickness" Value="2"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Этот универсальный стиль обеспечивает профессиональный внешний вид, хорошую производительность и легкую кастомизацию под ваши нужды. Вы можете адаптировать его, добавляя дополнительные триггеры и свойства в зависимости от требований вашего приложения.