성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
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# - 다른 윈도우 프로그램이 실행되었음을 인식하는 방법</h1> <p> 아래와 같은 질문이 있군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 다른 프로세스 실행 후 포커스 가져오기 ; <a target='tab' href='https://www.sysnet.pe.kr/3/0/5820'>https://www.sysnet.pe.kr/3/0/5820</a> </pre> <br /> 물론, 가장 좋은 방법은 다른 프로세스에서 준비가 되었을 때 특정 signal을 set하는 것입니다. 하지만, 그게 안 된다면 어떻게든 다른 방법을 찾아야 합니다. 예제와 함께 ^^ 설명해 볼까요?<br /> <br /> 우선, WinForm #1 응용 프로그램을 다음과 같이 만듭니다.<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.Diagnostics; using System.Runtime.InteropServices; namespace WinFormsApp1 { public partial class Form1 : Form { // <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20241128-00/?p=110586'>Why does my program successfully take foreground only when running under the debugger?</a> // <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20090220-00/?p=19083'>Foreground activation permission is like love: You can’t steal it, it has to be given to you</a> [DllImport("user32.dll")] static extern bool SetForegroundWindow(IntPtr hWnd); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { System.Windows.Forms.Timer t = new System.Windows.Forms.Timer(); t.Interval = 1000; t.Tick += (s, ev) => { t.Stop(); <span style='color: blue; font-weight: bold'>Process p = Process.Start("WinFormsApp2.exe"); Thread.Sleep(1000); SetForegroundWindow(this.Handle); </span> }; t.Start(); } } } </pre> <br /> 하는 일은 WinFormsApp2.exe를 실행 후, 1초 대기한 다음 자신의 윈도우를 상위에 위치시키는 SetForegroundWindow를 호출하고 있습니다. 그다음 WinFormsApp2.exe를 기본 Windows Forms 프로젝트로 만든 후, 위의 프로그램을 실행하면 의도한 대로 실행이 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 1. WinFormsApp1.exe 실행 2. 1초 후, 자동으로 WinFormsApp2.exe 실행 3. 1초 후, WinFormsApp1을 SetForegroundWindow로 지정 </pre> <br /> 그런데, 여기서 문제가 있습니다. 바로 WinFormsApp2.exe로부터 포커스를 뺏어올 대기 시간을 1초로 지정한 것인데요, 이것이 왜 문제가 되는지 재현을 위해 WinFormsApp2.exe의 Program.cs에 다음과 같은 코드를 추가합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > namespace WinFormsApp2 { internal static class Program { [STAThread] static void Main() { <span style='color: blue; font-weight: bold'>Thread.Sleep(2000);</span> // ApplicationConfiguration.Initialize(); Application.Run(new Form1()); } } } </pre> <br /> 이제 다시 실행해 보면 당연히 WinFormsApp1.exe는 입력 포커스를 가져가지 못합니다. 왜냐하면, 1초 후 #1 응용 프로그램은 SetForegroundWindow를 호출했고, 다시 1초의 시간이 지나서 그제야 #2 응용 프로그램이 화면에 떴기 때문입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> Windows는 이런 경우에 한해 사용할 수 있는 옵션을 하나 제공하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > WaitForInputIdle function (winuser.h) ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-waitforinputidle'>https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-waitforinputidle</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'> Waits until the specified process has finished processing its initial input and is waiting for user input with no input pending, or until the time-out interval has elapsed.<br /> </div><br /> <br /> 위의 조건을 만족하려면 대상 프로그램은 메시지 큐를 가지고 있어야 합니다. 즉, 그 메시지 큐를 이용해 입력을 처리할 준비가 될 때까지 대기한다는 건데요, 닷넷(C#)에서는 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforinputidle'>Process.WaitForInputIdle</a> 메서드를 이용해 동일한 기능을 실행할 수 있습니다.<br /> <br /> 예제와 같은 상황에서는, #1에서 #2 프로그램을 실행할 때 WaitForInputIdle을 실행하는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > t.Tick += (s, ev) => { t.Stop(); Process p = Process.Start("WinFormsApp2.exe"); <span style='color: blue; font-weight: bold'>p.WaitForInputIdle();</span> Thread.Sleep(1000); SetForegroundWindow(this.Handle); }; </pre> <br /> 그럼 다시 정상적으로 입력 포커스를 가져오는 것을 확인할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 하지만, WaitForInputIdle 역시 문제가 있습니다. 우선, 대상 프로그램이 메시지 루프를 가져야 하기 때문에 콘솔 프로그램에는 쓸 수 없습니다. 달리 말하면, 해당 응용 프로그램에 메시지 큐가 생성되면, 즉, 메시지 루프를 시작하는 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage'>GetMessage</a>가 호출되는 순간 WaitForInputIdle은 조건을 만족하기 때문에 대기를 끝냅니다.<br /> <br /> 따라서, 메시지 루프가 생성된 후, 어떤 식으로든 #2 응용 프로그램의 윈도우가 늦게 뜬다면 SetForegroundWindow는 그 역할을 하지 못합니다. 이에 대한 테스트도 역시 간단하게, #2 응용 프로그램의 Form Load 이벤트에 Sleep을 추가해 확인할 수 있습니다.<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 Form1_Load(object sender, EventArgs e) { Thread.Sleep(1000 * 3); } </pre> <br /> 그러면 우리는 ^^ 다시, 이에 대한 예방책으로 #2의 Main Window가 떴다는 것을 한 번 더 확인하는 절차를 둘 수 있습니다. 가령, Main Window의 Caption을 구할 수 있는 단계까지 대기하도록 만드는 겁니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > t.Tick += (s, ev) => { t.Stop(); Process p = Process.Start("WinFormsApp2.exe"); p.WaitForInputIdle(); System.Diagnostics.Trace.WriteLine("Waited"); <span style='color: blue; font-weight: bold'>StringBuilder sb = new StringBuilder(4096); while (true) { IntPtr ptr = p.MainWindowHandle; GetWindowText(ptr, sb, sb.Capacity); if (sb.Length != 0) { break; } Thread.Sleep(16); }</span> Thread.Sleep(100); SetForegroundWindow(this.Handle); }; </pre> <br /> 점점 더 복잡해지죠? ^^ 어쩔 수 없습니다, 남이 만든 프로그램과 함께 연동한다는 것은 언제나 이렇게 확률을 높이는 방법을 점점 더 추가하는 식으로 구현하게 됩니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=2017&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
7175
(왼쪽의 숫자를 입력해야 합니다.)