음...
[Application.OnIdle이벤트란?]
VCL에는
Application 이라는 전역 객체가 있습니다.
이 객체(class-method)에서 시스템 메세지큐의 메세지를 가져와(PeakMessage)
Dispatch시켜주고 있습니다.
메시지큐에
처리할 메세지가 없을때를
Idle상태라고 하는데...
이때 Application.OnIdle이벤트가 발생하게 됩니다.
* VCL-Control에 MouseEnter / MouseLeave를 이 Idle상태일대 체크해서 처리하며
* Action및 여러가지 처리를 이때에 하고 있습니다.
개발자들도 이 이벤트를 이용해서 여러가지를 처리하곤 합니다.
[OnIdle이벤트 사용의 문제점]
그런데..
만약 서로다른 유닛에 Idle상태일때 작업을 처리하고 싶은것이 있을때는 어떻게 해야할까요?
다음과 같이 각각 연결해주면
AppIdle1과 AppIdle2 둘다 이벤트가 발생할까요?
void __fastcall TForm1::FormCreate(TObject *Sender)
{
Application->OnIdle=AppIdle1;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::AppIdle1(TObject *Sender,bool &bDone)
{
Memo1->Lines->Add(Now().FormatString("1-hh:nn:ss:zzz"));
bDone=true;
}
//=======================================================
void __fastcall TForm2::FormCreate(TObject *Sender)
{
Application->OnIdle=AppIdle2;
}
//---------------------------------------------------------------------------
void __fastcall TForm2::AppIdle2(TObject *Sender,bool &bDone)
{
Memo2->Lines->Add(Now().FormatString("2-hh:nn:ss:zzz"));
bDone=true;
}
위 코드와 같이 하면 둘중 하나만 (나중에 이벤트핸들러를 연결한) 이벤트가 발생할것 입니다.
문론 같은 Exe라면
* Idle상태일때 처리할 작업을 한곳으로 모을수도 있고
* 하나의 Event-Handler에서 다른유닛의 함수를 호출해서 처리할수도 있을것입니다.
하지만 만약 Package나 Dll에서 Application.OnIdle이벤트를 사용하고 싶을때는 어떻게 해야할까요?
또한 같은 exe(Project)라 하더라도, 유닛을 include하기 곤란한 상황이면 어덯게 하죠?
그런경우는 다음과 코드와 같이 이벤트를 연결해서 호출해주는 방법이 있습니다.
TIdleEvent OrgIdleEvent;
void __fastcall TForm1::FormCreate(TObject *Sender)
{
OrgIdleEvent=Application->OnIdle;
Application->OnIdle=AppIdle1;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::AppIdle1(TObject *Sender,bool &bDone)
{
Memo1->Lines->Add(Now().FormatString("1-hh:nn:ss:zzz"));
if(OrgIdleEvent)
{
OrgIdleEvent(Sender,bDone);
}
}
하지만 좀 불편하죠?
[OnIdle이벤트 다중 걸기..]
다음 유닛은 Application.OnIdle이벤트를 여러개 걸수 있는 유닛입니다.
unit AppMultiIdleEventMgr;
interface
uses
Forms,Classes,Windows;
type
PIdleEventArray = ^TIdleEventArray;
TIdleEventArray = array[0..MaxListSize - 1] of TIdleEvent;
// Application.OnIdle이벤트 ManagerThread
TAppIdleManager = class(TThread)
private
ArrSize: Integer;
EventCount: Integer;
EventArr: PIdleEventArray;
private
procedure Grow();
procedure AddIdleEvent(IdleEvent: TIdleEvent);
procedure RemoveIdleEvent(IdleEvent: TIdleEvent);
procedure ClearIdleEvent(IdleEvent: TIdleEvent);
procedure DoActionIdle;
public
constructor Create(CreateSuspended: Boolean);
procedure Execute(); override;
procedure EventSynchronize;
end;
TOpenForm = class(TForm)
public
procedure OpenUpdateActions();
end;
procedure AddIdleEvent(IdleEvent: TIdleEvent);
procedure RemoveIdleEvent(IdleEvent: TIdleEvent);
procedure ClearIdleEvent(IdleEvent: TIdleEvent);
implementation
var
AppIdleMgr: TAppIdleManager;
{ TAppIdleManager }
procedure TAppIdleManager.Grow();
begin
ArrSize:=Arrsize*2;
ReallocMem(EventArr, ArrSize * SizeOf(PIdleEventArray));
end;
procedure TAppIdleManager.AddIdleEvent(IdleEvent: TIdleEvent);
begin
EventArr[EventCount]:=IdleEvent;
Inc(EventCount);
if(EventCount=ArrSize)then
begin
Grow();
end;
end;
procedure TAppIdleManager.ClearIdleEvent(IdleEvent: TIdleEvent);
var
i: Integer;
begin
for i := 0 to EventCount - 1 do
begin
EventArr[i]:=nil;
end;
end;
constructor TAppIdleManager.Create(CreateSuspended: Boolean);
begin
ArrSize:=16;
Grow();
EventCount:=0;
inherited;
end;
procedure TAppIdleManager.DoActionIdle;
var
I: Integer;
begin
for I := 0 to Screen.CustomFormCount - 1 do
with Screen.CustomForms[I] do
if HandleAllocated and IsWindowVisible(Handle) and
IsWindowEnabled(Handle) then
begin
TOpenForm(Screen.CustomForms[I]).OpenUpdateActions;
end;
end;
procedure TAppIdleManager.RemoveIdleEvent(IdleEvent: TIdleEvent);
var
i: Integer;
bMatch: Boolean;
begin
bMatch:=False;
for i := 0 to EventCount - 1 do
begin
if( bMatch=False)then
begin
if( @IdleEvent = @EventArr[i] ) then
begin
bMatch:=True;
end;
end
else
begin
EventArr[i-1]:=EventArr[i];
end;
end;
if bMatch=true then
begin
Dec(EventCount);
end;
end;
procedure TAppIdleManager.EventSynchronize;
var
i: Integer;
bMergeDone: Boolean;
bDone: Boolean;
begin
bMergeDone:=False;
for i := 0 to EventCount - 1 do
begin
if( Application.Terminated)then Break;
bDone := True;
EventArr[i](Application,bDone);
if(bDone=True)then bMergeDone:=True;
end;
if(bMergeDone=True)then
begin
DoActionIdle;
end;
end;
procedure TAppIdleManager.Execute;
begin
while True do
begin
if( Application.Terminated)then Break;
Synchronize(EventSynchronize);
Sleep(1);
end;
end;
//======================================================
procedure AddIdleEvent(IdleEvent: TIdleEvent);
begin
AppIdleMgr.AddIdleEvent(IdleEvent);
end;
procedure RemoveIdleEvent(IdleEvent: TIdleEvent);
begin
AppIdleMgr.RemoveIdleEvent(IdleEvent);
end;
procedure ClearIdleEvent(IdleEvent: TIdleEvent);
begin
AppIdleMgr.ClearIdleEvent(IdleEvent);
end;
{ TOpenForm }
procedure TOpenForm.OpenUpdateActions;
begin
UpdateActions;
end;
initialization
//
AppIdleMgr:=TAppIdleManager.Create(False);
finalization
AppIdleMgr.Free;
end.
사용방법은?
C++Builder의 경우 위 유닛을 프로젝트에 포함하시구
다음과 같이 쓰시면 됩니다.
#include "AppMultiIdleEventMgr.hpp"
void __fastcall TForm1::FormCreate(TObject *Sender)
{
AddIdleEvent(AppIdle1);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
RemoveIdleEvent(AppIdle1);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::AppIdle1(TObject *Sender,bool &bDone)
{
Memo1->Lines->Add(Now().FormatString("1-hh:nn:ss:zzz"));
bDone=true;
}
//============================================================================
void __fastcall TForm2::FormCreate(TObject *Sender)
{
AddIdleEvent(AppIdle2);
}
//---------------------------------------------------------------------------
void __fastcall TForm2::FormDestroy(TObject *Sender)
{
RemoveIdleEvent(AppIdle2);
}
//---------------------------------------------------------------------------
void __fastcall TForm2::AppIdle2(TObject *Sender,bool &bDone)
{
Memo2->Lines->Add(Now().FormatString("2-hh:nn:ss:zzz"));
bDone=true;
}
[MultiIdleEvent 동작원리]
위 유닛은
Thread의 Syncronize를 계속해서 호출해주므로
gui-thread에서 Syncrinize함수를 호출할때
등록된 idle-event함수를 여러개 호출해 주도록 만든것입니다.
[Application.OnIdle이벤트와 다른점들..]
사실 위 유닛은 실제 Application.OnIdle이벤트랑은 동작이 틀린 부분이 많습니다.
다른점1 - Applicaiton-OnIdle이벤트는 Form-Caption영역을 Capture했을때는 event가 발생하지 않습니다만
위 유닛은 계속해서 발생합니다.
다른점2 - Applicaiton-OnIdle이벤트는 Call-by-Ref로 넘긴 Done값이 따라 UpdateActions등이 호출됩니다.
위 유닛은 여러개중 하나의 함수에서도 Done값이 true이면 UpdateActions를 호출하도록 만들었습니다.
그밖에서도 세밀히 분석해보면 다른점이 여러가지더 있습니다.
[마무리..]
Applicaiton.OnIdle이벤트와 똑같이 동작하지는 않지만..
충분히 비슷하게 동작하며..
Idle이벤트를 다른유닛 또는 Package등에서도 걸어서 사용할수 있는 대안이 되리라 생각하빈다.
[주의할점]
위 AppMultiIdleEventMgr 유닛은
에러 처리등을 크게 신경쓰지 않고 만들었습니다.
실무에 적용하시려면 약간의 보완작업이 필요할듯 합니다.
그럼..
디자인 타임 시에... OnIdle 이벤트 핸들러가 필요한 폼에 TApplicationEvents 컴포넌트를 끌어다 넣고....,
TApplicationEvents 컴포넌트의 OnIdle 이벤트 핸들러를 등록해 주는 것으로 간단하게 처리 됩니다.
<Unit1.cpp>
void __fastcall TForm1::AppEventOnIdle(TObject *Sender, bool &Done)
{
Caption = "On Idle...";
}
<Unit2.cpp>
void __fastcall TForm2::AppEventOnIdle(TObject *Sender, bool &Done)
{
Caption = "On Idle...";
}
이벤트는 자동으로 TApplicationEvents 을 갖고있는 폼들에 디스패치 되기 때문에 코딩 해줄 필요가 없죠.