WPF QuickStart系列之樣式和模板(Style and Template)


在WPF桌面程序中,當我們想構建一個統一的UI表現時(在不同操作系統下,顯示效果一致),此時我們就需要使用到WPF中的樣式和模板技術。簡單來說,如果我們需要簡單的給一個Button設置寬,高,Margin等,可以使用Style來指定這一系列的屬性。可以把Style理解為一個屬性的集合。如果需要完全改變控件的樣子,就需要使用到Template技術,相當於給控件換一層皮,不過Button還是Button,它原有的行為(Click事件)還存在。而且我們僅需要在XAML中遍可以完成對樣式和模板的定義和重寫。非常簡潔方便。

首先通過一個例子了解Style。

 <Window.Resources>
        <Style x:Key="numericStyle" TargetType="{x:Type Button}">
            <Setter Property="FontSize" Value="20" />
            <Setter Property="Margin" Value="4" />
            <Setter Property="Padding" Value="6" />
            <Setter Property="Effect">
                <Setter.Value>
                    <DropShadowEffect Color="Blue"/>
                </Setter.Value>
            </Setter>
        </Style>
        <Style TargetType="Button" x:Key="operatorStyle"
            BasedOn="{StaticResource numericStyle}">
            <Setter Property="FontWeight" Value="ExtraBold" />
            <Setter Property="Effect">
                <Setter.Value>
                    <DropShadowEffect Color="Red" />
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <TextBox Background="Cyan" IsReadOnly="True" Grid.ColumnSpan="4" FontSize="20"/>
        <Button Content="7" Style="{StaticResource numericStyle}" Grid.Row="1"/>
        <Button Content="8" Style="{StaticResource numericStyle}" Grid.Row="1" Grid.Column="1"/>
        <Button Content="9" Style="{StaticResource numericStyle}" Grid.Row="1" Grid.Column="2"/>
        <Button Content="4" Style="{StaticResource numericStyle}" Grid.Row="2"/>
        <Button Content="5" Style="{StaticResource numericStyle}" Grid.Row="2" Grid.Column="1"/>
        <Button Content="6" Style="{StaticResource numericStyle}" Grid.Row="2" Grid.Column="2"/>
        <Button Content="1" Style="{StaticResource numericStyle}" Grid.Row="3"/>
        <Button Content="2" Style="{StaticResource numericStyle}" Grid.Row="3" Grid.Column="1"/>
        <Button Content="3" Style="{StaticResource numericStyle}" Grid.Row="3" Grid.Column="2"/>
        <Button Content="0" Style="{StaticResource numericStyle}" Grid.Row="4"/>
        <Button Content="=" Style="{StaticResource operatorStyle}" Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2">
            <Button.Effect>
                <DropShadowEffect Color="Green"/>
            </Button.Effect>
        </Button>
        <Button Content="+" Style="{StaticResource operatorStyle}" Grid.Row="4" Grid.Column="3"/>
        <Button Content="-" Style="{StaticResource operatorStyle}" Grid.Row="3" Grid.Column="3"/>
        <Button Content="X" Style="{StaticResource operatorStyle}" Grid.Row="2" Grid.Column="3"/>
        <Button Content="/" Style="{StaticResource operatorStyle}" Grid.Row="1" Grid.Column="3"/>
    </Grid>

運行效果:

通過上面的示例可以看到,

1. Style中包含了很多Setter,每個Setter都會對應着不同屬性的設置。正如博客開頭講到的一樣。Style是一組屬性的集合;

2. 在Style中可以設置TargetType,指示這個Style是給哪一個控件使用的;

3. Style可以繼承,例如操作按鈕的Style繼承了數字按鈕的Style,使用BaseOn,然后引用到Style的資源即可;

4. Style的優先級,=按鈕,在Style中設置了Button的DropShadowEffect為紅色,然后在Button內部我們設置DropShadowEffect為藍色,最后顯示的效果可以看出來,=按鈕最終顏色為藍色。可以理解為后來者居上。

