【问题标题】:How to reuse a Style with ContentControl to make code more compact?如何使用 ContentControl 重用 Style 以使代码更紧凑?
【发布时间】:2016-04-21 16:08:39
【问题描述】:

我有以下(工作)代码:

<StackPanel>
    <Menu>
        <Menu.Resources>
            <Style TargetType="{x:Type MenuItem}" x:Key="MenuItemStyle">
                <Style.Triggers>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Background" Value="LightGray" />
                        <Setter Property="Foreground" Value="LightSlateGray" />
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="True">
                        <Setter Property="Background" Value="LightGray" />
                        <Setter Property="Foreground" Value="Black" />
                    </Trigger>
                </Style.Triggers>
            </Style>
            <Style TargetType="{x:Type MenuItem}" x:Key="DeleteMenuStyle" BasedOn="{StaticResource MenuItemStyle}">
                <Setter Property="Icon">
                    <Setter.Value>
                        <ContentControl Style="{StaticResource CrossIconScalable}"
                                Width="15"
                                Height="15"/>
                    </Setter.Value>
                </Setter>
            </Style>
            <Style TargetType="{x:Type MenuItem}" x:Key="SaveMenuStyle" BasedOn="{StaticResource MenuItemStyle}">
                <Setter Property="Icon">
                    <Setter.Value>
                        <ContentControl Style="{StaticResource SaveButtonScalable}"
                                Width="15"
                                Height="15"/>
                    </Setter.Value>
                </Setter>
            </Style>
        </Menu.Resources>
        <MenuItem>
            <MenuItem.Header>
               <!-- ... -->
            </MenuItem.Header>
            <MenuItem Name="SaveImageMenu" Header="{Binding MenuItemSaveTxt}"
                      Click="SaveImageMenu_OnClick" Style="{StaticResource SaveMenuStyle}" />
            <MenuItem Name="DeleteViewMenu" Header="{Binding MenuItemCancTxt}"
                      Click="DeleteViewMenu_OnClick" Style="{StaticResource DeleteMenuStyle}" />
        </MenuItem>
    </Menu>
</StackPanel>

<!-- StaticResources definition -->
<Style TargetType="ContentControl" x:Key="SaveButtonScalable">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ContentControl">
                <Viewbox Stretch="Uniform">
                    <Canvas Name="Capa_1" Width="32" Height="32">
                        <Canvas.RenderTransform>
                            <TranslateTransform X="0" Y="0" />
                        </Canvas.RenderTransform>
                        <Canvas.Resources />
                        <Canvas Name="g3">
                            <Path Name="path5" Fill="{TemplateBinding Foreground}">
                                <Path.Data>
                                    <PathGeometry Figures="M26 0h-2v13H8V0H0v32h32V6L26 0z M28 30H4V16h24V30z"
                                                  FillRule="NonZero" />
                                </Path.Data>
                            </Path>
                            <Rectangle Canvas.Left="6" Canvas.Top="18" Width="20" Height="2" Name="rect7"
                                       Fill="{TemplateBinding Foreground}" />
                            <Rectangle Canvas.Left="6" Canvas.Top="22" Width="20" Height="2" Name="rect9"
                                       Fill="{TemplateBinding Foreground}" />
                            <Rectangle Canvas.Left="6" Canvas.Top="26" Width="20" Height="2" Name="rect11"
                                       Fill="{TemplateBinding Foreground}" />
                            <Rectangle Canvas.Left="18" Canvas.Top="2" Width="4" Height="9" Name="rect13"
                                       Fill="{TemplateBinding Foreground}" />
                        </Canvas>
                    </Canvas>
                </Viewbox>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="ContentControl" x:Key="CrossIconScalable">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ContentControl">
                <Viewbox Stretch="Uniform">
                    <Canvas Name="svg2" Width="32" Height="32">
                        <Canvas.RenderTransform>
                            <TranslateTransform X="0" Y="0"/>
                        </Canvas.RenderTransform>
                        <Canvas.Resources/>
                        <Path Name="path4">
                            <Path.Data>
                                <PathGeometry Figures="m0 0h32v32h-32z" FillRule="NonZero"/>
                            </Path.Data>
                        </Path>
                        <Path Name="path6" Fill="{TemplateBinding Foreground}">
                            <Path.Data>
                                <PathGeometry Figures="m2 26 4 4 10-10 10 10 4-4-10-10 10-10-4-4-10 10-10-10-4 4 10 10z" FillRule="NonZero"/>
                            </Path.Data>
                        </Path>
                    </Canvas>
                </Viewbox>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

