WPF Style TriggerでIsMouseOverの色が変わらない原因と対処法|ControlTemplateとVSMで完全解決

WPF でマウスホバー時に色が変わらない――そんな「あるある」を、原因別に一気に片付けます。Style.Triggers の基本から ControlTemplate、VisualStateManager、ヒットテストや優先順位の落とし穴まで、実務でハマりやすいポイントを動くサンプルとチェックリストで徹底解説。この記事だけで「なぜ変わらないのか」が分かり、狙いどおりに変わる実装へ最短で到達できます。

目次

WPF Trigger でホバー時の色が変わらない問題を一網打尽にする

「<Style.Triggers> で IsMouseOver=True にしたのに色が変わらない」。この現象の原因はひとつではありません。WPF には 依存関係プロパティの優先順位、テンプレート、ヒットテスト など複数の仕組みが関係しており、どこか1カ所でも噛み合わないと期待どおりに動作しません。以下に、現場で遭遇しやすい順に原因と対策をまとめます。

動作する最小コード(まずは正解を確認)

最初に「動く」最小サンプルを提示します。これが手元で正しく色が切り替わるなら、問題は他にあります。

&lt;Rectangle Width="100" Height="100" Stroke="Black"&gt;
    &lt;Rectangle.Style&gt;
        &lt;Style TargetType="Rectangle"&gt;
            &lt;Setter Property="Fill" Value="Green" /&gt;
            &lt;Style.Triggers&gt;
                &lt;Trigger Property="IsMouseOver" Value="True"&gt;
                    &lt;Setter Property="Fill" Value="Red" /&gt;
                &lt;/Trigger&gt;
            &lt;/Style.Triggers&gt;
        &lt;/Style&gt;
    &lt;/Rectangle.Style&gt;
&lt;/Rectangle&gt;

この例が意図どおり Green ⇆ Red に切り替わるのに、実アプリで変わらないなら、以下のいずれか(または複合)が原因です。

原因と対策:チェックリスト

スタイルの適用ミス(場所・TargetType・キー)

Style は定義しただけでは適用されません。定義場所・キーの有無・TargetType の一致が肝です。

ケース書き方作用範囲落とし穴
要素直下に定義<要素.Style><Style TargetType="要素"/></要素.Style>当該要素のみTargetType が不一致だと一切効かない
Window/UserControl.Resources<Style x:Key="MyStyle" TargetType="Button"/>明示適用した要素Style="{StaticResource MyStyle}" を忘れがち
暗黙スタイル<Style TargetType="Button">...</Style>(キー無し)同スコープの全 Buttonキーを付けた瞬間「暗黙」ではなくなる

まずは TargetType を対象要素に合わせる、リソース定義したら適用する、暗黙か明示かを統一する を確認しましょう。

変更対象プロパティの取り違え(Fill vs Background)

Shape 派生と Control 派生で変えるプロパティが違います。ここを間違えるといくら Trigger を書いても無反応です。

代表的要素色を変えるプロパティ補足
Rectangle, Ellipse, Path(Shape 系)Fill(塗りつぶし)/Stroke(枠線)Border ではなく Shape 自身の塗り
Button, Border, TextBox など(Control 系)Background(背景)/BorderBrush(枠線)テンプレート次第で内部の Border に反映
Grid, StackPanel など PanelBackgroundデフォルト null(ヒットテスト不可)に注意

疑わしいときは、いったん 要素の型を明示した最小サンプルに切り分けて挙動を確認すると切り分けが速いです。

ローカル値に負けている(依存関係プロパティの優先順位)

WPF ではプロパティに複数の「候補値」が与えられたとき、優先順位で最終値が決まります。典型的には、要素に直接 Background="Blue" などのローカル値を設定していると、Style の Trigger では上書きできません。

&lt;Button Content="NG例" Background="Green"&gt;
    &lt;Button.Style&gt;
        &lt;Style TargetType="Button"&gt;
            &lt;Style.Triggers&gt;
                &lt;Trigger Property="IsMouseOver" Value="True"&gt;
                    &lt;Setter Property="Background" Value="Orange"/&gt;  &lt;!-- ← 効かない --&gt;
                &lt;/Trigger&gt;
            &lt;/Style.Triggers&gt;
        &lt;/Style&gt;
    &lt;/Button.Style&gt;
