views:

82

answers:

4

I am attempting to write a specialized onscreen keyboard (OSK) for an application I'm writing in C#. To facilitate this, I've created a form which has several buttons on it representing keys, and clicking them calls SendKeys and sends out the appropriate keys.

This form is owned by the main window that is shown when the application first starts, using the Owner property. This way, the OSK pops up whenever the user focuses the application, and it stays on top of the main window if it said main window is dragged over it.

This all works great, but because I have modal dialogs that I also want to use with the OSK, I attempted to create it in a separate thread, complete with its own message loop (via Application.Run) so it could still be usable with any modal dialogs in the main thread.

The problem with this is that, obviously, being in a separate thread can cause InvalidOperationExceptions because of cross-threaded calls. One specific example of this is when calling Application.Run(osk) from the new thread, a cross thread error occurs because it is attempting to update the window's handle with the owner (the main window).

My question is, is it possible to have an owned form on a thread that is separate from the owner in a safe manner? And, if failing that, is it possible to emulate an owned form's characteristics (namely being Always On Top for only the main window, and popping up when the main window is focused)?

Thanks, and sorry if this is confusing.

A: 

Why not use Control.Invoke to make cross-threaded calls to avoid InvalidOperationException?

Danny Chen
Because the main error happens when I call `Application.Run`, which is caused by the OSK's owner being the main window. It attempts to do some internal stuff with associating the handle with the owner, and that causes a cross-threaded exception. I can't control this because it is within `System.Windows.Forms`.
nasufara
@nasufara: So you use `Application.Run(new OSK())` in the main form?
Danny Chen
I essentially do `new Thread(() => Application.Run(new OSK())).Start();` in the main window's constructor (there is some more initialization stuff, but its not relevant, and is left out for brevity).
nasufara
But why do you do Application.Run at all? Why do you need to have your forms Modal?
Jaco Pretorius
+1  A: 

I'll take a stab at this. Try running the OSK as a separate process.

YWE
The problem with this is that I don't get the behavior of owned tool windows, mainly Always On Top for just the owner of the form, and when the main form is focused, the owned window also pops up alongside it.
nasufara
A: 

Try using ShowDialog for the OSK as well instead of Application.Run - ShowDialog creates a message loop and ends it when the window is closed, and perhaps will solve your problems.

new Thread(() => new OSK().ShowDialog());
configurator
+1  A: 

I think this is actually a bug in Windows Forms. Somewhat inevitable due to the way it checks for access to the Handle property by the wrong thread. The SDK docs for SetParent are not explicit about it, it states the requirement is that both windows belong to the same application. No mention of having to belong to the same thread. I know for a fact that the 'same application' requirement is not hard, there's appcompat code in Windows that makes this work for windows from different processes. Adobe Acrobat ab/used this for a long time. Which definitely absolves the 'same thread' requirement.

Well, punt the problem and try it out. Set Control.CheckForIllegalCrossThreadCalls to false before you set the owner, back to true afterward. And test the heck out of it. If you have trouble then try pinvoking SetParent() directly instead of setting the Owner. Windows Forms actually uses SetWindowLongPtr which is not recommended by the SDK.

Hans Passant
Sounds good. What did you end up using?
Hans Passant