如您所见,这里有一些代码重复:

  • 两个声明的Style 元素(DeleteMenuStyleSaveMenuStyle)除了用于每个ContenControl&lt;Style&gt; 之外是相同的。
  • SaveButtonScalableCrossIconScalable 样式对于父标记是相同的,但它们在内部的 &lt;Canvas&gt; 标记中有所不同。

我想重构它以创建更紧凑且没有任何重复的代码。

我该怎么做?

【问题讨论】:

  • 代码的大量重复在哪里?你能解释一下吗?
  • 在这两种样式中,除了静态资源之外的所有内容都是重复的。我想将两个声明合并为一个
  • CrossIconScalableSaveButtonScalable 样式中有什么?鉴于您正在使用ControlControl,您是否可以为每种类型的菜单项声明DataTemplate,将适当的视图模型对象绑定为上下文?如果没有一个好的minimal reproducible example,就很难理解你的选择是什么,即如何适应你的代码的其余部分,以及你可能有哪些选项来清理它们。
  • @PeterDuniho 抱歉信息不足,现在我为我的问题添加了更多细节:)
  • 抱歉,这仍然不是一个好的minimal reproducible example。它更详细,但仍然不完整。 Canvas 中的内容很重要,因为真正做到这一点的最佳方法是为内容声明DataTemplate,然后将MenuItem.DataContext 设置为内容,而不是弄乱Style。根据您代码中的“SVG”,我怀疑您有 PathGeometry 或类似名称,它可以是模板的数据类型,然后将该几何图形应用于模板中的图形。我可以发布一个示例作为答案,但没有具体细节,我不能确定它会有所帮助。

标签: c# wpf xaml refactoring reusability


【解决方案1】:

好的,这已经足够接近一个好的 MCVE,我想我可以提供一些有用的信息。 :)

在您的特定情况下,您似乎希望能够将 Foreground 值向下传播到模板中,因此 DataTemplate 不会起作用,至少在没有创建新的辅助数据结构的情况下不会起作用那项工作。因此,坚持ControlTemplate 的想法,您可以将您发布的 XAML 合并为以下内容:

<Window x:Class="TestSO36775094RefactorStyle.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
  <!--
      Adding the 'p' namespace qualifier above allows Style elements to be
      formatted correctly on Stack Overflow
  -->
  <Window.Resources>
    <StreamGeometry x:Key="saveButtonGeometry">
      F1 M26,0 h-2 v13 H8 V0 H0 v32 h32 V6 L26,0 z M28,30 H4 V16 h24 V30 z
      M6,18 h20 v2 h-20 z m0,4 h20 v2 h-20 z m0,4 h20 v2 h-20 z M18,2 h4 v9 h-4 z
    </StreamGeometry>

    <StreamGeometry x:Key="crossButtonGeometry">
      F1 m2 26 4 4 10-10 10 10 4-4-10-10 10-10-4-4-10 10-10-10-4 4 10 10z
    </StreamGeometry>

    <ControlTemplate x:Key="geometryContentTemplate" TargetType="ContentControl">
      <Viewbox Stretch="Uniform">
        <Canvas Name="Capa_1" Width="32" Height="32">
          <Canvas.RenderTransform>
            <!--
                Not sure why you set this here, since translating 0,0
                does nothing, but I've left it in :)
            -->
            <TranslateTransform X="0" Y="0" />
          </Canvas.RenderTransform>
          <Canvas.Resources />
          <Canvas Name="g3">
            <Path Name="path5" Fill="{TemplateBinding Foreground}" Data="{Binding}"/>
          </Canvas>
        </Canvas>
      </Viewbox>
    </ControlTemplate>
  </Window.Resources>

  <StackPanel>
    <Menu>
      <Menu.Resources>
        <p:Style TargetType="{x:Type MenuItem}" x:Key="MenuItemStyle">
          <p:Style.Triggers>
            <Trigger Property="IsEnabled" Value="False">
              <Setter Property="Background" Value="LightGray" />
              <Setter Property="Foreground" Value="LightSlateGray" />
            </Trigger>
            <Trigger Property="IsEnabled" Value="True">
              <Setter Property="Background" Value="LightGray" />
              <Setter Property="Foreground" Value="Black" />
            </Trigger>
          </p:Style.Triggers>
        </p:Style>

        <!--
            These styles do nothing other than set the Icon property, so if you
            wanted to, you could just set each of these ContentControl instances
            on the MenuItem.Icon value directly, and then just use MenuItemStyle
            as the actual style for each MenuItem.
        -->
        <p:Style TargetType="{x:Type MenuItem}" x:Key="DeleteMenuStyle" BasedOn="{StaticResource MenuItemStyle}">
          <Setter Property="Icon">
            <Setter.Value>
              <ContentControl Template="{StaticResource geometryContentTemplate}"
                              DataContext="{StaticResource crossButtonGeometry}"/>
            </Setter.Value>
          </Setter>
        </p:Style>
        <p:Style TargetType="{x:Type MenuItem}" x:Key="SaveMenuStyle" BasedOn="{StaticResource MenuItemStyle}">
          <Setter Property="Icon">
            <Setter.Value>
              <ContentControl Template="{StaticResource geometryContentTemplate}"
                              DataContext="{StaticResource saveButtonGeometry}"
                              Width="15" Height="15"/>
            </Setter.Value>
          </Setter>
        </p:Style>
      </Menu.Resources>
      <MenuItem Header="Menu">
        <MenuItem Name="SaveImageMenu" Header="{Binding MenuItemSaveTxt}"
                  Click="SaveImageMenu_OnClick" Style="{StaticResource SaveMenuStyle}" />
        <MenuItem Name="DeleteViewMenu" Header="{Binding MenuItemCancTxt}"
                  Click="DeleteViewMenu_OnClick" Style="{StaticResource DeleteMenuStyle}" />
      </MenuItem>
    </Menu>
  </StackPanel>
