ContentPresenter ворует ресурсы?

В простом представлении я хочу отобразить две кнопки с изображениями содержимого, которые предоставляются через ресурсы в дополнительном файле 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 как-то мешает совместно использовать ресурсы?


person tabina    schedule 28.07.2017    source источник


Ответы (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, который реализован отдельно для каждой кнопки, к которой он применяется (таким образом, каждая кнопка получает свой собственный экземпляр изображения):

<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.

person Grx70    schedule 28.07.2017
comment
Спасибо за этот подробный ответ. Всегда хорошо знать, почему что-то ведет себя именно так, а вы прекрасно это описали. Спасибо! Я использовал второе решение, и оно отлично работает! - person tabina; 28.07.2017

Как объяснялось в другом ответе, у вас должен быть элемент управления Image в ContentTemplate кнопки.

Объявите простой стиль кнопки изображения с изображением, свойство Source которого привязано к фактическому содержимому:

<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>
person Clemens    schedule 28.07.2017