简体   繁体   English

Wpf - 创建一个控件/元素作为资源并在 XAML 中使用它

[英]Wpf - Create a control / element as a resource and use it in XAML

I have multiple DataGrid objects in different tabs of WPF window that share the same columns.我在 WPF window 的不同选项卡中有多个DataGrid对象,它们共享相同的列。 I can define a column as a resource like this:我可以像这样将列定义为资源:

<TabControl.Resources>
    <DataGridTextColumn x:Key="NameGridColumn" Binding="{Binding Name}" IsReadOnly="True" Width="*">
        <DataGridTextColumn.Header>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Name" VerticalAlignment="Center"/>
                <Button Command="{Binding SortByNameCommand}" Content="▲" Background="Transparent" BorderThickness="0" Margin="5 0 0 0"/>
            </StackPanel>
        </DataGridTextColumn.Header>
    </DataGridTextColumn>
</TabControl.Resources>

But how can I then use these resource columns in a DataGrid ?但是我怎样才能在DataGrid中使用这些资源列呢?

<TabItem Header="Tab 1">
    <DataGrid ItemsSource="{Binding ItemsCollection}" CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="False" AutoGenerateColumns="False">
        <DataGrid.Columns>
            
            <????>
            <!-- notice that I have many columns, not just one -->

        </DataGrid.Columns>
    </DataGrid>
</TabItem>

As I know WPF has restriction - UI element can belong only to one parent control.据我所知 WPF 有限制 - UI 元素只能属于一个父控件。 So it seems to you can't reuse one column in different grids.所以看起来你不能在不同的网格中重复使用一列。

But you can create UserControl and specify in XAML Grid with your columns.但是您可以创建 UserControl 并在 XAML Grid 中指定您的列。 In such case if DataContext will be the same (for example same VM -> you can simple use it in different places of your application).在这种情况下,如果 DataContext 相同(例如相同的 VM -> 您可以在应用程序的不同位置简单地使用它)。

Not the ideal answer, but fairly good.不是理想的答案,但相当不错。

This is an answer specific for data grid column headers (the columns you define are not actual controls, but representations for controls that will be generated later).这是特定于数据网格列标题的答案(您定义的列不是实际控件,而是稍后将生成的控件的表示形式)。 If you want an answer for actual controls in general, you can resort to this answer如果你想要一般实际控制的答案,你可以求助于这个答案


You can use a DataTemplate to define the headers:您可以使用DataTemplate来定义标头:

<TabControl.Resources>
    <!--Converter to change arrow buttons appearance-->
    <local:EnabledToBrushConverter x:Key="EnabledToBrushConverter"/>
    
    <!--Style for all arrow buttons-->
    <Style TargetType="Button">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="Margin" Value="5 0 0 0"/>
    </Style>
    
    <!-- each data template is a header -->
    <DataTemplate x:Key="NameGridHeader">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Name" VerticalAlignment="Center"/>
            <Button Command="{Binding Path=DataContext.SortByNameCommand, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}" Content="▲" 
                    Foreground="{Binding Path=DataContext.IsSortByName, Converter={StaticResource EnabledToBrushConverter}, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}"/>
        </StackPanel>
    </DataTemplate>

    <DataTemplate x:Key="AnotherHeader">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Another value" VerticalAlignment="Center"/>
            <Button Command="{Binding Path=DataContext.SortByAnotherValueCommand, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}" Content="▼"
                    Foreground="{Binding Path=DataContext.IsSortByAnotherValue, Converter={StaticResource EnabledToBrushConverter}, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}"/>
        </StackPanel>
    </DataTemplate>
</TabControl.Resources>

And you can use the HeaderTemplate property of a DataGridTextColumn or a DataGridTemplateColumn .您可以使用DataGridTextColumnDataGridTemplateColumnHeaderTemplate属性。

<DataGrid.Columns>
    <DataGridTextColumn Header="Type" Binding="{Binding ItemType}" IsReadOnly="True"/>
    <DataGridTextColumn HeaderTemplate="{StaticResource NameGridHeader}" Binding="{Binding Name}" IsReadOnly="True" Width="*"/>
    <DataGridTextColumn HeaderTemplate="{StaticResource AnotherHeader}" Binding="{Binding AnotherValueString}" IsReadOnly="True"/>
</DataGrid.Columns>

<!-- Define as many datagrids as you like using the same column header templates -->

The downsides are:缺点是:

  • Cannot declare the column bindings in the template (might be an advantage if you want the same header for different data, though)无法在模板中声明列绑定(不过,如果您希望对不同的数据使用相同的 header,这可能是一个优势)
  • Cannot declare an entire column (header and cell template), must use separate cell templates (with DataGridTemplateColumn 's CellTemplate or CellEditingTemplate ) or styles if it's the case无法声明整个列(标题和单元格模板),必须使用单独的单元格模板(使用DataGridTemplateColumnCellTemplateCellEditingTemplate )或 styles(如果是这种情况)
  • Bindings in the headers only work with RelativeSource set to some element above it标头中的绑定仅适用于将RelativeSource设置为其上方的某个元素

There is a way to use DataTemplate for the Cell and another one for the Header:有一种方法可以将 DataTemplate 用于 Cell,另一种用于 Header:

                <DataTemplate x:Key="Head1Template">
                    <TextBlock Text="Product Name"/>
                </DataTemplate>
                <DataTemplate x:Key="Head1CellTemplate">
                    <TextBlock Text="{Binding Product}"/>
                </DataTemplate>

and using it in the DataGrid:并在 DataGrid 中使用它:

<DataGridTemplateColumn  CellTemplate="{StaticResource Head1CellTemplate}" HeaderTemplate="{StaticResource Head1Template}"/>

But how can I then use these resource columns in a DataGrid?但是我怎样才能在 DataGrid 中使用这些资源列呢?

You can't.你不能。 At least not using pure XAML.至少不使用纯 XAML。

What you can to is to define the entire DataGrid , including all columns, as a resource or a separate control and reuse it like any other control, eg:您可以做的是定义整个DataGrid ,包括所有列,作为资源或单独的控件,并像任何其他控件一样重用它,例如:

<local:CustomDataGrid ItemsSource="{Binding ItemsCollection}" />

Another option is to redefine the column(s) in each tab but define the StackPanel as a resource and set the header of each column like this:另一种选择是重新定义每个选项卡中的列,但将StackPanel定义为资源并设置每个列的 header,如下所示:

<DataGridTextColumn Binding="{Binding Name}" IsReadOnly="True" Width="*"
    Header="{StaticResource theStackPanel}">

You would then define the StackPanel something like this:然后,您将像这样定义StackPanel

<StackPanel x:Key="theStackPanel" x:Shared="false" Orientation="Horizontal">
    <TextBlock Text="Name" VerticalAlignment="Center"/>
    <Button Command="{Binding SortByNameCommand}" Content="▲" Background="Transparent" BorderThickness="0" Margin="5 0 0 0"/>
</StackPanel>

Either way, you cannot add a column resource to <DataGrid.Columns> in XAML. You can do it programmatically though:无论哪种方式,您都无法将列资源添加到 XAML 中的<DataGrid.Columns> 。您可以通过编程方式执行此操作:

dataGrid.Columns.Add(tabControl.Resources["NameGridColumn"] as DataGridColumn);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM