WPF 如何自定義圖標——應用篇——自定義控件

WPF 如何自定義圖標——應用篇——自定義控件

引言

結合前面我們已經能夠較爲靈活的在CS和XAML中使用我們自定義的圖標。接下來,我們結合一些框架元素(FrameElement)的特點實現我們自己自定義控件的定義與使用。自定義控件的特點:靈活,重構能力強。

圖標類控件給人非常醒目的感覺,我們通過自定義Control、Button和CheckBox控件來實現自定義控件的定義與使用。

編碼環境:Win10 Visual Studio 2019 .NET Framework 4.7.2 / .NET Core 3.1

應用

1.IconControl的定義與使用

IconControl是一個用於呈現IconData的圖標控件,只用於圖標呈現,別無他用。

(1)新建–自定義控件–命名爲“IconControl”,此時系統會爲我們自動生成一個Theme文件夾以及Generic.xaml文件和在新建的路徑下生成IconControl.cs文件。

(2)打開IconControl.cs,將內容替換爲下面的代碼:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Deamon.UiCore
{
    /// <summary>
    /// 按照步驟 1a 或 1b 操作,然後執行步驟 2 以在 XAML 文件中使用此自定義控件。
    ///
    /// 步驟 1a) 在當前項目中存在的 XAML 文件中使用該自定義控件。
    /// 將此 XmlNamespace 特性添加到要使用該特性的標記文件的根
    /// 元素中:
    ///
    ///     xmlns:MyNamespace="clr-namespace:Deamon.UiCore"
    ///
    ///
    /// 步驟 1b) 在其他項目中存在的 XAML 文件中使用該自定義控件。
    /// 將此 XmlNamespace 特性添加到要使用該特性的標記文件的根
    /// 元素中:
    ///
    ///     xmlns:MyNamespace="clr-namespace:Deamon.UiCore;assembly=Deamon.UiCore"
    ///
    /// 您還需要添加一個從 XAML 文件所在的項目到此項目的項目引用,
    /// 並重新生成以避免編譯錯誤:
    ///
    ///     在解決方案資源管理器中右擊目標項目,然後依次單擊
    ///     “添加引用”->“項目”->[瀏覽查找並選擇此項目]
    ///
    ///
    /// 步驟 2)
    /// 繼續操作並在 XAML 文件中使用控件。
    ///
    ///     <MyNamespace:IconControl/>
    ///
    /// </summary>
    public class IconControl : Control
    {
        public IconControl()
        {
            DefaultStyleKey = typeof(IconControl);
        }

        public static readonly DependencyProperty IconDataProperty = DependencyProperty.Register(nameof(IconData), typeof(Geometry), typeof(IconControl));

        public Geometry IconData
        {
            get => (Geometry)GetValue(IconDataProperty);
            set => SetValue(IconDataProperty, value);
        }

    }
}

上面註釋中也簡單的介紹瞭如何使用自定義控件。

(3)打開Generic.xaml,將IconData屬性綁定到控件模板上。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Deamon"
    xmlns:uicore="clr-namespace:Deamon.UiCore"
    >

    <Style TargetType="{x:Type uicore:IconControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type uicore:IconControl}">

                    <ContentControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                            <Viewbox>
                                <Path Data="{TemplateBinding IconData}" Fill="Black" Stroke="Gray" StrokeThickness="1"/>
                            </Viewbox>
                        </Border>
                    </ContentControl>

                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

說明:Generic.xaml資源文件定義了該自定義控件的默認樣式,如果沒有默認樣式,系統會出錯,也可以自己在其他資源文件定義樣式,但是一定要在App.xaml中加載資源以確保程序在使用時存在默認樣式。

(4)使用IconControl。IconUsageView.xaml

<UserControl x:Class="Deamon.View.PERSONALIZATION.IconUsage.IconUsageView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Deamon.View.PERSONALIZATION.IconUsage"
             xmlns:uicore="clr-namespace:Deamon.UiCore"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <StackPanel>
            <uicore:IconControl Width="100" Height="100" IconData="{Binding IconSource.Forward,Source={StaticResource ServiceLocator},UpdateSourceTrigger=PropertyChanged}" />
            <uicore:IconControl Width="100" Height="100" IconData="{Binding IconSource.Back,Source={StaticResource ServiceLocator},UpdateSourceTrigger=PropertyChanged}" />
            <uicore:IconControl Width="100" Height="100" IconData="{Binding IconSource.DoubleDown,Source={StaticResource ServiceLocator},UpdateSourceTrigger=PropertyChanged}" />
        </StackPanel>
    </Grid>
