성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Get Started with Milvus Vector DB i...
[정성태] cyberark/PipeViewer - A tool that...
[정성태] WinForms in a 64-Bit world – our st...
[정성태] 예제에서 SELECT_SQL도 내부적으로는 SqlCommand/...
[victor] SELECT_LINQ SELECT_SQL 같은 쿼리인...
[victor] 답변 갑사합니다. 예외(Exception)가 났습니다. ...
[정성태] 일단, 위의 방식대로 하면 예외(Exception) 없이 잘 동...
[정성태] Windows 10 (버전 1809)에 이런 기능이 ^^ 추가되...
[정성태] pde windbg extension ; https://lea...
[정성태] // GetEnumerator extensions for Ran...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - Control의 Invalidate, Update, Refresh 차이점</h1> <p> (2021-02-27: Refresh가 Control 타입에 있었군요, 크게 내용이 달라지는 것은 없지만 Refresh 내용만 더 추가했습니다.)<br /> <br /> 아래와 같은 질문이 있군요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 사용자 지정 컨트롤 생성시 Invalidate, Update, Refresh의 차이점 ; <a target='tab' href='https://www.sysnet.pe.kr/3/0/5474'>https://www.sysnet.pe.kr/3/0/5474</a> </pre> <br /> <span style='text-decoration: line-through'>사실, Refresh는 특정 컨트롤에서 정의한 것이므로 해당 컨트롤의 도움말 등을 통해 정보를 얻어야 합니다.</span> 반면 Control에서 정의한 Invalidate와 Update는 분명한 차이점이 있습니다. <br /> <br /> 역어셈블로 소스 코드를 들여다보면 Invalidate의 경우 다음과 같이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public void Invalidate() { Invalidate(invalidateChildren: false); } public void Invalidate(bool invalidateChildren) { if (!IsHandleCreated) { return; } if (invalidateChildren) { SafeNativeMethods.RedrawWindow(new HandleRef(window, Handle), null, NativeMethods.NullHandleRef, 133); } else { using (new MultithreadSafeCallScope()) { SafeNativeMethods.InvalidateRect(new HandleRef(window, Handle), null, (controlStyle & ControlStyles.Opaque) != ControlStyles.Opaque); } } NotifyInvalidate(ClientRectangle); } [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern bool InvalidateRect(HandleRef hWnd, NativeMethods.COMRECT rect, bool erase); </pre> <br /> 결국 InvalidateRect Win32 API를 호출하는 역할을 하는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > InvalidateRect function (winuser.h) ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect'>https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect</a> </pre> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> The invalidated areas accumulate in the update region until the region is processed when the next WM_PAINT message occurs or until the region is validated by using the ValidateRect or ValidateRgn function.<br /> <br /> The system sends a WM_PAINT message to a window whenever its update region is not empty and there are no other messages in the application queue for that window.<br /> </div><br /> <br /> 중요한 것은, <a target='tab' href='https://www.sysnet.pe.kr/2/0/12539'>WM_PAINT 메시지로 GetMessage/PeekMessage의 메시지 루프를 거쳐 처리</a>된다는 점입니다.<br /> <br /> 반면 Update의 경우에는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public void Update() { SafeNativeMethods.UpdateWindow(new HandleRef(this.window, this.InternalHandle)); } [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern bool UpdateWindow(HandleRef hWnd); </pre> <br /> UpdateWindow Win32 API를 그대로 호출하는 방식으로 처리합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > UpdateWindow function (winuser.h) ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-updatewindow'>https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-updatewindow</a> </pre> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> The UpdateWindow function updates the client area of the specified window by sending a WM_PAINT message to the window if the window's update region is not empty. <span style='color: blue; font-weight: bold'>The function sends a WM_PAINT message directly to the window procedure</span> of the specified window, <span style='color: blue; font-weight: bold'>bypassing the application queue</span>. If the update region is empty, no message is sent. </div><br /> <br /> 문서에 따라, UpdateWindow는 WM_PAINT를 메시지 루프를 거치지 않고 곧바로 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/learnwin32/writing-the-window-procedure'>Window Procedure 함수</a>를 실행하는 식으로 처리한다는 차이점이 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 저 문서상의 설명이 실제로 Winform에서 어떻게 동작하는지 간단하게 테스트를 해볼까요?<br /> <br /> WinForm 디자인 화면에 TextBox 하나와 Button을 놓은 후 Button.Click 이벤트에 다음과 같은 코드를 작성해 둡니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Threading; using System.Windows.Forms; namespace WindowsFormsApp1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { <span style='color: blue; font-weight: bold'>this.textBox1.Text = "qwer"; this.textBox1.Invalidate(); Thread.Sleep(-1);</span> } } } </pre> <br /> 위와 같은 경우, TextBox.Text 속성에 "qwer"을 지정하고 "Invalidate"를 호출해 WM_PAINT를 전송했으므로 이것이 처리되기 위해서는 반드시 메시지 루프가 돌아야 합니다. 하지만 위의 코드에서는 Thread.Sleep을 이용해 메시지 루프 실행을 막고 있으므로 버튼이 눌렸을 때 TextBox에는 여전히 빈 문자열만 출력이 되고 화면은 멈추게 됩니다.<br /> <br /> 자, 그럼 Invalidate를 다음과 같이 Update로 바꿔보면 어떨까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private void button1_Click(object sender, EventArgs e) { this.textBox1.Text = "qwer"; // this.textBox1.Invalidate(); <span style='color: blue; font-weight: bold'>this.textBox1.Update();</span> Thread.Sleep(-1); } </pre> <br /> Update는 Window Procedure를 직접 실행하기 때문에 저 호출로 WM_PAINT 처리가 되어 TextBox에는 "qwer" 글자가 그려지게 됩니다. 따라서 Thread.Sleep으로 인해 메시지 루프 실행이 막혀도 화면에는 이미 개발자가 원하는 텍스트가 출력된 상태로 화면이 멈춥니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 마지막으로 Refresh는 역시 소스 코드를 보면 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public virtual void Refresh() { Invalidate(invalidateChildren: true); Update(); } </pre> <br /> 그러니까, 결국 Invalidate + Update에 불과한 것인데요, 단지 Invalidate 단계에서 InvalidateRect 대신 RedrawWindow를 호출하는 정도의 차이가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public void Invalidate(<span style='color: blue; font-weight: bold'>bool invalidateChildren</span>) { if (!IsHandleCreated) { return; } if (invalidateChildren) { // #define RDW_ALLCHILDREN 0x0080 // #define RDW_ERASE 0x0004 // #define RDW_INVALIDATE 0x0001 <span style='color: blue; font-weight: bold'>SafeNativeMethods.RedrawWindow(new HandleRef(window, Handle), null, NativeMethods.NullHandleRef, 133);</span> // 133 == 0x85 } else { using (new MultithreadSafeCallScope()) { SafeNativeMethods.InvalidateRect(new HandleRef(window, Handle), null, (controlStyle & ControlStyles.Opaque) != ControlStyles.Opaque); } } NotifyInvalidate(ClientRectangle); } [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern bool RedrawWindow(HandleRef hwnd, NativeMethods.COMRECT rcUpdate, HandleRef hrgnUpdate, int flags); </pre> <br /> RedrawWindow Win32 API 문서를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > RedrawWindow function (winuser.h) ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-redrawwindow'>https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-redrawwindow</a> </pre> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> /*<br /> By default, the windows affected by RedrawWindow depend on whether the specified window has the WS_CLIPCHILDREN style. Child windows that are not the WS_CLIPCHILDREN style are unaffected; non-WS_CLIPCHILDREN windows are recursively validated or invalidated until a WS_CLIPCHILDREN window is encountered. The following flags control which windows are affected by the RedrawWindow function.<br /> <br /> RDW_ALLCHILDREN<br /> Includes child windows, if any, in the repainting operation.<br /> <br /> RDW_ERASE<br /> Causes the window to receive a WM_ERASEBKGND message when the window is repainted. The RDW_INVALIDATE flag must also be specified; otherwise, RDW_ERASE has no effect.<br /> <br /> RDW_INVALIDATE<br /> Invalidates lprcUpdate or hrgnUpdate (only one may be non-NULL). If both are NULL, the entire window is invalidated.<br /> */<br /> </div><br /> <br /> 사용된 플래그에 따라 자식 윈도우까지도 invalidate 시킨다는 차이점이 있습니다. 따라서, 자식 윈도우를 소유하지 않는 TextBox 등의 컨트롤에는 굳이 Refresh를 호출할 필요가 없습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1545
(왼쪽의 숫자를 입력해야 합니다.)