Reputation: 654
I want to make a render loop to render on a WPF Window or a WinForm. Therefore I want to use SharpGL (https://sharpgl.codeplex.com/). To make my loop I made a thread:
public void Run()
{
IsRunning = true;
this.Initialize();
while (IsRunning)
{
Render(/* arguments here */);
// pausing and stuff
}
Dispose();
}
In Render I want to send the Draw Calls to the GPU. So far there is no problem. But Winforms and WPF need their own thread and loop. So I can't just create a Window and draw onto like in Java with LWJGL (https://www.lwjgl.org/), which I used before. I have to start another thread, that runs the Form in an Application (I cut out error handling to make it short):
[STAThread]
private void HostWinFormsApplication()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(true);
// get Display
Display = Engine.MainModule.Resolve<IDisplay>();
Display.Initialize();
Display.Closing += (s, e) => Stop();
Application.Run(Display as Form);
}
When my Renderer wants to access the OpenGL-Control on my Form and use it, an error occurs, as WinForms (and WPF) don't want their Controls to be manipulated by other Threads. So maybe an Invoke is an option, but this would delay my drawcalls and become a bottleneck. A timer isn't an option, too, as it isn't accurate and unflexible... And I simply don't like it. And doing everything inside the Window Code may be possible, but I want an application being independent of its Display, so that it can be changed. It should be an application having a Display not a Display running an application. In LWJGL I just had the possibility to create and initialize a Display and then simply use it. The only thing to consider was updating it and everything went fine. So I just want to create a Window in my render thread and draw onto. If I do it this way, the window just gets unusable and greyish as it needs this .Net-Loop. Is there any possibility to realize that or does anybody know another way to create Windows? Can I handle the window loop manually?
Any idea is welcome.
If there would be a way to do this with a WPF Window it would be awesome. Then I could have an OpenGL Control and all WPF-Stuff to make an Editor!
Upvotes: 1
Views: 2383
Reputation: 5294
Based on my question: How to make a render loop in WPF?
WPF render loop
The best way to do this is to use the per-frame callbacks provided by the static
CompositionTarget.Rendering event.
WinForms render loop
Below is the code of a WinForms render loop class that I made based on this blog post:
Just use:
WinFormsAppIdleHandler.Instance.ApplicationLoopDoWork += Instance_ApplicationLoopDoWork;
WinFormsAppIdleHandler.Instance.Enabled = true;
Class:
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 = 5; //You can play/test with this value
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
}
}
Upvotes: 1
Reputation: 654
The solution was to call
Application.DoEvents();
in my Render Loop manually, as someone told me. So I didn't had to use Application.Run
and were able to use my Form in the thread of the loop:
public void Run()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(true);
IsRunning = true;
this.Initialize();
MyForm = new CoolRenderForm();
MyForm.Show();
while (IsRunning)
{
Render(/* arguments here */);
Application.DoEvents();
// refresh form
// pausing and stuff
}
Dispose();
}
Upvotes: 1