7

How can I create a loop that will be continuously executed whenever the message loop is idle in 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.

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. Below is the code of a WinForms render loop class that I made based on the other article:

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
    }
}
Glen Thomas
  • 10,190
  • 5
  • 33
  • 65
Pedro77
  • 5,176
  • 7
  • 61
  • 91
  • 2
    Your question should be self-contained. We should be able to answer it without needing to follow any links. – Servy Jun 07 '13 at 19:53
  • Sorry boss, is it ok for you now? – Pedro77 Jun 07 '13 at 19:57
  • Meh, explaining what you want to do is probably more important than showing the code, although that's certainly better than just a link. – Servy Jun 07 '13 at 19:59
  • 2
    I came here to help you improve your question so that, being a better question, it's much more likely to get high quality answers instead of just being ignored. If you aren't interested in help improving your question then that's fine, but your edits indicate that you are. – Servy Jun 07 '13 at 20:11
  • 1
    [`Application.Idle`](https://msdn.microsoft.com/en-us/library/system.windows.forms.application.idle(v=vs.110).aspx#Anchor_1): *Because this is a static event, you must detach your event handlers when your application is disposed, or memory leaks will result.* – Reza Aghaei Feb 09 '17 at 19:30

2 Answers2

12

The best way to do this is to use the per-frame callbacks provided by the static CompositionTarget.Rendering event.

swalex
  • 3,885
  • 3
  • 28
  • 33
Oren
  • 3,273
  • 2
  • 19
  • 15
1

To elaborate a bit on the answer of Oren, you can attach a method to the Rendering event like this:

CompositionTarget.Rendering += Loop;

The Loop function can then update the properties of an element, in this example an element positioned on a Canvas:

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

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

metatron
  • 896
  • 7
  • 14