更好的算法来淡化winform
Posted
技术标签:
【中文标题】更好的算法来淡化winform【英文标题】:Better algorithm to fade a winform 【发布时间】:2012-09-11 22:42:00 【问题描述】:在搜索淡化 winform 的代码时,我在 MSDN 论坛上看到了这个 page。
for (double i = 0; i < 1; i+=0.01)
this.Opacity = i;
Application.DoEvents();
System.Threading.Thread.Sleep(0);
for
循环有一个非整数增量,根据我之前提出的问题,这不是一种好的编程技术(由于大多数小数的表示不精确)。
我想出了这个替代方案。
for (double i = 0; i < 100; ++i)
this.Opacity = i/100;
Application.DoEvents();
System.Threading.Thread.Sleep(0);
哪一个更高效?
如果有更好的表格淡化算法,我会很高兴的。
谢谢。
【问题讨论】:
@DarrenDavies 在 Win32,IIRC 上,Sleep(0)
是将线程的剩余时间返回给调度程序,以让其他线程有机会运行。也许在 .NET 上是一样的。 MSDN states: "如果您指定 0 毫秒,线程将放弃其时间片的剩余部分但保持就绪状态。"
简单地将int
更改为double
怎么样?
vb.net Single 在 C# 中是“float”,而不是 int。
您可以查看 Windows API - codeproject.com/Articles/43058/…
【参考方案1】:
忘记计时器(双关语)。
使用 Visual Studio 4.5 或更高版本,您只需 await
延迟任务。这种方法的一个优点是它是异步的,不像线程Sleep
或DoEvents
循环,它会在淡入淡出期间阻塞应用程序(以及前面提到的其他DoEvents
问题)。
private async void FadeIn(Form o, int interval = 80)
//Object is not fully invisible. Fade it in
while (o.Opacity < 1.0)
await Task.Delay(interval);
o.Opacity += 0.05;
o.Opacity = 1; //make fully visible
private async void FadeOut(Form o, int interval = 80)
//Object is fully visible. Fade it out
while (o.Opacity > 0.0)
await Task.Delay(interval);
o.Opacity -= 0.05;
o.Opacity = 0; //make fully invisible
用法:
private void button1_Click(object sender, EventArgs e)
FadeOut(this, 100);
在对对象应用任何透明度之前,您应该检查对象是否已被处置。我使用表单作为对象,但您可以传递任何支持透明度的对象,只要它正确转换即可。
【讨论】:
当我从后台工作人员完成事件中调用 FadeIn 时,'await' 行会导致死锁。我怎样才能避免这种情况? @NineTails 你试过将 FadeIn 方法包装在 this.BeginInvoke(new Action(() => FadeIn(...))) 中吗? @drake7707 谢谢。不过,我之前用 'SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());' 解决了我的问题 我在 WPF 中尝试了上述方法,它在没有线程修改的情况下完美运行(我确实必须删除对 Windows 窗体的引用,但这很容易)。我只需要先将 Window 不透明度设置为 0 才能使淡入工作。【参考方案2】:所以,首先,application.DoEvents should be avoided 除非您真的知道自己在做什么,并且确定这是对它的适当使用,并且您正在正确使用它。我相当肯定这里不是这种情况。
接下来,你如何控制衰落的速度?您基本上只是让计算机尽可能快地消失,并依靠操作(和后台进程)的固有开销来使其花费更长的时间。这真的不是很好的设计。您最好指定淡入淡出从一开始需要多长时间,以便它在机器之间保持一致。您可以使用 Timer
以适当的设置间隔执行代码,并确保 UI 线程在淡入淡出期间不被阻塞(不使用 DoEvents
)。
只需修改下面的duration
以更改淡入淡出所需的时间,并修改steps
以确定它有多“波折”。我将其设置为 100,因为这实际上是您的代码之前所做的。实际上,您可能不需要那么多,您可以降低到它开始变得波涛汹涌之前。 (步数越低,性能越好。)
此外,您不应该担心此类事情的性能。褪色是需要在大约一秒或不少于一秒的范围内测量的东西(人类能够感知它),而对于如今的任何计算机来说,它都可以做到这一点,远不止于此在一秒钟内,它甚至都不好笑。就计算而言,这将在一秒钟内几乎不消耗 CPU,因此尝试优化它肯定是微优化。
private void button1_Click(object sender, EventArgs e)
int duration = 1000;//in milliseconds
int steps = 100;
Timer timer = new Timer();
timer.Interval = duration / steps;
int currentStep = 0;
timer.Tick += (arg1, arg2) =>
Opacity = ((double)currentStep) / steps;
currentStep++;
if (currentStep >= steps)
timer.Stop();
timer.Dispose();
;
timer.Start();
【讨论】:
这是一个精明的评论,我特别喜欢你坚持“应该避免 DoEvents”的事实......它真的应该。我也做了一些非常相似的事情,但是你错过了一些重要的事情:你应该在某个时候停止计时器!!!在 Tick 处理程序中,包括检查if (Opacity >= 1.0) timer.Stop(); timer.Dispose();
@khovanskiiªn 谢谢,我完全忘了停止计时器;编辑。【参考方案3】:
我写了一个专门用于淡入淡出表单的类。它甚至支持ShowDialog
和DialogResults。
我已经扩展了它,因为我需要新功能,并且愿意接受建议。你可以看看这里:
https://gist.github.com/nathan-fiscaletti/3c0514862fe88b5664b10444e1098778
示例用法
private void Form1_Shown(object sender, EventArgs e)
Fader.FadeIn(this, Fader.FadeSpeed.Slower);
【讨论】:
【参考方案4】:for (double i = 0; i < 1; i+=0.01)
this.Opacity = i;
Application.DoEvents();
System.Threading.Thread.Sleep(0);
效率更高,因为浮点除法的数量比浮点加法(不影响 vm-flags)更昂贵。也就是说,您可以将迭代次数减少 1/2(即将步长更改为 i+=0.02)。人脑不会注意到 1% 的不透明度降低,而且成本也会更低,将其加速几乎 100%。
编辑:
for(int i = 0; i < 50; i++)
this.Opacity = i * 0.02;
Application.DoEvents();
System.Threading.Thread.Sleep(0);
【讨论】:
【参考方案5】:我将 Victor Stoddard 的方法应用于启动画面。我在 Form_Load 事件中使用它来淡入淡出,在 FormClosing 事件中使用它来淡出。 注意:在调用 fadeIn 方法之前,我必须将表单的不透明度设置为 0。
在这里你可以看到一个winform(生命周期)引发的事件顺序: https://msdn.microsoft.com/en-us/library/86faxx0d(v=vs.110).aspx
private void Splash_Load(object sender, EventArgs e)
this.Opacity = 0.0;
FadeIn(this, 70);
private void Splash_FormClosing(object sender, FormClosingEventArgs e)
FadeOut(this, 30);
private async void FadeIn(Form o, int interval = 80)
//Object is not fully invisible. Fade it in
while (o.Opacity < 1.0)
await Task.Delay(interval);
o.Opacity += 0.05;
o.Opacity = 1; //make fully visible
private async void FadeOut(Form o, int interval = 80)
//Object is fully visible. Fade it out
while (o.Opacity > 0.0)
await Task.Delay(interval);
o.Opacity -= 0.05;
o.Opacity = 0; //make fully invisible
【讨论】:
【参考方案6】:过去,我曾使用AnimateWindow
淡入/淡出生成的表单,该表单在SystemColor.WindowColor
中的整个应用程序中空白。
这个巧妙的小技巧提供了在类似向导的界面中隐藏/交换/显示屏幕的效果。我已经有一段时间没有做过这种事情了,但我在 VB 中使用了 P/Invoke,并在它自己的线程中运行了 API。
我知道您的问题是在 C# 中,但大致相同。这是我挖出来的一些可爱的 VB,自 2006 年以来就没有看过!显然,很容易调整它来淡入淡出你自己的表单。
<DllImport("user32.dll")> _
Public Shared Function AnimateWindow(ByVal hwnd As IntPtr, ByVal dwTime As Integer, ByVal dwFlags As AnimateStyles) As Boolean
End Function
Public Enum AnimateStyles As Integer
Slide = 262144
Activate = 131072
Blend = 524288
Hide = 65536
Center = 16
HOR_Positive = 1
HOR_Negative = 2
VER_Positive = 4
VER_Negative = 8
End Enum
Private m_CoverUp As Form
Private Sub StartFade()
m_CoverUp = New Form()
With m_CoverUp
.Location = Me.PointToScreen(Me.pnlMain.Location)
.Size = Me.pnlMain.Size
.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
.BackColor = Drawing.SystemColors.Control
.Visible = False
.ShowInTaskbar = False
.StartPosition = System.Windows.Forms.FormStartPosition.Manual
End With
AnimateWindow(m_CoverUp.Handle, 100, AnimateStyles.Blend) 'Blocks
Invoke(New MethodInvoker(AddressOf ShowPage))
End Sub
Private Sub EndFade()
AnimateWindow(m_CoverUp.Handle, 100, AnimateStyles.Blend Or AnimateStyles.Hide)
m_CoverUp.Close()
m_CoverUp = Nothing
End Sub
【讨论】:
这是 Visual Basic。 问题在 C# 中【参考方案7】:Victor Stoddard 很接近,但我发现如果它开始快速增加不透明度并在接近完全不透明度 1 时变慢,那么渐变会更令人愉悦。以下是对他的代码的轻微修改:
using System.Threading.Tasks;
using System.Windows.Forms;
public partial class EaseInForm : Form
public EaseInForm()
InitializeComponent();
this.Opacity = 0;
private async void Form_Load(object sender, EventArgs e)
while (this.Opacity < 1.0)
var percent = (int)(this.Opacity * 100);
await Task.Delay(percent);
this.Opacity += 0.04;
【讨论】:
以上是关于更好的算法来淡化winform的主要内容,如果未能解决你的问题,请参考以下文章
滚动 uiscrollview 时淡化 UITextView