Переопределение IsMouseOver на ControlTemplate приводит к исчезновению кнопки

#c# #wpf #xaml #controltemplate

#c# #wpf #xaml #controltemplate

Вопрос:

Я новичок в WPF, и я пытаюсь определить стиль кнопок для кнопок в моем приложении. Я добавил следующее ResourceDictionary и добавил его в ресурсы приложения в App.xaml :

 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:ReciepeFinder">

<Style x:Key="NavButton" TargetType="Button">

    <Setter Property="Width"
            Value="120" />
    <Setter Property="Height"
            Value="120" />
    <Setter Property="BorderThickness"
            Value="0" />

    <Setter Property="Background">
        <Setter.Value>
            <ImageBrush ImageSource="/ReciepeFinder;component/resources/images/home.png" />
        </Setter.Value>
    </Setter>

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver"
                             Value="True">
                        <Setter Property="Background">
                            <Setter.Value>
                                <ImageBrush ImageSource="/ReciepeFinder;component/resources/images/home_selected.png" />
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>

</Style>
  

В принципе, без наведения курсора я хочу, чтобы кнопка отображалась resources/images/home.png и при наведении курсора, resources/images/home_selected.png . Когда я пытаюсь определить ControlTemplate , как в приведенном выше коде, и запускаю приложение, кнопка просто не появляется. Однако, если я полностью удалю ControlTemplate тег, кнопка появится с соответствующим Background значением, но, очевидно, без эффекта наведения.
Что я делаю не так?

Дополнительный вопрос: Если бы я хотел использовать привязку, чтобы я мог просто задать путь для изображения без наведения и изображения при наведении в атрибутах других кнопок и заставить его вести себя таким же образом, как я мог бы этого добиться?

Ответ №1:

Если вы хотите изменить внешний вид элемента управления вместе с его визуальными состояниями, вам придется отредактировать его ControlTemplate . Однако шаблон элемента управления содержит определения обязательных частей элемента управления, а также его внешний вид в разных состояниях и переходы между ними. Чтобы ControlTemplate работать должным образом, вы должны реализовать все эти части и состояния. Вы можете найти информацию о необходимых частях и состояниях для каждого встроенного элемента управления в MSDN, например для Button .

Вместо того, чтобы создавать свой ControlTemplate с нуля, вы можете скопировать шаблон по умолчанию и адаптировать его, что намного проще. Вы можете извлечь ее с помощью Blend или Visual Studio.

Ваш шаблон управления не работает, потому что он по существу пуст. Нет ничего для отображения фона или любого содержимого. Он не содержит никаких визуальных элементов или элементов управления. Я создал для вас образец шаблона элемента управления, который должен соответствовать вашим требованиям.

 <Style x:Key="NavButton" TargetType="{x:Type Button}">
   <Setter Property="Background">
      <Setter.Value>
         <ImageBrush ImageSource="/ReciepeFinder;component/resources/images/home.png" />
      </Setter.Value>
   </Setter>
   <Setter Property="BorderThickness" Value="0"/>
   <Setter Property="HorizontalContentAlignment" Value="Center"/>
   <Setter Property="VerticalContentAlignment" Value="Center"/>
   <Setter Property="Padding" Value="1"/>
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type Button}">
            <Border x:Name="border" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" SnapsToDevicePixels="true"/>
            <ControlTemplate.Triggers>
               <Trigger Property="IsMouseOver" Value="true">
                  <Setter Property="Background" TargetName="border">
                     <Setter.Value>
                        <ImageBrush ImageSource="/ReciepeFinder;component/resources/images/home_selected.png" />
                     </Setter.Value>
                  </Setter>
               </Trigger>
               <Trigger Property="IsEnabled" Value="false">
                  <Setter TargetName="border" Property="Opacity" Value="0.5"/>
               </Trigger>
            </ControlTemplate.Triggers>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
</Style>
  

Border Отображается фон изображения. В общем, вы бы поместили a ContentPresenter внутри Border , потому что это показало бы содержимое, которое вы присвоили своему Content свойству Button , но поскольку вы отображаете изображения вместо содержимого, я опустил это.

Пожалуйста, обратите внимание, что внутри этого шаблона есть TemplateBinding s. Они используются для привязки указанных свойств к элементу управления, к которому применяется шаблон. Другими словами, если вы устанавливаете Background на кнопку напрямую или создаете Style производную от NavButton того места, где вы ее установили, будет использоваться это значение. Если вы просто жестко закодировали значение в шаблоне элемента управления, оно не изменилось бы.

Что касается вашего дополнительного вопроса: Когда вы используете эти TemplateBinding параметры, установка Background напрямую или в производном Style будет работать, как указано выше, так что вы можете использовать его повторно. Поскольку HoverBackground свойства нет, вам придется либо создать производную кнопку и добавить к ней свойство зависимости, либо создать присоединенное свойство, либо использовать, например, неиспользуемое Foreground свойство для этого, хотя это не кажется правильным, поскольку это грязный обходной путь. Это пример для Foreground просто потому, что его легко протестировать для вас, но он работает и для других опций, указанных выше.

 <Setter Property="Foreground">
   <Setter.Value>
      <ImageBrush ImageSource="/ReciepeFinder;component/resources/images/home_selected.png" />
   </Setter.Value>
</Setter>
  
 <Trigger Property="IsMouseOver" Value="true">
   <Setter Property="Background" TargetName="border" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"/>
</Trigger>
  

Пожалуйста, обратите внимание, что для доступа к свойству родительского шаблона в Setter вы должны использовать другой синтаксис привязки, который имеет тот же эффект, что и TemplateBinding .

Комментарии:

1. Большое вам спасибо! Я создал класс HoverableButton, который добавляет свойства onHover и NoHover и использовал их для привязки!

Ответ №2:

Потому что, когда вы устанавливаете ControlTemplate, вам в основном приходится переопределять способ отображения элемента управления. И вы определили только триггеры без какого-либо представления содержимого. Элемент управления ContentPresenter поможет вам в этом. Просто добавьте некоторую границу и презентацию содержимого после ControlTemplate.Запускает и ControlTemplate. Это должно сработать:

 </ControlTemplate.Triggers>
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>