컨트롤 및 해당 자식에 대한 페인팅을 일시 중단하려면 어떻게합니까?


184

큰 수정을해야하는 컨트롤이 있습니다. SuspendLayout과 ResumeLayout으로 충분하지 않은 동안 다시 그리기를 완전히 방지하고 싶습니다. 컨트롤 및 해당 자식에 대한 페인팅을 일시 중단하려면 어떻게합니까?


3
이 맥락에서 그림이나 그림이 무엇인지 설명해 주시겠습니까? (나는 .net을 처음 사용합니다) atleast는 링크를 제공합니다.
Mr_Green

1
.Net이 15 년 넘게 나왔다는 것은 부끄러운 일이며, 여전히 문제입니다. Microsoft 가 Windows X 맬웨어 받기 와 같이 화면 깜박임과 같은 실제 문제를 해결하는 데 많은 시간을 소비했다면 오래 전에 수정되었을 것입니다.
jww

@jww 그들은 그것을 고쳤다; 그것을 WPF라고합니다.
Philu

답변:


304

이전 작업에서 풍부한 UI 앱을 즉각적이고 매끄럽게 페인트하는 데 어려움을 겪었습니다. 표준 .Net 컨트롤, 사용자 지정 컨트롤 및 devexpress 컨트롤을 사용하고있었습니다.

많은 인터넷 검색 및 리플렉터 사용 후 WM_SETREDRAW win32 메시지가 나타났습니다. 이것은 컨트롤을 업데이트하는 동안 컨트롤 그리기를 실제로 중지하고 IIRC를 부모 / 포함 패널에 적용 할 수 있습니다.

이 메시지를 사용하는 방법을 보여주는 매우 간단한 클래스입니다.

class DrawingControl
{
    [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( Control parent )
    {
        SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
    }

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

C # 및 WM_SETREDRAW에 대한 Google에 대한 자세한 토론이 있습니다.

C # 지터

현수 레이아웃

그리고 누가 관심을 가질 지에, 이것은 VB에서 비슷한 예입니다.

Public Module Extensions
    <DllImport("user32.dll")>
    Private Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Boolean, ByVal lParam As IntPtr) As Integer
    End Function

    Private Const WM_SETREDRAW As Integer = 11

    ' Extension methods for Control
    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control, ByVal Redraw As Boolean)
        SendMessage(Target.Handle, WM_SETREDRAW, True, 0)
        If Redraw Then
            Target.Refresh()
        End If
    End Sub

    <Extension()>
    Public Sub SuspendDrawing(ByVal Target As Control)
        SendMessage(Target.Handle, WM_SETREDRAW, False, 0)
    End Sub

    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control)
        ResumeDrawing(Target, True)
    End Sub
End Module

44
정말 좋은 답변이자 큰 도움이되었습니다! Control 클래스에 대해 SuspendDrawing 및 ResumeDrawing 확장 메서드를 만들었으므로 모든 컨텍스트에서 컨트롤에 대해 호출 할 수 있습니다.
Zach Johnson

2
당신이 그것을 축소하고 확장하면 자식을 재배치 할 때 노드를 올바르게 새로 고치는 트리와 같은 컨트롤이 있었으며 그 결과 추악한 깜박임이 발생했습니다. 이 문제를 해결하기 위해 완벽하게 작동했습니다. 감사! (재미있게 충분히 제어가 이미 sendMessage 첨부 수입 WM_SETREDRAW를 정의하지만, 실제로는 아무것도 사용하지 않은 지금은 않습니다..)
neminem

7
이것은 특히 유용하지 않습니다. 이것은 정확히 무엇을 Control위한 기본 클래스 의 모든 윈폼 컨트롤이 이미 수행 을 위해 BeginUpdateEndUpdate방법. 메시지를 직접 보내는 것이 이러한 방법을 사용하여 어려운 작업을 수행하는 것보다 낫지는 않으며 다른 결과를 얻을 수 없습니다.
코디 그레이

13
@Cody Gray-예를 들어 TableLayoutPanel에는 BeginUpdate가 없습니다.
TheBlastOne

4
컨트롤이 표시되기 전에 코드에서 이러한 메서드를 호출 할 수있는 경우주의하십시오.이 호출 Control.Handle은 창 핸들을 강제로 작성하여 성능에 영향을 줄 수 있습니다. 예를 들어, 컨트롤이 표시되기 전에 컨트롤에서 컨트롤을 이동 한 경우 SuspendDrawing미리 호출하면 이동 속도가 느려집니다. if (!parent.IsHandleCreated) return두 가지 방법 모두를 확인 해야 합니다.
oatsoda

53

다음은 ng5000과 동일한 솔루션이지만 P / Invoke를 사용하지 않습니다.

public static class SuspendUpdate
{
    private const int WM_SETREDRAW = 0x000B;

    public static void Suspend(Control control)
    {
        Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
            IntPtr.Zero);

        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgSuspendUpdate);
    }

    public static void Resume(Control control)
    {
        // Create a C "true" boolean as an IntPtr
        IntPtr wparam = new IntPtr(1);
        Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
            IntPtr.Zero);

        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgResumeUpdate);

        control.Invalidate();
    }
}

시도했지만 Suspend와 Resume 사이의 중간 단계에서는 무효화되었습니다. 이상하게 들릴 수 있지만 과도 상태에서 일시 중단되기 전에 상태를 유지할 수 있습니까?
사용자

2
컨트롤 일시 중지는 컨트롤 영역에서 전혀 드로잉이 수행되지 않으며 해당 표면의 다른 창 / 컨트롤에서 남은 부분이 생길 수 있음을 의미합니다. 컨트롤이 다시 시작될 때까지 (원하는 내용을 이해 한 경우) 이전 "상태"를 그리려면 DoubleBuffer를 true로 설정 한 컨트롤을 사용하려고 시도 할 수 있지만 제대로 작동하지는 않습니다. 어쨌든 나는이 기술을 사용하는 요점이 빠져 있다고 생각합니다. 사용자가 점진적으로 느린 객체 그림을 보는 것을 피하기위한 것입니다 (모두 함께 나타나는 것이 더 낫습니다). 다른 요구에 대해서는 다른 기술을 사용하십시오.
ceztko

4
좋은 대답이지만, 어디에 Message있고 어디에 있는지 보여주는 것이 훨씬 낫습니다 NativeWindow. 명명 된 클래스의 문서를 검색하는 Message것이 실제로 재미있는 것은 아닙니다.
darda

1
@pelesl 1) 자동 네임 스페이스 가져 오기 사용 (기호 클릭, ALT + Shift + F10), 2) Invalidate ()로 페인트되지 않은 자식이 예상되는 동작입니다. 부모 컨트롤을 짧은 기간 동안 만 일시 중단하고 모든 관련 자식이 무효화되면 다시 시작해야합니다.
ceztko

2
Invalidate()Refresh()어쨌든 하나만 따르지 않는 한 제대로 작동하지 않습니다 .
Eugene Ryabtsev

16

나는 보통 약간 수정 된 ngLink의 답변 버전을 사용합니다 .

public class MyControl : Control
{
    private int suspendCounter = 0;

    private void SuspendDrawing()
    {
        if(suspendCounter == 0) 
            SendMessage(this.Handle, WM_SETREDRAW, false, 0);
        suspendCounter++;
    }

    private void ResumeDrawing()
    {
        suspendCounter--; 
        if(suspendCounter == 0) 
        {
            SendMessage(this.Handle, WM_SETREDRAW, true, 0);
            this.Refresh();
        }
    }
}

이를 통해 일시 중단 / 재개 통화를 중첩 할 수 있습니다. 각각 SuspendDrawing과 일치해야합니다 ResumeDrawing. 따라서 공개하는 것은 좋지 않을 것입니다.


4
이렇게하면 두 통화의 균형을 유지하는 데 도움이됩니다 SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }.. 다른 옵션은 이것을 IDisposable클래스 에서 구현 하고 도면 부분을-문으로 using묶는 것입니다. 핸들은 생성자로 전달되어 도면을 일시 중단합니다.
Olivier Jacot-Descombes

오래된 질문이지만 알고 있지만 "false"를 보내는 것은 최신 버전의 c # / VS에서 작동하지 않는 것으로 보입니다. 나는 거짓을 0으로, 참을 1로 바꿔야했다.
Maury Markowitz

@MauryMarkowitz는 다음 과 같이 DllImport선언 합니까? wParambool
Ozgur Ozcitak

안녕,이 답변을 downvoting 사고, 죄송합니다!
Geoff

13

도면을 다시 활성화하는 것을 잊지 않으려면 다음을 수행하십시오.

