WPF实现Drawer抽屉控件

 更新时间:2024年11月08日 11:31:41   作者:WPF开发者  
这篇文章主要为大家详细介绍了如何使用WPF实现一个Drawer抽屉控件,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

Drawer 抽屉控件的实现

框架支持.NET4 至 .NET8

Visual Studio 2022;

抽屉控件的逻辑实现

定义了一个名为 Drawer 的自定义控件,继承自 HeaderedContentControl,允许用户在应用程序中创建可展开和收起的抽屉。抽屉的显示和隐藏动画通过 Storyboard 实现,支持从不同方向()展开和收起。

1.定义模板

使用 TemplatePart 特性定义了两个模板:BorderHeaderTemplateName 和 BorderMarkTemplateName,分别代表抽屉的蒙板部分。

[TemplatePart(Name = BorderHeaderTemplateName, Type = typeof(Border))]
[TemplatePart(Name = BorderMarkTemplateName, Type = typeof(Border))]
public class Drawer : HeaderedContentControl

2.定义依赖属性

  • Position 抽屉展开的方向()。
  • IsOpen 抽屉是否打开,状态变化时调用 OnIsOpenChanged 方法。
public static readonly DependencyProperty EdgePositionProperty =
    DependencyProperty.Register("Position", typeof(Position), typeof(Drawer),
        new PropertyMetadata(Position.Left));

public static readonly DependencyProperty IsOpenProperty =
    DependencyProperty.Register("IsOpen", typeof(bool), typeof(Drawer),
        new PropertyMetadata(false, OnIsOpenChanged));

3.属性实现

public Position Position
{
    get => (Position) GetValue(EdgePositionProperty);
    set => SetValue(EdgePositionProperty, value);
}

public bool IsOpen
{
    get => (bool) GetValue(IsOpenProperty);
    set => SetValue(IsOpenProperty, value);
}

4.状态变化处理

OnIsOpenChanged 方法根据 IsOpen 属性的值,控制抽屉的显示或隐藏和动画效果。

private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var ctrl = d as Drawer;
    if (ctrl != null)
    {
        if (ctrl.IsOpen)
        {
            ctrl._headerBorder.Visibility = Visibility.Visible;
            ctrl._enterStoryboard.Begin();
        }
        else
        {
            ctrl._exitStoryboard.Begin();
        }
    }
}

5.模板应用

OnApplyTemplate 方法在控件模板应用时调用,获取模板中的 Border 部件并注册 Loaded 和 MouseDown 事件。

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    _headerBorder = GetTemplateChild(BorderHeaderTemplateName) as Border;
    if (_headerBorder != null)
        _headerBorder.Loaded += HeaderBorder_Loaded;
    _markBorder = GetTemplateChild(BorderMarkTemplateName) as Border;
    if (_markBorder != null)
        _markBorder.MouseDown += MarkBorder_MouseDown;
}

6.动画实现

