How to avoid visual artifacts when hosting WPF user controls within a WinForms MDI app?
Asked Answered
C

4

6

When hosting WPF user controls within a WinForms MDI app there is a drawing issue when you have multiple forms that overlap each other that causes very distinct visual artifacts. These artifacts are mostly visible after dragging one child form over another one that also hosts WPF content or by allowing the edges of the child form to be clipped by the main MDI parent when dragging it around. After the drag and drop of the child form is completed the artifacts stay around generally but I've found that setting focus to a different application's window and then refocusing back on to my application window that it is redrawn and all is good again until the child forms are moved once again. Please see the image below which demonstrates the problem.

Screenshot of described artifacts

Those at Microsoft insist that the WinForms MDI is already a sufficient solution for MDI and doesn't need reinventing in WPF although I find it hard to believe they tried creating a WPF app this way because of the obvious shortcomings.

UPDATE: A few extra notes that I left out is that if I create these Forms without setting the MdiParent they are created as regular forms and this issue doesn't happen. This issue seems unique to the WinForms MDI scenario. Also I've currently running on Windows 7 Enterprise and I'm aware the results may be quite different on Windows XP but I haven't been able to test this.

UPDATE: I've found a few other related resources on this issue that I thought I should share.

Chaise answered 7/4, 2010 at 4:10 Comment(0)
C
2

Well, I may have found a solution although it feels like a bit of a hack. It appears that if you call the Refresh method on the MDI parent whenver a child MDI Form is moved that the noted artifacts go away. Visually things appear a bit jittery when dragging a window but it seems much more acceptable than the example I showed in my original post.

private void Form1_Move(object sender, EventArgs e)
{
    this.ParentForm.Refresh();

    System.Diagnostics.Debug.WriteLine(string.Format("Form Moved to: ({0},{1})", this.Left, this.Top));
}

I've tried many combinations in the same vein such as refreshing just the child window that was being moved by calling methods such as Update(), Invalidate(), Refresh() and also I've tried these same methods on the MDI parent as well as Dispatcher.Invoke(DispatcherPriority.Render, ...) and InvalidateVisual() on my hosted WPF control but none of those other methods worked accept for calling Refresh() specifically on the MDI parent.

I realize that this probably isn't the optimal solution since I'm forcing the whole main application window to refresh every time a child window moves a few pixels but as for right now it's the only reasonable solution that I found that works. If anybody else has any alternative solutions or any improvements upon this I will gladly accept your answer instead.

Chaise answered 7/4, 2010 at 15:50 Comment(0)
C
9

It appears that another workaround is to revert to software rendering as opposed to taking advantage of hardware acceleration. This was the suggestion by Marco Zhou on the MSDN Forums.

public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
        this.Loaded += delegate
        {
            var source = PresentationSource.FromVisual(this);
            var hwndTarget = source.CompositionTarget as HwndTarget;
            if (hwndTarget != null)
            {
                hwndTarget.RenderMode = RenderMode.SoftwareOnly;
            }
        };
    }
}

I've tested this and this solution seems to work very well and so far is the only solution that I've found for solving this problem within a FoxPro interop scenario which is very similar to the WinForms one I posted about originally. For now I'm planning to use my original Refresh on the MDI Parent solution for my WinForms project but then for my other native interop applications such as when my WPF controls are hosted in Visual FoxPro I'll use this solution. That is unless of course if a more elegant solution is discovered for either of the cases.

Also it's important to note that from what I'm aware software rendering is the only option on XP systems and normally Visual FoxPro nore WinForms normally take advantage of the same type of hardware acceleration that native WPF apps do on Vista OS and up. So using this option may not be as bad as it sounds when you do have to deal with interop. Currently I'm not aware of any related side effects when using this solution but if there are any those would have to be taken into serious consideration.

Chaise answered 7/4, 2010 at 16:29 Comment(0)
C
2

Well, I may have found a solution although it feels like a bit of a hack. It appears that if you call the Refresh method on the MDI parent whenver a child MDI Form is moved that the noted artifacts go away. Visually things appear a bit jittery when dragging a window but it seems much more acceptable than the example I showed in my original post.

private void Form1_Move(object sender, EventArgs e)
{
    this.ParentForm.Refresh();

    System.Diagnostics.Debug.WriteLine(string.Format("Form Moved to: ({0},{1})", this.Left, this.Top));
}

I've tried many combinations in the same vein such as refreshing just the child window that was being moved by calling methods such as Update(), Invalidate(), Refresh() and also I've tried these same methods on the MDI parent as well as Dispatcher.Invoke(DispatcherPriority.Render, ...) and InvalidateVisual() on my hosted WPF control but none of those other methods worked accept for calling Refresh() specifically on the MDI parent.

I realize that this probably isn't the optimal solution since I'm forcing the whole main application window to refresh every time a child window moves a few pixels but as for right now it's the only reasonable solution that I found that works. If anybody else has any alternative solutions or any improvements upon this I will gladly accept your answer instead.

Chaise answered 7/4, 2010 at 15:50 Comment(0)
J
2

Check video drivers and try disabling hardware acceleration. Most artifacts are caused by bad drivers, failing video card, or insufficient time to complete the refresh.

First troubleshooting step: Update video drivers. Obvious, I know.

I had similar issue, checking my video card settings (NVidia Control Panel) showed global setting set very high causing a longer refresh interval which may be aborted if taking too long. Setting my settings back to defaults resolved most of the issue. But I also run hashing programs which use the GPU intensely so this is likely the cause of my remaing artifact issue which is very seldom now and mostly shows its ugly face in Visual Studio.

Another troubleshooting step I ran across is to disable hardware acceleration for WPF, this can be done in 'HKEY_CURRENT_USER/SOFTWARE/Microsoft/Avalon.Graphics', or maybe an application can do it BUT this is only for troubleshooting; never set these within an application because it will disable for ALL WPF applications. I do not have this registry setting nor did I add it so I am not sure of the success with it, but many say this resolved their issue. Also note some applications have this option available, try disabling it if available.

Another troubleshooting step is to make sure the video card is a proper tier level for rendering. Any card that supports DX9 or greater should be sufficient, but other factors are involved (as is my case) so just because it is on the list does not mean it is adequate for your purpose.

Finally, you can use the Visual Profiler (part of Windows SDK), and other tools, to help determine what is going on more precisely with WPF lacking performance in relation to graphics ability.

Rendering Tier level notes and WPF Performance information --> http://msdn.microsoft.com/en-us/library/vstudio/ms742196(v=vs.90).aspx

Hope this helps someone.

--Ryan Strassburg

Jig answered 17/1, 2013 at 18:42 Comment(0)
H
0

Your usercontrol or window loaded event ;

this.WindowState = System.Windows.WindowState.Minimized;

this.WindowState = System.Windows.WindowState.Normal;

it may seem bad solution. no need to hit your head against the wall.

A Turkish proverb says: the best code is the code is running :)

Hyphenate answered 25/8, 2015 at 15:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.