更好的算法来淡化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 延迟任务。这种方法的一个优点是它是异步的,不像线程SleepDoEvents 循环,它会在淡入淡出期间阻塞应用程序(以及前面提到的其他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 &gt;= 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

Swift - 使用 animateWithDuration 淡化 CAGradientLayer

如何淡化背景而不是仅仅改变颜色

以编程方式淡化颜色

如何淡化带有重叠文本的标题

如何使用 iCarousel 淡化 currentItem 之前的所有项目?