</Window>

这个想法是您只定义一次 ControlTemplate,然后在您使用模板实际声明 ContentControl 时引用不同的部分(即几何)。

我会注意到,在您的示例中,您的 CrossIconScalable 样式包含一个似乎未使用的 path4 元素。它没有指定任何填充,所以当你在那里有一个几何图形时,它对视觉外观没有任何影响。所以我把它留了下来。但是这样做确实有点“欺骗”;如果您的模板中确实有两个不同的部分需要以不同方式填充,则您不能完全使用上述方法,因为没有直接的方法来声明两个不同的DataContext 值(即两个不同的几何图形,每个填充值对应一个)。

在您的示例中,您希望能够使用 {TemplateBinding} 来引用父级的属性值,您需要通过创建一个帮助器类来表示几何图形来解决这个问题,例如类似:

class TemplateGeometry
{
    public Geometry ForegroundGeometry { get; set; }
    public Geometry BackgroundGeometry { get; set; }
}

然后你会像这样声明你的资源:

<l:TemplateGeometry x:Key="templateGeometry1">
  <l:TemplateGeometry.ForegroundGeometry>
    <StreamGeometry>
      <!-- your foreground geometry here -->
    </StreamGeometry>
  </l:TemplateGeometry.ForegroundGeometry>
  <l:TemplateGeometry.BackgroundGeometry>
    <StreamGeometry>
      <!-- your background geometry here -->
    </StreamGeometry>
  </l:TemplateGeometry.BackgroundGeometry>
</l:TemplateGeometry>

模板可能(部分)如下所示:

          <Canvas>
            <Path Fill="{TemplateBinding Background}" Data="{Binding BackgroundGeometry}"/>
            <Path Fill="{TemplateBinding Foreground}" Data="{Binding ForegroundGeometry}"/>
          </Canvas>

当然,您可以将DataContext 设置为辅助类实例,而不是直接设置几何:

              <ContentControl Template="{StaticResource geometryContentTemplate}"
                              DataContext="{StaticResource templateGeometry1}"
                              Width="15" Height="15"/>

以上只是基本技术。正如您可能看到的那样,您可以应用许多变体来完全实现您想要的。

最后,我只想提一下DataTemplate 方法非常相似。主要问题是所有DataTemplate 可以访问的是绑定上下文对象的成员,而不是{TemplateBinding} 成员。 DataTemplate 的优点之一是您可以通过使用模板的 DataType 属性来设置它,这样您甚至不需要显式引用模板。 ContentControl 会根据所使用的上下文对象的类型自动找到正确的模板并应用它。

【讨论】:

    猜你喜欢
    • 2011-04-17
    • 2015-01-26
    • 2019-06-29
    • 1970-01-01
    • 2022-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多