Windows窗体中的淡化效果

我试图使用Windows窗体向按钮,图片框和文本框添加一些淡化效果。

我知道我应该使用WPF,但是我从来没有使用它,这对我来说很复杂,而我的项目是现在提前切换平台。

我有这个,但不透明度/褪色效果根本不平滑。

public void Animation(Button button1) { var expandTimer = new System.Windows.Forms.Timer(); var contractTimer = new System.Windows.Forms.Timer(); expandTimer.Interval = 10; contractTimer.Interval = 10; DateTime animationStarted = DateTime.Now; TimeSpan animationDuration = TimeSpan.FromMilliseconds(250); int initialWidth = 75; int endWidth = 130; button1.MouseHover += (_, args) => { contractTimer.Stop(); expandTimer.Start(); animationStarted = DateTime.Now; button1.ForeColor = Color.DimGray; }; button1.MouseLeave += (_, args) => { expandTimer.Stop(); contractTimer.Start(); animationStarted = DateTime.Now; button1.ForeColor = Color.Red; }; expandTimer.Tick += (_, args) => { double percentComplete = (DateTime.Now - animationStarted).Ticks / (double)animationDuration.Ticks; button1.Visible = false; if (percentComplete >= 1) { expandTimer.Stop(); } else { button1.Width = (int)(initialWidth + (endWidth - initialWidth) * percentComplete); } }; contractTimer.Tick += (_, args) => { double percentComplete = (DateTime.Now - animationStarted).Ticks / (double)animationDuration.Ticks; button1.Visible = true; if (percentComplete >= 1) { contractTimer.Stop(); } else { button1.Width = (int)(endWidth - (endWidth - initialWidth) * percentComplete); } }; } 

如何在不必切换到WPF的情况下修复不那么平滑的过渡?

首先让我说,你可能甚至不在乎得到一个答案,因为这个问题差不多是1岁。 我觉得有必要发布这个答案,因为我认为它确实有助于回答OP的问题,而不是提供解决方案,而是提供如何获得解决方案的路线图……我认为这是最好的学习方式。

让我们开始…

转换是不稳定的,因为当鼠标hover在控件上时,“MouseHover”的事件处理程序会不断触发。 这会导致您的动画中断并使其显得不连贯。

我的建议是,使用“MouseEnter”事件来启动计时器。

稍微调查一下,您可能会发现两个计时器之间存在相似之处,并使用单个计时器通过创建自己的计时器来执行这两项任务,该计时器具有可由这两个事件触发的方法,从而使按钮增大或缩小。

此外,虽然人们已经建议WPF(我并不反对,WPF可能有用),但不要对System.Windows.Forms失去信心。

虽然在WPF中可能更容易支持,但这里的解决方案适用于WinForms。

当我需要动画时,我发现让一个单一的后台线程更容易在一个跟踪进度的实例上协调动画。 拥有一个简单的动画类实例,后台工作人员经常调用这个实例,这让人头疼得多。

AnimateButton

让我们首先看看我们如何为按钮设置动画。 对于内部管理和跟踪我们已经有动画的按钮,我们使用静态方法Animate 。 它将按钮和方向作为参数,然后查找或更新属于该按钮的实例。

关键特性是Execute方法,它是接口ICommandExecutor一个实现。 每次需要动画的下一步(以30 fps的速率调用)时,都会调用该方法。 在这种情况下,我只根据方向更改宽度,但如果需要,可以更改更多属性。 不要循环或阻止此方法,因为Excecute将在UI线程上运行,并且该线程不喜欢被持有太多。

  // resize a button private class AnimateButton : ICommandExecutor { // keep track of instances of this class static ConcurrentDictionary dict = new ConcurrentDictionary(); // Update or create an animation for a button static public void Animate(Button sender, Direction direction) { AnimateButton animate; // find it... if (dict.TryGetValue(sender, out animate)) { animate.SetDirection(direction); } else { // create a new one animate = new AnimateButton(sender); animate.SetDirection(direction); if (dict.TryAdd(sender, animate)) { Animations.List.Add(animate); } else { Trace.WriteLine("button not added ?!?"); } } } int initialWidth = 75; int endWidth = 130; public enum Direction { None, Shrink, Grow } Direction direction = Direction.None; readonly Button button; private AnimateButton(Button button) { this.button = button; } public void SetDirection(Direction direction ) { this.direction = direction; } // this gets called by the progress event public void Execute() { switch(direction) { case Direction.Grow: if (button.Width < endWidth) { button.Width++; } else { direction = Direction.None; } break; case Direction.Shrink: if (button.Width > initialWidth) { button.Width--; } else { direction = Direction.None; } break; } } } 

ActiveAnimations

在forms上,他们需要成为需要处理的动画的主列表。 ActiveAnimations类就是这样做的。 它包含一个ICommandExcutor实例的ConcurrentBag

  // for multiple animations private class Animations { // both the UI thread and backgroud thread will use this static ConcurrentBag activeAnimations = new ConcurrentBag(); public static ConcurrentBag List { get { return activeAnimations; } } } 

计时器

为了向前驱动动画,使用了Timer 。 我更喜欢那个,因为它具有切换到UI线程的能力,而无需额外的代码行。 定时器间隔设置为30毫秒。

 private void timer1_Tick(object sender, EventArgs e) { // loop over all active animations and have them execute one step foreach (var command in Animations.List) { command.Execute(); } } 

不要忘记在Form_Load事件中启动计时器:

  private void Form1_Load(object sender, EventArgs e) { timer1.Enabled = true; timer1.Start(); } 

hover和离开事件

当我们有管道时,我们现在可以在按钮上的事件中创建AnimateButton实例:

  private void button1_MouseHover(object sender, EventArgs e) { AnimateButton.Animate((Button)sender, AnimateButton.Direction.Grow); } private void button1_MouseLeave(object sender, EventArgs e) { AnimateButton.Animate((Button)sender, AnimateButton.Direction.Shrink); } 

您可以设置多个按钮动画。 我用2个按钮测试它,效果很好。 您始终可以考虑通过增加计时器的间隔来减少fps。

如果一切正确实现,那将是您的结果:

动画按钮在行动中