Style中不僅可以包含一系列的Setter,還可以包含Trigger。WPF中有三種Trigger,Property Trigger,Event Trigger,Data Trigger。下面我們介紹Property Trigger,沿用上面的示例,在鼠標點擊按鈕時,設置Transform效果。

        <Style TargetType="Button" x:Key="operatorStyle"
            BasedOn="{StaticResource numericStyle}">
            <Setter Property="FontWeight" Value="ExtraBold" />
            <Setter Property="Effect">
                <Setter.Value>
                    <DropShadowEffect Color="Red" />
                </Setter.Value>
            </Setter>

            <Style.Triggers>
                <Trigger Property="IsPressed" Value="True">
                    <Setter Property="RenderTransform">
                        <Setter.Value>
                            <TranslateTransform X="4" Y="4" />
                        </Setter.Value>
                    </Setter>
                </Trigger>
            </Style.Triggers>
        </Style>

運行效果如下:

Trigger表示當滿足某個/某些條件時觸發。上面的例子中,當IsPressed為True時,觸發了Transform的改變,當IsPressed為False時,自動恢復到初始狀態,不需要額外的代碼來恢復初始狀態。

不僅可以在Style中使用Trigger,還可以在DataTemplate,ControlTemplate中使用。

Property Trigger針對的是依賴屬性,那普通屬性改變時,如何觸發UI的改變呢?所以下面介紹另一種Trigger,Data Trigger。請看示例:

XAML:

        <ListBox HorizontalAlignment="Center" ItemsSource="{Binding .}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Border Margin="2" BorderBrush="Blue" BorderThickness="1" Padding="2" x:Name="_border">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="Auto" />
                                </Grid.RowDefinitions>
                                <TextBlock Text="{Binding Name}" FontSize="20" FontWeight="Bold" />
                                <TextBlock Grid.Row="1" Text="{Binding AuthorName}" FontSize="16" Foreground="Blue" />
                                <TextBlock Opacity=".5" FontWeight="Bold" FontStyle="Italic" Foreground="Red" TextAlignment="Right" Grid.RowSpan="2" VerticalAlignment="Center" Visibility="Hidden"
                                x:Name="_free" Text="Free!" Margin="4" FontSize="25"/>
                            </Grid>
                        </Border>
                    </Grid>
                    <DataTemplate.Triggers>
                        <DataTrigger Binding="{Binding IsFree}" Value="True">
                            <Setter Property="Background" TargetName="_border" Value="Yellow" />
                            <Setter Property="Visibility" Value="Visible" TargetName="_free" />
                        </DataTrigger>
                    </DataTemplate.Triggers>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

C#:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new List<Book>
            {
                new Book { Name = "Windows Internals",
                AuthorName = "Mark Russinovich", IsFree = false },
                new Book { Name = "AJAX Introduction",
                AuthorName = "Bhanwar Gupta", IsFree = true },
                new Book { Name = "Essential COM",
                AuthorName = "Don Box", IsFree = false },
                new Book { Name = "Blueprint for a Successful Presentation",
                AuthorName = "Biswajit Tripathy", IsFree = true }
            };
        }
    }

    public class Book
    {
        public string Name { get; set; }

        public bool IsFree { get; set; }

        public string AuthorName { get; set; }

    }

運行效果:

DataTrigger根據Binding查找特定屬性,當滿足條件時觸發。

下面簡單介紹下Event Trigger,請看示例代碼:

    <Grid>
        <Grid.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation From="0" To="1" Duration="0:0:5"
                            Storyboard.TargetProperty="Opacity" />
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Grid.Triggers>
        <TextBlock Text="Event Trigger Demo" FontSize="24"/>
    </Grid>

運行效果,Grid的透明度從0到1。

注意:Event Trigger只可以用於路由事件。

