在 UWP 中实现 Expander 控件

WPF 中的 Expander 控件在 Windows 10 SDK 中并不提供,本文主要说明,如何在 UWP 中创建这样一个控件。其效果如下图:

首先,分析该控件需要的一些特性,它应该至少包括如下三个属性:

  • Content: 最重要的属性,设置该属性,可以使 Expander 控件显示其内容;
  • Header: 控件的 Header;
  • IsExpand: 当前是否展开。

接下来是定义其 UI,在这里使用 Grid,添加两行,一行显示 Header,一行显示 Content,当 IsExpand 属性为 false 时,只要将 Content 那一行隐藏即可;此外,还需要一个 ToggleButton 用于控制该控件的展开与关闭。

OK。思路弄清楚后,开始实践,

1. 创建控件,并添加属性

在项目中添加一个 Templaded Control(模板化控件),名称为 Expander。为其添加三个依赖属性,代码如下:

   public static readonly DependencyProperty ContentProperty =
        DependencyProperty.Register("Content", typeof(object), typeof(Expander), new PropertyMetadata(null));

        public static readonly DependencyProperty HeaderProperty =
            DependencyProperty.Register("Header", typeof(object), typeof(Expander), new PropertyMetadata(null));

        public static readonly DependencyProperty IsExpandProperty =
            DependencyProperty.Register("IsExpand", typeof(bool), typeof(Expander), new PropertyMetadata(true));

        /// <summary>
        /// 控件的内容
        /// </summary>
        public object Content
        {
            get { return (object)GetValue(ContentProperty); }
            set { SetValue(ContentProperty, value); }
        }

        /// <summary>
        /// 控件的标题
        /// </summary>
        public object Header
        {
            get { return (object)GetValue(HeaderProperty); }
            set { SetValue(HeaderProperty, value); }
        }

        /// <summary>
        /// 返回或设置控件是否展开
        /// </summary>
        public bool IsExpand
        {
            get { return (bool)GetValue(IsExpandProperty); }
            set { SetValue(IsExpandProperty, value); }
        }

2. 定义UI

    在 Generic.xaml 中,找到 <Style TargetType="controls:Expander"> 节点,添加如下代码:

    <Style TargetType="controls:Expander">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="controls:Expander">
                    <Grid x:Name="grid"
                          Background="{TemplateBinding Background}"
                          BorderBrush="{TemplateBinding BorderBrush}"
                          BorderThickness="{TemplateBinding BorderThickness}">
                         <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <StackPanel Orientation="Horizontal">
                            <ToggleButton x:Name="toggleButton"
                                          Width="32"
                                          Height="32"
                                          Margin="0,0,4,0"
                                          BorderThickness="0"
                                          IsChecked="{Binding IsExpand,
                                                              RelativeSource={RelativeSource Mode=TemplatedParent},
                                                              Mode=TwoWay}">
                                <Path x:Name="arrow"
                                      Width="16"
                                      Height="16"
                                      Data="M15.289001,0L20.484007,0 31.650999,15.953003 29.055021,19.658005 20.415007,32 15.35501,32 15.289001,31.906998 24.621,18.572998 0,18.572998 0,13.326004 24.621,13.326004z"
                                      Fill="#DDFFFFFF"
                                      RenderTransformOrigin="0.5,0.5"
                                      Stretch="Uniform">
                                 </Path>
                            </ToggleButton>
                            <ContentControl VerticalAlignment="Center" Content="{TemplateBinding Header}" />
                        </StackPanel>
                        <ContentControl Grid.Row="1"
                                        HorizontalContentAlignment="Stretch"
                                        VerticalContentAlignment="Stretch"
                                        Content="{TemplateBinding Content}"
                                        Visibility="{Binding IsExpand,
                                                             RelativeSource={RelativeSource Mode=TemplatedParent},
                                                             Converter={StaticResource BooleanToVisibilityConverter},
                                                             Mode=TwoWay}" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

