PointerEntered/Exited 事件或 VisualStateGroup 標準狀態Silverlight/WPF 採用「樣板分離」模式:邏輯類別 (C#) 定義依賴屬性 + XAML ControlTemplate 定義視覺樹與狀態動畫。狀態切換由 VisualStateManager 自動處理。
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ImageButtonDemo
{
/// <summary>
/// 支援 Normal / MouseOver 兩種圖片切換的按鈕
/// </summary>
public class MyImageButton : Button
{
// 依賴屬性:正常狀態圖片
public static readonly DependencyProperty ImageNormalProperty =
DependencyProperty.Register(
nameof(ImageNormal),
typeof(ImageSource),
typeof(MyImageButton),
new PropertyMetadata(null));
// 依賴屬性:滑鼠懸停圖片
public static readonly DependencyProperty ImageHoverProperty =
DependencyProperty.Register(
nameof(ImageHover),
typeof(ImageSource),
typeof(MyImageButton),
new PropertyMetadata(null));
public ImageSource ImageNormal
{
get => (ImageSource)GetValue(ImageNormalProperty);
set => SetValue(ImageNormalProperty, value);
}
public ImageSource ImageHover
{
get => (ImageSource)GetValue(ImageHoverProperty);
set => SetValue(ImageHoverProperty, value);
}
static MyImageButton()
{
// 設定預設樣式鍵 (Silverlight 必須)
DefaultStyleKeyProperty.OverrideMetadata(
typeof(MyImageButton),
new FrameworkPropertyMetadata(typeof(MyImageButton)));
}
public MyImageButton()
{
// Silverlight 需在建構式設定
DefaultStyleKey = typeof(MyImageButton);
}
}
}
<ResourceDictionary
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ImageButtonDemo">
<Style TargetType="local:MyImageButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyImageButton">
<Grid Background="{TemplateBinding Background}">
<!-- 視覺狀態群組 -->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<!-- Normal: 預設狀態,顯示 ImageNormal -->
<VisualState x:Name="Normal" />
<!-- MouseOver: 切換顯示 ImageHover -->
<VisualState x:Name="MouseOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="PART_HoverImage"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="PART_NormalImage"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!-- Pressed: 可選擇加入按下效果 (縮放/陰影) -->
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="PART_RootGrid"
Storyboard.TargetProperty="RenderTransform.ScaleX"
To="0.97" Duration="0:0:0.05" />
<DoubleAnimation
Storyboard.TargetName="PART_RootGrid"
Storyboard.TargetProperty="RenderTransform.ScaleY"
To="0.97" Duration="0:0:0.05" />
</Storyboard>
</VisualState>
<!-- Disabled: 灰階/半透明 -->
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="PART_NormalImage"
Storyboard.TargetProperty="Opacity"
To="0.4" Duration="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="PART_RootGrid" RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<ScaleTransform ScaleX="1" ScaleY="1" />
</Grid.RenderTransform>
<!-- 正常圖片 (預設顯示) -->
<Image x:Name="PART_NormalImage"
Source="{TemplateBinding ImageNormal}"
Stretch="Uniform"
Visibility="Visible" />
<!-- 懸停圖片 (預設隱藏) -->
<Image x:Name="PART_HoverImage"
Source="{TemplateBinding ImageHover}"
Stretch="Uniform"
Visibility="Collapsed" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
<UserControl x:Class="ImageButtonDemo.MainPage"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ImageButtonDemo"
Width="400" Height="300">
<Grid Background="White">
<local:MyImageButton
Width="120" Height="120"
HorizontalAlignment="Center" VerticalAlignment="Center"
ImageNormal="/Assets/btn_normal.png"
ImageHover="/Assets/btn_hover.png"
ToolTipService.ToolTip="點擊執行動作"
Click="MyImageButton_Click" />
</Grid>
</UserControl>
private void MyImageButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("自訂圖片按鈕被點擊了!");
// 執行業務邏輯...
}
WPF 可使用 Trigger 簡化無需 VisualStateManager (但 VSM 更標準、支援 Blend 設計):
<Style TargetType="local:MyImageButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyImageButton">
<Grid>
<Image x:Name="NormalImg" Source="{TemplateBinding ImageNormal}" Stretch="Uniform" />
<Image x:Name="HoverImg" Source="{TemplateBinding ImageHover}" Stretch="Uniform" Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="NormalImg" Property="Visibility" Value="Collapsed" />
<Setter TargetName="HoverImg" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="RenderTransform">
<Setter.Value><ScaleTransform ScaleX="0.95" ScaleY="0.95" /></Setter.Value>
</Setter>
<Setter Property="RenderTransformOrigin" Value="0.5,0.5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
| 平台 | 推薦做法 |
|---|---|
| WPF (.NET 6+) | 繼續使用 ControlTemplate + VSM,或改用 Button + ContentTemplate + DataTrigger 純 XAML 實現 |
| WinUI 3 / Windows App SDK | 使用 Button + VisualStateGroup (CommonStates: Normal, PointerOver, Pressed, Disabled),PointerOver 取代 MouseOver |
| .NET MAUI | 使用 ImageButton (內建 Source/HoverSource/PressedSource/DisabledSource) 或自訂 ControlTemplate |
| Blazor / Web | CSS :hover / :active 切換背景圖片,或元件庫 (MudBlazor, Radzen) 內建按鈕變體 |
System.Windows.Controls → 同,但組件不同OverrideMetadata,Silverlight 用建構式 DefaultStyleKeyThemes/Generic.xaml,Silverlight 合併在 App.xaml 或頁面 Resources/Assets/img.png),WPF 用 Pack URI (pack://application:,,,/Assets/img.png)PreviewMouseDown 等通道事件