HeaderBorder_Loaded 方法根据 Position 的不同,设置不同的动画,并根据属性值控制动画的执行。

        private void HeaderBorder_Loaded(object sender, RoutedEventArgs e)
        {
            TranslateTransform translateTransform;
            DoubleAnimation animation, exitAnimation;
            switch (Position)
            {
                case Position.Left:
                case Position.Right:
                    _headerWidth = _headerBorder.ActualWidth;
                    if (Position == Position.Left)
                        translateTransform = new TranslateTransform(-_headerWidth, 0);
                    else
                        translateTransform = new TranslateTransform(_headerWidth, 0);
                    _headerBorder.RenderTransform = new TransformGroup
                    {
                        Children = new TransformCollection {translateTransform}
                    };
                    animation = new DoubleAnimation
                    {
                        From = Position == Position.Left ? -_headerWidth : _headerWidth,
                        To = 0,
                        Duration = TimeSpan.FromMilliseconds(300)
                    };

                    Storyboard.SetTarget(animation, _headerBorder);
                    Storyboard.SetTargetProperty(animation, new PropertyPath("RenderTransform.Children[0].X"));
                    _enterStoryboard = new Storyboard();
                    _enterStoryboard.Children.Add(animation);

                    exitAnimation = new DoubleAnimation
                    {
                        From = 0,
                        To = Position == Position.Left ? -_headerWidth : _headerWidth,
                        Duration = TimeSpan.FromMilliseconds(300)
                    };

                    Storyboard.SetTarget(exitAnimation, _headerBorder);
                    Storyboard.SetTargetProperty(exitAnimation, new PropertyPath("RenderTransform.Children[0].X"));
                    _exitStoryboard = new Storyboard();
                    _exitStoryboard.Completed += delegate
                    {
                        if (!IsOpen)
                            _headerBorder.Visibility = Visibility.Collapsed;
                    };
                    _exitStoryboard.Children.Add(exitAnimation);
                    break;
                case Position.Top:
                case Position.Bottom:
                    _headerHeight = _headerBorder.ActualHeight;
                    if (Position == Position.Top)
                        translateTransform = new TranslateTransform(0, -_headerHeight);
                    else
                        translateTransform = new TranslateTransform(0, _headerHeight);
                    _headerBorder.RenderTransform = new TransformGroup
                    {
                        Children = new TransformCollection {translateTransform}
                    };
                    animation = new DoubleAnimation
                    {
                        From = Position == Position.Top ? -_headerHeight : _headerHeight,
                        To = 0,
                        Duration = TimeSpan.FromMilliseconds(300)
                    };

                    Storyboard.SetTarget(animation, _headerBorder);
                    Storyboard.SetTargetProperty(animation, new PropertyPath("RenderTransform.Children[0].Y"));
                    _enterStoryboard = new Storyboard();
                    _enterStoryboard.Children.Add(animation);

                    exitAnimation = new DoubleAnimation
                    {
                        From = 0,
                        To = Position == Position.Top ? -_headerHeight : _headerHeight,
                        Duration = TimeSpan.FromMilliseconds(300)
                    };

                    Storyboard.SetTarget(exitAnimation, _headerBorder);
                    Storyboard.SetTargetProperty(exitAnimation, new PropertyPath("RenderTransform.Children[0].Y"));
                    _exitStoryboard = new Storyboard();
                    _exitStoryboard.Completed += delegate
                    {
                        if (!IsOpen)
                            _headerBorder.Visibility = Visibility.Collapsed;
                    };
                    _exitStoryboard.Children.Add(exitAnimation);
                    break;
            }

            _headerBorder.Visibility = Visibility.Collapsed;
            _headerBorder.Loaded -= HeaderBorder_Loaded;
        }

GitHub 源码地址

Gitee 源码地址

抽屉控件的样式实现

Position 属性的触发器:根据 Position 属性的值(TopRightBottom),调整 PART_Header 的对齐方式。

IsOpen 属性的触发器:如果 IsOpen 为 False,将 PART_Mark 的可见性设置为 Collapsed,在抽屉关闭时隐藏。

<Style
    x:Key="WD.Drawer"
    BasedOn="{StaticResource WD.ControlBasicStyle}"
    TargetType="{x:Type controls:Drawer}">
    <Setter Property="Background" Value="{DynamicResource WD.BackgroundSolidColorBrush}" />
    <Setter Property="BorderBrush" Value="{DynamicResource WD.BaseSolidColorBrush}" />
    <Setter Property="BorderThickness" Value="1" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type controls:Drawer}">
                <controls:WDBorder
                    x:Name="Border"
                    Margin="{TemplateBinding Margin}"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    CornerRadius="{Binding Path=(helpers:ElementHelper.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
                    SnapsToDevicePixels="True">
                    <controls:SmallPanel Clip="{Binding RelativeSource={RelativeSource AncestorType=controls:WDBorder}, Path=ContentClip}">
                        <Border x:Name="PART_Content">
                            <ContentPresenter Content="{TemplateBinding Content}" />
                        </Border>
                        <Border
                            x:Name="PART_Mark"
                            Background="{DynamicResource WD.PrimaryTextSolidColorBrush}"
                            Opacity=".5" />
                        <Border
                            x:Name="PART_Header"
                            HorizontalAlignment="Left"
                            Background="{TemplateBinding Background}"
                            Effect="{StaticResource WD.NormalShadowDepth}">
                            <ContentPresenter Content="{TemplateBinding Header}" />
                        </Border>
                    </controls:SmallPanel>
                </controls:WDBorder>
                <ControlTemplate.Triggers>
                    <Trigger Property="Position" Value="Top">
                        <Setter TargetName="PART_Header" Property="HorizontalAlignment" Value="Stretch" />
                        <Setter TargetName="PART_Header" Property="VerticalAlignment" Value="Top" />
                    </Trigger>
                    <Trigger Property="Position" Value="Right">
                        <Setter TargetName="PART_Header" Property="HorizontalAlignment" Value="Right" />
                        <Setter TargetName="PART_Header" Property="VerticalAlignment" Value="Stretch" />
                    </Trigger>
                    <Trigger Property="Position" Value="Bottom">
                        <Setter TargetName="PART_Header" Property="HorizontalAlignment" Value="Stretch" />
                        <Setter TargetName="PART_Header" Property="VerticalAlignment" Value="Bottom" />
                    </Trigger>
                    <Trigger Property="IsOpen" Value="False">
                        <Setter TargetName="PART_Mark" Property="Visibility" Value="Collapsed" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