&lt;/Button&gt;

対策

  • 要素のローカル値(属性や要素子要素)を削除し、既定値も Style の Setter で定義する。
  • やむを得ずローカル値が必要なら、ControlTemplate 内の Trigger(後述)で内部パーツに直接適用する。
  • 既存要素からローカル値をクリアするにはコードビハインドで ClearValue(Control.BackgroundProperty) が使える。

テンプレートが上書きしている(ControlTemplate / VisualStateManager)

Button など多くの Control は テンプレートで内部構造(Border や ContentPresenter など)を組み立て、テーマごとに VisualStateManager の状態(Normal/MouseOver/Pressed/Disabled)で色を切り替えます。このとき、Style の Trigger で Control の Background を変えても、テンプレート内部がそれを使っていないと見た目は変わりません。

対策A:テンプレートが Background を参照するようにする

&lt;Button Content="Hover"&gt;
    &lt;Button.Template&gt;
        &lt;ControlTemplate TargetType="Button"&gt;
            &lt;Border x:Name="Chrome" CornerRadius="4"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"&gt;
                &lt;ContentPresenter Margin="8" HorizontalAlignment="Center" VerticalAlignment="Center"/&gt;
            &lt;/Border&gt;
            &lt;ControlTemplate.Triggers&gt;
                &lt;Trigger Property="IsMouseOver" Value="True"&gt;
                    &lt;Setter TargetName="Chrome" Property="Background" Value="Orange"/&gt;
                &lt;/Trigger&gt;
            &lt;/ControlTemplate.Triggers&gt;
        &lt;/ControlTemplate&gt;
    &lt;/Button.Template&gt;
&lt;/Button&gt;

対策B:VisualStateManager を使って確実に切り替える

&lt;Button Content="VSM"&gt;
    &lt;Button.Template&gt;
        &lt;ControlTemplate TargetType="Button"&gt;
            &lt;Border x:Name="Chrome" CornerRadius="4" Background="SteelBlue"&gt;
                &lt;ContentPresenter Margin="8" HorizontalAlignment="Center" VerticalAlignment="Center"/&gt;
            &lt;/Border&gt;
            &lt;VisualStateManager.VisualStateGroups&gt;
                &lt;VisualStateGroup x:Name="CommonStates"&gt;
                    &lt;VisualState x:Name="Normal"/&gt;
                    &lt;VisualState x:Name="MouseOver"&gt;
                        &lt;Storyboard&gt;
                            &lt;ColorAnimation Storyboard.TargetName="Chrome"
                                            Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
                                            To="#FFEF6C00" Duration="0:0:0.05"/&gt;
                        &lt;/Storyboard&gt;
                    &lt;/VisualState&gt;
                    &lt;VisualState x:Name="Pressed"&gt;
                        &lt;Storyboard&gt;
                            &lt;DoubleAnimation Storyboard.TargetName="Chrome"
                                             Storyboard.TargetProperty="(UIElement.Opacity)"
                                             To="0.85" Duration="0:0:0.05"/&gt;
                        &lt;/Storyboard&gt;
                    &lt;/VisualState&gt;
                    &lt;VisualState x:Name="Disabled"/&gt;
                &lt;/VisualStateGroup&gt;
            &lt;/VisualStateManager.VisualStateGroups&gt;
        &lt;/ControlTemplate&gt;
    &lt;/Button.Template&gt;
&lt;/Button&gt;

テンプレートを使うと、ローカル値や Style の優先順位に左右されず、内部パーツへ直接適用できるため、より確実です。

ヒットテストが無効で IsMouseOver が立っていない

IsMouseOver は ヒットテスト可能な部分にマウスが乗ったときのみ True になります。Panel 系の Background 既定値は null で、透明ではなく「何もない」ためヒットテスト不可です。

&lt;Grid&gt;  &lt;!-- 何も塗っていないとヒットしない --&gt;
    &lt;Grid.Style&gt;
        &lt;Style TargetType="Grid"&gt;
            &lt;Style.Triggers&gt;
                &lt;Trigger Property="IsMouseOver" Value="True"&gt;
                    &lt;Setter Property="Background" Value="LightYellow"/&gt; &lt;!-- ← 変わらない --&gt;
                &lt;/Trigger&gt;
            &lt;/Style.Triggers&gt;
        &lt;/Style&gt;
    &lt;/Grid.Style&gt;
&lt;/Grid&gt;

対策:まず Background="Transparent" を設定してヒットテストを有効にします。

&lt;Grid Background="Transparent"&gt;
    &lt;Grid.Style&gt;
        &lt;Style TargetType="Grid"&gt;
            &lt;Style.Triggers&gt;
                &lt;Trigger Property="IsMouseOver" Value="True"&gt;
                    &lt;Setter Property="Background" Value="LightYellow"/&gt;
                &lt;/Trigger&gt;
            &lt;/Style.Triggers&gt;
        &lt;/Style&gt;
    &lt;/Grid.Style&gt;
&lt;/Grid&gt;

また、親要素の IsHitTestVisible="False" が下層に影響している場合や、IsEnabled="False" のときにテンプレートの「Disabled」状態に固定されているケースもあります。必要に応じて親から辿って確認しましょう。

XAML 構文ミス(閉じ忘れ・引用符)

細かな構文エラーで Silent に無視されることもあります。例えば Value="#ffffff/>" のように引用符やスラッシュが欠けていると正しく解釈されません。正しくは次のとおりです。

&lt;Setter Property="Background" Value="#FFFFFF" /&gt;

VS の XAML エディタで下線やエラー一覧を必ず確認し、最小再現の XAML に切り出して原因を特定しましょう。

リソース参照の選択(StaticResource / DynamicResource)

色をリソース化するのは保守性の面で推奨です。ただしホバー時に ランタイムで色リソースを差し替える可能性があるなら DynamicResource を、固定色なら StaticResource を使います。

&lt;Window.Resources&gt;
    &lt;SolidColorBrush x:Key="ButtonNormal" Color="#FF1976D2"/&gt;
    &lt;SolidColorBrush x:Key="ButtonHover"  Color="#FF2196F3"/&gt;
&lt;/Window.Resources&gt;

&lt;Style TargetType="Button"&gt;
    &lt;Setter Property="Background" Value="{StaticResource ButtonNormal}"/&gt;
    &lt;Style.Triggers&gt;
        &lt;Trigger Property="IsMouseOver" Value="True"&gt;
            &lt;Setter Property="Background" Value="{StaticResource ButtonHover}"/&gt;
        &lt;/Trigger&gt;
    &lt;/Style.Triggers&gt;
&lt;/Style&gt;

アプリのテーマ切り替えを想定する場合は次のようにします。

&lt;Setter Property="Background" Value="{DynamicResource ButtonNormal}"/&gt;

ItemsControl 内の “どこに Trigger を書くか” 問題

ListBox や ListView では、見た目を司るのが「アイテムコンテナ(ListBoxItem 等)」です。DataTemplate に Trigger を書いてもコンテナの背景は変わりません。ItemContainerStyle に書きましょう。

&lt;ListBox ItemsSource="{Binding Users}"&gt;
    &lt;ListBox.ItemContainerStyle&gt;
        &lt;Style TargetType="ListBoxItem"&gt;
            &lt;Setter Property="Background" Value="White"/&gt;
            &lt;Style.Triggers&gt;
                &lt;Trigger Property="IsMouseOver" Value="True"&gt;
                    &lt;Setter Property="Background" Value="AliceBlue"/&gt;
                &lt;/Trigger&gt;
                &lt;Trigger Property="IsSelected" Value="True"&gt;
                    &lt;Setter Property="Background" Value="LightSkyBlue"/&gt;
                &lt;/Trigger&gt;
            &lt;/Style.Triggers&gt;
        &lt;/Style&gt;
    &lt;/ListBox.ItemContainerStyle&gt;
&lt;/ListBox&gt;

複合条件(MultiTrigger / MultiDataTrigger)

「ホバー中 かつ 有効状態のときだけ色を変える」など複合条件は MultiTrigger が便利です。

&lt;Style TargetType="Button"&gt;
    &lt;Setter Property="Background" Value="Gray"/&gt;
    &lt;Style.Triggers&gt;
        &lt;MultiTrigger&gt;
            &lt;MultiTrigger.Conditions&gt;
                &lt;Condition Property="IsMouseOver" Value="True"/&gt;
                &lt;Condition Property="IsEnabled"   Value="True"/&gt;
            &lt;/MultiTrigger.Conditions&gt;
            &lt;Setter Property="Background" Value="Orange"/&gt;
        &lt;/MultiTrigger&gt;
    &lt;/Style.Triggers&gt;
&lt;/Style&gt;

よくある UI 要素別の完成レシピ

Border のホバー色切り替え(ヒットテスト込み)

&lt;Border CornerRadius="6" Padding="12" Background="Transparent"&gt;
    &lt;Border.Style&gt;
        &lt;Style TargetType="Border"&gt;
            &lt;Setter Property="Background" Value="Transparent"/&gt;
            &lt;Style.Triggers&gt;
                &lt;Trigger Property="IsMouseOver" Value="True"&gt;
                    &lt;Setter Property="Background" Value="#10FF9800"/&gt;
                    &lt;Setter Property="BorderBrush" Value="#FF9800"/&gt;
                    &lt;Setter Property="BorderThickness" Value="1"/&gt;
                &lt;/Trigger&gt;
            &lt;/Style.Triggers&gt;
        &lt;/Style&gt;
    &lt;/Border.Style&gt;
    &lt;TextBlock Text="Hover me"/&gt;
&lt;/Border&gt;

TextBox の枠線をホバーで強調

&lt;Style TargetType="TextBox"&gt;
    &lt;Setter Property="BorderBrush" Value="#BDBDBD"/&gt;
    &lt;Setter Property="BorderThickness" Value="1"/&gt;
    &lt;Style.Triggers&gt;
        &lt;Trigger Property="IsMouseOver" Value="True"&gt;
            &lt;Setter Property="BorderBrush" Value="#2196F3"/&gt;
            &lt;Setter Property="BorderThickness" Value="2"/&gt;
        &lt;/Trigger&gt;
    &lt;/Style.Triggers&gt;
&lt;/Style&gt;

MenuItem / ContextMenu はアイテム側に書く

&lt;Style TargetType="MenuItem"&gt;
    &lt;Setter Property="Background" Value="Transparent"/&gt;
    &lt;Style.Triggers&gt;
        &lt;Trigger Property="IsHighlighted" Value="True"&gt;
            &lt;Setter Property="Background" Value="#FFE3F2FD"/&gt;
        &lt;/Trigger&gt;
    &lt;/Style.Triggers&gt;
&lt;/Style&gt;

MenuItem は IsMouseOver ではなく IsHighlighted を使うと安定します。

色リソース設計(保守性と一貫性)

複数コントロールで同じ配色を使うなら、色を Brush リソースに切り出すと変更が容易になります。

<Window.Resources>
    <SolidColorBrush x:Key="Primary"        Color="#FF1976D2"/>
    <SolidColorBrush x:Key="PrimaryHover"   Color="#FF2196F3"/>
    <SolidColorBrush x:Key="Danger"         Color="#FFD32F2F"/>
    <SolidColorBrush x:Key="DangerHover"    Color="#FFE53935"/>
</Window.Resources>

 

間違いが起きやすいパターンを具体例で修正

暗黙スタイルと明示スタイルの衝突

暗黙スタイル(キー無し)と、個別に Style="{StaticResource …}" を指定した明示スタイルは併用されません。両方の Setter/Trigger を効かせたいなら BasedOn を使って継承します。

<Style x:Key="BaseButton" TargetType="Button"> ... </Style>

 

コードビハインドで Brush を共有していて色が一括で変わる

Brush は Freezable で参照共有されます。Trigger 内で同じ Brush インスタンスを操作すると、意図せず他のコントロールも変わることがあります。Setter では新しい Brush を割り当てるか、各用途で別キーの Brushを使い分けましょう。

Binding と Trigger の競合

プロパティに Binding を張ると、それはローカル値扱いとなり、Style の Setter/Trigger では上書きできません。色をデータに応じて変えたいなら DataTrigger や MultiDataTrigger を使います。

&lt;Style TargetType="Border"&gt;
    &lt;Style.Triggers&gt;
        &lt;DataTrigger Binding="{Binding IsError}" Value="True"&gt;
            &lt;Setter Property="Background" Value="{StaticResource Danger}"/&gt;
        &lt;/DataTrigger&gt;
        &lt;MultiDataTrigger&gt;
            &lt;MultiDataTrigger.Conditions&gt;
                &lt;Condition Binding="{Binding IsError}" Value="False"/&gt;
                &lt;Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="True"/&gt;
            &lt;/MultiDataTrigger.Conditions&gt;
            &lt;Setter Property="Background" Value="{StaticResource PrimaryHover}"/&gt;
        &lt;/MultiDataTrigger&gt;
    &lt;/Style.Triggers&gt;
