I need to host WPF control inside IE, therefore I'm trying to implement IHTMLPainter and IElementBehavior interfaces. I'd like to build my custom behavior and use it inside IE, but the problem is how to draw WPF control by just having IntPtr hdc parameter.
Probably I can get Drawing.Graphics by the following code:
Graphics.FromHdc(hdc);
But I'm not sure that this is the best way. Please advise
I'm assuming you want to be able to make use of the advanced features of WPF within a MSHTML context. In that case, Graphics.FromHdc(hdc); will not do the trick for you. The resulting Graphics object will have no way to receive WPF content because WPF uses a retained-mode system and its MILCore rendering engine uses Direct3D not GDI+.
I'll give you one sure way to use WPF features inside a IHTMLPainter, plus pointers to another way that would likely be faster if you can get it to work.
Bitmap copying solution
An easy solution is to simply copy the background provided by MSHTML into an ImageBrush, use RenderTargetBitmap to let WPF render to a bitmap, then copy it back to the device.
To do this, construct your WPF content in any Visual subclass with a Background property (eg Grid or Border), then in your IHTMLPainter.Draw() method, just do the following:
Create a System.Drawing.Bitmap corresponding to rcUpdate
BitBlt from the given DC into the System.Drawing.Bitmap
Construct an ImageSoure from the System.Drawing.Bitmap (see recent SO answers for details)
Construct an ImageBrush from the BitmapSource using a viewport/viewbox that will lay it behind the portion of the visual corresponding to rcUpdate
Set your root visual's background to the ImageBrush
Set the RenderTransform on the root visual so that the rcUpdate portion starts at (0,0)
Render the root visual to a RenderTargetBitmap of rcUpdate size
BitBlt the RenderTargetBitmap to the rcUpdate area of the DC
This should work well, be simple to implement, and work for any WPF content including advanced features such as 3D, BitmapEffects, etc. The only disadvantage is that those two bitmap copies might slow things down somewhat.
Note that if you know your WPF Visual is totally opaque you can completely skip steps 1-5 and simply render your Visual to a RenderTargetBitmap and BitBlt it to the device.
Direct3D possibility (partial solution)
Obviously it would be faster to avoid all this bitmap copying during render. This is most likely possible, but I can only give you some ideas to point the way -- it will take a lot of trial and error and probably some undocumented calls to make it work.
Since WPF renders using Direct3D, obviously you would prefer to get a Direct3D surface from MSHTML and paint on it. Doing this requires two things: Getting the surface from MSHTML, and getting MILCore to draw on it.
IHTMLPainter has a flag HTMLPAINTER_3DSURFACE to request a Direct3D surface in its GetPainterInfo call, but I couldn't find any examples of how to use HTMLPAINTER_3DSURFACE. I suspect it could be figured out with a little trial and error.
I did not find any way to get WPF's native component "MILCore" to accept a Direct3D surface to paint on instead of a hWnd. There is no documentation on MILCore, and the only public API for setting up rendering tree, HwndSource, doesn't seem to be able to do the job.
Rendering behaviors through IHTMLPainter and IElementBehavior are meant to alter or supplement the display of existing elements in a page, not to render content for user controls. If you're looking to use WPF controls in a page, this is not the path to take. Instead, consider creating a blank windowed UserControl with ActiveX support, then do either of the following.
Add your WPF control at runtime as a member of the UserControl.
Perform WPF activities using the window handle (HWND) of the control.
Alternatively, you could just use Silverlight to make user controls. Silverlight has a pretty good subset of WPF display features, and even manually constructed Silverlight content is easier to manage than trying to get .NET Windows + ActiveX Hosting + WPF working.
If I've mistaken your question and you're truly intent on using WPF to perform drawing activities in an element behavior, Graphics.FromHdc() is an acceptable way to get a usable Graphics object. You should attach to the HDC specified in the Draw() callback.
Draw Method (IHTMLPainter) # MSDN
You could also attach to the window handle (HWND) of the document view (retrieved via IOleWindow), if your WPF activities involve the entire viewport. The window object can be cast to IOleWindow for this purpose (see IHTMLWindow2).
IOleWindow Interface # MSDN
IHTMLWindow2 Interface # MSDN
Related
Background: My client has a very extensive proprietary forms library which is effectively implemented in C (actually, it's a proprietary object-oriented language that basically wraps Windows controls and interacts with them with SendMessage(), SetStyle(), etc.)
Problem I want to solve: Whenever I drag/resize a top-level window (or drag a splitter) in an app implemented in the above framework, there is massive flicker. The top-level window is repainted, and any controls it contains repaint themselves.
Question 1: Is there a way to surgically introduce double-buffering into the forms library. In particular, I want to know if I can implement double-buffering using standard Windows GDI functions.
For example, if I could cause the top-level windows to be double-buffered such that all child windows of the top-level window are automatically drawn double-buffered as well. An even better alternative would be to be able to introduce double-buffering on any arbitrary window and have all its children inherit this.
The best solution would somehow cause the BeginPaint() function for child controls to return a handle to the DC of the offscreen back buffer so that I don't have to write special code for each individual control class.
Question 2: Is there a way (such as a set of flags) to cause generic Windows controls (EDIT, BUTTON, and so on) to draw themselves double-buffered? This would be a worse solution than a more generic approach that would just seamlessly give them the back buffer to draw on, but it might also be acceptable.
All help greatly appreciated. Please let me know if I can clarify anything for you.
Look into WS_EX_COMPOSITED, which is an extended window style that turns on double-buffering for the window. It may be enough to set this style on the parent of the controls.
You actually might be able to wrap all your window drawing code with C that executes C#, and that way there is already a double-buffered implementation for you.
How to eliminate flicker in Windows.Forms custom control when scrolling?
I have inherited a large MFC application which contains a CComboBox subclass that overrides OnPaint. Currently it does all its drawing by hand (with lines and rectangles), and renders a combo box that looks decidedly Windows 98-style. However, it otherwise works great and provides a lot of useful custom functionality that we rely on, and rewriting the entire control is probably not an option.
I would like to modernize it so that the OnPaint draws in Aero style where available (falling back to the old code when modern theming is unavailable). I've done this with some other custom controls we have, like buttons, and it works great for our purposes. I know there are some tiny behaviors that it won't get right, like gentle highlights on mouse-hover, but that's not a big deal for this app.
I have access to the CVisualStylesXP ckass, so I've already got the infrastructure to make calls like OpenThemeData, GetThemeColor or DrawThemeBackground pretty easily (via LoadLibrary so we don't force Vista as a min-system). Unfortunately, I don't know the proper sequence of calls to get a nice looking combo box with the theme-appropriate border and drop-down button.
Anyone know what to do here?
Honestly, I don't know why they originally tried to override OnPaint. Is there a good reason? I'm thinking that at least 99% of the time you are just going to want to override the drawing of the items in the ComboBox. For that, you can override DrawItem, MeasureItem, and CompareItem in a derived combo box to get the functionality you want. In that case, the OS will draw the non-user content specific to each OS correctly.
I think you best shot without diving in the depth of xp theming and various system metrics is take a look at this project: http://www.codeproject.com/Articles/2584/AdvComboBox-Version-2-1
Check the OnPaint of the CAdvComboBox class - there is a full implementation of the control repainting including xp theme related issues.
Not sure if it's the same situation - but when I faced this problem (in my case with subclassed CButtons), solving it only required changing the control declaration to a pointer and creating the control dynamically.
Let's assume that your subclassed control is called CComboBoxExt.
Where you had
CComboBoxExt m_cComboBoxExt;
You'll now have
CComboBoxExt* m_pcComboBoxExt;
And on the OnInitDialog of the window where the control is placed, you create it using
m_pcComboBoxExt = new CComboBoxExt();
m_pcComboBoxExt->Create(...)
Since this is now a pointer, don't forget to call DestroyWindow() and delete the pointer on termination.
This solved my particular problem - if your control is declared in the same way, consider giving it a try.
We have a WPF application (.Net 4.0) using a Docking Control (Actipro). We can dock out the docking windows. In that case, a "real" Window is created and the content is assigned to that window.
Of course, moving stuff in the Visual Tree will re-trigger the complete layouting. This is problematic, because in one of these docking windows, we have a diagramming control (Mindfusion Diagramming,WPF control) that can take up to 10 seconds to completely layout itself (very large diagrams).
I don't think that there's any direct solution to this problem. I wonder however how other programmers with similar issues approached this problem. Is there any clever way to avoid recalculating the layout?
In theory, nothing really changes since the diagram is inside a ScrollViewer, so whenever it is placed, the amount of avaiable space remains the same(infinite).
Edit: Also note that the diagram control inside is interactive. We need Drag&Drop.
Here is an idea.
Create a custom class inheriting from Decorator.
Wrap your diagramming control inside the decorator.
Override MeasureOverride and simply call base.Measure but store the result in a field before returning.
Add a property which enables you to disable the measure call. If the property is true simply return the previous size in MeasureOverride instead of calling base.Measure.
Set the property while changing the visual hierarchy.
From the top of my head I can't think of any reason why this shouldn't work.
I have actually done something very similar not too long ago. When implementing the sliding animation for the side panels in NovaMind I used a Decorator to prevent the content from performing layout while the panel animates its width. I calculated the size with the final width, stored it and then used MeasureOverride to fake the current size... This prevented the performance issues involved when trying to animate the width of a complex control. :)
Another possibility is that the problem isn't related to layout so much as the "severing" of the visual tree when moving the content from one window to another. This seems to cause a slew of recalculations for dependency properties, which if your visual tree for content was like mine, upwards of 2000 controls, it was really slow.
I couldn't find an elegant solution to this using Actipro docking library itself, so I thought how I could divert WPF from doing this behavior. The solution I came up with was to create my content as a single WinFormsHost control with a single child of a WinForms UserControl. Then, I made that WinForms UserControl have it's content be the WPF based content that should appear as the docking window content. I figured that when WPF started walking the visual tree from the top to re-evaluate all dependency properties when the tree was "snipped", it would run into the WinForms control and stop.
My Actipro docking tool windows used to take 6 seconds or so just to switch tabs or to float. Now they are essentially instaneous. You have to ensure that any command handlers are not at the application level, but instead at your WPF content level and you might have to finagle wtih the location of some style files, but it worked fantastic.
you might want to replace your diagramming control in the visual tree with an Image, render the diagram offscreen and use rendertargetbitmap to convert the rendered diagram to an image, which you can use as the source for the Image in the visual tree.
something like this:
// image is the Image from the visual tree
int h = image.ActualHeight;
int w = image.ActualWidth;
// layout the diagram to the size of the image
diagram.Measure(new Size(w, h));
diagram.Arrange(new Rect(newSize(w,h)));
diagram.UpdateLayout();
// render the diagram to a bitmap
RenderTargetBitmap bmp = new RenderTargetBitmap((int)w, (int)h, 96, 96, PixelFormats.Default);
bmp.Render(diagram);
// set the source of your image to the bitmap
image.Source = bmp;
in the example if PixelFormats.Default doesn't seem to work, you might try PixelFormats.Pbgra32, which I think is a more common format to use in this type of thing.
you might also be able to use a VisualBrush in a similar manner. I can imagine in the long run you could probably create a wrapper class for the diagram to automatically display the image copy and re-layout the diagram only if something changes (ie a part of the diagram or the size).
Why is it that I have to set the WindowStyle property to None on a WPF form to get transparency, but in Winforms I can do it on any form, and retain borders, standard buttons, etc? Clearly the API supports this, so I'm not clear on what's special about WPF that would make this an issue.
I'm guessing that WPF is jumping through some DirectX or OpenGL hoops, while Winforms is just setting the alpha for the window via the API, but I could be way off base.
Agreed, this is heavy handed:
private void VerifyConsistencyWithAllowsTransparency(WindowStyle style)
{
if (AllowsTransparency && style != WindowStyle.None)
{
throw new InvalidOperationException(SR.Get(SRID.MustUseWindowStyleNone));
}
}
WPF uses the exact same mechanism to implement this as Windows Forms, layered windows. There is no obvious reason it wouldn't work the same way in WPF. The code snippet, lifted from Window.cs, simply rules it out. There is however one hint from the UsesPerPixelOpacity property:
When you enable per-pixel opacity, the system no longer draws the non-client area. This is because the intended purpose of UsesPerPixelOpacity is to show non-rectangular top-level UI that works in interoperation scenarios, and showing the rectangular non-client area defeats that purpose.
"interoperation scenarios", I guess.
i would like to create a simple winforms or wpf application where i can drag and drop virtual "cards". this below is not exactly what i want to do, but it the closest thing that i found on the web to represent the user interface.
http://www.greenpeppersoftware.com/confluence/plugins/advanced/gallery-slideshow.action?imageNumber=1&pageId=24870977&decorator=popup&galleryTitle=Task+board+and+transitions
so basically i want to have columns in the GUI where i can drag and drag from one to the other.
My questions are:
would this be easier in winforms or wpf
where do i start?
In both winForms and WPF dragging and dropping can be done in a similar way by working with the events on the target DragOver and Drop.
However with WPF you have other options. You will also be able to make the application look better by having a thumbnail as you drag (this is possible in winforms but harder to achieve).
Have a look at this WPF sample it uses a helper class and think it does exactly what you need.
I agree with John in that WinForms and WPF are quite close to one another w.r.t. drag'n'drop. But WPF offers more of a "common base" for ItemsControl, allowing to implement more independent of the final UI elements used (ListBox, ListView, TreeView... can be easily switched). And obviously WPF allows much more fancy effects.
I would strongly recommend this blog post:
http://www.beacosta.com/blog/?p=53
both for some drag'n'drop basics and for a clean WPF drag'n'drop approach.
It shows a nice implementation of a rather generic helper for drag'n'drop from/to WPF ItemsControls, I really like that "Insertion Adorner". And I do like that the drag'n'drop code is nicely separated from the user control itself by using attached properties, which makes it much easier to use and maintain.
It would probably be slightly easier in WPF because of the Thumb control which provides easy to use built-in support for dragging. (If I remember correctly, in WinForms you would need to handle the mouse events yourself, whereas the WPF Thumb does this for you and translates them into drag start, delta and end events.)
However if you are much more familiar with one framework than the other than that would probably dwarf the difference that the Thumb control would make.
You should also have a look around for toolkits/frameworks that could handle this for you -- I think they exist for both WinForms and WPF (not sure though).
A good way for darg and drop are explained as
Detect a drag as a combinatination of MouseMove and MouseLeftButtonDown
Find the data you want to drag and create a DataObject that contains the format, the data and the allowed effects.
Initiate the dragging by calling DoDragDrop()
Set the AllowDrop property to True on the elements you want to allow dropping.
Register a handler to the DragEnter event to detect a dragging over the drop location. Check the format and the data by calling GetDataPresent() on the event args. If the data can be dropped, set the Effect property on the event args to display the appropriate mouse cursor.
When the user releases the mouse button the DragDrop event is called. Get the data by calling the GetData() method on the Data object provided in the event args.
You can find the complete article here