// 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);
}
}
}
}