본문 바로가기
IT-개발,DB

[VC++/IE] 툴밴드(Tool Band)란?

by SB리치퍼슨 2010. 9. 7.



출처 :  http://blog.naver.com/xinfra/80007923632

1. 툴밴드(Tool Band)란?
-----------------------------

이건 IE의 확장자입니다. IE의 상단에 CoolBar가 보이시죠? 그 안에 여러개의 ToolBar들이 들어 있는데, 거기다가 내가 만든 툴바를 집어 넣고 싶을때 쓰는겁니다.
보기/도구모음 메뉴에서 (또는 쿨바 위에서 오른클릭) 툴바들을 보이고 감추고 할 수 있죠.

이 놈과 유사품으로 Explorer Bar와 Desk Band가 있습니다.
Explorer Bar는 보기/탐색창 메뉴에서 선택되는 항목으로 브라우저의 왼쪽에 세로로 튀어 나옵니다.
Desk Band는 IE 확장자가 아니라 윈도우즈 확장자라고 해야 하나요? 모니터 젤 아래쪽에 작업표시줄이 있죠? (시작버튼이 있는 줄) 거기에 들어가는 밴드입니다.
이 둘은 오늘 강좌의 주메뉴인 ToolBand와 거의 똑같습니다. MSDN에도 세 놈을 같이 취급하고 있구요.

아마도 오늘은 상당히 복잡하고도 긴 글이 될것 같은데요. 예제로 만들어 볼 밴드는 델마당 검색밴드입니다.
이름을 델밴드라고 붙였는데요(대일밴드가 아님) 델마당 홈페이지 왼쪽 상단에 게시판 검색 폼이 있죠? 그 놈과 같은 기능을 하는데, 이건 밴드로 붙는 겁니다.


2. 들어가기 전에...
-----------------------------

오늘은 확장자들을 좀 더 고급스럽게 등록하는 방법을 한 번 살펴보도록 하겠습니다.
지금까지는 COM 등록 따로(델파이메뉴에서), 확장자 등록 따로(REG 화일로) 했었는데요, 이제는 한 방에 모조리 등록해 버리는 방법을 알아보도록 하지요.
처음에 마법사로 프로그램 시작 준비를 하면, 맨 아래쪽에

initialization
 TComObjectFactory.Create(ComServer, TDelSearch, Class_DelSearch,
   'DelSearch', '', ciMultiInstance, tmApartment);
end.

이런게 하나 생겼죠? 도데체 이게 뭐하는 놈인지 궁금하지 않으셨나요? 컴포넌트 공장?
이 TComObjectFactory 라는 클래스는 프로그램 동작시에 필요한 게 아닙니다. 바로 설치/제거 시에 사용되는 건데요. 델파이메뉴에서 Register ActiveX Server 또는 Unregister ActiveX Server를 누르면 바로 이 공장이 가동되는 겁니다.
혹은 도스창에서 Regsvr32.exe 로 설치할 경우도 마찬가지 입니다. (델파이도 아마 이걸 이용할지도..)
TComObjectFactory 는 기본적인 기능 즉, COM 서버로 등록하고 제거하고 하는 기능 밖에 없기때문에 다른 부분의 레지스트리도 같이 조작하기 위해서는 공장을 따로 만들어야합니다. 간단히 TComObjectFactory 를 상속받아 만들면 되는데요, 오늘 예제 프로그램에 들어가 있는 공장은 이렇습니다.

type
 ComFacProc = procedure;

 TComFac = class (TComObjectFactory)
 private
   FInstallProc, FUnInstallProc : ComFacProc;
 public
   constructor Create(ComServer: TComServerObject; ComClass: TComClass;
     const ClassID: TGUID; const ClassName, Description: string;
     const InstallProc, UnInstallProc : ComFacProc;
     Instancing: TClassInstancing; ThreadingModel: TThreadingModel = tmSingle);
   procedure UpdateRegistry(Register: Boolean); override;
 end;

