简体   繁体   English

如何在 WPF 中制作渲染循环?

[英]How to make a render loop in WPF?

How can I create a loop that will be continuously executed whenever the message loop is idle in WPF?如何创建一个循环,只要 WPF 中的消息循环空闲,该循环就会连续执行?

The goal here is to perform some long running graphical update, such as refreshing a PicktureBox , that is capable of consuming whatever free resources are available but shouldn't freeze the UI or otherwise take priority over any other operations in the message queue.这里的目标是执行一些长时间运行的图形更新,例如刷新PicktureBox ,它能够消耗任何可用的免费资源,但不应冻结 UI 或以其他方式优先于消息队列中的任何其他操作。

I noticed this blog post which provides the code to do this in a winforms application, but I don't know how to translate it to a WPF application.我注意到 这篇博客文章提供了在 winforms 应用程序中执行此操作的代码,但我不知道如何将其转换为 WPF 应用程序。 Below is the code of a WinForms render loop class that I made based on the other article:下面是我根据另一篇文章制作的 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
    }
}

最好的方法是使用静态CompositionTarget.Rendering事件提供的每帧回调。

To elaborate a bit on the answer of Oren, you can attach a method to the Rendering event like this:为了详细说明 Oren 的答案,您可以将一个方法附加到 Rendering 事件,如下所示:

CompositionTarget.Rendering += Loop;

The Loop function can then update the properties of an element, in this example an element positioned on a Canvas:然后 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 https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.compositiontarget.rendering?view=net-5.0

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM