Я построил 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>
Спасибо за ваш комментарий. Да, я удалил Header
из DataGridRowHeader
и т. д., это опечатка. Я думаю, вы правильно поняли мой вопрос. Я где-то читал, что не могу переопределить стиль клиента, поскольку он конфликтует со стилем UserControl
. Я также пытаюсь использовать DependencyProperty
просто для привязки ControlTemplate
, например <Setter Property = "Template" Value = "{Binding DataGridRowStyleControlTemplate}" />
, но у меня возникает странная проблема. Свойство DataGridRowStyleControlTemplate не найдено в типе CustomDataGrid.
Я думаю, вам не следует устанавливать DataGrid.RowStyle внутри, если он содержит обязательную конфигурацию. Вы должны ожидать, что стиль будет переопределен извне. Однако стиль обычно применяется совокупно, поскольку внешний стиль объединяется со стилем по умолчанию. Но у UserControl
нет стиля по умолчанию. По этой причине CustomDataGrid
должен быть пользовательским элементом управления, который расширяет DataGrid
и определяет стиль по умолчанию в Generic.xaml. Таким образом, вы можете установить значения по умолчанию в стиле и позволить клиентскому коду определять свой собственный стиль, например. чтобы установить собственный шаблон.
Что касается ошибки, похоже, что свойство зависимости зарегистрировано для неправильного типа (не typeof(CustomDataGrid)
).
Подводя итог, преобразуйте UserControl
в пользовательский элемент управления и установите значения по умолчанию в стиле по умолчанию, определенном в Generic.xaml. Затем клиентский код может определить свой собственный стиль для определения шаблона. Если вы добавите свойство зависимости DataGridRowTemplate
, то привяжите его к этому свойству из установщика стиля по умолчанию, используя {TemplateBinding}
. Это позволяет клиенту дополнительно определить шаблон с помощью DataGridRowTemplate
в дополнение к определению стиля. Это хороший пример, показывающий, как пользовательские элементы управления обеспечивают большую гибкость при настройке.
Вам нравится иметь пример?
Вам следует преобразовать свой элемент управления в пользовательский элемент управления, который определяет значение по умолчанию 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
.
Я обновил пример, чтобы показать, как установить шаблон DataGridRow по умолчанию. «Я хочу сохранить стиль CustomDataGrid по умолчанию в том виде, в котором он находится внутри его XAML». Извините, не могли бы вы объяснить мне, что вы имеете в виду, пожалуйста? Я также добавил пример, показывающий, как настроить перенаправленную команду для DataGridCVolumnHeader.Command
для замены события. В общем, используйте перенаправленные команды или перенаправленные события. Оба могут быть обработаны на родителе CustomDataGrid
.
Другой вариант — использовать именованные части шаблона, а затем из вызова переопределения метода CustomDataGrid.OnApplyTemplate
GetTemplateChild("elementName")
. Затем вы можете настроить этот элемент. Это общий шаблон для добавления поведения, например. обработчики событий или команды в шаблоне: используйте перенаправленные команды и перенаправленные события. Они будут пузыриться до CustomDataGrid
. Здесь вы сможете с ними справиться. Зарегистрируйте перенаправленные команды и перенаправленные события в конструкторе.
Итак, для вашего ContextMenu
вам нужно следовать приведенному выше примеру и реализовать набор маршрутизируемых команд, которые затем можно использовать, как показано выше, для установки свойства MenuItem.Command
. Надеюсь, это поможет. Если что-то не ясно, пожалуйста, спрашивайте.
Спасибо за ваши объяснения, но боюсь, мне все еще нужно переделать кое-что (в частности, удалить весь код, для которого я написал CustomDataGrid
)..
Хорошо, жаль, что пример не очень помог. Я действительно пытался удовлетворить ваши требования. Но зачем удалять код из выделенного кода и куда его перемещать? Все находится именно там, где ему место.
Разве в вашем последнем XAML TargetType не должен быть
DataGridRowHeader
? Вы спрашиваете, как назначить собственный шаблон заголовка строки вашемуCustomDataGrid
?DataGrid
имеет для этой цели свойствоRowHeaderTemplate
. Клиент вашегоCustomDataGrid
может просто установить свойствоCustomDataGrid.RowHaderTemplate
. Не пытайтесь установить его изнутриCustoDataGrid
. Считайте это кастомизацией на стороне клиента. Хотя очень вероятно, что я неправильно понял задачу.