</UserControl>

2.IconButton的定義與使用

IconButton是純圖標按鈕。基類是Button。

(1)新建(自定義控件)IconButton.cs

(2)打開IconButton.cs,將內容替換爲下面的代碼:

 public class IconButton : Button
    {
        static IconButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(IconButton), new FrameworkPropertyMetadata(typeof(IconButton)));
        }

        public static readonly DependencyProperty IconDataProperty = DependencyProperty.Register(nameof(IconData), typeof(Geometry), typeof(IconButton));

        public IconButton() { DefaultStyleKey = typeof(IconButton); }

        public Geometry IconData
        {
            get => (Geometry)GetValue(IconDataProperty);
            set => SetValue(IconDataProperty, value);
        }
    }

(3)打開Generic.xaml,將IconData屬性綁定到控件模板上。直接將下面的樣式複製到該文件中。

 	<SolidColorBrush x:Key="Accent" Color="#FF0B7AFF" />
    <SolidColorBrush x:Key="ModernButtonText" Color="#333333"/>
    <SolidColorBrush x:Key="ModernButtonTextHover" Color="#333333" />
    <SolidColorBrush x:Key="ButtonBackgroundHover" Color="#dddddd" />
    <SolidColorBrush x:Key="ButtonTextDisabled" Color="#a1a1a1" />
    <SolidColorBrush x:Key="ModernButtonTextPressed" Color="#333333" />
    <SolidColorBrush x:Key="ModernButtonTextDisabled" Color="#a1a1a1" />
    
    <Style TargetType="uicore:IconButton">
        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
        <Setter Property="HorizontalContentAlignment" Value="Center" />
        <Setter Property="VerticalContentAlignment" Value="Center" />
        <Setter Property="Foreground" Value="{DynamicResource ModernButtonText}" />
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Cursor" Value="Hand"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="uicore:IconButton">
                    <Grid x:Name="grid"
                          Background="{TemplateBinding Background}"
                          MinHeight="36"
                          MinWidth="36">
                        <Path x:Name="icon"
                              Data="{TemplateBinding IconData}" 
                              Width="15"
                              Height="15"
                              Fill="{TemplateBinding Foreground}"
                              Stretch="Uniform"
                              HorizontalAlignment="Center"
                              VerticalAlignment="Center"
                              Opacity="0.8">
                            <Path.LayoutTransform>
                                <ScaleTransform x:Name="IconScale"/>
                            </Path.LayoutTransform>
                        </Path>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Foreground" Value="{DynamicResource ModernButtonTextHover}" />
                            <Setter Property="Opacity" TargetName="icon" Value="1"/>
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="IconScale"
                                                         Storyboard.TargetProperty="ScaleX"
                                                         To="1.2"
                                                         Duration="0:0:0.15" />
                                        <DoubleAnimation Storyboard.TargetName="IconScale"
                                                         Storyboard.TargetProperty="ScaleY"
                                                         To="1.2"
                                                         Duration="0:0:0.15" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="IconScale"
                                                         Storyboard.TargetProperty="ScaleX"
                                                         To="1"
                                                         Duration="0:0:0.15" />
                                        <DoubleAnimation Storyboard.TargetName="IconScale"
                                                         Storyboard.TargetProperty="ScaleY"
                                                         To="1"
                                                         Duration="0:0:0.15" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.ExitActions>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="True">
                            <Setter Property="Foreground" Value="{DynamicResource Accent}" />
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Foreground" Value="{DynamicResource ButtonTextDisabled}" />
                            <Setter TargetName="icon" Property="Opacity" Value="0.3" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

從定義的樣式來看,原來用於顯示的Content屬性沒有在模板中體現,因此在使用時即使設置了也沒有任何作用。

(4)使用

