※ 본 강좌는 델파이4의 Help를 기초로 작성되었습니다. 본인이 델파이4를 접한
지 얼마되지 않아 내용상 오류가 있을 수 있습니다.
델파이4의 컴퍼넌트 팔레트중 Standard팔레트를 살펴보면 마지막에 처음보는 컴
퍼넌트 하나가 추가된 것을 볼 수 있다. TActionList라 이름 붙여진 이 컴퍼넌트
는 윈도우와 메뉴와 버튼을 아이콘으로 가지고 있다. 도대체 무엇을 하는 컴퍼넌
트 인가? 또 한가지, Form, Button등 대부분의 VCL 컴퍼넌트의 프로퍼티 중
Action이라는 프로퍼티가 델파이4에 들어오면서 추가되었다. 이 프로퍼티는 어디
에 사용되는가? 아쉽게도 Action에 대한 상세한 내용은 델파이 헬프를 통해 찾아
볼 수 없었다. 헬프상의 개괄적인 내용과 본인이 살펴본 사항들을 함께 묶어 강
좌를 진행해 보겠다.
위의 예에 폼을 닫는 메뉴아이템인 CloseMenuItem과 역시 폼을 닫는 기능을 하는
스피드 버튼인 CloseButton이 있다. 이들은 정확히 동일한 기능을 한다. 이 말은
곧 Caption, Checked, Enabled, HelpContext, Hint, ImageIndex, ShortCut,
Visible등의 프로퍼티들이 동일하게 변경되어야 한다는 것을 말한다. 예를들어
CloseMenuItem이 Disable될 상황이라면 CloseButton 역시 Disable되어야 할 것이
다. 기존의 델파이 버전에서는 이런 처리를 각각 따로 해주어야 했다. 하지만 이
것을 TAction을 사용하여 한꺼번에 처리할 수 있다. 아래의 그림을 보자.
위에서 CloseMentItem과 CloseButton의 Action 프로퍼티를 Action1으로 연결한
다. 여기서 CloseMenuItem, CloseButton과 같이 Action의 영향을 받는 오브젝트
들을 Action1의 클라이언트라고 부른다.
이렇게 하면 Action1의 캡션을 바꾸면 그 클라이언트인 CloseMenuItem의 Caption
과 CloseButton의 Caption이 동시에 바뀐다. 이런식으로 Action1의 프로퍼티만을
변경하면 그것에 연결된 다른 오브젝트들의 프로퍼티 중 같은 이름의 프로퍼티가
있다면 Action1과 동일한 값으로 변경된다. 하지만 이것이 Action의 주용도는 아
니다.
Action의 주용도는 사용자가 컨트를을 Click했을 때 Action의 OnExecute 이벤트
핸들러가 호출되는 등 사용자가 어떤 작용을 했을 때 그에 대한 처리를 한꺼번에
처리할 수 있다는데 있다. 몇몇 VCL 컨트롤들에 Action 프로퍼티와
ExecuteAction등의 메쏘드가 추가되었는데 이것들이 클라이언트로 작동하게 만드
는 지원이다.
이 Action들을 리스트로 관리하는 것이 TActionList 컴퍼넌트다. 위에서 살펴본
것과 같이 ActionList의 주된 용도는 '사용자 명령들'에 대한 반응을 한곳으로
집중시키기 위한 것이다. 다시 말해 메뉴나 버튼등 사용자 명령에 반응하는 컨트
롤들을 통합관리하는 것이다.
델파이4에서 File-New를 통해 MDI나 SDI 어플리케이션을 생성시키면 그곳에
ActionList가 자리잡는다. 메뉴와 스피드 버튼간의 관리를 ActionList를 통해 처
리하고 있다. 컴퍼넌트 팔레트의 Standard 부분에 자리잡을 만큼 앞으로 델파이
의 기본 컴퍼넌트로서 주요한 역할을 할 것이라 기대된다.
델파이에는 기본적으로 TAction에서 상속받은 TEditAction, TWindowAction,
TDataSetAction등의 Action들이 미리 정의되어 있다. 이것은 코드가 필요없이 사
용자 반응을 통합 처리하도록 지원된다. 예를들어 Edit 메뉴와 관련된 여러 가지
Action들이 Edit...라는 이름으로 정의되어 있다. Action List를 더블클릭하면
나타나는 다이얼로그에서 오른쪽 클릭을 해 컨텍스트 메뉴를 띄운다. 여기서 New
Standard Action을 선택하면 델파이에서 기본적으로 제공되는 Action들의 리스트
를 볼 수 있다. 각 Action은 category별로 정리되어 ActionList에 나타난다. 몇
가지를 추가해 확인해 보기 바란다. 여러분들이 이런 미리 정의된 Action들을 작
성할 수 있다. 하지만 대부분의 경우 미리 정의된 Action을 사용하지 않고 그대
로 코딩을 통해 이용한다. {Delphi4}\Demos\RichEdit의 예를 보면 미리 정의된
Action을 이용하지 않고 개별의 Action을 이용한다.
○ ActionList를 통한 Action 제어
TAction은 앞서 살펴보았듯이 사용자 명령에 대한 응답을 통합관리하기 위해 지
원된다. Action에 연결되어 관리를 받는 오브젝트들은 Action의 클라이언트라고
부른다. 이 클라이언트들은 주로 메뉴 아이템이나 버튼종류(TToolButton,
TSpeedButton, TMenuItem, TButton, TCheckBox, TRadioButton등)이다.
TActionList는 TAction의 리스트를 관리하는 컴퍼넌트다. 디자인타임에서 Action
을 다루기 위해서는 이 컴퍼넌트를 이용해야 한다. TActionLink가 Action과 그
클라이언트간의 연결을 관리한다.
우선 Standard 컴퍼넌트 팔레트에서 ActionList를 선택 Form이나 DataModule 위
에 놓아보자. ActionList를 더블클릭하면 Action List Editor가 뜬다. 이것을 통
해 Action들을 추가하고 삭제, 정렬할 수 있다. 해당 Action의 각 프로퍼티는 오
브젝트 인스펙터상에 나타난다. Name 프로퍼티는 Action의 구분자가 된다. 나머
지 프로퍼티와 이벤트(Caption, Checked, Enabled, HelpContext, Hint,
ImageIndex, ShortCut, Visible)등은 그 Action과 연결된 컨트롤(이를 클라이언
트 컨트롤이라 부른다)의 각 프로퍼티에 영향을 준다. 예를들어 클라이언트 컨트
롤이 메뉴 아이템이라면 위의 프로퍼티 모두의 영향을 받는다. 델파이4에 들어오
면서 메뉴아이템은 각각의 아이콘을 가질 수 있어 ImageIndex에도 영향을 받는
다. Win32 팔레트에서 ImageList 컴퍼넌트를 폼에 놓고 그것을 ActionList의
ImageList 프로퍼티에 지정하면 ImageIndex가 유효하게 된다.
Form, Button, MenuItem등의 컨트롤들에는 대부분 Action이라는 프로퍼티가 있
다. ActionList에 Action을 한 개 이상 추가하면 그것이 컨트롤의 Action 프로퍼
티에 나타나며 드롭다운을 이용해 선택할 수 있다. 이렇게 컨트롤의 Action 프로
퍼티를 선택하면 그 컨트롤은 ActionList 아이템중 한 Action의 클라이언트 컨트
롤이 되는 것이다.
사용 예를들어 보자. 우선 폼에 ActionList를 하나 놓는다. 그위에 TDataSet,
TDataSource, TDBGrid를 놓고 이들을 하나의 테이블로 연결한다. DBGrid에 테이
블의 내용이 나타날 것이다. 우선 ActionList1을 더블클릭해 다이얼로그를 연다.
오른쪽 버튼을 눌러 New Standard Action을 선택하던지 아니면 Ctrl+Insert를 누
른다. Standard Action들의 리스트가 나타나는데 이들 중 TDataSetCancel,
TDataSetEdit, TdataSetFirst, TDataSetInsert, TDataSetLast, TDataSetNext,
TDataSetPost, TDataSetPrior, TDataSetRefresh들을 선택한다. DataSet category
안에 모두 잡힐 것이다. 이제 폼에 버튼을 여러개 두고 각 버튼의 Action을
Cancel1, Edit1등의 Action으로 연결하고 어플리케이션을 실행시킨다. 각각의 상
황에 적절히 변화되는 버튼들을 볼 수 있다. 이것들을 메뉴아이템과도 연결할 수
있다.
다른 예를들어보자. 일반적은 TCustomAction을 이용할 경우의 예이다. 이것을 이
용하면 미리 정의된 처리가 없기 때문에 OnExecute, OnHint, OnUpdate를 이용한
코드를 Action에 기입해 주어야 한다.
OnExecute 이벤트 핸들러는 관련 클라이언트 컨트롤에 클릭이 발생했을 때 호출
된다.
procedure TForm1.Action1Execute(Sender: TObject);
begin
ShowMessage('이곳에 클릭했을 때의 처리를 기입합니다');
end;
OnHint 이벤트 핸들러는 클라이언트 컨트롤이 힌트가 필요할 때 처리된다. 이것
은 Action의 Hint 프로퍼티로도 처리되므로 거의 이용되지 않는다. 하지만, 힌트
가 경우에 따라 달라지는 등의 경우 이용할 수 있다.
procedure TForm1.Action1Hint(var HintStr: String; var CanShow: Boolean);
begin
HintStr := '이 Action의 클라이언트는 같은 힌트를 가진다';
CanShow := True;
end;
OnUpdate 이벤트 핸들러는 Idle인 상태에서 호출된다. 이것을 통해 주로 컨트롤
의 Enable이나 Checked등의 설정을 한다.
procedure TForm1.Action1Update(Sender: TObject);
begin
if IsModified then
begin
BitBtn1.Enabled := False;
CheckBox1.Enbeld := False;
end
else
begin
BitBtn1.Enabled := True;
CheckBox1.Enabled := True;
end;
end;
[참고] 간혹 ActionList의 다이얼로그에서 New Action을 통해 TCustomAction을
그대로 이용할 경우, 그 클라이언트들이 모두 Disable되어 있는 것을 보게 된다.
그 이유는 Action이 처리되지 않으면 그 클라이언트들을 모두 Disable 시켜버리
기 때문이다. 이 것을 막는 방법은 DisableIfNoHandler 프로퍼티를 False로 바꾸
어 주면 된다.
○ Action Link와 프로퍼티간의 연결
ActionLink는 Action과 각 VCL 오브젝트들(컴퍼넌트,컨트롤)의 프로퍼티를 연결
하는 역할을 한다. 사실, ActionLink는 내부적으로 구성되고 사용되기 때문에 개
발자가 관여할 여지는 없다. Action의 한 프로퍼티를 바꾸면 해당 Action과 연결
된 모든 클라이언트의 프로퍼티가 바뀐다. 이런 프로퍼티들로는 Caption,
Checked, Enabled, HelpContext, Hint, ImageIndex, ShortCut, Visible이 있다.
반대의 경우는 어떠한가? 클라이언트 컨트롤의 프로퍼티에서 Caption, Checked,
Enabled, HelpContext, Hint, ImageIndex, ShortCut, Visible 중 하나를 변경하
면 그것이 Action에 영향을 주는가? 아니다. 그 컨트롤 하나만 변경되고 Action
이나 그 Action의 클라이언트 컨트롤들에는 영향을 미치지 않는다. 그 자신의 프
로퍼티만 변경된다.
[주의] 다시 확인하자. Action의 프로퍼티를 변경하면 그 클라이언트 컨트롤의
프로퍼티가 바뀌지만 클라이언트 컨트롤에서의 변경은 Action에 영향을 주지 않
는다.
○ Action의 실행
클라이언트 컴퍼넌트나 컨트롤를 클릭하면 열결된 Action의 OnExecute 이벤트가
호출된다. 아래의 코드는 Action이 실행될 때 OnExcute 이벤트 핸들러를 통해 클
라이언트 컨트롤인 툴바의 Visible 프로퍼티를 변경하는 예를 보여준다.
procedure TForm1.Action1Execute(Sender: TObject);
begin
ToolBar1.Visible := not ToolBar1.Visible;
end;
참고: 만약 tool button이나 menu item을 사용하고 있다면 그것을 위한 Images
프로퍼티를 수동으로 ActionList의 Images 프로퍼티로 연결시켜주어야 한다.
ImageIndex 프로퍼티가 동적으로 클라이언트에 연결되는 경우에라도 마찬가지다.
아래는 Cut1이라고 불리는 Action이 실행되는 과정을 보여주고 있다. 여기서 클
라이언트인 Speedbutton1은 자신의 Action 프로퍼티로 Cut1에 연결되고
Speedbutton1의 Click 메쏘드는 Cut1의 Execute 메쏘드를 호출하게 된다.
1. SpeedButton1의 클릭이 Cut1.Execute이 호출되게 한다.
2. Cut1은 우선 자신의 ActionList인 ActionList1에게 실행될 기회를 주기위해.
ActionList1.ExecuteAction을 호출한다. 이때 Cut1 자신을 인자로 준다.
3. ActionList1은 ExecuteAction을 위해 자신의 이벤트 핸들러인 OnExecute을 호
출한다. ExecuteAction은 ActionList에 있는 모든 action들에 적용된다.
OnExecute 핸들러는 Handled라는 인자를 가지고 있는데 디폴트로 False가 되
돌려진다. 만약에 핸들러가 할당되었고 이벤트를 처리했다면 True를 돌려야
한다. True가 되면 일련의 작업이 거기서 끝나게 된다. 예를들면 아래와 같
다.
procedure TForm1.ActionList1ExecuteAction(Action: TBasicAction;
var Handled: Boolean);
begin
{ 다른 남은 Action들을 더 이상 처리하게 하지 않기 위해 True를 설정 }
Handled := True;
end;
만약 위의 시점에서 실행이 처리되지 않았다면 나머지 Action들을 위해
Handled를 False로 주던지 아무런 값을 설정하지 않으면 된다. 기본값이
False이므로 값을 지정하지 않아도 무방하다. 이렇게 하면 ActionList의 다른
Action들을 위해 일련의 작업은 계속된다.
4. Cut1 action은 Application 오브젝트의 ExecuteAction 메쏘드에 라우트된다.
이 메쏘드는 OnActionExecute 이벤트 핸들러를 호출한다. ExectueAction 메
쏘드는 어플리케이션에 있는 모든 Action List들에게 처리를 전달한다. 처리
방법은 Action List의 ExecuteAction과 동일하다. OnActionExecute 핸들러가
작성되어 있고 그것이 이벤트를 처리하였다면 Handled 인자에 True를 설정하
여야 한다. 이렇게 하면 일련의 작업들은 여기서 끝난다. 예를들면 아래와 같
다.
procedure TForm1.ApplicationExecuteAction(Action: TBasicAction;
var Handled: Boolean);
begin
{ 다른 Application안의 ActionList에게 이벤트를 전달하는 것을 막음 }
Handled := True;
end;
만약의 실행이 Application 이벤트 핸들러에서 처리되지 않았다면 Cut1은
CM_ACTIONEXECUTE 메지시를 그 자신을 인자로 하여 어플리케이션의 WndProc로
보낸다. 그렇게 하면 Application은 해당 Action을 실행시킬 타겟을 찾는다.
○ Action의 업데이트
Application이 idle상태일 때, 비주얼 컨트롤이나 화면에 나타난 메뉴아이템과
연결된 모든 Aciton의 OnUpdate 이벤트가 발생한다. 이것은 어플리케이션이
enable이나 disable등의 작업을 중앙에서 한코드로 처리할 수 있는 기회를 부여
한다. 예를들어 아래의 코드는 Toolbar가 Visible일 때 Action의 Checked를 True
로 만드는 OnUpdate핸들러의 예다.
procedure TForm1.Action1Update(Sender: TObject);
begin
{ 툴바가 현재 Visible인가 아닌가를 나타냄 }
(Sender as TAction).Checked := ToolBar1.Visible;
end;
{Delphi4}\Demos\RichEdit의 내용을 살펴보면 Action의 사용에 대해 배울 수 있
다.
○ Action과 관련된 소스코드
기본적인 Action들이 StdActns, DBActns 유닛에 포함되어 있다. 이들을
Predefined Action이라고 부른다. Pre-defined Action은 크게 TEditAction,
TWindowAction, TDataSetAction의 세그룹으로 나눈다. Action 컴퍼넌트를 작성하
기 위해 이것들을 살펴보는 것이 좋다.
StdActns에는 Edit메뉴와 관련된 Cut, Copy, Paste등을 처리하기 위한 Action과
Window 메뉴와 관련된 Close, Cascade, TileHorizontal, TileVertical,
MinimizeAll, Arrage등을 처리하는 Action들이 들어 있다. 이것들은
New-Projects-SDI Application, New-Projects-MDI Application를 이용해 SDI나
MDI 어플리케이션을 생성해 보면 그 사용법을 자세히 알 수 있다.
DBActns.pas에서는 First, Prior, Next, Last, Insert, Delete, Edit, Post,
Cancel, Refresh등의 DataSet의 조작과 관련된 Action들이 자리잡고 있다.
컴퍼넌트를 작성할 때 StdActns와 DBActns 유닛을 예로 참고하면 좋다. 이들은
Action과 연결된 control이나 컴퍼넌트들이 어떻게 동작할 것인가를 설정하는 좋
은 예들이다. TEditAction과 TWindowAction들은 일반적으로 HandlesTarget,
UpdateTarget 메쏘드를 오버라이드하며 나머지 메쏘드들을 이용해 타겟을 특정
클래스나 오브젝트로 제한한다. 각 Action별 동작을 위해서는 ExecuteTarget을
통해 그 동작을 설정한다.
이들의 실제 소스코드를 보면 아주 간단하다는 것을 알 수 있다. 소스코드를 열
어 TEditAction 부분을 살펴보자. 강좌란에서 500라인 넘게 올릴 수 없고 한 강
좌에 이 내용을 마치고 싶은 생각에 소스코드는 이곳에 기입하지 않았다.
GetControl은 Target을 TCustomEdit로 Typecast한다. ExecuteTarget 메쏘드를 오
버라이드하여 사용자가 클릭하였을 때 어떻게 동작할 것인가를 지정한다. 이렇게
많이 사용되는 Action은 상속받은 클래스로 처리하지만 일반적인 다른 Action들
은 해당 Action의 OnClick을 이용해 처리하는 것이 일반적이다.
○ Action이 타겟을 찾는 법
1. 우선 CM_ACTIONEXECUTE 메시지가 어플리케이션에 주어지면 먼저 그것을 화면
상의 활성폼(Active Form)으로 보낸다. 만약에 화면상의 활성폼이 없다면 어
플리케이션은 그것을 메인폼으로 보낸다. 이 메시지에는 Action이 인자로 들
어있다. 이 Action을 Cut1이라고 하자.
2. 메시지를 받은 폼이 Form1이라고 가정하자. Form1은 현재 활성 컨트롤을 찾아
컨트롤의 ExecuteAction 메쏘드를 호출한다. 이 때 인자로는 Cut1이 주어진
다.
3. 활성 컨트롤이 Memo1이라고 가정하자. Memo1은 Cut1의 HandlesTarget 메쏘드
를 호출한다. 이 때 인자로 Memo1 자신을 주어서 자신이 적당한 타겟인지를
Cut1이 확인할 수 있게 한다. 만약 Memo1이 적절한 타겟이 아니라면
HandlesTarget은 False를 되돌리며 Memo1의 ExecuteAction 역시 False를 되돌
린다.
4. 만약 Memo1이 Cut1을 위한 적당한 타겟이라면, HandelsTarget은 True를 되돌
린다. 이 때, Memo1은 Cut1.ExecuteTarget을 호출하고 인자로 자신을 준다.
마지막으로 Memo1의 선택부분을 Cut1.ExecuteTarget은 클립보드로 잘라 넣는
다.
5. 만약 컨트롤이 적당한 타겟이 아니면 일련의 작업이 계속된다. Form1은 자신
의 ExecuteAction 메쏘드를 호출한다. 만약 Form1이 적당한 타겟이라면
Cut1.ExecuteTaget 메쏘드를 호출하고 자신을 인자로 넘긴다. 만약 Form1이
적당한 타겟이 아니라면 Form1은 타겟을 찾을 때 까지 자신이 포함하고 있는
모든 Visible 컨트롤들의 ExecuteAction을 호출한다.
[참고] 간혹 ActionList의 다이얼로그에서 New Action을 통해 TCustomAction을
그대로 이용할 경우, 그 클라이언트들이 모두 Disable되어 있는 것을 보게 된다.
그 이유는 Action이 처리되지 않으면 그 클라이언트들을 모두 Disable 시켜버리
기 때문이다. 이 것을 막는 방법은 DisableIfNoHandler 프로퍼티를 False로 바꾸
어 주면 된다.
○ Action을 등록시키는 법
자신이 작성한 Action 컴퍼넌트들을 IDE에 등록시키거나 등록해제하기 위해서는
ActnList 유닛에 있는 아래 두 개의 프로시져를 이용한다.
procedure RegisterActions(const CategoryName: string; const AClasses: array
of TBasicActionClass);
procedure UnRegisterActions(const AClasses: array of TBasicActionClass);
TAction을 관리해주는 Observer 컴포넌트 입니다...
TAction은 (Menu On Button etc) OnAction event에 연결되구요...
아주 유용한 것은 커트롤들의 Caption 및 Enable/disable control이 쉬워집니다.
TAction.Cation 및 TAction.Enable의 속서을 수정하면
이 TAction으로 연결된 컨트롤들이 동시에 적용이되는 것죠^^
그럼 즐 프하세요.