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;
}
抽屉控件的样式实现
position 属性的触发器:根据 position 属性的值(top、right、bottom),调整 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抽屉控件内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论