C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 팁&트릭
C++Builder Programming Tip&Tricks
[1076] [VCL] Application.OnIdle 이벤트 여러개 연결하기
장성호 [nasilso] 9038 읽음    2012-03-06 03:05
음...

[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 유닛은
에러 처리등을 크게 신경쓰지 않고 만들었습니다.
실무에 적용하시려면 약간의 보완작업이 필요할듯 합니다.

그럼..
빌더(TWx) [builder]   2012-03-06 08:17 X
Thread를 사용하거나 코딩할 필요 없이, IDE에서 제공되는 TApplicationEvents 컴포넌트를 이용하면 됩니다.
디자인 타임 시에... 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 을 갖고있는 폼들에 디스패치 되기 때문에 코딩 해줄 필요가 없죠.
장성호 [nasilso]   2012-03-06 10:21 X
아.. 감사합니다.

TApplicationEvents를 제대로 써본적이 없어서 잘 몰랐습니다.


방금 확인해 보니..
TMultiCaster 라는 놈이 
TApplicationEvents 가 생성되는 갯수 만큼 List로 관리하고 있으면서
Idle이벤트를 호출해주네요
//--------------------------------------------------------------
procedure TMultiCaster.DoIdle(Sender: TObject; var Done: Boolean);
var
  I: Integer;
begin
  BeginDispatch;
  try
    for I := Count - 1 downto 0 do
    begin
      AppEvents[I].DoIdle(Sender, Done);
      if FCancelDispatching then Break;
    end;
  finally
    EndDispatch;
  end;
end;
//--------------------------------------------------------------

그런데..
이  TMultiCaster 라는 놈도 전역객체인 Application의 이벤트에 연결되어있네요
//--------------------------------------------------------------
constructor TMultiCaster.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FAppEvents := TComponentList.Create(False);
  with Application do
  begin
    OnActionExecute := DoActionExecute;
    OnActionUpdate := DoActionUpdate;
    OnActivate := DoActivate;
    OnDeactivate := DoDeactivate;
    OnException := DoException;
    OnHelp := DoHelp;
    OnHint := DoHint;
    OnIdle := DoIdle;
    OnMessage := DoMessage;
    OnMinimize := DoMinimize;
    OnRestore := DoRestore;
    OnShowHint := DoShowHint;
    OnShortCut := DoShortcut;
{$IF DEFINED(CLR)}
    OnShutDown := DoShutDown;
{$IFEND}
    OnSettingChange := DoSettingChange;
    OnModalBegin := DoModalBegin;
    OnModalEnd := DoModalEnd;
  end;
end;
//--------------------------------------------------------------


결론적으로 TApplicationEvents 를 쓰는경우에

Application.OnIdle ,  Application.OnException  등의 이벤트를 코딩으로 직접연결해서 쓰면 안되겠네요

그것만 주의하면  TApplicationEvents를 쓰는것이 최상인듯 합니다.


...

+ -

관련 글 리스트
1076 [VCL] Application.OnIdle 이벤트 여러개 연결하기 장성호 9038 2012/03/06
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.