可以看出:
    a) ToggleButton 的 IsChecked 属性绑定了控件的 IsExpand 属性, 绑定表达式 {Binding IsExpand,RelativeSource={RelativeSource Mode=TemplatedParent}} 是 {TemplateBinding IsExpand} 的另一种写法,在这种写法中,我们可以添加 Binding 对象的其它属性,如这里的 Mode=TwoWay,这样可以实现 ToggleButton 与 控件的 IsExpand 属性彼此互相的控制;
    b) ContentControl 的 Visibility 与 a) 同理,略微复杂的是,这里用了一个 Converter,用于在 Bool 和 Visibility 枚举之间转换;
    c) 我们为 ToggleButton 控件的 Content 属性设置了一个 Path 用来形象地表达 Expander 当前的状态。

3. 定义 VisualState

我们为该控件定义两个 VisualState,分别代表正常状态和展开状态,即 Normal 与 Expanded,通过切换这两种状态可以完成该控件的UI变化,这里主要是对 ToggleButton 的 Content 进行动画设置。

在 Path 中为其添加 RotateTransform,代码如下:

                      <Path x:Name="arrow"
                                      Width="16"
                                      Height="16"
                                      Data="M15.289001,0L20.484007,0 31.650999,15.953003 29.055021,19.658005 20.415007,32 15.35501,32 15.289001,31.906998 24.621,18.572998 0,18.572998 0,13.326004 24.621,13.326004z"
                                      Fill="#DDFFFFFF"
                                      RenderTransformOrigin="0.5,0.5"
                                      Stretch="Uniform">
                                    <Path.RenderTransform>
                                        <RotateTransform x:Name="pathRotate" />
                                    </Path.RenderTransform>
                                </Path>

在 Grid 中添加 VisualState,代码如下:

                    <Grid x:Name="grid"
                          Background="{TemplateBinding Background}"
                          BorderBrush="{TemplateBinding BorderBrush}"
                          BorderThickness="{TemplateBinding BorderThickness}">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition From="Normal"
                                                      GeneratedDuration="0:0:0.2"
                                                      To="Expanded" />
                                    <VisualTransition From="Expanded"
                                                      GeneratedDuration="0:0:0.2"
                                                      To="Normal" />
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Normal">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0:0:0"
                                                         Storyboard.TargetName="pathRotate"
                                                         Storyboard.TargetProperty="Angle"
                                                         To="0">
                                            <DoubleAnimation.EasingFunction>
                                                <QuinticEase EasingMode="EaseOut" />
                                            </DoubleAnimation.EasingFunction>
                                        </DoubleAnimation>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Expanded">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0:0:0"
                                                         Storyboard.TargetName="pathRotate"
                                                         Storyboard.TargetProperty="Angle"
                                                         To="90">
                                            <DoubleAnimation.EasingFunction>
                                                <QuinticEase EasingMode="EaseIn" />
                                            </DoubleAnimation.EasingFunction>
                                        </DoubleAnimation>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        ...

这里,我们可以看到,除了两个 VisualState 外,我们还定义了两个 VisualTransition,用来设置切换此两种状态时的过度时间。

提示:关于 Content 区域的隐藏与显示,也可以通过在 VisualState 添加动画来控制,不过在上面的代码中,我们利用了 ToggleButton 以及它的 IsCheced 属性来控制其显示与隐藏,较为简洁地了实现这一功能。

接下来,我们需要在代码中来控制何时在这两种状态间切换,在 Expander.cs 中添加如下代码:

       private ToggleButton button;

        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            button = GetTemplateChild("toggleButton") as ToggleButton;
            button.Loaded += (s, e) => { ChangeControlState(false); };
            button.Checked += (s, e) => { ChangeControlState(); };
            button.Unchecked += (s, e) => { ChangeControlState(); };
        }

        /// <summary>
        /// 改变控件的 VisualState
        /// </summary>
        /// <param name="useTransition">是否使用 VisualTransition,默认使用</param>
        private void ChangeControlState(bool useTransition = true)
        {
            if (button.IsChecked.Value)
            {
                VisualStateManager.GoToState(this, "Expanded", useTransition);
            }
            else
            {
                VisualStateManager.GoToState(this, "Normal", useTransition);
            }
        }

可以看出,我们为 ToggleButton 添加事件响应来切换状态。之所以在 Load 时也来改检查并更改状态,是因为,如果在使 Expander 控件时,如果为它设置 IsExpand 为 true 时,那么加载时,会及时更新控件状态为 Expanded ,否则将默认为 Normal。

