WPF自定义ListBox(二)
Helloj2ee的自定义ListBox(一),虽然很用心,但看出来反响不强烈。只能这一篇再给力一点。虽然评论很少,但是也看出一些讯息。
讯息之一,就是Helloj2ee的WPF看不懂。的确在这里写的不是基本的东西,而是对其WPF的相关基础概念掌握了之后,才能看的一个系列。自定义控件在《葵花宝典——WPF自学手册》一书中,是放到第六卷——华山之巅,足见其高阶。因此Helloj2ee在后面列出的参考文献,就很有价值了。因为您若是看不懂,不妨去参考相关文献,即便您没有葵花宝典一书,但总可以知道需要掌握的相关概念是什么,参阅其他书又何妨?
讯息之二,是提供的源码无法打开,那么Helloj2ee已经更新过。如果解压还是失败,您不妨在下面的回帖中告诉我。
说到给力,那么只有让这个控件在本章当中酷起来或者炫起来,才是给力的王道。
CircularPanel的布局之美
给力之前,Helloj2ee还是得枯燥一下,CircularPanel的那几个参数。第一个参数是CircularPanel的初始角度(InitialAngle)。
剩下的参数在该图中可以展现。Align属性实质是指的ListBoxItem的旋转点,Left表示外接矩形的左上角点,Center表示外接矩形的中心点,Right表示外接矩形的右下角点。而Radius指的是从圆心到旋转点的半径。AngleItem则是ListBoxItem之间的角度差。如下图所示:
ListBoxItem的外观
ListBoxItem的外观也需要通过模板来自定义。注意观察下图。实际上这样一个ListBoxItem是由如下五个元素组成的,分别是一个Path,三个Rectangle和一个TextBlock组成的。可能有人要问,为什么是三个Rectangle,实际上只是为了让这个边框一层一层更有层次感。
好了,我们来看看代码。

<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid x:Name="gridSwatch" Height="180" Background="#00000000" RenderTransformOrigin="0.447,0.88" Width="55" Cursor="Hand" >
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform/>
<RotateTransform Angle="90"/>
<TranslateTransform X="-65.75" Y="-71.05"/>
</TransformGroup>
</Grid.RenderTransform>
<Path x:Name="overShape" Data="M0,3.0000024 C0,1.3431468 1.3431457,0 3,0 L47,0 C48.656853,0 50,1.3431468 50,3.0000024 L50,185.00012 C50,186.65698 48.656853,188.00014 47,188.00014 C30.666666,190.16705 26.666666,222.49997 24.666666,231.5 C22.833332,222.49997 14,187.83371 3,188.00014 C1.3431457,188.00014 0,186.65698 0,185.00012 z" Fill="#FF3C3C3C" Stroke="{x:Null}" Margin="0,-4,0,-47.5" IsHitTestVisible="False" Visibility="Collapsed"/>
<Rectangle x:Name="shadow3" Margin="-4,-8,-4,-8" Fill="#FF000000" Stroke="{x:Null}" RadiusX="7" RadiusY="7" Opacity="0.08" IsHitTestVisible="False"/>
<Rectangle x:Name="shadow2" Margin="-3,-7,-3,-7" Fill="#FF000000" Stroke="{x:Null}" RadiusX="6" RadiusY="6" Opacity="0.08" IsHitTestVisible="False"/>
<Rectangle x:Name="shadow1" Margin="-2,-6,-2,-6" Fill="#FF000000" Stroke="{x:Null}" RadiusX="5" RadiusY="5" Opacity="0.08" IsHitTestVisible="False"/>
<Rectangle x:Name="whiteSwatch" Margin="-1,-5,-1,-5" Fill="#FFFFFFFF" Stroke="{x:Null}" RadiusX="4" RadiusY="4" IsHitTestVisible="False"/>
<TextBlock FontSize="22" FontFamily="华文行楷" Text="{Binding XPath=ImageText}" >
<TextBlock.RenderTransform>
<TransformGroup>
<RotateTransform Angle="90"/>
<TranslateTransform X="30" Y="30"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
上面的代码有几处,Helloj2ee简单说明一下。
(1)ListBoxItem自定义的模板,首先是一个Grid面板,该面板用了好几个变换,构成了一个变换组,尽管有诸如ScaleTransform等都未填写,但是这是为后面的动画埋下伏笔。
(2)这样的Path,的确难以用VS2010绘制出来,那么使用Expression Blend绘制就好,此外IsHitTestVisible属性设置为False,表明它并不接受用户的输入,只是作为一个基本的图元构成,同时设置了该Path在平时并不可见。
(3)注意TextBlock通过XPath属性设置了和XML文件的绑定。
好了再次运行程序,您看到了什么?
即使整个ListBox已经变样,但是它选择的效果可以说极其不好,因此我们必须再接再力,给它添加上一些动画效果。添加动画的效果主要靠控件模板的Trigger来触发了。大家先看代码,然后Helloj2ee再加以解释。

<EventTrigger RoutedEvent="ListBoxItem.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="gridSwatch" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeySpline="0.5,0,0.5,1" KeyTime="00:00:00.1000000" Value="1.2"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="gridSwatch" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
<SplineDoubleKeyFrame KeySpline="0.5,0,0.5,1" KeyTime="00:00:00.1000000" Value="1.2"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="overShape" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="ListBoxItem.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="gridSwatch" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeySpline="0.5,0,0.5,1" KeyTime="00:00:00.2000000" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="gridSwatch" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
<SplineDoubleKeyFrame KeySpline="0.5,0,0.5,1" KeyTime="00:00:00.2000000" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="overShape" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Panel.ZIndex"
Value="1" />
</Trigger>
</ControlTemplate.Triggers>
主要是在MouseEnter和MouseLeave事件里做文章,当鼠标滑过某个ListBoxItem,我们通过改变它的ScaleTransform,让它变大。而离开ListBoxItem再通过改变ScaleTransform,让它恢复原状。比如像这样的语法,就未必有很多人能够写出来。请大家留心。
光是变换还是不够的,因为当鼠标滑过的时候,很有可能希望这个ListBoxItem凸现出来,那么要用到的就是Panel.ZIndex这个附加属性,通过改变这个值,让鼠标滑过的ListBoxItem凸现出来。
好了,现在的ListBoxItem已经完全变样了,但是还有美中不足。最为美中不足的就是ListBoxItem选中的时候,会出现一个虚框,这样的视觉效果确实很不好。
解决之道,又是一句话。
还有美中不足,比如选中某一个ListBoxItem,我们该作如何反应呢。这里helloj2ee就不再写这样的代码了。各位去尝试一下吧。
剩下的Helloj2ee需要做的是让这个对话框透明起来和加上中间的小圆点。中间的小圆点并不难加,让对话框完全透明也So Easy。那么Helloj2ee向各位奉上代码就好了。如果真的新手不知道该如何让不规则对话框透明的话,那么不妨参见葵花宝典第8章8.3.4节好了。
源代码:/Files/helloj2ee/ListBoxDemo3.rar
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?