C# WPF 动画图像按钮(无按钮)
一个可重用的类似按钮的控件,
引言
为 Windows 应用程序改善用户交互的一种常用方法是在按钮中放置图像,以可视化它们执行的操作,尤其是在操作比“提交”操作更抽象的情况下。 实现此目的最简单的方法是将简单的位图图像用作 Button
UIElement
的背景画笔。 然而,有时按钮点击动画的默认行为是不可取的。 在本文中,我希望概述一个可重用的 Rectangle
,以模拟 Button
的一般行为,但具有简单的图像过渡,并显示在 Grid
“快速工具栏”中。
简单来说,我试图创建一个控件,在用户点击它时闪烁图像负片,而无需使用按钮。
启动时图像缓存
为了消除显示动画时的任何延迟,我发现将所有图像加载到应用程序启动时的一个 Dictionary
集合中会更好。
这些是我在构建此控件时使用的图像。
我将这些图像作为项目中的资源添加。 它们都是 29x29 像素。
![]() | ![]() |
// the image dictionary
private Dictionary<string, ImageSource> _imageCache;
// helper function to convert resource bitmaps into a BitmapSource
// since resource images are loaded as plain bitmaps
private BitmapSource CreateSourceFromBitmap(System.Drawing.Bitmap bitmap)
{
return System.Windows.Interop.Imaging.CreateBitmapSourceFrom HBitmap(
bitmap.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions()
);
}
// loading the images into memory
private void LoadImages()
{
_imageCache = new Dictionary<string, ImageSource>();
_imageCache.Add("Search", CreateSourceFromBitmap(Properties.Resources.Search));
_imageCache.Add("SearchInverted", CreateSourceFromBitmap(Properties.Resources.SearchInverted));
}
这样,我们的图像就预加载好了,可以进行快速过渡动画。
快速工具栏
QuickBar
是一个简单的网格,用于包含按钮。 我在 Grid
中定义了按钮的大部分属性,以获得统一的外观。 此外,所有按钮事件都连接到单个事件,用于所有按钮。 我稍后会讨论如何处理它。 我选择这种方法,以便不需要为每个按钮处理事件处理程序。
<Grid x:Name="_grid_Content_QuickBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="38">
<!-- Add more columns for more buttons -->
<ColumnDefinition Width="1*">
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="Rectangle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Margin" Value="3" />
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
<Setter Property="RadiusX" Value="2" />
<Setter Property="RadiusY" Value="2" />
<Setter Property="Stroke" Value="#858585" />
<Setter Property="StrokeThickness" Value="1.5" />
<EventSetter Event="MouseEnter" Handler="QuickMenu_MouseEnter" />
<EventSetter Event="MouseLeave" Handler="QuickMenu_MouseLeave" />
<EventSetter Event="MouseDown" Handler="QuickMenu_MouseClick" />
</Style>
</Grid.Resources>
<Rectangle Grid.Column="0" ToolTip="Launch new search" x:Name="rect_Search">
<Rectangle.Fill>
<ImageBrush ImageSource=".\Resources\Search.png" Stretch="Uniform"/>
</Rectangle.Fill>
</Rectangle>
</Grid>
事件连接
由于我决定通过单个事件处理程序处理所有事件,因此需要一些代码来处理它们。 虽然我使用了在单个处理程序中处理 MouseDown
事件的方法,但将该事件处理程序添加到每个按钮同样容易。
// event function mapper
private Dictionary<string, Action> _buttonEventHandlers;
// a method to register events. Called during app initialization
private void RegisterEventHandlers()
{
_buttonEventHandlers = new Dictionary<string, Action>();
_buttonEventHandlers.Add(rect_Search.Name, SearchClick);
}
// button event handlers
// this one changes the button outline to highlight which button the mouse is over
private void QuickMenu_MouseEnter(object sender, RoutedEventArgs e)
{
Rectangle rect = sender as Rectangle;
if (rect != null)
{
rest.Stroke = Brushes.Thistle; // change the color of the rectangle border on mouseover
}
}
// this one removes the highlight when the mouse leaves the button
private void QuickMenu_MouseLeave(object sender, RoutedEventArgs e)
{
Rectangle rect = sender as Rectangle;
if (rect != null)
{
rect.Stroke = new SolidColorBrush
(Color.FromArgb(255, 85, 85, 85); // change the color back to the original on mouseleave
}
}
// this one handles the user clicks
private void QuickMenu_MouseClick(object sender, MouseButtonEventArgs e)
{
Rectangle rect = sender as Rectangle;
Action action;
if (rect != null)
{
action = _buttonEventHandlers[rect.Name];
action?.Invoke();
}
}
MouseLeave
事件的一个更好的方法是在启动代码中定义默认的 SolidColorBrush
,以提高一点性能,而不是每次点击时都创建一个新的画笔。动画按钮
为了确保每个按钮正确处理其图像,我添加了这段代码,它在映射到按钮点击事件的 Action
函数中调用。 动画是通过 ObjectAnimationUsingKeyFrames
类完成的。 ObjectAnimationUsingKeyFrames
与其他 KeyFrameAnimation
类不同之处在于,帧之间没有过渡平滑处理。
// the animation code
private void QuickButtonClicked(Rectange rect, ImageSource originalImg, ImageSource alternateImg)
{
ObjectAnimationUsingKeyFrames animation = new ObjectAnimationUsingKeyFrames()
{
AutoReverse = true,
Duration = new Duration(TimeSpan.FromSeconds(0.125)), // duration is very important here
RepeatBehavior = new RepeatBehavior(2D) // flash twice
};
var startFrame = new DiscreteObjectKeyFrame();
startFrame.Value = originalImg;
startFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.0));
animation.KeyFrames.Add(startFrame);
var altFrame = new DiscreteObjectKeyFrame();
altFrame.Value = alternateImg;
altFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.0625));
animation.KeyFrame.Add(altFrame);
rect.Fill.BeginAnimation(ImageBrush.ImageSourceProperty, animation);
}
// the button click handler
private void SearchClick()
{
QuickButtonClicked(rect_Search, _imageCache["Search"], _imageCache["SearchInverted"]);
// ToDo: respond to the click
}
在这里,我使用了 DiscreteObjectKeyFrame
类来定义动画的每个帧。 由于只有两个帧,所以很简单。 有两个重要部分,帧 Value
,设置为将显示的图像,以及 KeyTime
,指示在动画时间轴中移动到下一个帧的时间。 对于只有两个帧的情况,我将第二个 KeyTime
设置为持续时间的一半。
值得注意的是,虽然我使用了一个简单的动画,该动画在鼠标点击时快速切换两个图像,但使用更多的复杂图像动画,包含数十个 KeyFrame
,应该不会有太大的困难。
结论
我仍在学习 WPF 动画的力量,以创造更好的用户体验。 由于我对此遇到了一些困难,我想分享我学到的经验。
历史
- 2016/11/10:初始发布