ContentPresenter 窃取资源?
ContentPresenter steals resources?
在一个简单的视图中,我想显示两个按钮,它们具有内容图像,它们是通过额外 style.xaml 中的资源提供的,该资源加载在 App.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 是否以某种方式阻止共享资源?
诊断
这种行为的核心原因是一个元素在可视化树中只能出现一次。
首先,ResourceDictionary
中的资源默认是共享的,即每次使用特定键获取资源时,您总是会得到相同的实例。在你的情况下,你总是得到相同的 Style
实例,这也意味着总是只有一个 Image
实例(对于每种样式)。
因此,如果您将相同的样式应用于两个按钮,框架会尝试将相同的 Image
实例放在两个不同的地方,这是不允许的。为避免这种情况,在尝试将图像加载到可视化树中时,如果它已经在可视化树中,则首先从先前的位置卸载它。
这就是图像仅在最后一个按钮中可见的原因(按照它们的加载顺序)。
解决方案
这个问题基本上有两种解决方案。
1.禁用资源共享
您可以禁用资源共享,这样每次从 ResourceDictionary
获取资源时,您都会得到该资源的一个新实例。为此,您在资源上设置 x:Shared="False"
:
<Style x:Key="AddButtonWithIconStyle" x:Shared="False" TargetType="{x:Type Button}">
(...)
</Style>
2。在您的风格中使用 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.
中模板的用途
如其他答案中所述,您应该在 Button 的 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>
在一个简单的视图中,我想显示两个按钮,它们具有内容图像,它们是通过额外 style.xaml 中的资源提供的,该资源加载在 App.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 是否以某种方式阻止共享资源?
诊断
这种行为的核心原因是一个元素在可视化树中只能出现一次。
首先,ResourceDictionary
中的资源默认是共享的,即每次使用特定键获取资源时,您总是会得到相同的实例。在你的情况下,你总是得到相同的 Style
实例,这也意味着总是只有一个 Image
实例(对于每种样式)。
因此,如果您将相同的样式应用于两个按钮,框架会尝试将相同的 Image
实例放在两个不同的地方,这是不允许的。为避免这种情况,在尝试将图像加载到可视化树中时,如果它已经在可视化树中,则首先从先前的位置卸载它。
这就是图像仅在最后一个按钮中可见的原因(按照它们的加载顺序)。
解决方案
这个问题基本上有两种解决方案。
1.禁用资源共享
您可以禁用资源共享,这样每次从 ResourceDictionary
获取资源时,您都会得到该资源的一个新实例。为此,您在资源上设置 x:Shared="False"
:
<Style x:Key="AddButtonWithIconStyle" x:Shared="False" TargetType="{x:Type Button}">
(...)
</Style>
2。在您的风格中使用 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.
中模板的用途如其他答案中所述,您应该在 Button 的 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>