WPF, как расширить стиль ресурса UserControl

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

Я хотел бы расширить некоторые стили (каждый элемент управления, который использует это UserControl, будет иметь свои собственные преобразователи для изменения способа отображения строк с помощью разделителей и т. д.), но я получаю следующую ошибку:

System.Windows.Markup.XamlParseException: ''Установить свойство 'System.Windows.ResourceDictionary.DeferrableContent' вызвало исключение.

InvalidOperationException: невозможно повторно инициализировать экземпляр ResourceDictionary.

<DataGrid x:Class = "CustomDataGrid">

    <DataGrid.Resources>

        <!--  Data Grid Row Header  -->
        <Style x:Key = "DataGridRowStyle" TargetType = "{x:Type DataGridRow}">

            <!-- I would like to use the specific ControlTemplate (e.g. add row separators) defined in the control which uses this UserControl -->

        </Style>

    </DataGrid.Resources>

    <DataGrid.Style>

        <!--  DataGrid  -->
        <Style TargetType = "{x:Type DataGrid}">

            <Setter Property = "RowStyle" Value = "{StaticResource DataGridRowStyle}" />

        </Style>

    </DataGrid.Style>

</DataGrid>
    <controls:CustomDataGrid>

        <controls:CustomDataGrid.Resources>

            <!--  Data Grid Row Header  -->
            <Style TargetType = "{x:Type DataGridRow}">

                <Setter Property = "Template">
                    <Setter.Value>
                        <ControlTemplate TargetType = "{x:Type DataGridRow}">

                            <!-- Border, SelectiveScrollingGrid and DataGridCellsPresenter included in the ControlTemplate with specific value converters, etc. -->

                        </ControlTemplate>
                    </Setter.Value>
                </Setter>

            </Style>

        </controls:CustomDataGrid.Resources>

    </controls:CustomDataGrid>

Разве в вашем последнем XAML TargetType не должен быть DataGridRowHeader? Вы спрашиваете, как назначить собственный шаблон заголовка строки вашему CustomDataGrid? DataGrid имеет для этой цели свойство RowHeaderTemplate. Клиент вашего CustomDataGrid может просто установить свойство CustomDataGrid.RowHaderTemplate. Не пытайтесь установить его изнутри CustoDataGrid. Считайте это кастомизацией на стороне клиента. Хотя очень вероятно, что я неправильно понял задачу.

BionicCode 22.06.2024 10:38

Спасибо за ваш комментарий. Да, я удалил Header из DataGridRowHeader и т. д., это опечатка. Я думаю, вы правильно поняли мой вопрос. Я где-то читал, что не могу переопределить стиль клиента, поскольку он конфликтует со стилем UserControl. Я также пытаюсь использовать DependencyProperty просто для привязки ControlTemplate, например <Setter Property = "Template" Value = "{Binding DataGridRowStyleControlTemplate}" />, но у меня возникает странная проблема. Свойство DataGridRowStyleControlTemplate не найдено в типе CustomDataGrid.

Ludovic Wagner 22.06.2024 10:55

Я думаю, вам не следует устанавливать DataGrid.RowStyle внутри, если он содержит обязательную конфигурацию. Вы должны ожидать, что стиль будет переопределен извне. Однако стиль обычно применяется совокупно, поскольку внешний стиль объединяется со стилем по умолчанию. Но у UserControl нет стиля по умолчанию. По этой причине CustomDataGrid должен быть пользовательским элементом управления, который расширяет DataGrid и определяет стиль по умолчанию в Generic.xaml. Таким образом, вы можете установить значения по умолчанию в стиле и позволить клиентскому коду определять свой собственный стиль, например. чтобы установить собственный шаблон.

BionicCode 22.06.2024 11:04

Что касается ошибки, похоже, что свойство зависимости зарегистрировано для неправильного типа (не typeof(CustomDataGrid)).

BionicCode 22.06.2024 11:05