示例

示例引入 WPFDevelopers 的 Nuget

<UserControl
    x:Class="WPFDevelopers.Samples.ExampleViews.DrawerExample"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:WPFDevelopers.Samples.Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
    d:DesignHeight="450"
    d:DesignWidth="800"
    mc:Ignorable="d">
    <controls:CodeViewer>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <wd:Drawer
                x:Name="MyDrawerTop"
                Margin="2"
                Position="Top">
                <wd:Drawer.Header>
                    <Grid Height="120">
                        <StackPanel HorizontalAlignment="Center">
                            <TextBlock FontSize="18" Text="Drawer" />
                            <Button HorizontalAlignment="Center" Content="contents..." />
                        </StackPanel>
                    </Grid>
                </wd:Drawer.Header>
                <wd:Drawer.Content>
                    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Bottom">
                        <TextBlock Text="抽屉从顶部置滑出,点击遮罩区关闭。" />
                        <Button
                            Margin="0,10"
                            HorizontalAlignment="Center"
                            Click="ButtonTop_Click"
                            Content="Open"
                            Style="{StaticResource WD.PrimaryButton}" />
                    </StackPanel>
                </wd:Drawer.Content>
            </wd:Drawer>
            <wd:Drawer
                x:Name="MyDrawerBottom"
                Grid.Column="1"
                Margin="2"
                wd:ElementHelper.CornerRadius="3"
                Position="Bottom">
                <wd:Drawer.Header>
                    <Grid Height="130">
                        <StackPanel HorizontalAlignment="Center">
                            <TextBlock FontSize="18" Text="Drawer" />
                            <Button
                                HorizontalAlignment="Center"
                                wd:ElementHelper.CornerRadius="3"
                                Content="contents..."
                                Style="{StaticResource WD.DangerDefaultButton}" />
                        </StackPanel>
                    </Grid>
                </wd:Drawer.Header>
                <wd:Drawer.Content>
                    <StackPanel HorizontalAlignment="Center">
                        <TextBlock Text="抽屉从底部滑出,点击遮罩区关闭。" />
                        <Button
                            Margin="0,10"
                            HorizontalAlignment="Center"
                            wd:ElementHelper.CornerRadius="3"
                            Click="ButtonBottom_Click"
                            Content="Open"
                            Style="{StaticResource WD.DangerPrimaryButton}" />
                    </StackPanel>
                </wd:Drawer.Content>
            </wd:Drawer>

            <wd:Drawer
                x:Name="MyDrawerLeft"
                Grid.Row="1"
                Margin="2"
                wd:ElementHelper.CornerRadius="3"
                Position="Left">
                <wd:Drawer.Header>
                    <Grid Width="140" Background="HotPink">
                        <StackPanel>
                            <TextBlock
                                HorizontalAlignment="Center"
                                FontSize="18"
                                Text="Drawer" />
                            <Button
                                HorizontalAlignment="Center"
                                wd:ElementHelper.CornerRadius="3"
                                Content="contents..."
                                Style="{StaticResource WD.SuccessDefaultButton}" />
                        </StackPanel>
                    </Grid>
                </wd:Drawer.Header>
                <wd:Drawer.Content>
                    <StackPanel HorizontalAlignment="Center">
                        <TextBlock Text="抽屉从左侧滑出,点击遮罩区关闭。" />
                        <Button
                            Margin="0,10"
                            HorizontalAlignment="Center"
                            wd:ElementHelper.CornerRadius="3"
                            Click="ButtonLeft_Click"
                            Content="Open"
                            Style="{StaticResource WD.SuccessPrimaryButton}" />
                    </StackPanel>
                </wd:Drawer.Content>
            </wd:Drawer>
            <wd:Drawer
                x:Name="MyDrawerRight"
                Grid.Row="1"
                Grid.Column="1"
                Margin="2"
                Position="Right">
                <wd:Drawer.Header>
                    <Grid Width="100">
                        <StackPanel HorizontalAlignment="Center">
                            <TextBlock
                                HorizontalAlignment="Center"
                                FontSize="18"
                                Text="Drawer" />
                            <Button
                                HorizontalAlignment="Center"
                                Content="contents..."
                                Style="{StaticResource WD.WarningDefaultButton}" />
                        </StackPanel>
                    </Grid>
                </wd:Drawer.Header>
                <wd:Drawer.Content>
                    <StackPanel HorizontalAlignment="Center">
                        <TextBlock Text="抽屉从右侧滑出,点击遮罩区关闭。" />
                        <Button
                            Margin="0,10"
                            HorizontalAlignment="Center"
                            Click="ButtonRight_Click"
                            Content="Open"
                            Style="{StaticResource WD.WarningPrimaryButton}" />
                    </StackPanel>
                </wd:Drawer.Content>
            </wd:Drawer>
        </Grid>
        <controls:CodeViewer.SourceCodes>
            <controls:SourceCodeModel CodeSource="/WPFDevelopers.SamplesCode;component/ExampleViews/DrawerExample.xaml" CodeType="Xaml" />
            <controls:SourceCodeModel CodeSource="/WPFDevelopers.SamplesCode;component/ExampleViews/DrawerExample.xaml.cs" CodeType="CSharp" />
        </controls:CodeViewer.SourceCodes>
    </controls:CodeViewer>