最后,我们为控件添加一个 ContentPropertyAttribute,并设置其 Name 为 Content,这样,该控件的 Content 属性就作为此控件的内容属性(ContentPropery)。简言之,可以省去 <xxx:Expander.Content> 这个节点,类似在 Button 中直接添加其 Content 一样。代码如下:

    [ContentProperty(Name = "Content")]
    public sealed class Expander : Control

至此,一个 Expander 控件就完成了,至于你还有额外、其它的需求(如样式的修改等),则可在此基础上进行修改。

如果你有什么更好的建议或其它观点,请留言,互相交流。

源代码下载

参考资料:

What is ContentPropertyAttribute?

时间: 2024-10-12 10:04:58

在 UWP 中实现 Expander 控件的相关文章

Win10 UWP开发系列——开源控件库:UWPCommunityToolkit

原文:Win10 UWP开发系列--开源控件库:UWPCommunityToolkit 在开发应用的过程中,不可避免的会使用第三方类库.之前用过一个WinRTXamlToolkit.UWP,现在微软官方发布了一个新的开源控件库—— UWPCommunityToolkit 项目代码托管在Github上:https://github.com/Microsoft/UWPCommunityToolkit 包括以下几个类库: 都可以很方便的从Nuget上安装. NuGet Package Name des

wpf中遍历界面控件的方法

/// <summary>        /// 遍历界面中的所有控件        /// </summary>        /// <param name="uiControls"></param>        private void SetNotEditable(UIElementCollection uiControls)        {            foreach (UIElement element in u

android 在布局中动态添加控件

第一步 Java代码 final LayoutInflater inflater = LayoutInflater.from(this); 第二步:获取需要被添加控件的布局 Java代码 final LinearLayout lin = (LinearLayout) findViewById(R.id.LinearLayout01); 第三步:获取需要添加的布局(控件) Java代码 LinearLayout layout = (LinearLayout) inflater.inflate( R

使用Devexpress中的CharControl控件,需要控制AxisY轴的显示范围,需要使用该控件的BoundDataChanged事件

一.控制ChartControl的Y轴范围 使用Devexpress中的CharControl控件,需要控制AxisY轴的显示范围,需要使用该控件的BoundDataChanged事件,具体代码如下: 该代码实现的效果如下: 参考文献: [1]AxisY Range Auto https://www.devexpress.com/Support/Center/Question/Details/Q266328 二.设置GridControl表格中的超级链接: 1.首先在表格的设计器中添加一个Hyp

C# dataGridView控件中加入comboBox控件及注意事项

DataGridViewComboBoxColumn pCombo; private void Teaching_Add_Load(object sender, EventArgs e) { MyDBase DB = new MyDBase(DBUser.sserver,DBUser.DBName, DBUser.suser, DBUser.spasswd); DataSet DS= DB.GetRecordset("select * from view_teach_tmp"); da

WPF 中动态改变控件模板

在某些项目中,可能需要动态的改变控件的模板,例如软件中可以选择不同的主题,在不同的主题下软件界面.控件的样式都会有所不同,这时即可通过改变控件模板的方式实现期望的功能. 基本方法是当用户点击切换主题按钮是加载新的资源字典,并使用新加载的资源字典替代当前的资源字典这时要用到ResourceManager. 假设现有两个不同的资源字典文件Dictionary1.xaml和Dictionary2.xaml存在于Themes文件夹内: 在MainPage中使用其中一个资源字典作为默认样式文件: <Win

FormView的插入模板中的DropDownList控件参数

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DT

SplendidCRM中给来自EditView中的listbox控件设置选中值或数据源

DropDownList list = this.findContol("aas") as DropDownList;list.DataSource = new DataTable() ------------------------------- Control ctl = this.FindControl("NAME");            if (ctl != null)            {                if (ctl is Dro

vs2010中的ADO控件及绑定控件

要在项目中添加某一个ActiveX控件,则该ActiveX控件必须要注册.由于VS2010中,并没有自动注册ADO及ADO数据绑定控件(Microsoft ADO Data Control,Microsoft DataCombo等),参考http://msdn.microsoft.com/zh-cn/library/dsb06ab6(v=vs.100).aspx 故没法在插入ActiveX控件对话框中找到它们. 若要在VS2010中获得这种控件,需要另外下载然后注册,或者利用以前的VS版本获得.