【问题标题】:ContentPresenter steals resources?ContentPresenter 窃取资源?
【发布时间】:2018-01-04 03:42:12
【问题描述】:

在一个简单的视图中,我想显示两个按钮,它们具有内容图像,它们是通过加载在 App.xaml 中的额外 style.xaml 中的资源提供的。

<BitmapImage x:Key="AddIcon"  UriSource="pack://application:,,,/WpfTestBench;component/Images/plus.png"></BitmapImage>

<BitmapImage x:Key="RemoveIcon"  UriSource="pack://application:,,,/WpfTestBench;component/Images/minus.png"></BitmapImage>

<Style x:Key="AddButtonWithIconStyle" TargetType="{x:Type Button}">
  <Setter Property="Content">
    <Setter.Value>
      <Image Source="{DynamicResource AddIcon}" Stretch="None" 
           VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Setter.Value>
  </Setter>
</Style>

<Style x:Key="RemoveButtonWithIconStyle" TargetType="{x:Type Button}">
  <Setter Property="Content">
    <Setter.Value>
      <Image Source="{DynamicResource RemoveIcon}" Stretch="None" 
           VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Setter.Value>
  </Setter>
</Style>

在我看来,我使用这样的样式:

<DockPanel Margin="0,0,5,0" DockPanel.Dock="Bottom" LastChildFill="False" >
   <Button Command="{Binding DeleteCommand}" DockPanel.Dock="Right"  
              Style="{StaticResource RemoveButtonWithIconStyle}"/>
   <Button Command="{Binding AddCommand}" DockPanel.Dock="Right" Margin="5,0" 
              Style="{StaticResource AddButtonWithIconStyle}"/>
</DockPanel>

到目前为止,这看起来不错。 但是,当我通过 ContentPresenter 将另一个视图添加到该视图中时,该视图具有基本相同的内容(同样具有上述按钮样式),前两个按钮(父视图)的显示不再起作用。我得到的只是两个带有按钮功能的小圆圈。

<ContentPresenter Content="{Binding SomeEmbeddedViewModel, UpdateSourceTrigger=PropertyChanged}"/>

为什么会这样? ContentPresenter 是否以某种方式阻止共享资源?

【问题讨论】:

    标签: c# wpf button styles contentpresenter


    【解决方案1】:

    正如另一个答案中所解释的,您应该在按钮的 ContentTemplate 中有 Image 控件。

    声明一个简单的图像按钮样式,其图像的源属性绑定到实际内容:

    <Style x:Key="ImageButtonStyle" TargetType="Button">
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Image Source="{Binding}"/>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    ...
    <Button Style="{StaticResource ImageButtonStyle}"
            Content="{DynamicResource RemoveIcon}" .../>
    

    您也可以将样式声明为默认按钮样式,可能在 DockPanel 中:

    <DockPanel>
        <DockPanel.Resources>
            <Style TargetType="Button">
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <Image Source="{Binding}" Stretch="None"/>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </DockPanel.Resources>
    
        <Button Command="{Binding DeleteCommand}" DockPanel.Dock="Right"  
                Content="{DynamicResource RemoveIcon}"/>
    
        <Button Command="{Binding AddCommand}" DockPanel.Dock="Right" Margin="5,0" 
                Content="{DynamicResource AddIcon}"/>
    </DockPanel>
    

    【讨论】:

      【解决方案2】:

      诊断

      这种行为的核心原因是一个元素在可视化树中只能出现一次。

      首先,ResourceDictionary 中的资源默认是共享的,即每次使用特定键获取资源时,您总是会获得相同的实例。在您的情况下,您始终会获得相同的 Style 实例,这也意味着始终只有一个 Image 实例(对于每种样式)。

      因此,如果您将相同的样式应用于两个按钮,框架会尝试将相同的Image 实例放在两个不同的位置,这是不允许的。为了避免这种情况,在尝试将图像加载到可视化树中时,如果它已经在可视化树中,则首先从之前的位置卸载它。

      这就是为什么图像仅在最后一个按钮中可见(按加载顺序)。

      解决方案

      这个问题基本上有两种解决方案。

      1.禁用资源共享

      您可以禁用资源共享,以便每次从ResourceDictionary 获取资源时都会获得该资源的新实例。为此,您在资源上设置了x:Shared="False"

      <Style x:Key="AddButtonWithIconStyle" x:Shared="False" TargetType="{x:Type Button}">
          (...)
      </Style>
      

      2。在你的风格中使用ContentTemplate

      您可以定义一个ContentTemplate,而不是将图像作为按钮的内容,该ContentTemplate 是为每个应用它的按钮单独实现的(因此每个按钮都有自己的图像实例):

      <Style x:Key="RemoveButtonWithIconStyle" TargetType="{x:Type Button}">
          <Setter Property="ContentTemplate">
              <Setter.Value>
                  <DataTemplate>
                      <Image Source="{DynamicResource RemoveIcon}" Stretch="None" 
                             VerticalAlignment="Center" HorizontalAlignment="Center"/>
                  </DataTemplate>
              </Setter.Value>
          </Setter>
      </Style>
      

      我个人建议您使用第二种解决方案,因为这正是 WPF 中模板的用途。

      【讨论】:

      • 感谢您的详细回答。知道为什么某件事的行为方式和你完美地描述了它总是很好的。谢谢!我使用了第二种解决方案,效果很好!
      猜你喜欢
      • 2013-07-11
      • 2020-06-06
      • 1970-01-01
      • 2011-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-02
      • 1970-01-01
      相关资源
      最近更新 更多