</UserControl>

效果图

到此这篇关于WPF实现Drawer抽屉控件的文章就介绍到这了,更多相关WPF抽屉控件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C# 实现FTP客户端的小例子

    C# 实现FTP客户端的小例子

    这篇文章主要介绍了C# 如何实现FTP客户端,文中实例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • C#中Invoke的具体使用

    C#中Invoke的具体使用

    本文主要介绍了C#中Invoke的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-08-08
  • 分析C# Dictionary的实现原理

    分析C# Dictionary的实现原理

    对于C#中的Dictionary类相信大家都不陌生,这是一个Collection(集合)类型,可以通过Key/Value(键值对的形式来存放数据;该类最大的优点就是它查找元素的时间复杂度接近O(1)。那么什么样的设计能使得Dictionary类实现O(1)的时间复杂度呢
    2021-06-06
  • C#序列化与反序列化(Serialize,Deserialize)实例详解

    C#序列化与反序列化(Serialize,Deserialize)实例详解

    这篇文章主要介绍了C#序列化与反序列化(Serialize,Deserialize)的方法,实例分析了C#序列化与反序列化的常见技巧,需要的朋友可以参考下
    2015-06-06
  • Unity使用LineRender实现绘画功能

    Unity使用LineRender实现绘画功能

    这篇文章主要为大家详细介绍了Unity使用LineRender实现绘画功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-07-07
  • c#使用windows服务更新站点地图的详细示例

    c#使用windows服务更新站点地图的详细示例

    这篇文章主要介绍了c#使用windows服务更新站点地图的详细示例,需要的朋友可以参考下
    2014-04-04
  • C#学习笔记之适配器模式详解

    C#学习笔记之适配器模式详解

    这篇文章主要为大家详细介绍了C#学习笔记之适配器模式的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • C#使用QRCode生成海报图并嵌入定位带logo的二维码

    C#使用QRCode生成海报图并嵌入定位带logo的二维码

    这篇文章主要为大家详细介绍了C#如何使用QRCode生成海报图并嵌入定位带logo的二维码,文中的示例代码讲解详细,需要的小伙伴可以参考下
    2024-03-03
  • Unity UI拖拽模型选择功能

    Unity UI拖拽模型选择功能

    这篇文章主要为大家详细介绍了Unity UI拖拽模型选择功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-01-01
  • C#调用SQL Server中有参数的存储过程

    C#调用SQL Server中有参数的存储过程

    这篇文章介绍了C#调用SQL Server中有参数存储过程的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03

最新评论