<UserControl x:Class="Deamon.View.PERSONALIZATION.IconButtonView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Deamon.View.PERSONALIZATION"
             xmlns:uicore="clr-namespace:Deamon.UiCore"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <WrapPanel>
            <uicore:IconButton IconData="{Binding IconSource.Add,Source={StaticResource ServiceLocator}}"/>
            <uicore:IconButton IconData="{Binding IconSource.Video,Source={StaticResource ServiceLocator}}"/>
            <uicore:IconButton IconData="{Binding IconSource.NewFile,Source={StaticResource ServiceLocator}}"/>
        </WrapPanel>
    </Grid>
</UserControl>

純按鈕的圖標搞定了。

3.IconCheckBox的定義與使用

IconCheckBox是一個CheckBox,本例中只存在兩種狀態(選中、未選中)。

(1)新建自定義控件並命名爲IconCheckBox.cs。

(2)打開IconCheckBox.cs,將內容替換爲下面的代碼:

    public class IconCheckBox : CheckBox
    {
        public static readonly DependencyProperty IconDataProperty = DependencyProperty.Register(nameof(IconData), typeof(Geometry), typeof(IconCheckBox));

        public IconCheckBox() { DefaultStyleKey = typeof(IconCheckBox); }

        public Geometry IconData
        {
            get => (Geometry)GetValue(IconDataProperty);
            set => SetValue(IconDataProperty, value);
        }
    }

(3)打開Generic.xaml,將IconData屬性綁定到控件模板上。直接將下面的樣式複製到該文件中。

	<Style TargetType="{x:Type uicore:IconCheckBox}">
        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
        <Setter Property="HorizontalContentAlignment" Value="Center" />
        <Setter Property="VerticalContentAlignment" Value="Center" />
        <Setter Property="Foreground" Value="{DynamicResource ModernButtonText}" />
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Cursor" Value="Hand"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type uicore:IconCheckBox}">
                    <Grid x:Name="grid"
                          Background="{TemplateBinding Background}"
                          MinHeight="36"
                          MinWidth="36">
                        <Ellipse Width="30"
                                 Height="30"
                                 Name="bg"/>
                        <Path x:Name="icon"
                              Data="{TemplateBinding IconData}" 
                              Width="14"
                              Height="14"
                              Fill="{TemplateBinding Foreground}"
                              Stretch="Uniform"
                              HorizontalAlignment="Center"
                              VerticalAlignment="Center"
                              Opacity="0.7"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Foreground" Value="{DynamicResource ModernButtonTextHover}" />
                            <Setter Property="Opacity" TargetName="icon" Value="1"/>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="True">
                            <Setter TargetName="icon" Property="Fill" Value="{DynamicResource Accent}" />
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Foreground" Value="{DynamicResource ModernButtonTextDisabled}" />
                            <Setter TargetName="icon" Property="Opacity" Value="0.3" />
                        </Trigger>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter Property="Fill" Value="{DynamicResource Accent}" TargetName="icon"/>
                            <Setter Property="Stroke" Value="{DynamicResource Accent}" TargetName="bg"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

(4)使用

<UserControl x:Class="Deamon.View.PERSONALIZATION.IconCheckBoxView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Deamon.View.PERSONALIZATION"
             xmlns:uicore="clr-namespace:Deamon.UiCore"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <StackPanel >
            <uicore:IconCheckBox IconData="{Binding Path=IconSource.Camera,Source={StaticResource ServiceLocator}}"/>
            <uicore:IconCheckBox IconData="{Binding Path=IconSource.Close,Source={StaticResource ServiceLocator}}"/>
            <uicore:IconCheckBox IconData="{Binding Path=IconSource.Speaker,Source={StaticResource ServiceLocator}}"/>
            <uicore:IconCheckBox IconData="{Binding Path=IconSource.Mic,Source={StaticResource ServiceLocator}}"/>
            <uicore:IconCheckBox IconData="{Binding Path=IconSource.Cursor,Source={StaticResource ServiceLocator}}"/>
        </StackPanel>  
    </Grid>
</UserControl>

總結

OK,到這裏就差不多結束了。自定義控件是個非常好的東西,能夠設計出很多我們喜歡的控件,同時他對WPF的一些特性支持相比於用戶控件要寬得多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章