&lt;/Style&gt;

構文・配置の基本チェック

  • <Style.Triggers> は必ず <Style> または <ControlTemplate> の内側に置く。
  • Trigger の Property は 依存関係プロパティである必要がある(普通の CLR プロパティは不可)。
  • TargetType を省略した Setter は、Style の TargetType に適用される。テンプレート内で他要素に適用するには TargetName を使う。
  • 色コードは #RRGGBB か #AARRGGBB。閉じスラッシュや引用符忘れに注意。

デバッグ術:最短で原因を見抜く

  1. 最小再現を作る:問題の要素だけを抜き出した XAML にして、先頭の最小コードと比較。
  2. Live Visual Tree と Live Property Explorer を使う:実行中に該当要素を選び、問題のプロパティ(たとえば Background)の Effective Value が「どこから来たか」を確認。ローカル値か、Style Setter か、Template Trigger かが一目瞭然。
  3. ヒットテスト線引き:Panel系に Background="Transparent" を付与して挙動が変わるか試す。
  4. テンプレートを外す:一時的に Template=null(または極小テンプレート)で変化を観測。Template が原因か切り分け。
  5. ローカル値クリア:該当プロパティを XAML から削除・ClearValue で無効化し、Style でのみ設定して再確認。

プロダクション品質の実装例(Button を丸ごと置き換え)

ローカル値やテーマの違いに左右されない「堅い」実装例です。配色はリソース、状態切り替えは VSM、影や押下も考慮します。

<Window.Resources>
    <SolidColorBrush x:Key="BtnBg"       Color="#FF1976D2"/>
    <SolidColorBrush x:Key="BtnBgHover"  Color="#FF1E88E5"/>
    <SolidColorBrush x:Key="BtnBgPress"  Color="#FF1565C0"/>
    <SolidColorBrush x:Key="BtnFg"       Color="White"/>


<Style x:Key="SolidButton" TargetType="Button">
    <Setter Property="Foreground" Value="{StaticResource BtnFg}"/>
    <Setter Property="Background" Value="{StaticResource BtnBg}"/>
    <Setter Property="Padding" Value="10,6"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border x:Name="Chrome" CornerRadius="6" Background="{TemplateBinding Background}">
                    <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="CommonStates">
                        <VisualState x:Name="Normal"/>
                        <VisualState x:Name="MouseOver">
                            <Storyboard>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Chrome"
                                    Storyboard.TargetProperty="Background">
                                    <DiscreteObjectKeyFrame KeyTime="0:0:0"
                                        Value="{StaticResource BtnBgHover}"/>
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </VisualState>
                        <VisualState x:Name="Pressed">
                            <Storyboard>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Chrome"
                                    Storyboard.TargetProperty="Background">
                                    <DiscreteObjectKeyFrame KeyTime="0:0:0"
                                        Value="{StaticResource BtnBgPress}"/>
                                </ObjectAnimationUsingKeyFrames>
                                <DoubleAnimation Storyboard.TargetName="Chrome"
                                                 Storyboard.TargetProperty="Opacity"
                                                 To="0.95" Duration="0:0:0.05"/>
                            </Storyboard>
                        </VisualState>
                        <VisualState x:Name="Disabled">
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="Chrome"
                                                 Storyboard.TargetProperty="Opacity"
                                                 To="0.5" Duration="0:0:0"/>
                            </Storyboard>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>







 

「それでも変わらない」時の最終確認ポイントまとめ

症状疑うべき点確認・対処
Trigger が反応しないヒットテスト不可 / 無効状態 / 覆い要素がイベントを奪うBackground=”Transparent” を付与、IsEnabled/IsHitTestVisible を確認、親・子の重なり順を見直す
反応はするが見た目が変わらないテンプレートが色を使っていない / 別部品が上描きTemplateBinding を導入、ControlTemplate.Triggers または VSM で内部パーツを直接変更
特定要素だけ変わらないローカル値 / Binding が入っている属性を削除、ClearValue、Style に集約。Binding を DataTrigger に変更
ItemsControl の行だけ変わらないDataTemplate に書いているItemContainerStyle に移動(ListBoxItem / ListViewItem など)
特定テーマでのみ不具合テーマ固有テンプレートの差異独自テンプレートを適用して差異を吸収

