ShowMessage로 메세지박스를 보여주려고 하면
아주 가끔
원치않게 다른폼뒤에 메세지 박스가 뜬다던가?
또는 Top폼이 바뀌면서 메세지 박스가 뜬다던가 하는 현상이 있습니다.
얼마전 델마당에도 질문이 올라왔었구
어제 포럼에도 올라왔네요
http://www.delmadang.com/community/bbs_view.asp?bbsNo=17&bbsCat=0&st=&keyword=&indx=408159&keyword1=&keyword2=&page=18
http://cbuilder.borlandforum.com/impboard/impboard.dll?action=read&db=bcb_qna&no=55272
개인적인 경험으로 저런 현상이 나타나는 원인을 집어보면..
[원인s]
원인1. ShowMessage로 뜬 메세지박스보다 일반폼이 나중에 Show되는 경우
무엇인가 설정하면서 메세지박스가 떴는데...
그상태에서 무슨 이벤트가 발생하여 ( 네트워크로 무슨 정보가 온다던가 , timer에 이벤트가 있다던가)
Form을 Show해주면
나중에 Show한 폼이 메세지박스보다 앞에 뜹니다.
void __fastcall TForm1::Button2Click(TObject *Sender)
{
Timer1->Enabled=true;
ShowMessage("Form2가 앞에 뜰까 ShowMessage가 앞에 뜰까?");
}
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
Timer1->Enabled=false;
Form2->Show();
}
위와같은 경우 메세지박스가 뜬후 메세지박스위에 Form이 뜨게 됩니다.
위 코드는 억지로 그런 현상을 만든것인데 저런현상이 생각보다 종종 있더군요
원인2. WndParent가 Application->Handle이 아닌경우에..
VCL에서 모든 폼은 윈도우핸들 생성시에 설정하는
CreateParams의 WndParent 가 기본적으로는 Application->Handle 이 됩니다.
(MDI Child폼 빼구)
그런데 메신져처럼 대화창을 작업표시줄에 표시하고 싶다면
Form의 CreateParams에서
WndParent를 GetDesktopWindow() 로 바꿔주면 되죠
그런데 문제는 WndParent를 DesktopWindow로 바꾼 Form에서
ShowMessage를 호출하면
WndParent가 Application->Handle인 폼이 앞으로 나오고 그 앞에 메세지박스가 뜹니다.
void __fastcall TForm1::Button2Click(TObject *Sender)
{
Form2->ShowModal();
}
//=================================================================
void __fastcall TForm2::CreateParams(TCreateParams &Params)
{
TForm::CreateParams(Params);
Params.WndParent= GetDesktopWindow();
}
void __fastcall TForm2::Button2Click(TObject *Sender)
{
ShowMessage("이 메세지박스는 어디에 뜰까?");
}
이 경우에는 Form2에서 띄운 메세지 박스는 Form1위에 뜨고
메세지박스가 Form2에 가려져 보이지 않게될수도 있습니다.
Form2가 Modal로 뜬 상황이니..
메세지박스을 앞으로 가져올수도 없는 상황이되죠..
기타 원인..
그밖에도 잘모르지만 여러가지 원인이 있을수 있을것 입니다.
[문제해결을 위해 알아야 할것]
먼저 알아야 할 사실을 사실은
1. ShowMessage로 띄우는 메세지박스는 Win32 api MessageBox로 띄우는 윈도우와 다른놈입니다.
그냥 다른놈이 아니라 TForm을 상속받은 TMessageForm을 ShowModal로 띄워주는것 뿐이라는 사실입니다.
Dialogs 유닛에 보면 다음과 같이 선언되어있죠!
TMessageForm = class(TForm)
그러니 TForm1 , TForm2 같이 TForm을 상속받은폼이나 TMessageForm이나 비슷한놈인게죠
2. 두번째 CreateParams 에 정해주는 WndParent 가 뭐냐는 것입니다.
//
이놈은 실제 Form의 윈도우핸들을 생성할때 사용하는 API인 CreateWindowEx 함수의
9번째 들어가는 파라메터로 부모윈도우를 나타내는 것입니다.
http://www.winapi.co.kr/reference/Function/CreateWindowEx.htm
VCL( C++빌더 또는 Delphi) 에서는 기본적으로
Applicaiton->Handle의 부모윈도우는 Desktop윈도우이구
다른 모든 Form의 부모윈도우(hWndParent)는 Applicaiton->Handle 이 됩니다.
참조
http://serious-code.net/moin.cgi/Scrap_2fInsideTheVcl_2fPart3
SetPaent로 정해주는 부모윈도우와는 좀 개념이 다르죠
SetParent로 정해주는 parent는 - 보여주는 영역과 관련있다고 생각할수 있구요
CreateWindowEx 에서 정해주는 wndParent는 - 보여주는 위치(Z-Order)와 관련있다고 할수 있습니다.
결론적으로 메세지박스및 폼이 뜨는 위치는 모두 Z-Order와 관련된 문제입니다.
Form의 Style을 StayOnTop으로 설정해주는것도 Z-Order 문제이구요
Z-Order 때문에 ShowMessage, MessagBox,Form등 모든윈도우가 원하는 위치(z-order)에 뜨지 않고
엉뚱한 위치(Z-Order)에 뜰수 있는것이죠
[해결방법]
원인을 이해 했으니 해결방법도 Z-Order를 어떻게 조절해줄수 있을까 하는 관점에서 찾아나가시면 될것입니다.
해결방법1.
먼저 위에 원인2번과 같은 경우에
개인적으로 ShowMessage를 새로 만들어서 사용하기도 합니다. (ShowMessageEx)
Dialog 유닛에 있는 소스를 참조하여 TMessageFormEx 를 만들어 사용하는데
TMessageFormEx 에서 CreateParams의 WndParent를 Application->Handle로 하지 않고
Screen->ActiveForm->Handle로 하도록 만들어 사용합니다.
이렇게 하면 항상 현재 ActiveForm 이 WndParnet 가 되기때문에
Active된 폼에서 ShowMessageEx를 호출할때 메세지박스가 현재폼보다는 항상 앞에뜨게 되죠
또는 그냥 win32api MessageBox를 씁니다.
MessageBox 함수의 첫번째 인자가 WndParent 가 되므로
MessageBox(this->Handle , ******) ;
이런식으로 사용하죠
해결방법2.
원인1과 같은 경우는
Screen객체의 OnActiveFormChange 이벤트 핸들러를 이용합니다.
OnActiveFormChange이벤트는 말그대로 Active된 폼이 바뀔때 발생합니다.
ShowMessage로 메세지박스를 띄우면 TMessageForm이 뜨게 되는데
이때 Screen의 OnActiveFormChange 이벤트가 발생하게 되죠
이 이벤트에서 TMessageForm의 Z-Order를 TopMost로 설정해주면
어떤 폼보다 앞에 뜨게 됩니다.
(단점 다른프로그램이 Active되어도 그 앞에 뜰수가 있습니다. StayOnTop처럼)
//c++builder
void __fastcall TForm1::FormCreate(TObject *Sender)
{
Screen->OnActiveControlChange=OnScreenActiveFormChange;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::OnScreenActiveFormChange(TObject *Sender)
{
if( Screen->ActiveForm!=NULL)
{
String sClsName=Screen->ActiveForm->ClassName();
if(sClsName=="TMessageForm")
SetWindowPos(Screen->ActiveForm->Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
}
//---------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ShowMessage("이 메세지 항상 Top으로 뜨나?");
}
//delphi
procedure TForm1.FormCreate(Sender: TObject);
begin
Screen.OnActiveFormChange:=OnScreenActiveFormChange;
end;
procedure TForm1.OnScreenActiveFormChange(Sender: TObject);
begin
if Assigned(Screen.ActiveForm) and (Screen.ActiveForm.ClassName='TMessageForm') then
SetWindowPos(Screen.ActiveForm.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE);
end;
이상입니다.
그럼..
RAD2007까지는 TMessageForm을 이용하는데..
RAD2010에서는 전혀 다른 방식으로 동작하는듯 하네요(rad2009는 확인해 보지 못함)
rad2010에서는 위와같은 현상이 잘 나지 않을듯 합니다.(아직 격어보지 못해서..)