Подводя итог, преобразуйте UserControl в пользовательский элемент управления и установите значения по умолчанию в стиле по умолчанию, определенном в Generic.xaml. Затем клиентский код может определить свой собственный стиль для определения шаблона. Если вы добавите свойство зависимости DataGridRowTemplate, то привяжите его к этому свойству из установщика стиля по умолчанию, используя {TemplateBinding}. Это позволяет клиенту дополнительно определить шаблон с помощью DataGridRowTemplate в дополнение к определению стиля. Это хороший пример, показывающий, как пользовательские элементы управления обеспечивают большую гибкость при настройке.

BionicCode 22.06.2024 11:09

Вам нравится иметь пример?

BionicCode 22.06.2024 11:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
94
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вам следует преобразовать свой элемент управления в пользовательский элемент управления, который определяет значение по умолчанию Style в Generic.xaml. Таким образом, клиент вашего элемента управления может определить Style, не удаляя значения по умолчанию, установленные вашим внутренним Style.

Таким образом, клиент может предоставить ControlTemplate для DaraGridRow, просто определив настройку Style. Это будет альтернатива введенному вами свойству DtaGridRowTemplate.

Также более надежно зарегистрировать обратный вызов изменения свойства зависимости с помощью свойства CustomDataGRid.DataGridRowTemplate, которое делегирует новое свойство ControlTemplate свойству CustomDataGrid.Template. Эта связь между двумя объектами недвижимости является решающей. Вы не хотите, чтобы эта ссылка была нарушена, когда клиент определяет Style, который устанавливает OverridesDefaultStyle в true. Это будет в том случае, если эта ссылка установлена ​​с помощью Style (это прекрасный пример того, почему действительно необходим выделенный код - потому что в своем вопросе вы говорили, что вам придется удалить весь выделенный код из-под вашего контроля).

Исправленная и улучшенная версия вашего элемента управления может выглядеть следующим образом:

CustomDataGrid.cs

public class CustomDataGrid : DataGrid
{
  public static readonly DependencyProperty DataGridRowTemplateProperty = DependencyProperty.Register(
    nameof(DataGridRowTemplate),
    typeof(ControlTemplate),
    typeof(CustomDataGrid),
    new PropertyMetadata(default));

  public ControlTemplate DataGridRowTemplate
  {
    get => GetValue(DataGridRowTemplateProperty) as ControlTemplate;
    set => SetValue(DataGridRowTemplateProperty, value);
  }

  public static readonly RoutedCommand ColumnHeaderFilterCommand
    = new RoutedCommand(nameof(ColumnHeaderFilterCommand), typeof(CustomDataGrid));

  static CustomDataGrid()
  {
    // Register the default Style for this control
    DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomDataGrid), new FrameworkPropertyMetadata(typeof(CustomDataGrid)));
  }  

  public CustomDataGrid()
  {
    var columnheaderFilterCommandBinding = new CommandBinding(
      ColumnHeaderFilterCommand, 
      ExecutedColumnHeaderFilterCommand, 
      CanExecuteColumnHeaderFilterCommand);
    this.CommandBindings.Add(columnheaderFilterCommandBinding);
  }

  private void CanExecuteColumnHeaderFilterCommand(object sender, CanExecuteRoutedEventArgs e)
  {
    object commandParameter = e.Paramter;
    e.CanExecute = true;
  }

  private void ExecutedColumnHeaderFilterCommand(object sender, ExecutedRoutedEventArgs e)
  {
    object commandParameter = e.Paramter;

    // TODO::Execute header click action
  }

  protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
  {
    if (element is DataGridRow dataGridRow
      && this.DataGridRowTemplate is not null)
    {
      dataGridRow.Template = this.DataGridRowTemplate;
    }

    base.PrepareContainerForItemOverride(element, item);
  }
}

Generic.xaml
Добавьте папку Themes в корень вашего проекта. Затем добавьте в эту папку файл XAML ResourceDictionary и назовите его Generic.xaml.

<ResourceDictionary>

  <!-- 
    The default Style for CustomDataGrid. 
    Unless an external Style sets OverridesDefaultStyle to TRUE, 
    external styles will be merged into the default Style. 
    This way you don't have to be afraid of external Styles. 
  -->
  <Style TargetType = "CustomDataGrid" 
         BasedOn = "{StaticResource {x:Type DataGrid}}">

    <!-- Set defaults here -->

    <!-- For example, set the default template for the `DataGridRow` -->
    <Setter Property = "DataGridRowTemplate">
      <Setter.Value>
        <Controltemplate TargetType = "DataGirdRow">
           ...
        </ControlTemplate>
      </Setter.Value>
    </Setter>

    <!-- Set up the ColumnHeader using routed commands -->
    <Setter Property = "ColumnHeaderStyle">    
      <Setter.Value>  
        <Style TargetType = "DataGridColumnHeader">
          <Setter Property = "Command"
                  Value = "{x:Static CustomDataGrid.ColumnHeaderFilterCommand}" />
        </Style>
      </Setter.VAlue>
    </Setter>
  </Style>
</ResourceDictionary>

Теперь клиент может либо установить шаблон, определив собственный стиль, который больше не будет нарушать ваш внутренний стиль по умолчанию, либо установив свойство DataGridRowTemplate.

Извините, я все еще не уверен, как определить значение по умолчанию ControlTemplate свойства зависимостей DataGridRowTemplateProperty. Я хочу сохранить стиль CustomDataGrid по умолчанию в том виде, в котором он находится внутри XAML. Кроме того, этот стиль содержит другой стиль для DataGridColumnHeader, который содержит обратные вызовы, такие как DataGridHeaderFilterToggleButton_Click (для работы со всплывающим окном и фильтрации столбцов). Позже я хотел бы сделать то же самое с ContextMenu с общими Refresh и Reset методами, которые я расширяю специально для некоторых CustomDataGrid таких CenterCurrentDate.

Ludovic Wagner 23.06.2024 20:43

Я обновил пример, чтобы показать, как установить шаблон DataGridRow по умолчанию. «Я хочу сохранить стиль CustomDataGrid по умолчанию в том виде, в котором он находится внутри его XAML». Извините, не могли бы вы объяснить мне, что вы имеете в виду, пожалуйста? Я также добавил пример, показывающий, как настроить перенаправленную команду для DataGridCVolumnHeader.Command для замены события. В общем, используйте перенаправленные команды или перенаправленные события. Оба могут быть обработаны на родителе CustomDataGrid.

BionicCode 23.06.2024 22:01

Другой вариант — использовать именованные части шаблона, а затем из вызова переопределения метода CustomDataGrid.OnApplyTemplateGetTemplateChild("elementName"). Затем вы можете настроить этот элемент. Это общий шаблон для добавления поведения, например. обработчики событий или команды в шаблоне: используйте перенаправленные команды и перенаправленные события. Они будут пузыриться до CustomDataGrid. Здесь вы сможете с ними справиться. Зарегистрируйте перенаправленные команды и перенаправленные события в конструкторе.

BionicCode 23.06.2024 22:01

Итак, для вашего ContextMenu вам нужно следовать приведенному выше примеру и реализовать набор маршрутизируемых команд, которые затем можно использовать, как показано выше, для установки свойства MenuItem.Command. Надеюсь, это поможет. Если что-то не ясно, пожалуйста, спрашивайте.

BionicCode 23.06.2024 22:01

Спасибо за ваши объяснения, но боюсь, мне все еще нужно переделать кое-что (в частности, удалить весь код, для которого я написал CustomDataGrid)..

Ludovic Wagner 24.06.2024 19:52

Хорошо, жаль, что пример не очень помог. Я действительно пытался удовлетворить ваши требования. Но зачем удалять код из выделенного кода и куда его перемещать? Все находится именно там, где ему место.

BionicCode 24.06.2024 22:29

Другие вопросы по теме