在上一篇掌握控件核心基石後,本文將聚焦於 控件的視覺表現層:字體屬性如何在視覺樹中自動繼承、Style 如何集中管理外觀、ControlTemplate 如何徹底重塑控件結構、系統內建資源 如何讓 App 符合 Windows 設計語言、以及 VisualState / VisualStateManager 如何實現豐富的互動回饋。
Control 基類定義了 FontFamily、FontSize、FontWeight、FontStyle、Foreground 等字體相關屬性,這些屬性在視覺樹中具有 屬性值繼承 特性:子元素若未顯式設定,會自動使用最近祖先元素的值。
<Page
x:Class="XamlDemo.Controls.UI.FontInherit"
FontSize="100"> <!-- 頁面層級設定 -->
<Grid Background="Transparent">
<StackPanel Margin="120 0 0 0">
<!-- 繼承 Page 的 FontSize=100 -->
<TextBlock Text="FontSize = 100" />
<UserControl FontSize="50"> <!-- 區域覆寫 -->
<!-- 繼承 UserControl 的 FontSize=50 -->
<TextBlock Text="FontSize = 50" />
</UserControl>
</StackPanel>
</Grid>
</Page>
💡 實務建議:在 Page 或 App.xaml 設定基礎字體大小,子控件只需在必要時覆寫,即可全站風格一致且易維護。
Style 針對特定 TargetType 定義一組 Setter,可套用至任意符合型別的 FrameworkElement。
x:Key:顯式樣式鍵值,需透過 {StaticResource Key} 套用TargetType:目標型別(必填)BasedOn:繼承另一個樣式,實現樣式層級複用Setter.Property / Value:設定屬性值<Page.Resources>
<!-- 顯式樣式:需指定 Key -->
<Style x:Key="MyTextStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="24"/>
<Setter Property="Foreground" Value="#0000FF"/>
</Style>
<Style x:Key="MyTextStyle2" TargetType="TextBox">
<Setter Property="FontSize" Value="24"/>
<Setter Property="Foreground" Value="#FF0000"/>
</Style>
<!-- 隱式樣式:無 Key,自動套用於所有目標型別 -->
<Style TargetType="TextBox" BasedOn="{StaticResource MyTextStyle}">
<Setter Property="TextAlignment" Value="Center"/>
</Style>
</Page.Resources>
<StackPanel>
<!-- 套用顯式樣式 -->
<TextBox Text="我是 TextBox" Style="{StaticResource MyTextStyle}" />
<!-- 自動套用隱式樣式 -->
<TextBox Text="我是 TextBox" />
<Button Content="動態切換樣式" Click="btnChangeStyle_Click" />
</StackPanel>
private void btnChangeStyle_Click(object sender, RoutedEventArgs e)
{
// 從頁面資源取得樣式並套用
txtStyleDemo.Style = (Style)root.Resources["MyTextStyle2"];
}
ControlTemplate 定義控件的完整視覺結構,透過 ContentPresenter 呈現 Content,透過 TemplateBinding 綁定控件屬性(如 Background、Foreground)。
Template="{StaticResource MyControlTemplate}"Button.Template 內撰寫Style.Setter.Property="Template"<Grid.Resources>
<ControlTemplate x:Key="MyControlTemplate" TargetType="Button">
<Border BorderBrush="Red" BorderThickness="1">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Right" Foreground="Red" />
</Grid>
</Border>
</ControlTemplate>
<Style x:Key="MyStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="Red" BorderThickness="1">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Right" Foreground="Red" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<StackPanel>
<Button Content="資源引用" Template="{StaticResource MyControlTemplate}" Background="Yellow" />
<Button Content="內聯模板" Background="Yellow">
<Button.Template>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="1">
<Grid Background="Yellow">
<ContentPresenter HorizontalAlignment="Right" Foreground="Red" />
</Grid>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
<Button Content="Style 包裹模板" Style="{StaticResource MyStyle}" Background="Yellow" />
<Button Content="動態切換模板" Click="btnChangeControlTemplate_Click" />
</StackPanel>
private void btnChangeControlTemplate_Click(object sender, RoutedEventArgs e)
{
btnControlTemplateDemo.Template = (ControlTemplate)root.Resources["MyControlTemplate2"];
}
WinRT 提供大量 ThemeResource / StaticResource 涵蓋色彩、筆刷、字體、尺寸、動畫緩動等,直接引用即可確保 App 與系統風格一致、支援高對比度、自動適配深/淺色主題。
<Border Padding="12,4,12,4"
BorderThickness="{StaticResource ButtonBorderThemeThickness}"
BorderBrush="{StaticResource ButtonBorderThemeBrush}"
Background="{StaticResource ButtonBackgroundThemeBrush}">
<TextBlock Text="我是一個長得像 Button 的 TextBlock"
FontWeight="SemiBold"
FontFamily="{StaticResource ContentControlThemeFontFamily}"
FontSize="{StaticResource ControlContentThemeFontSize}"
Foreground="{StaticResource ButtonForegroundThemeBrush}" />
</Border>
<Button Content="真正的 Button" />
💡 提示:在 Visual Studio XAML 編輯器輸入 {StaticResource 會自動列出所有可用系統資源。
控件在不同互動狀態(Normal、PointerOver、Pressed、Disabled、Focused...)下呈現不同外觀,透過 VisualStateGroup 分組、VisualState 定義狀態動畫、VisualTransition 定義狀態轉場,最終由 VisualStateManager.GoToState 驅動。
<ControlTemplate x:Key="myControlTemplate" TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="borderBrush"
Storyboard.TargetProperty="Color"
To="Green" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="grid"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0:0:0.3"
Value="{StaticResource ButtonPressedBackgroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="contentPresenter"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource ButtonPressedForegroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!-- 狀態轉場:Normal → PointerOver 1 秒彈性緩動 -->
<VisualStateGroup.Transitions>
<VisualTransition To="PointerOver" GeneratedDuration="0:0:1">
<VisualTransition.GeneratedEasingFunction>
<ElasticEase EasingMode="EaseInOut" />
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused" />
<VisualState x:Name="Unfocused" />
<VisualState x:Name="PointerFocused" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="border" BorderThickness="10">
<Border.BorderBrush>
<SolidColorBrush x:Name="borderBrush" Color="Red" />
</Border.BorderBrush>
<Grid x:Name="grid" Background="{TemplateBinding Background}" Width="500" Height="200">
<ContentPresenter x:Name="contentPresenter"
VerticalAlignment="Center" HorizontalAlignment="Center"
FontSize="24.667"
Foreground="{TemplateBinding Foreground}" />
</Grid>
</Border>
</Grid>
</ControlTemplate>
private void btnVisualStateManager_Click(object sender, RoutedEventArgs e)
{
// 強制將 btnDemo 切換到 PointerOver 狀態,並啟用轉場動畫
VisualStateManager.GoToState(btnDemo, "PointerOver", true);
}
VisualStateManager.GoToState(this, "StateName", true) 切換狀態VisualState.x:Name 完全一致本文完整串聯了控件視覺表現的完整鏈路:
善用這些機制,能讓你的 App 擁有專業、一致、可維護的視覺體驗,同時大幅降低重複樣式碼。