上面我們介紹了三種Trigger,但是它們都是使用與滿足某一個條件然后觸發。如果要滿足一些條件才觸發,我們可以使用MultiTrigger,請看示例:

    <Window.Resources>
        <Style x:Key="HoverButtonStyle" TargetType="{x:Type Button}">
            <Style.Triggers>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsMouseOver" Value="True" />
                        <Condition Property="IsDefault" Value="True" />
                    </MultiTrigger.Conditions>
                    <Setter Property="Background" Value="Cyan" />
                    <Setter Property="Effect">
                        <Setter.Value>
                            <DropShadowEffect />
                        </Setter.Value>
                    </Setter>
                </MultiTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel Orientation="Vertical">
        <Button Content="Move mouse over me" FontSize="20"
            HorizontalAlignment="Center" Margin="20" Padding="6"
            x:Name="theButton" Style="{StaticResource HoverButtonStyle}"/>
        <CheckBox Content="Default button" Margin="10"
            IsChecked="{Binding IsDefault, ElementName=theButton,
            Mode=TwoWay}" FontSize="15"/>
    </StackPanel>

運行效果:

注意:只有兩種MultiTrigger,除了上面這種,還有MultiDataTrigger。用於當多個數據屬性滿足某一條件時觸發。

下面通過一個示例來介紹ControlTemplate的使用,

例如有兩個"原生態"的的RadioButton,

    <StackPanel Orientation="Horizontal">
        <RadioButton Content="作業練習" IsChecked="True" Margin="10,5,10,0"/>
        <RadioButton Content="考試測驗" Margin="0,5,10,0"/>
    </StackPanel>

在Win 10 和Win 7中的顯示效果如下:

同樣的控件在Win10與Win7下顯示效果不一致,下面我們對RadioButton進行"整容",

    <Window.Resources>
        <Style x:Key="RadioButtonStyle01" TargetType="RadioButton">
            <Setter Property="SnapsToDevicePixels" Value="True" />
            <Setter Property="OverridesDefaultStyle" Value="True" />
            <Setter Property="Foreground" Value="#565656"/>
            <Setter Property="Background" Value="#EDEEEF"/>
            <Setter Property="FontSize" Value="12"/>
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="Width" Value="100"/>
            <Setter Property="Height" Value="45"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="RadioButton">
                        <Border x:Name="MainBorder" BorderThickness="1" BorderBrush="#8B99BC" CornerRadius="1" Background="#F0F2F2">
                            <Grid>
                                <Image x:Name="imgChecked" Visibility="Collapsed" Source="/ControlTemplatingDemo;component/Resources/Images/Completed_02.png" Width="20" Height="20" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,-8,-10,0"/>
                                <ContentPresenter RecognizesAccessKey="True" Content="{TemplateBinding ContentControl.Content}" 
                                                      ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" 
                                                      ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" Margin="5" HorizontalAlignment="Center" 
                                                      VerticalAlignment="Center" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                            </Grid>
                        </Border>

                        <ControlTemplate.Triggers>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter TargetName="MainBorder" Property="Background" Value="#239FFF"/>
                                <Setter TargetName="imgChecked" Property="Visibility" Value="Visible"/>
                                <Setter Property="Foreground" Value="White"/>
                                <Setter TargetName="MainBorder" Property="BorderBrush" Value="#239FFF"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <StackPanel Orientation="Horizontal">
        <RadioButton Content="作業練習" Style="{StaticResource RadioButtonStyle01}" IsChecked="True" Margin="10,5,10,0"/>
        <RadioButton Content="考試測驗" Style="{StaticResource RadioButtonStyle01}" Margin="0,5,10,0"/>
    </StackPanel>

經過ControlTemplate樣式重寫后的RadioButton:

現在RadioButton在不同操作系統下外貌一致了。

為了在不同OS下獲得相同的顯示效果,我們需要對WPF的控件進行樣式的重寫。對控件樣式的重寫,可以理解為對它的表現進行重組。我們可以通過Blend來查看控件的內部構造,然后根據項目需求對控件進行重寫。

感謝您的閱讀。代碼點擊這里下載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM