Using VisualTreeHelper.GetDrawing to get around the Z-Order limitation when hosting Windows Forms controls
Several times I have had needed to put WPF content in front of Windows Forms control hosted in a WPF app...
Unfortunately, this is a limitation in the interop story, WindowsFormsHost elements are always drawn on top, and don't get affected by z-order ...
The first time I did this, I was working with an imaging company, so it was easy as their activex rendered onto a bitmap already.. so we used that as a workaround: create a bitmap, and then 'replace' the control with the bitmap at specific times/triggers..
The next time, I tried to use the same workaround .. but this time, I wrote some 'ugly' code to make a simple winforms control render into a bitmap ... [trust me it was ugly, took me a day+, used interop, ec.] ... Now, I see this post from LLobo telling me there was an API for this ... way to make me feel dumber than usual :( ...
So, I am putting it here so no one else misses it like I did, also as a reference as now I have to email Robert -sorry man - to tell him to replace our old code :) The API is VisualTreeHelper.GetDrawing.. Wrote the simplest test, here is kinda what it looks like [will do for a quick airspace demo :) ]
Explanation of the screen above:
The Left hand side is a WindowsFormsHost control hosting a MonthCalendar Windows Forms controls. . The RHS is a Rectangle witha DrawingBrush of the WindowsFormsHost in the LHS... Across both UIElements, there is supposed to be a red rectangle overlayed [imagine this could be any other element or a flying animation, etc.] ... Notice that on the Left, the Red rectangle is behind the WindowsFormsHost. . on the right, you get the right visual experience. .
You could easily use this strategy to swap the WindowsFormsHost in and out ... don't you think??
The code is trivial, I packaged it inside a Dispatcher callback - I don't think it is absolutely needed, but felt safer, trying to make sure windowsformshost has been drawn] ....
void Window1_Loaded(object sender, RoutedEventArgs e)
{
Matrix m =
PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget.TransformToDevice;
double dx = m.M11;
double dy = m.M22;
System.Windows.Forms.MonthCalendar mc = new System.Windows.Forms.MonthCalendar();
mc.Width = (int)(wfHost.Width * dx);
mc.Height = (int)(wfHost.Height * dy);
wfHost.Child = mc;
this.Dispatcher.BeginInvoke (System.Windows.Threading.DispatcherPriority.Background ,
new DispatcherOperationCallback(delegate
{
DrawingGroup dg = VisualTreeHelper.GetDrawing( wfHost );
DrawingBrush db = new DrawingBrush ( dg );
db.TileMode = TileMode.None;
db.Stretch = Stretch.None;
rect.Fill= db ;
return null;
} ), null );
}
Full sample is here.
I tested with several custom windows forms controls seems to work ... That said, I could not get Lester's demo to work with the IE frame ...
I believe this same code will work for Win32 interop (HwndHost) but did not test it, if you try it and it does not work ping me..
Comments
Anonymous
March 07, 2007
That's really nice sample, but the problem is still exists. You are using WindowsFormHost graphics as brush for your rectangle, so the hosted application will not interact with the user.Anonymous
March 14, 2007
Yes Tamir, you are right.. this does not solve the problem.. it is just a way to create the illusion that the problem is not there.. Swapping the right control in and out is not hard. I did not write a sample for the swap, because it is controls specific, some people would want to swap on click, or focus, mouse enter, etc..