public static void SuspendDrawing(Control control, Action action)
{
    SendMessage(control.Handle, WM_SETREDRAW, false, 0);
    action();
    SendMessage(control.Handle, WM_SETREDRAW, true, 0);
    control.Refresh();
}

용법:

SuspendDrawing(myControl, () =>
{
    somemethod();
});

1
어떤 경우에 대한 action()예외를 throw? (시도 / 마지막 사용)
David Sherret

8

interop을 사용하지 않는 멋진 솔루션 :

항상 그렇듯이 CustomControl에서 DoubleBuffered = true를 활성화하십시오. 그런 다음 FlowLayoutPanel 또는 TableLayoutPanel과 같은 컨테이너가있는 경우 이러한 각 유형에서 클래스를 파생하고 생성자에서 이중 버퍼링을 사용하십시오. 이제 Windows.Forms 컨테이너 대신 파생 컨테이너를 사용하십시오.

class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel
{
    public TableLayoutPanel()
    {
        DoubleBuffered = true;
    }
}

class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
    public FlowLayoutPanel()
    {
        DoubleBuffered = true;
    }
}

2
그것은 확실히 유용한 기술입니다-내가 ListViews에 꽤 자주 사용하는 기술이지만 실제로 다시 그리기가 발생하는 것을 막지는 않습니다. 그들은 여전히 ​​화면 밖에서 발생합니다.
Simon

4
당신은 맞습니다, 그것은 화면 밖 다시 그리기 문제가 아니라 깜박임 문제를 해결합니다. 깜박임에 대한 해결책을 찾을 때이 스레드와 같은 여러 관련 스레드가 발견되었으며 발견했을 때 가장 관련성 높은 스레드에 게시하지 않았을 수 있습니다. 그러나 대부분의 사람들은 페인팅을 일시 중단하려고 할 때 아마도 스크린 외부 페인팅보다 더 명백한 문제인 화면상의 페인팅을 언급하고있을 것입니다. 따라서 다른 시청자가이 스레드에서이 솔루션이 도움이된다고 생각합니다.
Eugenio De Hoyos

OnPaint를 재정의합니다.

6

ng5000의 답변에 따라이 확장을 사용하는 것이 좋습니다.

        #region Suspend
        [DllImport("user32.dll")]
        private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
        private const int WM_SETREDRAW = 11;
        public static IDisposable BeginSuspendlock(this Control ctrl)
        {
            return new suspender(ctrl);
        }
        private class suspender : IDisposable
        {
            private Control _ctrl;
            public suspender(Control ctrl)
            {
                this._ctrl = ctrl;
                SendMessage(this._ctrl.Handle, WM_SETREDRAW, false, 0);
            }
            public void Dispose()
            {
                SendMessage(this._ctrl.Handle, WM_SETREDRAW, true, 0);
                this._ctrl.Refresh();
            }
        }
        #endregion

사용하다:

using (this.BeginSuspendlock())
{
    //update GUI
}

4

다음은 pinvoke를 사용하지 않는 VB 확장 버전을 가져 오기 위해 ceztko와 ng5000을 결합한 것입니다.

Imports System.Runtime.CompilerServices

Module ControlExtensions

Dim WM_SETREDRAW As Integer = 11

''' <summary>
''' A stronger "SuspendLayout" completely holds the controls painting until ResumePaint is called
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub SuspendPaint(ByVal ctrl As Windows.Forms.Control)

    Dim msgSuspendUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, System.IntPtr.Zero, System.IntPtr.Zero)

    Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)

    window.DefWndProc(msgSuspendUpdate)

End Sub

''' <summary>
''' Resume from SuspendPaint method
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub ResumePaint(ByVal ctrl As Windows.Forms.Control)

    Dim wparam As New System.IntPtr(1)
    Dim msgResumeUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, wparam, System.IntPtr.Zero)

    Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)

    window.DefWndProc(msgResumeUpdate)

    ctrl.Invalidate()

End Sub

End Module

4
wpf 양식과 혼합 된 winforms를 사용하고 화면 깜박임을 처리하는 WPF 앱으로 작업하고 있습니다. 이 코드를 어떻게 활용해야하는지 혼란스러워합니다. winform 또는 wpf 창에 표시됩니까? 아니면 이것이 나의 특정 상황에 적합하지 않습니까?
nocarrier

3

나는 이것이 이미 대답 한 오래된 질문이라는 것을 알고 있지만 여기에 내가 취한 것이 있습니다. 업데이트 일시 중단을 IDisposable로 리팩터링했습니다. 이렇게하면 명령문에서 실행하려는 명령문을 묶을 수 있습니다 using.

