#c# #wpf #xaml
#c# #wpf #xaml
Вопрос:
У меня есть кнопка без хрома в приложении WPF, которая использует приведенный ниже стиль XAML: однако есть 2 вещи, которые я пытаюсь с ней сделать.
- Я хочу, чтобы отображаемое изображение было одним из двух изображений, в зависимости от свойства привязки из datacontext шаблона, в котором находится кнопка. Свойство является логическим. Показывает одно изображение, если False, другое, если True.
- Я бы хотел, чтобы отображаемое изображение менялось при наведении курсора мыши на кнопку и было изображением, которое будет отображаться, когда указанное выше свойство привязки имеет значение True.
Я пробовал несколько вещей, но, похоже, ничего не работает. Я попробовал конвертер с привязанным значением, подобным этому:
<x:ArrayExtension x:Key="ThumbsDown" Type="BitmapImage">
<BitmapImage UriSource="/Elpis;component/Images/thumbDown.png" />
<BitmapImage UriSource="/Elpis;component/Images/thumbBan.png" />
</x:ArrayExtension>
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var data = (object[])parameter;
if (data.Length != 2 || value == null || value.GetType() != typeof(bool))
return data[0];
if(!((bool)value))
return data[0];
else
return data[1];
}
<Button Name="btnThumbDown" Grid.Column="1" Style="{StaticResource NoChromeButton}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Background="Transparent"
Click="btnThumbDown_Click">
<Image Width="32" Height="32" Margin="2"
Source="{Binding Banned,
Converter={StaticResource binaryChooser},
ConverterParameter={StaticResource ThumbsDown}}"/>
</Button>
Но это вызывает 2 проблемы. Ничего, что я делаю для наведенного изображения, больше не работает, и дизайнер выдает исключение.
Может сработать триггер непрозрачности снизу, так как я действительно просто хочу, чтобы он зависал сейчас.
Есть мысли о том, как заставить это правильно работать?
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="NoChromeButton" TargetType="{x:Type ButtonBase}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ButtonBase}">
<Grid x:Name="Chrome" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}" RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.75"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="Opacity" Value="1.0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Ответ №1:
Вам нужно будет сделать несколько вещей:
- Настройте
ImageBrush
ресурсы для каждого из изображений, которые вы хотите отобразить. - Создайте
ControlTemplate
для кнопки, которая имеет встроенныйStyle
указатель мышиTrigger
. - Установите изображение на
Button
, привязавBackground
свойство к одному изImageBrush
ресурсов из пункта 1. - Используйте наведение курсора
Trigger
, чтобы изменитьBackground
свойство на другойImageBrush
ресурс.
Смотрите код ниже для простого примера (вам придется использовать свои собственные изображения вместо «вверх» и «вниз»).
<Window x:Class="ButtonHover.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:ButtonHover"
Title="MainWindow" Height="227" Width="280">
<Window.Resources>
<ImageBrush x:Key="imageABrush"
ImageSource="/ButtonHover;component/Resources/up.png"/>
<ImageBrush x:Key="imageBBrush"
ImageSource="/ButtonHover;component/Resources/down.png"/>
<ControlTemplate x:Key="buttonTemplate" TargetType="Button">
<Border BorderThickness="3" BorderBrush="Black">
<Border.Style>
<Style>
<Style.Setters>
<Setter Property="Border.Background"
Value="{StaticResource imageABrush}"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Border.Background"
Value="{StaticResource imageBBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</ControlTemplate>
</Window.Resources>
<Grid>
<Button Height="75"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="75"
Template="{StaticResource buttonTemplate}" />
</Grid>
</Window>
Ключевым моментом здесь является реализация триггера на Style
из Border
, а не на Button
напрямую. Я не смог заставить ее работать при попытке установить свойство Border
из Style
, применяемое к Button
. Вы также можете заменить Border
на Rectangle
или любое другое ContentControl
свойство BackGround
.
Отсюда настройте так, как считаете нужным для своего приложения.
Комментарии:
1. Как я могу заставить стиль работать для нескольких кнопок, которые имеют разные изображения?
2. Я бы предложил либо создать
UserControl
, либо подклассButton
, который может ссылаться как на «обычное» изображение, так и на изображение «Наведения курсора мыши». Однако, похоже, что Эрик опередил меня.
Ответ №2:
Я бы решил это, добавив Image
элемент управления к ControlTemplate
кнопке, а затем создал MultiDataTrigger
с одним условием для IsMouseOver
и одним условием для логического свойства, к которому вы хотите привязаться. Затем триггер устанавливает источник изображения, когда он активен.
Ниже приведен стиль, который выполняет это. Я предположил, что кнопка имеет DataContext, который содержит свойство boolean, и что свойство boolean называется myBoolean .
<Style x:Key="NoChromeButton" TargetType="{x:Type ButtonBase}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<!-- I assume that the button has a DataContext with a boolean property called MyBoolean -->
<ControlTemplate TargetType="{x:Type ButtonBase}">
<Grid x:Name="Chrome" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<!-- Not sure about what the button should look like, so I made it an image to the left
and the button's content to the right -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="ButtonImage" Grid.Column="0" Source="/Elpis;component/Images/thumbBan.png" />
<ContentPresenter Grid.Column="1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}" RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<!-- As I understand it, ThumbBan should be shown when IsMouseOver == True OR the bound boolean is true,
so if you invert that you could say that the ThumbDown image should be shown
when IsMouseOver == false AND the bound boolean is false, which is what this trigger does -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, ElementName=Chrome}" Value="False" />
<Condition Binding="{Binding MyBoolean}" Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="ButtonImage" Property="Source" Value="/Elpis;component/Images/thumbDown.png" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.75"/>
</Trigger>
<!-- The trigger that sets opacity to 1 for IsMouseOver false is not needed, since 1 is the
default and will be the opacity as long as the trigger above is not active -->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Возможно, вам придется переключить, какое изображение должно отображаться при активном триггере, если я неправильно понимаю требования.
Хитрость здесь в том, чтобы использовать MultiDataTrigger
, чтобы можно было объединить два упомянутых вами условия. Обычно вы, вероятно, использовали бы Trigger
вместо DataTrigger
при привязке к IsMouseOver
элементу управления. Но поскольку вам нужен DataTrigger
для логического свойства, IsMouseOver
привязка может быть записана как DataTrigger
, используя ElementName
свойство привязки. Делая это, вы можете использовать MultiDataTrigger для объединения двух.
Обновить:
Чтобы добавить поддержку настройки используемых изображений, а также того, к какому свойству привязываться, для каждого экземпляра кнопки я бы подкласс Button
класса и добавил пару DependencyProperties
.
public class ImageButton : Button
{
public static readonly DependencyProperty ActiveImageUriProperty =
DependencyProperty.RegisterAttached("ActiveImageUri", typeof(Uri), typeof(ImageButton),
new PropertyMetadata(null));
public static readonly DependencyProperty InactiveImageUriProperty =
DependencyProperty.RegisterAttached("InactiveImageUri", typeof(Uri), typeof(ImageButton),
new PropertyMetadata(null));
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(ImageButton),
new PropertyMetadata(false));
public Uri ActiveImageUri
{
get { return (Uri)GetValue(ActiveImageUriProperty); }
set { SetValue(ActiveImageUriProperty, value); }
}
public Uri InactiveImageUri
{
get { return (Uri)GetValue(InactiveImageUriProperty); }
set { SetValue(InactiveImageUriProperty, value); }
}
public bool IsActive
{
get { return (bool)GetValue(IsActiveProperty); }
set { SetValue(IsActiveProperty, value); }
}
}
Затем этот класс можно было бы использовать следующим образом:
<SomeNamespace:ImageButton Height="23" Width="100" Content="Button 1"
ActiveImageUri="/Elpis;component/Images/thumbBan.png"
InactiveImageUri="/Elpis;component/Images/thumbDown.png"
IsActive="{Binding MyBoolean}" />
<SomeNamespace:ImageButton Height="23" Width="100" Content="Button 2"
ActiveImageUri="/Elpis;component/Images/someOtherImage.png"
InactiveImageUri="/Elpis;component/Images/yetAnotherImage.png"
IsActive="{Binding SomeOtherBooleanProperty}" />
Шаблон управления затем может быть изменен, чтобы выглядеть следующим образом:
<Style TargetType="SomeNamespace:ImageButton">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type SomeNamespace:ImageButton}">
<Grid x:Name="Chrome" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="ButtonImage" Grid.Column="0"
Source="{Binding ActiveImageUri, RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter Grid.Column="1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}" RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
<ControlTemplate.Triggers>
<!-- The "active" image should be shown when IsMouseOver == True OR the bound boolean is true,
so if you invert that you could say that the "inactive" image should be shown
when IsMouseOver == false AND the bound boolean is false, which is what this trigger does -->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="False" />
<Condition Property="IsActive" Value="False" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter TargetName="ButtonImage" Property="Source"
Value="{Binding InactiveImageUri, RelativeSource={RelativeSource TemplatedParent}}" />
</MultiTrigger.Setters>
</MultiTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.75" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Основные изменения здесь заключаются в том, что для источника изображения установлены значения свойств зависимости вместо жестко закодированных URI, и что MultiDataTrigger
был изменен на MultiTrigger
, который привязывается к свойствам зависимости. Ранее путь к логическому свойству также был жестко запрограммирован, но теперь это настраивается путем изменения привязки к IsActive
свойству при создании кнопки, как показано в примере выше.
Комментарии:
1. Хорошо … вот одна проблема с этим… Этот стиль используется на МНОГИХ разных кнопках, все с разными изображениями. Поэтому мне нужно сделать это таким образом, чтобы изображения были установлены вне стиля, который просто хранится в файле style.xaml и повторно отображается на каждой кнопке.
2. Я обновил ответ, чтобы разрешить настройку того, какие изображения использовать и к какому логическому свойству привязываться.
3. ПРИЯТНО … попробую сегодня вечером. Подумал, что для этого потребуется некоторый код, но я полностью согласен с тем, что это просто новый тип управления.
4. Рад, что смог помочь! Просто проверяя, вы знаете, что на самом деле приняли другой ответ, верно? Просто хотел убедиться, что это было сделано намеренно.
5. О боже! Извините … нет, я полностью хотел принять ваш. Исправлено 🙂