zoukankan      html  css  js  c++  java
  • LooklessControl

    Lookless controls vs User Controls. Lookless controls usage patterns(LooklessControl与UserControl的比较。不可见控件的使用模式)

    Introduction

    I work for a company that started using WPF relatively recently. As a result, a lot of software engineers are switching to programming C# WPF from other platforms and languages. The easiest and most intuitive way of creating a re-usable visual control in WPF is by utilizing the built in VS2015 template for UserControl.(最方便的就是VS中内置的UserControl)

    The purpose of this article to show that Lookless controls are actually a better choice than UserControls. As will be shown below, the Lookless controls provide considerably more flexibility and separation of concerns.(但是LookLessControl其实更好,它更灵活且降耦)

    I will also discuss the best practices and re-use patterns when it comes to Lookless Controls.(下面会说些LooklessControl的实战和复用模式)

    Reader 'webmaster442' noticed, most WPF literature calls Lookless controls - Custom controls. I wanted to avoid using this name for two reasons:(别将Lookless控件当做CustomControl)

    1. Some readers might be confused by the name and think that User Controls are also custom controls because they can be custom made.
    2. Visual Studio has a template for Custom Controls which is pretty much useless and confusing and does not serve any purpose from my point of view. So I do not want to encourage the readers to use it.(VS中提供的CustomControl很垃圾,不推荐使用)

    This article is for people who are starting to work with WPF and are curious about ways to improve WPF coding practices.(本文可以让你提高WPF编程习惯)

    Here are the topics covered in this article(文章中心点)

    • User Control Example(UserControl例子)
    • The Drawbacks of UserControls(UserControl缺陷)
    • A Lookless Control Example(LooklessControl例子)
    • Using Two Different Templates for the same Lookless Control(为同一个LooklessControl应用两个不同的Template)
    • Parametrizing Lookless Controls(参数化LooklessControl)
    • Accessing the Visuals for C# Code for Lookless Controls(通过C#代码访问LooklessControl的Visuals部分)
    • Summary
    • Appendix A. Creating a UserControl in Visual Studio
    • Appending B. Installing a Custom propdp Snippet
    • Appending C: Creating a WPF Resource Dictionary File

    An UserControl Example

    In WPF (and other frameworks) the controls are created in order to encapsulate some parts of re-usable UI functionality.

    Let us start by showing an example of creating and (re)using a WPF UserControl.

    The code for this example can be found under UserControlSample project.

    Running this project, you'll see the following window:

     

    There are two rows in the window - one for entering the name and the other for entering the passcode.

    Each of the rows consists of a label (non-editable text), an editable text box and an 'X' button, clicking which clears the corresponding text box.

    The two rows for entering the name and the passcode are represented by two very simple instances of the same EditableTextAndLabelUserControl class.

    Here is the MainWindow.xaml code that creates the window:

    <Window x:Class="UserControlSample.MainWindow"

     

            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

     

            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

     

            xmlns:local="clr-namespace:UserControlSample"

     

            Title="MainWindow"

     

            Height="350"

     

            Width="525">

        <Grid>

            <Grid.RowDefinitions>

                <RowDefinition Height="30"/>

                <RowDefinition Height="30"/>

                <RowDefinition Height="*"/>

            </Grid.RowDefinitions>

     

            <!-- user control for entering the name -->

            <local:EditableTextAndLabelUserControl Label="Enter your name:"

     

                                                   HorizontalAlignment="Left"

     

                                                   VerticalAlignment="Center"

     

                                                   Margin="10,0,0,0"/>

           

            <!-- user control for entering the passcode -->

            <local:EditableTextAndLabelUserControl Label="Enter your passcode:"

     

                                                   HorizontalAlignment="Left"

     

                                                   VerticalAlignment="Center"

     

                                                   Margin="10,0,0,0"

     

                                                   Grid.Row="1"/>

        </Grid>

    </Window>

    The code for EditableTextAndLabelUserControl is also part of the same project contained within XAML file EditableTextAndLabelUserControl.xaml and C# file EditableTextAndLabelUserControl.xaml.cs. (For instructions on how to create a UserControl in Visual Studio, please, check the Appendix A: Creating a UserControl.)(UserControl由cs文件和xaml文件组成)

    EditableTextAndLabelUserControl.xaml file contains the following simple XAML:

    <UserControl x:Class="UserControlSample.EditableTextAndLabelUserControl"

     

                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

     

                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

     

                 xmlns:local="clr-namespace:UserControlSample">

        <StackPanel Orientation="Horizontal"

     

                    VerticalAlignment="Stretch">

            <TextBlock x:Name="TheLabel"

     

                       Margin="0,0,2,0"/>

            <TextBox x:Name="TheTextBox"

     

                     Width="100"

     

                     Margin="2,0"/>

            <Button x:Name="TheClearButton"

     

                    Content="X"

     

                    Width="30"/>

        </StackPanel>

    </UserControl>

    The label is represented by TextBlock named TheLabel; the editable text is represented by TextBox named TheTextBox and the button is represented by Button control named TheClearButton (I like prepending XAML names by prefix "The" in order to distinguish them from the type names).

    The WPF XAML parsing engine creates the variables corresponding to the control names. These variables are accessible from the code behind (in our case contained within EditableTextAndLabelUserControl.xaml.cs file. This code is also very simple:(WPF XAML解析引擎创建与控件名称对应的变量,这些变量可以在与xaml文件匹配的cs文件中直接访问)

    public partial class EditableTextAndLabelUserControl : UserControl

    {

        public EditableTextAndLabelUserControl()

        {

            InitializeComponent();

     

            TheClearButton.Click += TheClearButton_Click;

        }

     

        private void TheClearButton_Click(object sender, RoutedEventArgs e)

        {

            Clear();

        }

     

        public void Clear()

        {

            EditableText = null;

        }

     

        public string Label

        {

            get

            {

                return TheLabel.Text;

            }

     

            set

            {

                TheLabel.Text = value;

            }

        }

     

        public string EditableText

        {

            get

            {

                return TheTextBox.Text;

            }

     

            set

            {

                TheTextBox.Text = value;

            }

        }

    It provides the public string properties Label and EditableText as a means of communicating with the rest of the application and a method Clear() invoked on the 'X' button click (this invocation is wired via the button's click event handler).

    The Drawbacks of UserControls

    As simple and powerful as UserControls can be,(UserControls既简单又强大) they have an inherent shortcoming(它有个固有的缺陷) - the visual representation is permanently tied to the C# code. (可视化xaml部分永远与后台逻辑cs代码绑定)The visual representation of EditableTextAndLabelUserControl control discussed above, cannot be changed(xaml部分无法被修改) - it will always remain a TextBlock followed by a TextBox followed by a Button arranged horizontally one after another. To be fair - some extra degree of freedom for UserControls can be achieved by parametrization (说句公道话,通过参数化设计,可以使UserControl拥有一些额外的自由度)(in the same way as will be discussed below for Lookless Controls), but their core visual representation is fixed and tied forever to the C# code behind(但是核心的可视化部分永远与后台C#代码绑定).

    The Lookless Controls completely overcome these limitation. (LooklessControl完全克服了这个限制)They usually consist of a number of non-visual methods and properties(它通常由一些列不可视化的方法和属性组成) and do not impose any constraints on the visual implementation(并且不对可视化部分约束). The visual implementation for the Lookless Controls is provided by XAML templates and styles(LooklessControl的可视化部分由XamlTemplate和Style来实现) and can be changed very easily without ANY changes to the control code itself or to the way the Lookless Control interacts with the rest of the application.(Lookless的可视化部分修改起来非常简单,不用修改与它关联的控制逻辑代码以及与程序其他部分交互的逻辑——改改样式和模板即可)

    A Lookless Control Example

    Code for the simple Lookless Control example is located under LooklessControlSample project. When it runs it shows exactly the same Window as the UserControl example above.(这个例子运行后与上面的UserControl例子效果相同)

    Take a look at EditableTextAndLabelControl class located within EditableTextAndLabelControl.cs file. (Note that it is a simple C# file - no special VS templates are necessary to create it).(核心部分就是个C#类文件,不用也没有VS专用的模板来创建)

    The class derives from System.Windows.Controls.Control class and it contains two string dependency properties Label and EditableText as well as a method Clear (which simply sets EditableText property to null).(这个类继承Control类,包含两依赖属性DP和一个方法)

    public class EditableTextAndLabelControl : Control

    {

        #region Label Dependency Property

        public string Label

        {

            get { return (string)GetValue(LabelProperty); }

            set { SetValue(LabelProperty, value); }

        }

     

        public static readonly DependencyProperty LabelProperty =

        DependencyProperty.Register

        (

            "Label",

            typeof(string),

            typeof(EditableTextAndLabelControl),

            new PropertyMetadata(null)

        );

        #endregion Label Dependency Property

     

     

        #region EditableText Dependency Property

        public string EditableText

        {

            get { return (string)GetValue(EditableTextProperty); }

            set { SetValue(EditableTextProperty, value); }

        }

     

        public static readonly DependencyProperty EditableTextProperty =

        DependencyProperty.Register

        (

            "EditableText",

            typeof(string),

            typeof(EditableTextAndLabelControl),

            new PropertyMetadata(null)

        );

        #endregion EditableText Dependency Property

     

     

        public void Clear()

        {

            this.EditableText = null;

        }

    The dependency properties code may look like a terrible mess for people who are not used to them, (如果你对DP不熟,你会觉得DP的代码很恶心,但你可以使用VS内置代码片段PROPDP来自动添加)but they can be very easily created with propdp snippet that is built into the Visual Studio, by typing propdp and then providing the name, the type and the default value of the dependency property. You tab in order to switch between the dependency property parts and press 'enter' after you are done.

    My propdp snippet (located under CODE/Snippets folder) is a slightly improved version of the one that comes with the Visual Studio. It arranges the code more vertically, provides the container class name automatically and creates a region for every dependency property. This snippet can be copied into the your NetFX30 snippet folder to replace the default propdp snippet. For detailed instructions on installing the snippet, please, look at Appendix B: Installing a Custom propdp Snippet.

    Once you define a dependency property you can use it as if it is a normal property with some extra features, in particular dependency property:(可以像使用普通属性来使用DP,它的一些特性)

    1. Provide change notifications that WPF bindings understand and thus can serve as WPF binding's source property.(具有修改通知功能,用于WPF绑定属性源)
    2. Can be used as WPF binding target property (the usual properties cannot be used for that).(绑定目标属性)
    3. Can be modified by WPF Styles.(通过样式修改)
    4. Can be animated.(动画控制)

     

    We are mostly interested in the fact that dependency properties can server as the source and the target properties of the WPF bindings. This is very important for the control to be able to communicate with the rest of the application.(DP既可作为绑定源,又可作为绑定目标;因此在与程序其他部分交互时,很重要。)

    For simplicity sake we placed the ControlTemplate for our Lookless Control into the MainWindow.xaml file - the Window.Resources section:(为了简单,作者将Lookless控件的ControlTemplate部分放到了MainWindow.xaml的<Window.Resources>标记下)

    <ControlTemplate x:Key="PlainHorizontalEditableTextTemplate"

     

                     TargetType="local:EditableTextAndLabelControl">

        <StackPanel Orientation="Horizontal"

     

                    VerticalAlignment="Stretch">

            <!-- bound to the Label dependency property of the control -->

            <TextBlock x:Name="TheLabel"

     

                       Text="{Binding Path=Label,

                                      RelativeSource={RelativeSource TemplatedParent}}"

     

                       Margin="0,0,2,0" />

            <!-- bound to the EditableText dependency property of the control -->

            <TextBox x:Name="TheTextBox"

     

                     Text="{Binding Path=EditableText,

                                    Mode=TwoWay,

                                    RelativeSource={RelativeSource TemplatedParent}}"

     

                     Width="100"

     

                     Margin="2,0" />

     

            <!-- We use Expression Blend's SDK's EventTrigger and CallMethodAction

                 objects to wire Clear() method of the control

                 to the Click event of the button -->

            <Button x:Name="TheClearButton"

     

                    Content="X"

     

                    Width="30">

                <i:Interaction.Triggers>

                    <i:EventTrigger EventName="Click">

                        <ei:CallMethodAction MethodName="Clear"

     

                                             TargetObject="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>

                    </i:EventTrigger>

                </i:Interaction.Triggers>

            </Button>

        </StackPanel>

    </ControlTemplate>

    Note that we are using bindings with RelativeSource set to TemplatedParent mode, e.g:(这里的绑定使用了RelativeSource TemplatedParent模式——也就是谁用我,我就和谁绑定)

    <TextBox x:Name="TheTextBox"

     

         Text="{Binding Path=EditableText,

                        Mode=TwoWay,

                        RelativeSource={RelativeSource TemplatedParent}}" 

     

    .../>

    The RelativeSource TemplatedParent instructs the binding to search for the source property on the control whose control template this XAML code belongs to - in our case, it is the EditableTextAndLabelControl.

    There is a shorthand for TemplatedParent binding - instead of writing all this binding XAML above, we could have simply written(这个绑定可以简化写成Text="{TemplateBinding EditableText}"这种绑定TemplateBinding仅仅适用于ControlTemplate编程中。)

    <TextBox x:Name="TheTextBox"

     

         Text="{TemplateBinding EditableText}" 

     

    .../> 

    I repeat, the binding above works only because we are programming a control template for a control. A more generic way would be to find the source object of the binding by type using AncestorType property of RelativeSource instead of TemplatedParent:(一种更通用的方法是:在RelativeSource中用AncestorType属性来代替TemplateParent)

    <TextBox x:Name="TheTextBox"

     

         Text="{Binding Path=EditableText,

                        Mode=TwoWay,

                        RelativeSource={RelativeSource AncestorType=local:EditableTextAndLabelControl}}" 

     

    .../>

    The code above is more generic because it would work anywhere under the visual tree from our EditableTextAndLabelControl, not only in the immediate template. (上面的代码使用AncestorType后,更具有通用性,可以在视觉树的任何地方使用,不仅仅可以直接在Template中使用)This might be useful if one tries to maximize the XAML re-use by using sub-controls with sub-templates(当尝试通过使用子模板、子控件来最大化xaml复用时,非常有用) or if one uses DataTemplates instead of ControlTemplate (both topics are beyond the scope of this article).(或者通过使用DataTemplate来代替ControlTemplate时)

    The Button's Click event is wired to the Clear() method of the EditableTextAndLabelControl via Expression Blend SDK functionality:(按钮的点击事件,通过ExpressionBlendSDK提供的功能与CS中的方法建立关联)

    <Button x:Name="TheClearButton"

     

            Content="X"

     

            Width="30">

        <i:Interaction.Triggers>

            <i:EventTrigger EventName="Click">

                <ei:CallMethodAction MethodName="Clear"

     

                                     TargetObject="{Binding RelativeSource={RelativeSource TemplatedParent}}" />

            </i:EventTrigger>

        </i:Interaction.Triggers>

    </Button> 

    You do not have to install MS Expression Blend in order to get this functionality. All you need to do is to reference two dlls that can be found under CODE/ExpressionBlenSDK folder: Microsoft.Expression.Interactions.dll and System.Windows.Interactivity.dll.(为了使用ExpressionBlend的这项功能,用不着专门安装MS Expression Blend,在CODE/ExpressionBlenSDK 目录下引用上面两dll就行)

    You also need to set references to two namespaces in your XAML file header:(当然xaml文件中要添加如下命名空间)

    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 

    As shown above, this functionality allows to hook to any routed event of any control and call any method on any bindable objects whenever the event fires.(ExpressionBlend的这项功能,允许关联任何控件的任何路由事件)

    A more common way of wiring an event to C# functionality is by using WPF Commands. Commands(一般情况下,为Event写逻辑是用Commands来做), however, are less flexible and more cumbersome for the following reasons:(但是,Commands不太灵活)

    • Commands can only be fired for the Click event of very few controls, i.e. Buttons and Menus(Commands仅能触发Click事件,而且支持的控件还很少) while the Expression Blend SDK functionality can used for any Routed Event on any control.(但,ExpressionBlendSDK可以支持任何控件的任何Event)
    • Commands usually require adding them to the C# code, while the Expression Blend SDK functionality can call any method within the C# code as long as the method has no arguments or if its arguments are the same as those of the routed events.(Commands通常需要在Cs代码中添加,但ExpressionBlendSDK可以调用C#代码中的任何方法,只要方法没参数,或者参数与路由事件相同即可)

     

    Note: The Expression Blend SDK functionality comes with many other popular packages, e.g. if you are using Prism you should already have the required Expression Blend SDK dlls.

    The MainWindow's code is very simple and very similar to that of the previous example:

    <!-- user control for entering the name -->

    <local:EditableTextAndLabelControl Label="Enter your name:"

     

                                       HorizontalAlignment="Left"

     

                                       VerticalAlignment="Center"

     

                                       Margin="10,0,0,0"

     

                                       Template="{StaticResource PlainHorizontalEditableTextTemplate}"/>

     

    <!-- user control for entering the passcode -->

    <local:EditableTextAndLabelControl Label="Enter your passcode:"

     

                                       HorizontalAlignment="Left"

     

                                       VerticalAlignment="Center"

     

                                       Margin="10,0,0,0"

     

                                       Grid.Row="1"

     

                                       Template="{StaticResource PlainHorizontalEditableTextTemplate}"/> 

    Note the references to the ControlTemplate via the StaticResource markup extension:

    Template="{StaticResource PlainHorizontalEditableTextTemplate}" 

    Note that the XAML code for both controls is very similar: we are using the same HorizontalAlignment, VerticalAlignment, Margin and Template properties for both of them. In order to re-use more XAML code we can use a WPF Style that sets those properties:(这俩控件实例,使用了相同的属性,为了提高XAML代码复用,可以使用Style样式来统一设置这些属性)

    <Style x:Key="TheEditableTextAndLabelControlStyle"

     

           TargetType="local:EditableTextAndLabelControl">

        <Setter Property="HorizontalAlignment"

     

                Value="Left"/>

        <Setter Property="VerticalAlignment"

     

                Value="Center"/>

        <Setter Property="Margin"

     

                Value="10,0,0,0"/>

        <Setter Property="Template"

     

                Value="{StaticResource PlainHorizontalEditableTextTemplate}"/>

    </Style> 

    This style should be placed in the Windows.Resources section of MainWindow.xaml file under the Template's definition (because the style refers to the template).

    Then, we can remove the properties defined in the Style from the controls, instead making the controls refer to the style in the following way:

    <!-- user control for entering the name -->

    <local:EditableTextAndLabelControl Label="Enter your name:"

     

                                       Template="{StaticResource PlainHorizontalEditableTextTemplate}"

     

                                       Style="{StaticResource TheEditableTextAndLabelControlStyle}"/>

     

    <!-- user control for entering the passcode -->

    <local:EditableTextAndLabelControl Label="Enter your passcode:"

     

                                       Grid.Row="1"

     

                                       Style="{StaticResource TheEditableTextAndLabelControlStyle}" /> 

    To even further simplify XAML, we can make the Style to be the default style for all EditableTextAndLabelControl by not setting the x:Key property on it. In that case we can even drop the reference to such Style from the controls:(可以通过去掉Style的x:key属性,来将Style变成某类控件的默认样式,来实现xaml代码的进一步简化)

    <!-- user control for entering the name -->

    <local:EditableTextAndLabelControl Label="Enter your name:"

     

                                       Template="{StaticResource PlainHorizontalEditableTextTemplate}"/>

     

    <!-- user control for entering the passcode -->

    <local:EditableTextAndLabelControl Label="Enter your passcode:"

     

                                       Grid.Row="1">  

    Finally, the best practice is to place the Styles and Templates into a separate ResourceDictionary file, usually located in a separate folder, so that they could be accessed from multiple places. In the further projects we will have a folder "Styles" containing the ResourceDictionarys for various controls.(最终,最好将Style和Template分开放到不同的ResourceDictionary文件中,并分别放到不同的文件夹中,将来项目中的多个地方就可以直接引用了)

    Using Two Different Templates for the same Lookless Control

    In the next sample I show how to create two completely different templates for the same lookless control. (下面的例子,作者将介绍如何为同一个LooklessControl 创建两个完全不同的模板Template)The sample is located under TwoDifferentTemplatesForTheSameLooklessControlSample project.

    Here is what you see if you run the project:

     

    Both, on the left and right I am displaying the same EditableTextAndLabelControl control, but on the left I am using PlainHorizontalEditableTextStyle Style and on the right I am using SomeCrazyEditableTextStyle Style:

    <Grid>

        ...

        <local:EditableTextAndLabelControl Label="Enter your name:"

     

                                           Style="{StaticResource PlainHorizontalEditableTextStyle}"

     

                                           Grid.Row="1"/>

     

        ...

     

        <local:EditableTextAndLabelControl Label="Enter your name:"

     

                                           Style="{StaticResource SomeCrazyEditableTextStyle}"

     

                                           Grid.Column="2"

     

                                           Grid.Row="1"/>

    </Grid> 

    Both Styles are defined in LooklessControlStyles.xaml Resource Dictionary file located under "Styles" project folder:

     

    In order to create a Resource Dictionary, please follow the steps specified in Appending C: Creating a WPF ResourceDictioary File.

    LooklessControlStyles.xaml Resource Dictionary defines two templates: "PlainHorizontalEditableTextTemplate" (same as in the previous example) and "SomeCrazyEditableTextTemplate" (the new one).

    It also has two styles: "PlainHorizontalEditableTextStyle" and "SomeCrazyEditableTextStyle" that use these two templates correspondingly.

    We are more interested in the "Crazy" Style and Template since the "Plain" ones have been described before.

    Here is the XAML of the "Crazy" Style:

    <Style TargetType="local:EditableTextAndLabelControl"

     

           x:Key="SomeCrazyEditableTextStyle">

        <Setter Property="HorizontalAlignment"

     

                Value="Left" />

        <Setter Property="VerticalAlignment"

     

                Value="Center" />

        <Setter Property="Margin"

     

                Value="10,0,0,0" />

        <Setter Property="Template"

     

                Value="{StaticResource SomeCrazyEditableTextTemplate}" />

    </Style> 

    And here is the "Crazy" Template:

    <ControlTemplate x:Key="SomeCrazyEditableTextTemplate"

     

                     TargetType="local:EditableTextAndLabelControl">

        <Grid>

            <Grid.RowDefinitions>

                <RowDefinition />

                <RowDefinition />

            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>

                <ColumnDefinition />

                <ColumnDefinition />

            </Grid.ColumnDefinitions>

            <!-- bound to the Label dependency property of the control -->

            <Label x:Name="TheLabel"

     

                   Content="{Binding Path=Label,

                                        RelativeSource={RelativeSource TemplatedParent}}"

     

                   Margin="0,0,2,0"

     

                   Background="Red"

     

                   Grid.Row="1"

     

                   RenderTransformOrigin="0.5,0.5">

                <Label.RenderTransform>

                    <RotateTransform Angle="-45" />

                </Label.RenderTransform>

            </Label>

            <!-- bound to the EditableText dependency property of the control -->

            <TextBox x:Name="TheTextBox"

     

                     Text="{Binding Path=EditableText,

                                    Mode=TwoWay,

                                    RelativeSource={RelativeSource TemplatedParent}}"

     

                     Width="100"

     

                     Margin="2,0"

     

                     Grid.Column="1" />

     

            <!-- We use Expression Blend's SDK's EventTrigger and CallMethodAction

                     objects to wire Clear() method of the control

                     to the Click event of the button -->

            <ToggleButton x:Name="TheClearButton"

     

                          Content="X"

     

                          Width="30"

     

                          Grid.Column="2"

     

                          Grid.Row="1">

                <i:Interaction.Triggers>

                    <i:EventTrigger EventName="Click">

                        <ei:CallMethodAction MethodName="Clear"

     

                                             TargetObject="{Binding RelativeSource={RelativeSource TemplatedParent}}" />

                    </i:EventTrigger>

                </i:Interaction.Triggers>

            </ToggleButton>

        </Grid>

    </ControlTemplate> 

    Note, that not only we arrange the controls differently, but we are using different controls - e.g. instead of TextBlock we are using Label control and instead of Button, we are using ToggleButton.

    Also note, that in order to make the Styles and Templates defined in a separate Resource Dictionary file visible within the MainWindow.xaml, we added the following code to its Window.Resources section(为了将定义到同一项目不同目录和文件中的Style和Templates在MainWindow中可以使用,需要在<Window.Resources>下添加如下资源字典引入合并代码)

    <Window.Resources>

        <ResourceDictionary>

            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary Source="Styles/LooklessControlStyles.xaml"/>

            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>

    </Window.Resources> 

    Often, the referred Resource Dictionary will be in a different project. In that case, the Source property of the merged resource dictionary should be set differently, e.g. assuming that we refer to "Styles/LooklessControlStyles.xaml" in a different project called "Controls" here is how the corresponding line would look:(当资源字典在不同项目下时,资源字典的引入路径如下所示为Controls项目下的样式引入,注意:以Component开头)

    <ResourceDictionary Source="/Controls;Component/Styles/LooklessControlStyles.xaml"/>

    Note, that we also added prefix "Component" to the corresponding path within the project.

    Parametrizing Lookless Controls

    We can further improve the re-use of XAML by making it dependent on some dependency properties of the Lookless Control.(通过参数化设计来提高xaml重用性)

    If you run ParametrizingLooklessControl project, and start typing the text in its TextBox you will see that the label text is blue, while the editable text is red:

     

    Here is the relevant content of the MainWindow.xaml file

    <local:EditableTextAndLabelControl Label="Enter your name:"

     

                                       Foreground="Blue"

     

                                       EditableTextForeground="Red"

     

                                       Style="{StaticResource PlainHorizontalEditableTextStyle}"/> 

    You can see, that Foreground property of the control is set to "Blue", while the property EditableTextForeground is set to "Red".

    Every WPF Control has the Foreground property, so, since EditableTextAndLabelControl class derives from Control it gets the Foreground property automatically.

    The dependency property EditableTextForeground has been, however, added to the EditableTextAndLabelControl class:

    #region EditableTextForeground Dependency Property

    public Brush EditableTextForeground

    {

        get { return (Brush)GetValue(EditableTextForegroundProperty); }

        set { SetValue(EditableTextForegroundProperty, value); }

    }

     

    public static readonly DependencyProperty EditableTextForegroundProperty =

    DependencyProperty.Register

    (

        "EditableTextForeground",

        typeof(Brush),

        typeof(EditableTextAndLabelControl),

        new PropertyMetadata(null)

    );

    #endregion EditableTextForeground Dependency Property 

    The Template (defined in LooklessControlStyles.xaml file) binds the corresponding TextBlock's and TextBox'es Foreground properties to the Foreground and EditableTextForeground properties of the Control in the following way:

    <!-- bound to the Label dependency property of the control -->

    <TextBlock x:Name="TheLabel"

     

               Foreground="{Binding Path=Foreground,

                                    RelativeSource={RelativeSource TemplatedParent}}"

     

               Text="{Binding Path=Label,

                              RelativeSource={RelativeSource TemplatedParent}}"

     

               Margin="0,0,2,0" />

    <!-- bound to the EditableText dependency property of the control -->

    <TextBox x:Name="TheTextBox"

     

             Foreground="{Binding Path=EditableTextForeground,

                                  RelativeSource={RelativeSource TemplatedParent}}"

     

             Text="{Binding Path=EditableText,

                                Mode=TwoWay,

                                RelativeSource={RelativeSource TemplatedParent}}"

     

             Width="100"

     

             Margin="2,0" /> 

    Accessing the Visuals From C# Code for Lookless Controls

    Sometimes (though not very often) it is necessary to access the parts defined in a Control Template from the C# code of the control. This, BTW is the only small advantage that UserControls have over Lookless Controls - it is easier to access the visuals defined in XAML for UserControls from C# code.(有时需要通过C#代码来访问Template中的部分内容,访问起来会比UserControl复杂)

    There is, however, a relatively simple way of accessing visual parts for Lookless Controls also.(这儿有个简单的方法来访问LooklessControl中的内容)

    Project AccessingVisualPartsFromCSharpSample shows how it can be done.

    If you run the project and click on "Enter your name:" label, you will see a modal dialog window containing text "label clicked" popping up:

     

    In order to continue working with the main window, you'll have to kill the popup first.

    Take a look at the Control Template within LooklessControlStyle.xaml file. The only things that changed in comparison to the previous samples is that the TextBlock has been renamed "PART_Label" and it has a transparent background:(模板中:TextBlock的名称改成了以PART_开头,且背景透明了)

    <TextBlock x:Name="PART_TheLabel"

     

               Background="Transparent"

     

               Text="{Binding Path=Label,

                              RelativeSource={RelativeSource TemplatedParent}}"

     

               Margin="0,0,2,0" />

    Prefix "PART_" for the names of the visuals that might be accessed from within C# code is a WPF useful common practice.(WPF的通用做法:需要在C#代码中访问的可视化部分,要以PART_开头)

    Transparency for the background of the TextBlock is introduced in order to better catch the mouse down events on it (otherwise it would only react when one clicks on the text itself)

    The relevant C# code addition is located at the top of EditableTextAndLabelControl.cs file:

    public class EditableTextAndLabelControl : Control

    {

        FrameworkElement _labelElement = null;

     

        public override void OnApplyTemplate()

        {

            base.OnApplyTemplate();

     

            // find _labelElement from the control template

            _labelElement =

                this.Template.FindName("PART_TheLabel", this) as FrameworkElement;

     

            // attach event handler to MouseLeftButtonDown event.

            _labelElement.MouseLeftButtonDown += _labelElement_MouseLeftButtonDown;

        }

     

        private void _labelElement_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

        {

            // create and show a modal window with text "label clicked!"

            Window dialogWindow = new Window() { Width = 150, Height = 100 };

     

            dialogWindow.Content =

                new TextBox { Text = "label clicked!" };

            dialogWindow.ShowDialog();

        }

    ...

    }

    We override OnApplyTemplate() method (to make sure that we can find the parts defined in the control template).(这里重写了OnApplyTemplate()方法,确保能找到模板中定义的UI元素,并为该元素添加事件)

    Then, we use Template.FindName(...) method to actually find the control.(通过使用Template.FindName()来查找控件)

    Other and more powerful ways of finding template parts is to use functionality described in Generic (Non-WPF) Tree to LINQ and Event Propagation on Trees. This approach will be described elsewhere.

    Summary

    In this article I explain why WPF Lookless Controls are more powerful that User Controls and describe several usage patterns for the Lookless Controls.

    History of Changes

    • Nov. 16, 2015 - added a table of contents; explained my using the term 'Lookless control' instead of 'Custom controls'
    • Nov 19, 2015 - added a missing link to the source code

    Appedix A: Creating a UserControl in Visual Studio

    In order to create a WPF UserControl in Visual Studio, right mouse click on the project in Solution Explorer; choose Add->New Item; then choose WPF on the left hand side and 'User Control (WPF)' on the right hand side and enter the name of the control:

     

     

    Appending B: Installing a Custom propdp Snippet

    You can install the custom propdp snippet that comes with the code (in CODE/Snippets folder) by going through the following steps:

    1. Open the Snippets Manager by going to the "Tools" menu in Visual Studio and clicking on "Code Snippets Manager" menu item: 
    2. Within the Snippets Manager, change the language to CSharp (using the drop down at the top) and click on "NetFX30" folder: 
    3. Copy the snippet folder location from the Location field within the Snippets Manager. Paste this location into File Explorer.
    4. Copy the propdp file that comes with this code into the snippet location.
    5. Restart your Visual Studio.

    Appending C: Creating a WPF Resource Dictionary File

    You can create a ResourceDictionary file in Visual Studio via the following steps:

    1. Right mouse click on the folder or project within the VS Solution Explorer in which you want the ResourceDictionary to be created.
    2. Choose WPF folder on the left and "Resource Dictionary (WPF)" on the right: 
    3. Choose the name of the Resource Dictionary file and press "Add" button
  • 相关阅读:
    python 生成器和推导式
    python 函数名 ,闭包 ,迭代器
    python 函数
    python BMI指数
    python 实现购物车的优化
    python 文件操作
    python set集合 深浅拷贝(难点)
    css中的float和position
    css一些简单的例子
    SQL测试题
  • 原文地址:https://www.cnblogs.com/nick-jd/p/12801101.html
Copyright © 2011-2022 走看看