iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0

為什麼需要 Style

  • 一致性:按鈕、輸入框、表格的外觀一致。
  • 可維護:修改一處資源,全站同步。
  • 可複用:專案擴充時不需重複貼樣式。

基本觀念:Resource 與 Style

資源(Resource)

把常用的顏色、間距、字型、邊角半徑集中管理:

<Window.Resources>
    <!-- 調色盤 -->
    <Color x:Key="PrimaryColor">#FF2B6CB0</Color>
    <Color x:Key="AccentColor">#FF0EA5E9</Color>
    <SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}"/>
    <SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/>
    <SolidColorBrush x:Key="TextBrush" Color="#222222"/>
    <SolidColorBrush x:Key="MutedTextBrush" Color="#666666"/>
    <SolidColorBrush x:Key="PanelBrush" Color="#F7F9FC"/>

    <!-- 間距與圓角 -->
    <x:Double x:Key="Radius">6</x:Double>
    <Thickness x:Key="Gap">12</Thickness>
    <Thickness x:Key="CellPadding">6,4</Thickness>

    <!-- 字型大小 -->
    <x:Double x:Key="TitleSize">20</x:Double>
    <x:Double x:Key="BodySize">14</x:Double>
</Window.Resources>

StaticResource vs DynamicResource

  • StaticResource:載入時解析,效能較佳;適用較少變化的資源。
  • DynamicResource:執行期可變更(例如切換 Light/Dark 主題)。

Style:定義共用外觀

1) 顯式樣式(指定 x:Key 使用)

<Window.Resources>
    <Style x:Key="PrimaryButton" TargetType="Button">
        <Setter Property="Background" Value="{StaticResource PrimaryBrush}"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="Padding" Value="12,8"/>
        <Setter Property="FontSize" Value="{StaticResource BodySize}"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="Cursor" Value="Hand"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border Background="{TemplateBinding Background}"
                            CornerRadius="{StaticResource Radius}">
                        <ContentPresenter HorizontalAlignment="Center"
                                          VerticalAlignment="Center"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="PART_Border" Property="Background" 
                                    Value="{StaticResource AccentBrush}"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Opacity" Value="0.5"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

<!-- 使用 -->
<Button Style="{StaticResource PrimaryButton}" Content="下載股票"/>

注意:若在 ControlTemplate 使用 Border 並需要觸發器指到該元素,請給它 x:Name="PART_Border",上例可再補上 Name 後於 Trigger 使用 TargetName

2) 隱式樣式(Implicit Style)

對指定型別全域生效、無需 x:Key:

<Window.Resources>
    <Style TargetType="TextBox">
        <Setter Property="Margin" Value="0,0,0,8"/>
        <Setter Property="Padding" Value="8,6"/>
        <Setter Property="FontSize" Value="{StaticResource BodySize}"/>
        <Setter Property="BorderBrush" Value="#DDDDDD"/>
        <Setter Property="BorderThickness" Value="1"/>
    </Style>
</Window.Resources>

<!-- 所有 TextBox 都會套用 -->
<TextBox Text="{Binding Query}" />

將 Style 抽出到 ResourceDictionary

專案結構建議:

/Styles
   Colors.xaml
   Typography.xaml
   Controls.Buttons.xaml
   Controls.DataGrid.xaml
   Theme.Light.xaml
   Theme.Dark.xaml

App.xaml 合併:

<Application ...>
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Styles/Colors.xaml"/>
                <ResourceDictionary Source="Styles/Typography.xaml"/>
                <ResourceDictionary Source="Styles/Controls.Buttons.xaml"/>
                <ResourceDictionary Source="Styles/Controls.DataGrid.xaml"/>
                <ResourceDictionary Source="Styles/Theme.Light.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

好處:集中資源、跨視窗共用、切換主題更容易。


DataGrid 美化實例

建立 Styles/Controls.DataGrid.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!-- 行為一致化 -->
    <Style TargetType="DataGrid">
        <Setter Property="AutoGenerateColumns" Value="False"/>
        <Setter Property="HeadersVisibility" Value="Column"/>
        <Setter Property="GridLinesVisibility" Value="None"/>
        <Setter Property="RowHeaderWidth" Value="0"/>
        <Setter Property="CanUserAddRows" Value="False"/>
        <Setter Property="CanUserDeleteRows" Value="False"/>
        <Setter Property="CanUserReorderColumns" Value="True"/>
        <Setter Property="CanUserResizeColumns" Value="True"/>
        <Setter Property="CanUserSortColumns" Value="True"/>
        <Setter Property="RowBackground" Value="White"/>
        <Setter Property="AlternatingRowBackground" Value="#FAFAFA"/>
        <Setter Property="RowHeight" Value="32"/>
        <Setter Property="ColumnHeaderHeight" Value="36"/>
        <Setter Property="HorizontalGridLinesBrush" Value="#EEEEEE"/>
        <Setter Property="VerticalGridLinesBrush" Value="#EEEEEE"/>
        <Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
        <Setter Property="BorderBrush" Value="#E6EAF0"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="CellStyle">
            <Setter.Value>
                <Style TargetType="DataGridCell">
                    <Setter Property="Padding" Value="{StaticResource CellPadding}"/>
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </Setter.Value>
        </Setter>
        <Setter Property="ColumnHeaderStyle">
            <Setter.Value>
                <Style TargetType="DataGridColumnHeader">
                    <Setter Property="FontWeight" Value="SemiBold"/>
                    <Setter Property="Foreground" Value="{StaticResource MutedTextBrush}"/>
                    <Setter Property="Background" Value="{StaticResource PanelBrush}"/>
                    <Setter Property="Padding" Value="8,4"/>
                    <Setter Property="BorderBrush" Value="#E6EAF0"/>
                    <Setter Property="BorderThickness" Value="0,0,0,1"/>
                </Style>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