class SuspendDrawingUpdate : IDisposable
{
    private const int WM_SETREDRAW = 0x000B;
    private readonly Control _control;
    private readonly NativeWindow _window;

    public SuspendDrawingUpdate(Control control)
    {
        _control = control;

        var msgSuspendUpdate = Message.Create(_control.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);

        _window = NativeWindow.FromHandle(_control.Handle);
        _window.DefWndProc(ref msgSuspendUpdate);
    }

    public void Dispose()
    {
        var wparam = new IntPtr(1);  // Create a C "true" boolean as an IntPtr
        var msgResumeUpdate = Message.Create(_control.Handle, WM_SETREDRAW, wparam, IntPtr.Zero);

        _window.DefWndProc(ref msgResumeUpdate);

        _control.Invalidate();
    }
}

2

스레드 에서 많은 GDI 근육을 볼 수 있기 때문에 이것은 훨씬 간단 하고 해킹 적 이며 특정 시나리오에만 적합합니다. YMMV

필자의 시나리오에서는 "부모"UserControl이라고하는 것을 사용하며 Load이벤트 중에 부모의 .Controls컬렉션과 부모의 컬렉션 에서 조작 할 컨트롤을 간단히 제거합니다.OnPaint 자식을 완전히 페인팅 할 수 있습니다. 특별한 방법으로 제어 할 수 있습니다. 자녀의 페인트 기능을 완전히 오프라인 상태로 만듭니다.

이제 아이 페인트 루틴을 Mike Gold 의이 개념을 기반으로하는 확장 방법으로 전달하여 Windows 양식을 인쇄합니다 .

여기 레이아웃에 수직 으로 렌더링하기 위해 하위 세트의 레이블이 필요 합니다.

Visual Studio IDE의 간단한 다이어그램

그런 다음 ParentUserControl.Load이벤트 처리기 에서이 코드를 사용하여 자식 컨트롤을 칠하지 않도록 합니다.

Private Sub ParentUserControl_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    SetStyle(ControlStyles.UserPaint, True)
    SetStyle(ControlStyles.AllPaintingInWmPaint, True)

    'exempt this control from standard painting: 
    Me.Controls.Remove(Me.HostedControlToBeRotated) 
End Sub

그런 다음 동일한 ParentUserControl에서 조작 할 컨트롤을 처음부터 그립니다.

Protected Overrides Sub OnPaint(e As PaintEventArgs)
    'here, we will custom paint the HostedControlToBeRotated instance...

    'twist rendering mode 90 counter clockwise, and shift rendering over to right-most end 
    e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    e.Graphics.TranslateTransform(Me.Width - Me.HostedControlToBeRotated.Height, Me.Height)
    e.Graphics.RotateTransform(-90)
    MyCompany.Forms.CustomGDI.DrawControlAndChildren(Me.HostedControlToBeRotated, e.Graphics)

    e.Graphics.ResetTransform()
    e.Graphics.Dispose()

    GC.Collect()
End Sub

ParentUserControl을 Windows 어딘가에 어딘가에 호스팅하면 Visual Studio 2015 가 디자인 타임과 런타임에 폼을 올바르게 렌더링한다는 것을 알았습니다 . Windows Form 또는 다른 사용자 정의 컨트롤에서 호스팅되는 ParentUserControl

이제 특정 조작으로 자식 컨트롤을 90도 회전하므로 해당 지역의 모든 핫스팟과 상호 작용이 파괴되었다고 확신합니다. 그러나 해결해야 할 문제는 미리보고 인쇄해야하는 패키지 레이블에 대한 것이 었습니다. 그것은 나를 위해 잘 작동했습니다.

고의적으로 고독한 컨트롤에 핫스팟과 컨트롤 기능을 다시 도입 할 수있는 방법이 있다면 언젠가는이 시나리오에 대해 배우고 싶습니다. 물론, WPF는 이러한 미친 OOTB를 지원합니다 .... 어이 .. WinForms는 여전히 굉장히 재미 있습니다.


-4

또는 사용 Control.SuspendLayout()하고 Control.ResumeLayout().


9
레이아웃과 페인팅은 서로 다른 두 가지입니다. 레이아웃은 컨테이너에 자식 컨트롤이 배열되는 방식입니다.
Larry
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.