복잡한것 같지만 실은 아주 단순합니다.
모든 기능을 그대로 상속 받으면서 설치시 실행될 함수, 제거시 실행될 함수를 각각 추가해 준것 뿐입니다.
이 예제에서는

procedure InstallProc;
begin
 REG_SetString(HKEY_CLASSES_ROOT, 'CLSID\' + GUIDToString(Class_DelSearch), '', BandCaption);
 REG_SetString(HKEY_CLASSES_ROOT, 'CLSID\' + GUIDToString(Class_DelSearch) + '\Implemented Categories\' + CATID_CommBand, '', '');
 REG_SetString(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Internet Explorer\Toolbar', GUIDToString(Class_DelSearch), '');
end;

procedure UnInstallProc;
begin
 REG_Delete(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Internet Explorer\Toolbar', GUIDToString(Class_DelSearch));
end;

initialization
 TComFac.Create(ComServer, TDelSearch,
                Class_DelSearch, 'DelSearch', '',
                InstallProc, UnInstallProc,           // 이 라인만 원래꺼와 다르죠.
                ciMultiInstance, tmApartment);
end.

이렇게 하면 원래의 COM 설치/제거 기능은 알아서 하고 저 덧붙인 함수들을 같이 수행하는 겁니다.
그런 이유로 오늘 부터는 REG 화일이 따라가지 않을 것입니다.
REG_xxx 함수들은 ComFac 유닛에 같이 포함되어 있습니다.


3. 레지스트리에 등록
-----------------------------

원래는 이게 뒤쪽에 나와야 하는데, 위에 공장 설명부분에 레지스트리 부분이 나왔기때문에 그냥 한꺼번에 해버릴려구요.

const
 CATID_DeskBand = '{00021492-0000-0000-C000-000000000046}';  // 태스크바 밴드 (DeskBand)
 CATID_InfoBand = '{00021493-0000-0000-C000-000000000046}';  // 탐색창        (ExplorerBar)
 CATID_CommBand = '{00021494-0000-0000-C000-000000000046}';  // 툴바 밴드     (ToolBand)

툴 밴드는 위의 공장처럼
 HKEY_CLASSES_ROOT\CLSID\{Your Band Object's CLSID GUID}\Implemented Categories\{00021494-0000-0000-C000-000000000046} (Key)
 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Toolbar\{Your Band Object's CLSID GUID} = '' (Value)
이렇구요.

탐색창은
 HKEY_CLASSES_ROOT\CLSID\{Your Band Object's CLSID GUID}\Implemented Categories\{00021493-0000-0000-C000-000000000046} (Key)
 HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Explorer Bars\{Your Band Object's CLSID GUID} (Key)
 HKEY_CLASSES_ROOT\CLSID\{Your Band Object's CLSID GUID}\Instance\CLSID = '{4D5C8C2A-D075-11D0-B416-00C04FB90376}' (Value)
 HKEY_CLASSES_ROOT\CLSID\{Your Band Object's CLSID GUID}\Instance\InitPropertyBag\Url = '탐색창의 컨텐츠가 있는 페이지의 URL' (Value)
을 각각 집어 넣습니다.

데스크밴드는 어떻게 등록하는지 모르겠군요. (--;) MSDN 설명서에는 이 부분이 없어요.
아시는 분은 꼬리를 달아주시길 바래요.

실제 MSDN예제들을 보면 저렇게 무식하게 레지스트리를 직접 건드리는게 아니라 준비된 인터페이스들 및 함수들을 이용해서 등록합니다.
이번 강좌에서 그걸 흉내 한번 내볼려다가 기능은 같은데 쓸데없이 복잡해지기만 할것 같아 그만 뒀습니다.
첨부파일에는 ComCat 이라는 유닛으로 포함은 되어 있지만 예제에서 사용은 하지 않습니다. 참고만 하세요.


4. 시작하자.
-----------------------------

지금까지와 같은 방법으로 새 프로젝트를 열고 COM공장부터 뜯어 고칩니다. 물론 그냥 두고 REG를 따로 세팅하는 방식으로 하셔도 상관은 없습니다.
그리고는 그대로 두고 File/"New Form" 메뉴를 누릅니다. 그렇습니다. 여태까지는 폼이 없는 확장자들이었지만 이건 폼이 필요합니다. 밴드를 넣으려면 밴드를 그려야죠.

ToolBand의 경우 ..

1. 폼에 CoolBar를 집어 넣는다.
2. CoolBar에 ToolBar를 집어 넣는다.
3. ToolBar에 놓고싶은 컨트롤들을 가져다 놓는다. (고수들은 ToolBar없이 CoolBar위에 바로 컨트롤을 올리기도 한다.)
4. 모양을 다듬는다. (폼의 BorderStyle는 bsNone로 하자)
  크기를 알맞게 조정하고 (AutoSize를 이용하면 편리하다) 폼의 Visible이 True로 되어있는지 확인도 하자. (누구처럼 그림 안나온다고 혼자 열받지 말자)

DeskBand, ExplorerBar의 경우는 해보질 않아 꼭 집어 말씀드리지는 못하겠구요. DeskBand는 뭐 ToolBand랑 똑 같을것 같구, ExplorerBar는 패널이나 아무컨트롤을 가져다 놓고 align을 alClient로 주면 되지 않을까 싶습니다.

자 이제 폼을 만들었으니, 메인유닛으로 돌아와서 그 폼의 유닛을 uses 절에 추가합니다.
추가하는 김에 ComObj, ShlObj, Shdocvw 등 필요한 유닛들도 모두 포함시킵니다.

이 세 개의 확장자들은 똑같이 IObjectWithSite, IPersistStream, IDeskBand 인터페이스를 구현합니다.
여기에 추가해서 "입력을 요구하는 컨트롤"이 폼에 존재할 경우에는 IInputObject 를 추가로 구현해야하고, 메뉴 항목에도 포함시키고 싶다면 IContextMenu를 또 추가해야합니다.
오늘 예제로 사용되는 놈은 이렇습니다.

 TDelSearch = class(TComObject, IObjectWithSite, IPersistStream, IDeskBand, IInputObject)
 private
   Band : TFormBand;                    // 앞에서 만든 폼이 Create 될 자리
   WinHandle : HWND;                    // IE CoolBar의 핸들을 보관할 자리
   InputObject : IInputObjectSite;      // 입력받는 부분이 있을경우 처리해야할 인터페이스
   HasFocus : Boolean;                  // 지금 내가 포커스를 가지고 있는지 아닌지?
   WebBrowser : IWebBrowser2;           // 웹브라우저

 protected
 {IObjectWithSite}
   function SetSite(const pUnkSite: IUnknown ):HResult; stdcall;
   function GetSite(const riid: TIID; out site: IUnknown):HResult; stdcall;

 {IPersist}
   function GetClassID(out classID: TCLSID): HResult; stdcall;
 {IPersistStream}
   function IsDirty: HResult; stdcall;
   function Load(const stm: IStream): HResult; stdcall;
   function Save(const stm: IStream; fClearDirty: BOOL): HResult; stdcall;
   function GetSizeMax(out cbSize: Largeint): HResult; stdcall;

 {IOleWindow}
   function GetWindow(out wnd: HWnd): HResult; stdcall;
   function ContextSensitiveHelp(fEnterMode: BOOL): HResult; stdcall;
 {IDockingWindow}
   function ShowDW(fShow: BOOL): HResult; stdcall;
   function CloseDW(dwReserved: DWORD): HResult; stdcall;
   function ResizeBorderDW(var prcBorder: TRect; punkToolbarSite: IUnknown; fReserved: BOOL): HResult; stdcall;
 {IDeskBand}
   function GetBandInfo(dwBandID, dwViewMode: DWORD; var pdbi: TDeskBandInfo): HResult; stdcall;

 {IInputObject}
   function UIActivateIO(fActivate: BOOL; var lpMsg: TMsg): HResult; stdcall;
   function HasFocusIO: HResult; stdcall;
   function TranslateAcceleratorIO(var lpMsg: TMsg): HResult; stdcall;
 end;

정말 만들어야할 함수들이 무지하게 많지요.
이걸 조목 조목 다 설명하려니 앞이 다 캄캄합니다요.
그래서 대충대충(^^;) 넘어가도록 하겠습니다.

먼저 쓸데없는 놈들부터 뽑아서 버리고 지나가죠.
입력받는 컨트롤이 있을경우 IInputObject를 반드시(굵은글씨) 구현하라고 되어 있길래 집어 넣었는데, 디버깅 결과 IE가 전혀 호출을 하지 않습니다. 왜 만들라고 했는지 모르겠군요 T.T
그래도 예제에는 원래 예제(Visual C용)를 그대로 델파이로 옮겨 놓았습니다.

IPersistStream도 이 예제에서는 써먹지 않습니다. MSDN이 말하길 ExplorerBar의 경우 데이터를 스트림으로 받아내거나 집어넣을때 써먹을수 있다고 합니다.
써먹든 말든 무조건 디폴트 리턴값을 돌려주면 되겠습니다.

그럼 이제는 핵심부를 살펴보도록 하죠.

SetSite란 놈은 초기화/마무리 부분이라고 생각하시면 되겠습니다.
초기화시에는 pUnkSite에 값이 담겨오고 마무리 부분에서는 nil이 넘어 옵니다.
---------------------------------------------------------------------------------------------
function TDelSearch.SetSite(const pUnkSite: IUnknown ):HResult; stdcall;
const
 IID_IOleWindow : TGUID = '{00000114-0000-0000-C000-000000000046}';  // 델파이는 이런 기본적인 것도 const로 만들어 놓질 않았다.
 IID_IInputObjectSite : TGUID = SID_IInputObjectSite;
var
 OW: IOleWindow;
begin
 if Assigned(InputObject) then begin         // 이게 할당 되어 있다면 마무리 호출이겟죠?
//     InputObject._Release;                
    InputObject := nil;
    end;

 Result := E_FAIL;
 WinHandle := 0;
 if Assigned(pUnkSite) then                  // IE CoolBar의 핸들을 얻어오기 위한 작업
 if SUCCEEDED(pUnkSite.QueryInterface(IID_IOleWindow, OW)) then begin
    OW.GetWindow(WinHandle);
//     OW._Release;
    end;

 if WinHandle<>0                             // 쿨바가 없거나 마무리 호출이면 WinHandle=0
    then Result := S_OK
    else Exit;

 WebBrowser := nil;
                                             // 이건 InputObject 인터페이스를 얻어오는 부분..
 if FAILED(pUnkSite.QueryInterface(IID_IInputObjectSite, InputObject)) then begin
    Result := E_FAIL;
    Exit;
    end;
                                             // 이건 웹브라우저의 인터페이스를 얻어 오는 부분
 if FAILED(((pUnkSite as IOleCommandTarget) as IServiceProvider).QueryService(IWebbrowserApp, IWebbrowser2, WebBrowser))
    then WebBrowser := nil;
                                             
 if not Assigned(Band) then begin            // 폼을 생성시키자. 단, CreateParented 로 생성할 것. Parent는 IE의 CoolBar.
    Band := TFormBand.CreateParented(WinHandle, WebBrowser);
    end;
 if not Assigned(Band) then Result := E_FAIL;
end;
---------------------------------------------------------------------------------------------
위에 보시면 _Release 부분이 모두 리마크가 되어 있죠? 원래는 다 _Release해 줘야하는데 델파이는 지가 알아서 한다고 프로그램에서 호출하지 말라고 합니다. 얼마나 잘 알아서 하는지 두고 보자구요. 실제로 _Release를 시키면 당장은 문제가 없는데, 프로그램 종료부분에서 문제가 발생합니다. 제가 이것땜에 엄청 고생을 했죠.

function TDelSearch.GetSite(const riid: TIID; out site: IUnknown):HResult; stdcall;
단지 QueryInterface의 역할을 하도록 만들라고 합니다. 위에서 받아낸 InputObject가 쓰이는 유일한 곳.
소스는 생략합니다.

function TDelSearch.GetWindow(out wnd: HWnd): HResult; stdcall;
내가 만든 폼의 핸들을 요청하는 통로입니다.
폼이 아직 안만들어 졌으면 Create하고, 핸들을 wnd에 넘겨주면 됩니다. 역시 소스 생략.

function TDelSearch.ShowDW(fShow: BOOL): HResult; stdcall;
내 밴드를 숨겨라/보여라 하는 명령이 전달되는 통로입니다.
True가 들어오면 보여주고, False가 들어오면 감추면 됩니다. 죽어도 감추기 싫다면 그냥 뻐팅겨도 됩니다.

function TDelSearch.CloseDW(dwReserved: DWORD): HResult; stdcall;
내 밴드를 뽀개라는 명령이 전달되는 통로입니다.
조용히 Destroy 하면 됩니다.

function TDelSearch.GetBandInfo(dwBandID, dwViewMode: DWORD; var pdbi: TDeskBandInfo): HResult; stdcall;
이건 정말 손이 많이 가는 부분입니다.
IE가 내 밴드에게 와서 이것저것 꼬치꼬치 물어보는 부분이기 때문입니다.
귀찮다고 안가르쳐주면 안되고 하나하나 다 가르쳐 줘야합니다.
질문은 pdbi.dwMask 에 담겨 옵니다.
DBIM_MINSIZE : 최소 사이즈가 얼마니? (IE창이 내 밴드 최소사이즈보다 더 작을 경우는 어쩔수 없이 무시됩니다.)
DBIM_MAXSIZE : 최대 사이즈가 얼마니? (-1로 지정하면 제한이 없어집니다.)
DBIM_INTEGRAL : 사이즈가 변할때 원하는 간격은 얼마니? (그리드 효과를 연상하시면 됩니다. 보통은 1이죠)
DBIM_ACTUAL : 니가 가장 원하는 사이즈는 얼마니? (가능하다면 이 사이즈를 유지하도록 해 줍니다.)
DBIM_TITLE : 니 밴드의 제목이 뭐야? (유니코드로 대답해야 합니다.)
DBIM_MODEFLAGS : 잡다한 기능중에 원하는거 있음 말해봐.
(잡다한 기능 - DBIMF_NORMAL:됐어. DBIMF_VARIABLEHEIGHT:밴드 높이를 조정 가능하게 해줘.
              DBIMF_DEBOSSED: 다른 밴드에 비해 움푹 들어간것 처럼 그려줘.
              DBIMF_BKCOLOR: 배경색을 crBkgnd에 든 값으로 그려줘)
DBIM_BKCOLOR : 배경색깔 칠할까? (pdbi.dwMask := pdbi.dwMask and (not DBIM_BKCOLOR); 아냐 없애줘)


5. 마치자.
-----------------------------

너무 지루한 내용이 되어 버렸네요.
이게 지금 동작은 하지만 문제가 되는 부분이 몇개 있습니다.
1. 검색어입력 Edit 컨트롤에 백스페이스가 안 먹힙니다. (T.T)
  이건 왜 이런지 도통 알 수가 없어요. 분명 IInputObject와 관련이 있을것 같은데 자료가 너무 부족해서 포기했습니다. 아시는 분 꼭 좀 알려주세요.
2. 검색어입력 Edit 컨트롤에 포커스가 넘어올때 SellectAll 설정이 안됩니다.
  이것 또한 원인을 찾지 못하고 있고 대책도 없습니다.
3. 쿨바위에서 오른쪽 버튼을 눌러 밴드감추기를 하면 팝업창이 한 번 더 튀어 나옵니다.
  (델파이로 만들었다고 차별하는거야 뭐야!)

이런 고장난 부분들은 숙제로 남겨두어야만 하겠네요.
혹시라도 수리가 가능하면 다시 꼬리를 달겠습니다.

참고사이트 : http://msdn.microsoft.com/workshop/browser/ext/overview/Bands.asp

 

반응형

댓글