使用時只要宣告 DataGrid,樣式會自動套用(隱式樣式)。


版面與排版建議(設計系統基礎)

  1. 色彩
  • 主要色(Primary):互動元素的底色。
  • 輔色(Accent):滑過或重要提示色。
  • 文本色:#222(主要)、#666(次要)。
  • 背景:#FFF(主)/#F7F9FC(次)。
  • 狀態色:成功、警告、錯誤(可另建)。
  1. 間距
  • 8pt 尺度(8/12/16/24/32)易於視覺整齊。
  • 控制項內邊距(Padding)建議 ≥ 6px,表格列高 ≥ 32px。
  1. 字體
  • 標題(20-24)、次標(16-18)、內文(14-16)。
  • 列表/表格建議 14,標題 16-18,清晰不擁擠。
  1. 圓角與陰影
  • 圓角 6-8,適度柔和。
  • WPF 陰影可用 DropShadowEffect(慎用,成本高),或用背景/邊框營造層級。

主題切換(Light / Dark)

建立 Theme.Light.xamlTheme.Dark.xaml 兩套資源,只差在顏色刷子值:

Theme.Light.xaml

<SolidColorBrush x:Key="WindowBgBrush" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextBrush" Color="#222222"/>
<SolidColorBrush x:Key="PanelBrush" Color="#F7F9FC"/>

Theme.Dark.xaml

<SolidColorBrush x:Key="WindowBgBrush" Color="#1F2430"/>
<SolidColorBrush x:Key="TextBrush" Color="#EAEAEA"/>
<SolidColorBrush x:Key="PanelBrush" Color="#2A3040"/>

切換主題(程式碼側)做法:

  1. 清空 Application.Current.Resources.MergedDictionaries 最後一筆主題資源。
  2. 動態加入 Light 或 Dark 的 ResourceDictionary(使用 DynamicResource 綁定顏色處處即時更新)。

範例:把 Day 20/21 的主畫面套上樣式

<DockPanel Background="{DynamicResource WindowBgBrush}">
    <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="{StaticResource Gap}">
        <TextBox Width="220" />
        <Button Style="{StaticResource PrimaryButton}" Content="下載股票" Margin="8,0,0,0"/>
        <Button Content="載入本地" Margin="8,0,0,0"/>
    </StackPanel>

    <DataGrid ItemsSource="{Binding Stocks}" Margin="{StaticResource Gap}">
        <DataGrid.Columns>
            <DataGridTextColumn Header="代號" Binding="{Binding Code}" Width="100"/>
            <DataGridTextColumn Header="名稱" Binding="{Binding Name}" Width="200"/>
            <DataGridTextColumn Header="產業" Binding="{Binding Industry}" Width="*"/>
        </DataGrid.Columns>
    </DataGrid>
</DockPanel>

常見問題與對策

  • 樣式沒生效:確認 Resource 的載入順序、路徑正確、是否被後載入的字典覆蓋。
  • 隱式樣式覆寫:個別控制項上若另指定 Style(或 Template),會覆蓋隱式樣式。
  • 效能:Template 過度複雜或大量陰影效果會拖慢 UI,優先用色塊與邊框呈現層級。
  • DynamicResource 無效:確保對應資源是在 MergedDictionaries 中被替換,而非在控制項本地覆蓋。

練習建議

  1. 把按鈕、TextBox、ComboBox、CheckBox 都做一套一致的 Style。
  2. 把 DataGrid 美化放入專用 ResourceDictionary,專案任一頁套用都一致。
  3. 做一個「主題切換」按鈕,動態切 Light/Dark。

小結

  • 建立「設計系統」式資源:色彩、字級、間距、圓角。
  • 用顯式/隱式 Style 統一控制項外觀,抽離到 ResourceDictionary。
  • 美化 DataGrid 以符合桌面應用的閱讀體驗。
  • 預留主題切換能力,使用 DynamicResource 即時換膚。


上一篇
Day 22 - 搜尋與篩選功能(結合 LiteDB 與 KD 黃金交叉)
下一篇
Day 24 - 抓取每日 K 線 → 計算 KD/MACD → 存回 DB
系列文
30天快速上手製作WPF選股工具 — 從C#基礎到LiteDB與Web API整合24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言