This project is read-only.

Xen in a Winform

Apr 17, 2009 at 4:10 AM

Question:  I'd like to host the WinFormsHostControl in a window created by native c++, is that going to be impossible because of an Xna requirement?

When you pass a WinFormsHostControl into Application.Run(), it looks up the parents of the control until it comes to a Windows Form, sets the parentForm and parentControl to the Form, and hooks the Form Closing event.  I think I can get the IntPtr for the parent window, and I can hook the Disposing event on the WinFormsHostControl instead of the form's Closing event.  But the assumption that the WinFormsHostControl is going to be a child of a .NET Form looks to be pretty seriously built-in to Application, and I'm guessing that's for a reason.  :-) 

So before I go wasting too much time, is there something obvious that's going to prove it impossible to parent the control in a native window?
Apr 17, 2009 at 4:27 AM
As far as I am aware, Windows forms is just a wrapper on top of native forms, so if you have the IntPtr handle to a window, then you can easily create a Control wrapper for it using System.Windows.Forms.Control.FromHandle(). I assume this also creates the correct class wrapper too, so an IntPtr to a form actually creates a Form class, etc. How well this would work, well, I have no idea. :-)
Apr 17, 2009 at 7:38 AM
Mmmmm, not sure I understand.  Assuming I could pass the IntPtr to a wrapper class, are you suggesting I'd just add the WinFormsHostControl to that and call Xen.Application.Run(WinFormsControl).  The Application class then doesn't need modification and will find the Form as the parent of the WinFormsHostControl?  If so, I need to describe what I'm trying to do a little more.

Actually, describing it is easy enough  I'm hoping to open a Xen window in a Visual Studio ToolWindow.

I did find a way to get the HWND of the main window of Visual Studio, but passing that to System.Windows.Forms.Control.FromIntPtr() returns null, as does System.Windows.Forms.Form.FromIntPtr().

I'm not sure what the parentForm is needed for.  Is Xna just using it to pump events?  So does it sound possible to get something running if I mod Xen.Application to add a third Run method which takes the WinFormsHostControl and the VS window's IntPtr, set parentForm to null, and add a parentFormHwnd field directly instead of looking recursively at the parent of the WinFormsHostControl?   I'd need to adjust WindowHandle to return the IntPtr if parentForm was null, and I'd still need to hook Control.Disposing instead of Form.Closed.  But does that sound even theoretically viable?
Apr 17, 2009 at 11:09 PM
Bloody hell in a VS tool window? :-) Crazy
I imagine it's quite possible, it'll just be rather complex. The only reason the parent form is used is simply to bind to the Closing event. (Which I just realised is not unbound).
So feel free to rip it to pieces. I'm quite sure it's buggy anyway (hence it's beta).
I'm sure VS will provide the ability to embed managed controls (even if they are just wrappers) into the IDE.
Apr 18, 2009 at 5:20 AM
Heh heh.  Yep, in a VS tool window.  Using VS Shell in isolated mode makes for quite a nice editor environment.  Adding a Xen client to a tool window makes it even nicer...

VS will display a managed form no problem.  My first test was to create a tool window that brought up a managed control containing Windows Media Player.  (Want to mess with your brain?  Write code while Media Player visualizations are displaying in a tool window next to where you're typing.)  But initially when I replaced WMP with a XenWinFormsHostControl there were no events being hooked up to respond to the message pump (which happens in Xna).
After Frankensteining Application and WinFormsHost.cs a bit, I've got Xen hooked to some of the events, but I'm not getting a Paint event, so it just displays a white control with a red X in it.  So some more work to do yet.

But while I was messing about in Application I found that in the XNAWinFormsHostAppWrapper constructor, this line:  (can't give you the line number because of my edits)
    formsDeviceService = new WinFormsHostGraphicsDeviceService(this, this.control.ClientSize.Width, this.control.ClientSize.Width )

I'm thinking that third parameter should have been *Height*, not Width again...
Apr 18, 2009 at 7:09 AM
Aw man... this is exactly what I needed a few months back when i developed this...
It would have been nicer to use the VS editor instead of finding a clone VS editor control.

Please let us know if you get it to work! =)
Apr 18, 2009 at 10:06 PM
Ok, basic functionality works.  I've got Tutorial2 displaying in a Visual Studio tool window.  Haven't tried anything more ambitious yet... I won't say it's working until I've tested the animation stress test and the particle tuts in a tool window.  But it definitely looks likely that everything will work ok.

The key here is that I was making things difficult on myself.  I automatically assumed the VS tool window had to host a user control and *not* a Form.  But in fact there's nothing about a Form that interferes with the normal operation of a VS tool window, so use the walkthrough in the VS SDK, and replace the user control they create with a Form.  Then in the toolwindow event handler when the window is displayed, create your Xen Application.  Run that, passing your Form ( created by the toolwindow ) as a parameter.  And it just works!  Doing it that way requires *no* changes to the Xen libraries at all.  Sweet!

I didn't anticipate the toolwindow events working the way they do, and that caused me some problems, too.  I'm definitely hooking the wrong event at the moment, and it's being called 3-6 times during the creation of the window.  Until I cached my Xen Application object and checked for its existence, I was creating three Applications when the tool window was displayed and that was causing Xen to deadlock (I think.)  

While I've got Tut2 displaying in a toolwindow, the code is definitely held together by duct tape and baling wire.  I need to spend more time researching the VS Extensibility docs to figure out exactly how I should be wiring things up.  But anyway, things are looking good at the moment.

Apr 18, 2009 at 11:12 PM
> The key here is that I was making things difficult on myself.  I automatically assumed the VS tool window had to host a user control and *not* a Form.

Form extends the Control class so technically it is a control ;)
Form : ContainerControl : ScrollableControl : Control, IComponent, IDisposable

You can even add a form to a form as well lol

x = new Form();
x.TopLevel = false;

Anyways, if you get the stress test running tell us how you did it =)
Apr 19, 2009 at 6:36 AM
Question about the ContentManager...

Looks like its created with its root directory set to the current working directory + "/Content"  Inside my VS project, the working directory is the GAC (!) and because of the way I'm launching the test copy of VS, in the experimental hive, I don't have direct control over that (not that I know of, anyway)  Ok, so not a problem, I can just set the content manager's root directory before its first use, or modify WinFormsHost to take a root directory to create the ContentManager with.

But... my app is throwing an exception during manager.Load<ModelData>().  Says it can't find the ContentTypeReader for ModelData.  And when I look at the dll in VS's ObjectManager, sure enough, it's not there.  It *is* there when I look at it in the Xen library solution.  What's going on?  The RuntimeReader class is internal, but I've loaded ModelData in projects using just the Xen.Ex dll before.  This is the first time I've tried loading content from a project that wasn't created with the Xna project wizard though.  Do I need to manually register the Xen RuntimeReaders with Xna somehow?
Apr 19, 2009 at 6:47 AM
Edited Apr 20, 2009 at 8:49 AM

Well that's an odd one.

What happens if you make the content type reader public? It could be a permissions issue (you don't always have permission to access non-public members, but it's rare).
The other possibility, is that it's simply not finding the .dll (The runtime reader is loaded by full class string name, including assembly name - without a path). It may be looking in the GAC for Ex if that's the working directory.


Also, if you have made any significant changes, such as signing the assembly (that could alter the assembly name) then you will need to recompile the model importer.

Apr 19, 2009 at 8:33 PM
Still fails to "find' the Runtime reader if I make it public.  Xen.Ex.dll module is being loaded ok.  In fact, I can see the Xen.Ex.Graphics.Content.ModelData+RuntimeReader in Intellisense at a breakpoint where I catch the exception.

I don't get it.  The only thing I can think of is that the ContentManager might be using Reflection to populate a list of all the ContentTypeReaders when it's created.  Oddly the Xen.Ex.dll isn't loaded until the ContentManager is loaded in my editor project.  But in the tutorials, it's already loaded before the ContentManager is instantiated.  I guess I could try calling something in Xen.Ex earlier on to get the module loaded first.
Apr 20, 2009 at 7:17 AM
Took a step backwards today and traced through the initialization code of Application and WinFormsHost.  Iwas getting Tut2 to run no problem if I created the window in VS, but not if VS created it on startup.

In WinFormsHost, starting at line 306, there are these lines:


How is logic.Initialize(); guaranteed to run before the first Update()?

It has to be... Update() calls State.SetGameTime(), which requires to be set.  That happens in XNALogic.Initialize().  State.SetGameTime() gets Application.Graphics, which throws if is null.  Ok, this never seems to happen using the Xen tutorials in Winforms. 

And when VS is allowed to start up completely and all the Xen intitialization work is completed before the WinFormsHostControl is visible, then the toolwindow is shown as a result of a menu command, there's no problem with painting.  The Application has been given a change to get the reference to the GraphicsDevice before State.SetGameTime() is called for the first time.

But if the VS toolwindow is visible on startup, VS starts passing the Paint message to the WinFormsHost before XNALogic.Initialize() completes.  What I can definitely say is that Application.Graphics is throwing an exception because graphics is null the first time State.SetGameTime() is called.  After that, the control never gets another Paint message.

What I don't understand is how the threads are synchronized, or even in what order they run, so that its guaranteed logic.Initialize() completes before Paint messages start flowing.  It's not just lucky that logic.Initialize() completely before the System.Application.Forms.Form.Run() statement is hit, is it?

FWIW, adding a try/catch block at the top of Paint to return if the Application.Graphics property throws cures everything.  That seems like an ugly hack to me, though.
Apr 20, 2009 at 8:59 AM
Honestly I don't remember writing that code (but then again, I've got a really nasty head cold right now, so I don't remember what I had for lunch :).
From memory it's mostly based on the official XNA winforms sample.. Looking through it looks like it even has some of the comments remaining.

I may have a look at it, but honestly I'm not thinking clearly enough right now to be able to deal with it properly.
Apr 21, 2009 at 5:57 AM
Hurts my head rereading what I'd written, and I don't have a head cold.  :-)

Anyway, I've got it working, though, unfortunately, not without two minor mods to WinFormsControl.  I feel comfortable that the changes aren't hacky any more.

Essentially, I had to do two things:  one was check whether the application had been initialized and not run the main loop until it had, and the other was to Invalidate the control from outside the OnPaint handler.

    So I've got the first statement in OnPaint changed to:
        if( device != null && HandleDeviceReset() && application.IsInitialised )
    I removed the check for DesignMode, since in DesignMode application.IsInitialised will return false.

    And I removed Invalidate() from the end of that if block. 

Oddly, the Xen tutorials continue to work in Winforms, even though the Invalidate() has been removed.  I have no idea how they continue to get Paint messages.
Anyway, in my VS project, I *do* need an external timer (I'm just using a Winforms timer at 16mSec, and calling Invalidate(true) on its Tick event) 
With those changes in place, everything's happiness.  I can load content, and I'm getting Update and Draw callbacks.

Next up is to understand why the Xen tutorials work in a Winform with no Invalidate(), and then to try viewing the Animation Stress Test and particle tutorials in the ToolWindow again.
Apr 21, 2009 at 6:39 AM
Ok. I might leave the Invalidate() in, because I think that was there in the original XNA windows forms code. the IsInitailised change makes sense though.
Instead of using a timer, you might want to use the System.Windows.Forms.Application.Idle static event to post a refresh of the control. However be aware this event will usually be called the moment the message pump is empty, so even though it's called Idle, if you always do something when it's fired, you will max out the cpu.
Apr 21, 2009 at 8:15 PM
Let me do some investigation on what that Invalidate is actually doing.  I had to remove it because it was starving the other controls in VS.  It doesn't immediately make sense to me why when I resized the Xen toolwindow it would get refreshed but any windows it overlapped wouldn't, but that was what was happening.  Removing the Invalidate from the WinFormshostControl loop fixed that issue, but that meant I had to do something to keep signalling the Xen main loop to run.  I'll test using the Idle message to do that trigger... it's something I've used in the past with success.  The internals of the VS toolwindows seems a little strange though and I was unable to insert an IMessageHandler in the Form in any way that worked, for example. 

But first I want to understand how the heck the tutorials continue to run in a window with the Invalidate removed.  That really confuses me.  You'd think there would be a single Paint message and then the Xen main loop wouldn't run again unless a user did something like moving or resizing the Form it's running in.  I agree that removing the Invalidate looks like a breaking change on the surface (and I can only imagine that it'll prove out to be so, but I want to understand it first.)
Apr 22, 2009 at 2:25 AM
The tuts *do* behave as expected with the Invalidate() removed from the DoPaint() method of the WinFormsHostControl.  They paint once, and then never again, unless you resize the wondow or something.  I must have run the tut without checking the Winforms box by mistake and been so amazed to see it animating that my mind completely shut off...
Apr 22, 2009 at 2:27 AM
Ok that's good.
I suppose adding a 'ManualRedraw' flag could be a way around this problem.
Apr 22, 2009 at 6:36 AM
Edited Apr 22, 2009 at 6:43 AM
That'd be sweet.

One other thing I've done is added a Property to Application called ContentPath, and I use it in the constructor of XNAWinFormsHostAppWrapper, line 291 of WinFormsHost.cs, passing parent.ContentPath as the second argument to the ContentManager constructor.

By making ContentPath default to String.Empty in Application there's no change to the default behaviour of Xen, but it allows me to set the path to the content in my Xen app's constructor.

I needed this to set the ContentManager's RootDirectory somewhere other than the LoadContent() methods.  Xna's ContentManager only allows you to set the RootDirectory before any content has been loaded, so trying to set it in a LoadContent() method would work initially but an exception would be thrown as soon as the Device needed to be reset and LoadContent() would be called again.

Idle event works fine.  I've got a bouncing sphere drawing on a BackgroundGradient and a TexturedElement all going on inside a toolwindow that can dock anywhere in VisualStudio, be hidden and restored etc.  I'll run the Stress Test and at least one particle example tomorrow.

The particle example ought to be particularly fun, because I ought to be able to edit the particles' XML descriptions in one VS editor, and have the Xen window immediately reflect the change when I save the XML.  That's the kind of thing I'm aiming for.
Apr 23, 2009 at 4:26 AM
Aaaaand, that might be the end of this ride...

I'm back at the original problem.  Xna's throwing an exception saying it can't find a RuntimeReader called Xen.Ex.Graphics.Content.ParticleSystemData+RuntimeReader when I try to load content.  (Similar reaction to ModelData)

And I have no idea what's going on.  Adding this line:
    Type t = Type.GetType("Xen.Ex.Graphics.Content.ParticleSystemData+RuntimeReader, Xen.Ex, Version=, Culture=neutral, PublicKeyToken=null");
immediately before the exception, and again after catching the exception, returns the RuntimeReader type, no problem, both times.  The text in the quotes is cut and pasted from the exception message. 

So a statement can run ok in my app, then go into Xna where the same statement causes an exception, then that exception is caught in my app where the same statement runs ok... how do you even start to think about debugging that?

Here's the Xna code that's causing the exception to be thrown:
        Type key = Type.GetType(readerTypeName);
        if (key == null)
            throw contentReader.CreateContentLoadException(FrameworkResources.TypeReaderNotFound, new object[] { readerTypeName });
... and again, the text in the quotes was the readerTypeName, cut and pasted from the exception.

Screwiest thing I've ever seen.
Apr 23, 2009 at 4:29 AM
Could you possibly send me a copy of the code that breaks. It doesn't have to be everything, just enough to get the error to occur. I know I possibly can't do anything myself, but I can get in contact with some people who might be able to help.
Apr 23, 2009 at 8:24 AM
Yeah, absolutely.  It might have to be everything though... fortunately this is a test project, so it only includes the VS SDK extensibility walkthrough code, the mods to Xen, the Form and the slightly modified tutorial 21 code.
Some of the files are a bit messy because I've been hacking a lot while working through the debugging.  Hopefully that doesn't distract anybody trying to debug it.