如何在WPF中创建渲染循环?

时间:2013-06-07 19:53:06

标签: c# wpf rendering frame-rate

如何在WPF中消息循环空闲时创建一个连续执行的循环?

此处的目标是执行一些长时间运行的图形更新,例如刷新PicktureBox,它能够消耗任何可用的免费资源,但不应冻结UI或以其他方式优先于任何其他操作在消息队列中。

我注意到this blog post提供了在winforms应用程序中执行此操作的代码,但我不知道如何将其转换为WPF应用程序。下面是我根据另一篇文章制作的WinForms渲染循环类的代码:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

namespace Utilities.UI
{
    /// <summary>
    /// WinFormsAppIdleHandler implements a WinForms Render Loop (max FPS possible).
    /// Reference: http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
    /// </summary>
    public sealed class WinFormsAppIdleHandler
    {
        private readonly object _completedEventLock = new object();
        private event EventHandler _applicationLoopDoWork;

        //PRIVATE Constructor
        private WinFormsAppIdleHandler()
        {
            Enabled = false;
            SleepTime = 10;
            Application.Idle += Application_Idle;
        }

        /// <summary>
        /// Singleton from:
        /// http://csharpindepth.com/Articles/General/Singleton.aspx
        /// </summary>
        private static readonly Lazy<WinFormsAppIdleHandler> lazy = new Lazy<WinFormsAppIdleHandler>(() => new WinFormsAppIdleHandler());
        public static WinFormsAppIdleHandler Instance { get { return lazy.Value; } }

        /// <summary>
        /// Gets or sets if must fire ApplicationLoopDoWork event.
        /// </summary>
        public bool Enabled { get; set; }

        /// <summary>
        /// Gets or sets the minimum time betwen ApplicationLoopDoWork fires.
        /// </summary>
        public int SleepTime { get; set; }

        /// <summary>
        /// Fires while the UI is free to work. Sleeps for "SleepTime" ms.
        /// </summary>
        public event EventHandler ApplicationLoopDoWork
        {
            //Reason of using locks:
            //http://stackoverflow.com/questions/1037811/c-thread-safe-events
            add
            {
                lock (_completedEventLock)
                    _applicationLoopDoWork += value;
            }

            remove
            {
                lock (_completedEventLock)
                    _applicationLoopDoWork -= value;
            }
        }

        /// <summary>
        /// FINALMENTE! Imagem ao vivo sem travar! Muito bom!
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Application_Idle(object sender, EventArgs e)
        {
            //Try to update interface
            while (Enabled && IsAppStillIdle())
            {
                OnApplicationIdleDoWork(EventArgs.Empty);
                //Give a break to the processor... :)
                //8 ms -> 125 Hz
                //10 ms -> 100 Hz
                Thread.Sleep(SleepTime);
            }
        }

        private void OnApplicationIdleDoWork(EventArgs e)
        {
            var handler = _applicationLoopDoWork;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Gets if the app still idle.
        /// </summary>
        /// <returns></returns>
        private static bool IsAppStillIdle()
        {
            bool stillIdle = false;
            try
            {
                Message msg;
                stillIdle = !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
            }
            catch (Exception e)
            {
                //Should never get here... I hope...
                MessageBox.Show("IsAppStillIdle() Exception. Message: " + e.Message);
            }
            return stillIdle;
        }

        #region  Unmanaged Get PeekMessage
        // http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
        [System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);

        #endregion
    }
}

2 个答案:

答案 0 :(得分:10)

执行此操作的最佳方法是使用静态CompositionTarget.Rendering事件提供的每帧回调。

答案 1 :(得分:0)

为了详细说明 Oren 的答案,您可以将一个方法附加到 Rendering 事件,如下所示:

CompositionTarget.Rendering += Loop;

然后循环函数可以更新一个元素的属性,在这个例子中是一个位于 Canvas 上的元素:

private void Loop(object sender, EventArgs e)
{
    LeftPos++;
    Canvas.SetLeft(ball, LeftPos);
}

https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.compositiontarget.rendering?view=net-5.0