Refresh after sending a WM_SETREDRAW message to the panel
Asked Answered
N

2

3
ControlHelper.SuspendDrawing(panel);
panel.Controls.Clear();
AddItemIdLabel();
AddLastEditedLabel();
AddDeleteButton();
AddSaveButton();
ControlHelper.ResumeDrawing(panel);

public static class ControlHelper
{
    [DllImport("user32.dll")]
    private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);

    private const int WM_SETREDRAW = 0xB;

    public static void SuspendDrawing(Control target)
    {
        SendMessage(target.Handle, WM_SETREDRAW, 0, 0);
    }

    public static void ResumeDrawing(Control target)
    {
        SendMessage(target.Handle, WM_SETREDRAW, 1, 0);
        target.Refresh();
    }
} 

If I test with the code above, parts of the panel aren't being refreshed. You can see the old controls from before the Clear() on places where no new controls have been added.

If I put the panel.Controls.Clear(); before the ControlHelper.SuspendDrawing(panel); everything works as intented but some flickering is visible which I'm trying to avoid.

So what's going on here? How can depending on whether I clear the collection of controls before or after the suspend make a difference?

Noyade answered 17/7, 2012 at 14:32 Comment(0)
G
1

Your target.Refresh() call is not fully invalidating the areas where the old controls were removed. Since Control.Refresh() is a 'virtual' method, it may be overridden and may have side side effects like this.

To guarantee complete coverage, you should use the Invalidate(true) & Update() methods within ResumeDrawing(). The Invalidate(true) method will set the entire region of the control and all child controls as invalid and the Update() method will repaint everything just once at the end.

Also, you should consider implementing those methods as .NET Extensions. That would automatically add the SuspendDrawing() and ResumeDrawing() methods to every System.Windows.Forms.Control where you have a USING for the namespace in which the Extensions are declared:

[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;

public static void SuspendDrawing(this Control Target)
{
    SendMessage(Target.Handle, WM_SETREDRAW, false, 0);
}

public static void ResumeDrawing(this Control Target)
{
    SendMessage(Target.Handle, WM_SETREDRAW, true, 0);
    Target.Invalidate(true);
    Target.Update();
}
Greylag answered 19/12, 2015 at 14:10 Comment(1)
Control.Refresh() is exactly a combination of Invalidate(true) + Update(). If it's overridden by a child control, it's probably for a good reason.Wersh
F
1

Watch out, the mentioned PInvoke is incorrect. It should be this:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
private const int WM_SETREDRAW = 11;

public static void SuspendDrawing(this Control Target)
{
    SendMessage(Target.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
}

public static void ResumeDrawing(this Control Target)
{
    SendMessage(Target.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
    Target.Invalidate(true);
    Target.Update();
}

Especially the return type being IntPtr instead of int (8 bytes on 64-bit, instead of 4 bytes) may crash your runtime at random.

Source: http://www.pinvoke.net/default.aspx/user32.sendmessage

Fionnula answered 2/2, 2021 at 14:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.