堅牢にするための設計指針

  • 「見た目はテンプレート、バリエーションはスタイル」の役割分担にする。共通の構造は Template、色や余白など派生は Style で。
  • ローカル値より Style 優先:ローカル値は最後の手段。原則 Setter/Trigger に寄せる。
  • Brush をリソース化し、Normal/Hover/Pressed/Disabled を命名規則でそろえる。
  • Live Visual Tree を常用し、Effective Value を目視で確認する習慣をつける。
  • パネルのホバー判定には Transparent を忘れない。null は透明ではない。

よくある質問(FAQ)

Q. Button に Background をローカル指定しても、ホバー時だけ色を変えたい。

A. Style.Trigger はローカル値を上書きできません。ControlTemplate の TargetName に対して色を変えるか、VSM で状態切り替えしてください。どうしても Style でやるなら、ローカル値を削除して Setter に移すのが王道です。

Q. Rectangle で Background を変えても効かない。

A. Shape 系は Background ではなく Fill です。テーブルの対応表を参照してください。

Q. Grid のホバー色が反応しない。

A. Background が null のためヒットテストされていません。Background="Transparent" を設定してから Trigger を書いてください。

Q. MenuItem のホバー判定は?

A. IsHighlighted を使うのが一般的です。テンプレートにより IsMouseOver が期待どおり動かないことがあります。

コピペOK:実務で使えるスニペット集

1) Window 全体の Button に暗黙スタイルでホバー色を適用

<Window.Resources>
    <SolidColorBrush x:Key="BtnNormal" Color="#FF455A64"/>
    <SolidColorBrush x:Key="BtnHover"  Color="#FF607D8B"/>
</Window.Resources>

 

2) Border をホバーで Elevation 風に

&lt;Style x:Key="HoverCard" TargetType="Border"&gt;
    &lt;Setter Property="Background" Value="White"/&gt;
    &lt;Setter Property="CornerRadius" Value="8"/&gt;
    &lt;Setter Property="BorderBrush" Value="#E0E0E0"/&gt;
    &lt;Setter Property="BorderThickness" Value="1"/&gt;
    &lt;Setter Property="Effect"&gt;
        &lt;Setter.Value&gt;&lt;DropShadowEffect Opacity="0" BlurRadius="8" ShadowDepth="0"/&gt;&lt;/Setter.Value&gt;
    &lt;/Setter&gt;
    &lt;Style.Triggers&gt;
        &lt;Trigger Property="IsMouseOver" Value="True"&gt;
            &lt;Setter Property="Effect"&gt;
                &lt;Setter.Value&gt;&lt;DropShadowEffect Opacity="0.3" BlurRadius="12" ShadowDepth="0"/&gt;&lt;/Setter.Value&gt;
            &lt;/Setter&gt;
            &lt;Setter Property="BorderBrush" Value="#BDBDBD"/&gt;
        &lt;/Trigger&gt;
    &lt;/Style.Triggers&gt;
&lt;/Style&gt;

3) ListView の行ホバー

&lt;Style TargetType="ListViewItem"&gt;
    &lt;Setter Property="Background" Value="White"/&gt;
    &lt;Setter Property="BorderBrush" Value="Transparent"/&gt;
    &lt;Style.Triggers&gt;
        &lt;Trigger Property="IsMouseOver" Value="True"&gt;
            &lt;Setter Property="Background" Value="#FFF5F5F5"/&gt;
        &lt;/Trigger&gt;
        &lt;Trigger Property="IsSelected" Value="True"&gt;
            &lt;Setter Property="Background" Value="#FFE3F2FD"/&gt;
            &lt;Setter Property="BorderBrush" Value="#90CAF9"/&gt;
        &lt;/Trigger&gt;
    &lt;/Style.Triggers&gt;
&lt;/Style&gt;

まとめ

WPF でホバー色が変わらないときは、次の順で疑えば解決が速いです。

  1. プロパティ名は正しいか(Shape は Fill、Control は Background)。
  2. スタイルは適用されているか(TargetType・キー・適用スコープ)。
  3. ローカル値や Binding に負けていないか(必要なら Template/ VSM で直接適用)。
  4. ヒットテストできるか(Transparent を設定)。
  5. ItemsControl はコンテナに書いたか(ListBoxItem 等)。

このチェックリストとサンプルを手元の XAML に当てはめれば、「変わらない」から「必ず変わる」へ設計を改善できます。色の切り替えはユーザー体験に直結します。Style・Template・VSM を正しく使い分け、読みやすく再利用しやすい XAML を目指しましょう。


付録:実装テンプレート(コピペベース)

Window.Resources にまとめた配色と共通スタイル

<Window.Resources>
    <SolidColorBrush x:Key="PrimaryBrush"          Color="#FF1976D2"/>
    <SolidColorBrush x:Key="PrimaryHoverBrush"     Color="#FF1E88E5"/>
    <SolidColorBrush x:Key="PrimaryPressedBrush"   Color="#FF1565C0"/>


<Style x:Key="HoverableRectangle" TargetType="Rectangle">
    <Setter Property="Fill" Value="Green"/>
    <Setter Property="Stroke" Value="Black"/>
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Fill" Value="Red"/>
        </Trigger>
    </Style.Triggers>
</Style>

<Style x:Key="PrimaryActionButton" TargetType="Button">
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="Background" Value="{StaticResource PrimaryBrush}"/>
    <Setter Property="Padding" Value="12,7"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border x:Name="Chrome" CornerRadius="6" Background="{TemplateBinding Background}">
                    <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="CommonStates">
                        <VisualState x:Name="Normal"/>
                        <VisualState x:Name="MouseOver">
                            <Storyboard>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Chrome"
                                    Storyboard.TargetProperty="Background">
                                    <DiscreteObjectKeyFrame KeyTime="0:0:0"
                                        Value="{StaticResource PrimaryHoverBrush}"/>
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </VisualState>
                        <VisualState x:Name="Pressed">
                            <Storyboard>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Chrome"
                                    Storyboard.TargetProperty="Background">
                                    <DiscreteObjectKeyFrame KeyTime="0:0:0"
                                        Value="{StaticResource PrimaryPressedBrush}"/>
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </VisualState>
                        <VisualState x:Name="Disabled">
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="Chrome"
                                                 Storyboard.TargetProperty="Opacity"
                                                 To="0.5" Duration="0:0:0"/>
                            </Storyboard>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>


 

利用例

&lt;StackPanel Orientation="Vertical" Spacing="12"&gt;
    &lt;Rectangle Style="{StaticResource HoverableRectangle}" Width="120" Height="80"/&gt;
    &lt;Button Content="実行" Style="{StaticResource PrimaryActionButton}"/&gt;
&lt;/StackPanel&gt;

チェックリスト(貼って使える簡易版)

  • Style の TargetType は合っているか。
  • Style を適用しているか(暗黙/明示を整理)。
  • 要素の色は Fill / Background のどちらか正しい方を変更しているか。
  • Panel 系は Background=”Transparent” でヒットテスト可能になっているか。
  • ローカル値や Binding で Style に勝っていないか。
  • テンプレートの中で TemplateBinding を使っているか、または ControlTemplate.Triggers/VSM で直接変えているか。
  • ItemsControl は ItemContainerStyle に Trigger を書いたか。
  • 構文ミス(閉じスラッシュ、引用符、名前解決)は無いか。

ケーススタディ:既存テーマの Button にだけ色が効かない

「同じ XAML でも Button だけ効かず、Border や TextBox は効く」場合、ほぼテンプレートが原因です。既定テーマの Button は VisualStateManager で状態を切り替えており、あなたの Style.Trigger が Background を変えても内部 Border がその値を参照していない可能性が高いです。テンプレートを書き換えるか、スタイルではなく テンプレートのトリガを使いましょう(本稿のサンプル参照)。

終わりに

WPF の Trigger は強力ですが、仕組みを正しく理解しないと「効かないトリガ」を量産してしまいます。この記事のサンプルをベースに、プロパティ優先順位・テンプレート・ヒットテストの3点を押さえれば、ホバー時の色切り替えは堅牢かつ再利用可能に実装できます。ぜひ自分のプロジェクトの共通スタイルに取り込み、UI の一貫性と生産性を高めてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次