// Copyright (c) 2010-2014 SharpDX - Alexandre Mutel // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Globalization; using System.Windows.Forms; using System.Runtime.InteropServices; using SharpDX.Win32; namespace SharpDX.Windows { /// /// RenderLoop provides a rendering loop infrastructure. See remarks for usage. /// /// /// Use static /// method to directly use a renderloop with a render callback or use your own loop: /// /// control.Show(); /// using (var loop = new RenderLoop(control)) /// { /// while (loop.NextFrame()) /// { /// // Perform draw operations here. /// } /// } /// /// Note that the main control can be changed at anytime inside the loop. /// public class RenderLoop : IDisposable { private IntPtr controlHandle; private Control control; private bool isControlAlive; private bool switchControl; /// /// Initializes a new instance of the class. /// public RenderLoop() {} /// /// Initializes a new instance of the class. /// public RenderLoop(Control control) { Control = control; } /// /// Gets or sets the control to associate with the current render loop. /// /// The control. /// Control is already disposed public Control Control { get { return control; } set { if(control == value) return; // Remove any previous control if(control != null && !switchControl) { isControlAlive = false; control.Disposed -= ControlDisposed; controlHandle = IntPtr.Zero; } if (value != null && value.IsDisposed) { throw new InvalidOperationException("Control is already disposed"); } control = value; switchControl = true; } } /// /// Gets or sets a value indicating whether the render loop should use the default instead of a custom window message loop lightweight for GC. Default is false. /// /// true if the render loop should use the default instead of a custom window message loop (default false); otherwise, false. /// By default, RenderLoop is using a custom window message loop that is more lightweight than to process windows event message. /// Set this parameter to true to use the default . public bool UseApplicationDoEvents { get; set; } /// /// Calls this method on each frame. /// /// true if if the control is still active, false otherwise. /// An error occured public bool NextFrame() { // Setup new control // TODO this is not completely thread-safe. We should use a lock to handle this correctly if (switchControl && control != null) { controlHandle = control.Handle; control.Disposed += ControlDisposed; isControlAlive = true; switchControl = false; } if(isControlAlive) { if(UseApplicationDoEvents) { // Revert back to Application.DoEvents in order to support Application.AddMessageFilter // Seems that DoEvents is compatible with Mono unlike Application.Run that was not running // correctly. Application.DoEvents(); } else { var localHandle = controlHandle; if (localHandle != IntPtr.Zero) { // Previous code not compatible with Application.AddMessageFilter but faster then DoEvents NativeMessage msg; while (Win32Native.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0) != 0) { if (Win32Native.GetMessage(out msg, IntPtr.Zero, 0, 0) == -1) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "An error happened in rendering loop while processing windows messages. Error: {0}", Marshal.GetLastWin32Error())); } // NCDESTROY event? if (msg.msg == 130) { isControlAlive = false; } var message = new Message() { HWnd = msg.handle, LParam = msg.lParam, Msg = (int)msg.msg, WParam = msg.wParam }; if (!Application.FilterMessage(ref message)) { Win32Native.TranslateMessage(ref msg); Win32Native.DispatchMessage(ref msg); } } } } } return isControlAlive || switchControl; } private void ControlDisposed(object sender, EventArgs e) { isControlAlive = false; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Control = null; } /// /// Delegate for the rendering loop. /// public delegate void RenderCallback(); /// /// Runs the specified main loop in the specified context. /// public static void Run(ApplicationContext context, RenderCallback renderCallback) { Run(context.MainForm, renderCallback); } /// /// Runs the specified main loop for the specified windows form. /// /// The form. /// The rendering callback. /// if set to true indicating whether the render loop should use the default instead of a custom window message loop lightweight for GC. Default is false. /// form /// or /// renderCallback public static void Run(Control form, RenderCallback renderCallback, bool useApplicationDoEvents = false) { if(form == null) throw new ArgumentNullException("form"); if(renderCallback == null) throw new ArgumentNullException("renderCallback"); form.Show(); using (var renderLoop = new RenderLoop(form) { UseApplicationDoEvents = useApplicationDoEvents }) { while(renderLoop.NextFrame()) { renderCallback(); } } } /// /// Gets a value indicating whether this instance is application idle. /// /// /// true if this instance is application idle; otherwise, false. /// public static bool IsIdle { get { NativeMessage msg; return (bool)(Win32Native.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0) == 0); } } } }