LazarusCompleteGuide:8.1

From 흡혈양파의 번역工房
Jump to navigation Jump to search

라자루스의 파일 대화창

라자루스는 각 플랫폼의 위젯 셋으로부터 컨트롤을 활용하여 파일을 열고 저장하기 위한 플랫폼 독립적인 방식들을 제공한다.

프로퍼티 설명
DefaultExt 사전 정의된 파일명 확장자, 문자열.
FileName 대화창이 닫힌 후 사전 정의되거나 선택된 파일명.
Filter 파일 타입 기술자와 그에 관련된 확장자 리스트.
Filterindex 대화창이 열릴 때 어떤 설명이 표시될 것인지 결정한다. 주로 1로 설정됨.
InitialDir 대화창을 여는 초기 디렉터리. 보통 비어 있고, 그래야만 현재 디렉터리가 사용된다.
Options 숨김 파일과 디렉터리를 표시할 것인지, 새 파일의 생성을 허용할 것인지등을 나타내는 옵션 집합.
Title 대화창 최상단에 표시되는 제목 표시줄.
Width, Height 대화창의 높이와 너비. 사전 정의된 값을 얻기 위해 보통 0으로 남겨둬야 한다.
Tag 컴포넌트 태그(component tag).
표 8.1: 파일과 디렉터리 대화창 컴포넌트의 프로퍼티


컴포넌트 팔레트의 Dialogs 탭은 아래와 같은 파일 선택 컨트롤을 포함한다: TOpenDialog, TSaveDialog, TSelectDirectoryDialog, TOpenPictureDialog, TSavePictureDialog. 각 이름은 그 용도를 설명한다. TSelectDirectoryDialog 는 나머지 4개의 컴포넌트와는 다른데, Gtk+/Gtk2와 Qt에 비해 윈도우에서 완전히 다른 컨트롤을 사용하기 때문이다. 파일 대화창들이 공유하는 프로퍼티를 표 8.1에 열거하였다. 파일 대화창은 5가지 대화창에서 모두 동일하다 (표 8.2 참고).

이벤트 이벤트가 트리거되는 시기
OnCanClose 사용자가 대화창 닫기를 시도할 때. 이벤트 프로시저에서 CanClose 를 False 로 설정하여 비활성화시킬 수 있다.
OnClose 대화창이 닫힐 때.
OnFolderChange 디렉터리가 변경될 때.
OnHelpClicked Help 버튼을 눌렀을 때.
OnSelectionChange 파일 선택이 변경되었을 때.
OnShow 대화창이 열릴 때.
OnTypeChange 필터 타입이 변경될 때.
표 8.2: 파일 대화창을 위한 이벤트 핸들러


보통은 이러한 이벤트에 응답하지 않아도 된다. 모든 대화창은 Execute 메소드가 호출될 때 FileName 프로퍼티에 파일명을 저장한다 (경로명 포함).

TSelectDirectoryDialog 는 대상 경로(target path)만 로딩한다. 메소드가 성공적으로 완료되었을 때만 true인데, 즉, Cancel이 아닌 OK 버튼으로 대화창이 닫혔음을 의미한다. (OK 대신 [Enter] 혹은 [Esc] 키를 이용할 수도 있다). TOpenDialog 를 이용하는 코드 예제는 다음과 같다:

if OpenDialog.Execute then
  Memo1.Lines.LoadFromFile(OpenDialog1.FileName);


읽어들일 파일을 선택하는데는 이것만 사용하면 된다. 하지만 파일을 저장하고 싶다면 아래와 같은 모습이 될것이다:

if OpenDialog.Execute then
  Memo1.Lines.SaveToFile(OpenDialog1.FileName);


그림을 로딩하고 저장하기 위해선 두 개의 xxxPictureDialogs 를 사용해야 한다. 둘은 미리보기를 제공하고 필터 프로퍼티에 서로 다른 기본 값을 가진다는 점에서 일반 대화창과 구별된다. 이 리스트는 이미 라자루스가 지원하는 그래픽 포맷으로 이미 채워져 온다.

Graphic
(*.bmp;*.xpm;*.png;*.pbm;*.pgm;*.ppm;*.ico;*.icns;*.cur;*.jpg;*.jpeg;*.jpe;*.jfif;*.tif;*.tiff;*.gif)
|*.bmp;*.xpm;*.png;*.pbm;*.pgm;*.ppm;*.ico;*.icns;*.cur;*.jpg;*.jpeg;*.jpe;*.jfif;*.tif;*.tiff;*.gif|
Bitmaps (*.bmp)|*.bmp|
Pixmap (*.xpm)|*.xpm|
Portable Network Graphic (*.png)|*.png|
Portable Pixmap (*.pbm;*.pgm;*.ppm)|*.pbm;*.pgm;*.ppm|
Icon (*.ico)|*.ico|
Mac OS X Icon (*.icns)|*.icns|
Cursor (*.cur)|*.cur|
Joint Picture Expert Group
(*.jpg;*.jpeg;*.jpe;*.jfif)|*.jpg;*.jpeg;*.jpe;*.jfif|
Tagged Image File Format (*.tif;*.tiff)|*.tif;*.tiff|
Graphics Interchange Format (*.gif)|*.gif|
All files (*.*)|*.*|


예제에서와 같이 LCL 내에서만 작업한다면 매우 수월하고 깔끔하게 작동할 것이다. 작업이 복잡해지는 경우는 LCL과 RTL이 함께 사용되는 경우인데, LCL 루틴은 모두 유니코드 (UTF8) 이름을 대상으로 하기 때문이다. 함수와 프로시저가 런타임 유닛의 것과 일치하도록 만들기 위해서는 모든 문자열 파라미터를 정규화(normalize)시킬 필요가 있다. 따라서 아래와 같은 인코딩 변환 함수를 사용해야 한다.

AssignFile(f, UTF8ToSys(OpenDialog1.FileName);


UTF8ToSys 를 호출하지 않고는 이름에 움라우트나 다른 특수 문자가 포함된 파일 및 디렉터리를 찾을 수 없을 것이다! Memo1.LoadFromFile() 과 Image1.Picture.LoadFromFile() 호출들은 LCL에 내부적이므로 (RTL로부터 호출은 없음) 이것이 불필요하다.


SysToUTF8() 을 이용해 역변환(reverse transformation)을 실행할 수 있다.


이 네 가지 파일 대화창은 운영체제 파일 대화창을 캡슐화한다. Windows TSelectDirectoryDialog 는 일반 파일 루틴에 의해 처리되지 않고 셸(shell) 디렉터리 대화창에 의해 처리되고, 대신 SHBrowseForFolder 로 가져온다(imported). 지식이 많은 윈도우 개발자들은 플랫폼 독립적 소프트웨어의 개발에 수반되는 제약들을 눈치 챌 것인데, 셸 API 대화창이 라자루스 컴포넌트보다 훨씬 더 강력하기 때문이다. 이를 완전히 활용하려면 소유권이 있는 윈도우 컴포넌트를 작성하거나 프로그램 내에서 가져오기(import)를 수동으로 처리해야할 것이다.


윈도우 API 함수 SHBrowseForFolder 는 라이브러리 shell32.dll 에서 내보내며, 아래와 같이 선언된다 (파스칼의 경우):

function SHBrowseForFolderA(var lpbi: TBrowseInfoA): PItemListId; StdCall;
function Sh8rowseForFolderW(var lpbi: TBrowseInfoW): PItemIDList; StdCall;


따라서 이 함수에는 두 가지 변형(variant)이 있는데, 하나는 AnsiStrings, 다른 하나는 WideStrings 를 이용한 것이다. 특수 레코드도 사용되고 있음을 볼 수 있을 것이다. SHBrowseForFolder 는 이미 라자루스에서 정의되어 있지만, 우리가 다시 구조를 선언하여 델파이에 적합하게 만들고자 하는 경우가 아니라면 (원한다면 할 수는 있다) 여기 표시된 일반 lpbi 대신 BrowseInfo 구조에 대한 포인터를 사용해야 할 것이다.

BrowseInfoA = record
  hwndOwner : HWND;           { owner's window handle }
  pidlRoot : PItemIDList;     { ItemList pointer to a structure for the root name }
                              { NIL for the desktop folder }
  pszDisplayName : PAnsiChar; { memory area for display name of selected item }
                              { must be MAX_PATH long }
  lpszTitle : PAnsiChar;      { text to go in the title above the tree }
  ulFlags : UINT;             { a set of flags that control the return stuff }
  lpfn : TFNBFFCallBack;      { pointer to a callback function }
  lParam : LPARAM;            { extra info that's passed back in callbacks }
  iImage : LongInt;           { output var: where to return the Image index }
                              { of the user-selected folder }
end;

PBrowseInfoA = ^BrowseInfoA;

BrowseInfoW = record
  hwndOwner : HWND;          { for descriptions see above record structure }
  pidlRoot : PItemIDList;    { the record is identical except for the WideChars }
  pszDisplayName : PWideChar;
  lpszTitle      : PWideChar;
  ulFlags        : UINT;
  lpfn           : TFNBFFCallBack;
  lParam         : LPARAM;
  iImage         : LongInt;
end;

PBrowseInfoW = ^BrowseInfoW;


하지만 이러한 레코드들은 (Ansi 버전과 Wide 버전 모두) 해답이 없는 질문들을 남겨둔다. 표 8.2은 ulFlags 필드 내에 플래그에 대해 사전 정의된 상수를 표시한다.

상수 설명
BIF_BROWSEFORCOMPUTER $1000 컴퓨터만 리턴될 것이다. 사용자가 다른 선택을 할 경우 OK 버튼이 비활성화될 것이다.
BIF_BROWSEFORPRINTER $2000 프린터만 리턴될 것이다. 그렇지 않을 경우 OK 버튼이 비활성화된다.
BIF_DONTGOBELOWDOMAIN $0002 도메인 아래의 네트워크 폴더가 표시되지 않을 것이다.
BIF_RETURNANCESTORS $0008 파일 시스템 조상들만 리턴한다. 사용자가 다른 것을 선택할 경우 OK 버튼이 회색으로 나타난다(grayed out).
BIF_RETURNONLYFSDIRS $0001 디렉터리만 리턴될 것이다. 그렇지 않을 경우 OK 버튼이 비활성화된다.
BIF_STATUSTEXT $0004 대화상자에 상태영역을 포함한다. 콜백(callback)은 그 영역에 텍스트를 입력시킬 수 있다.
표 8.3: ulFlags과 같이 사용가능한 상수


여기 특수 목적을 위해 시스템 대화창을 프로그램에 통합하는 방법을 설명하기 위한 메소드를 소개한다.

uses Windows, ActiveX, ShellAPI;
// This shell function, unlike the other two listed above, is not predefined in Lazarus:
function SHGetMalloc(var ppMalloc: IMalloc): HResult; StdCall;
                     external'shell32.dll';
procedure TForm1.Button1Click(Sender: TObject);
  var ppMalloc     : IMalloc;          // declared in Lazarus!
      pBuffer      : PChar;
      pidlPrograms : PItemIDList;      // PIDL for 'Program Files' folder
      pidlBrowse   : PItemIDList;      // PIDL for selected program group
      bi           : BrowseInfo;       // already defined!
      sResult      : String;
begin
// obtain pointer to Malloc interface:
  SHGetMalloc(ppMalloc);
  if ppMalloc = NIL then Exit;
// Allocate memory for text display of user's selection
  pBuffer := ppMalloc.Alloc(MAX_PATH);
  if pBuffer = NIL then Exit;
// PIDL to determine Application folder selection: passing the owner handle, an internal identifier of the special folder
// (here CSIDL_PROGRAMS) The return is in a PItemIDList:
  SHGetSpecialFolderLocation(Handle, CSIDL_PROGRAMS, pidlPrograms);
  if pidlPrograms = NIL then
  begin
    ppMalloc.Free(pBuffer);
    Exit;
  end;
// initialize BROWSEINFO
  with bi do begin
    hwndOwner := Handle;
    pidlRoot := pidlPrograms; // the result of SHGetSpecialFolderLocation
    pszDisplayName := pBuffer;
    // Note: UTF8 to AnsiString conversions are not always elegant or easy to understand:
    lpszTitle := PChar(UTF8ToSys('Select a program group'));
    ulFlags := 0;
    lpfn    := NIL;
    lParam := 0;
  end;
// The user can select a program group. With the definition of BrowseInfo in Lazarus you can use the following declaration:
  pidlBrowse := SHBrowseForFolder(@bi);
  if pidlBrowse = NIL then
  begin
    ppMalloc.Free(pidlBrowse);
    ppMalloc.Free(pidlPrograms);
    ppMalloc.Free(pBuffer);
  end;
// display name and show the path
  sResult := 'Selected program group:' + LineEnding;
  sResult := sResult + 'Display name: ' + SysToUTF8(pBuffer);
// normalise string and output the result;
  if SHGetPathFromIDList(pidlBrowse, pBuffer) then
    sResult := sResult + #13#10 + 'Path: ' + SysToUTF8(pBuffer);
  ShowMessage(sResult);
// Clean up:
  ppMalloc.Free(pidlBrowse);
  ppMalloc.Free(pidlPrograms);
  ppMalloc.Free(pBuffer);
end;


상수 폴더 설명
CSIDL_BITBUCKET 000Ah Recycle bin 사용자 휴지통 내의 파일을 포함하는 파일 시스템 내 디렉터리.
CSIDL_CONTROLS 0003h Control Panel 제어판 심볼(control panel symbol)을 포함하는 가상 폴더.
CSIDL_DESKTOP 0000h Windows Desktop 명칭 공간의 루트에 있는 가상 폴더.
CSIDL_DESKTOPDIRECTORY 0010h Physical desktop 데스크톱에 표시된 파일 객체가 물리적으로 저장되는 디렉터리 (Desktop 폴더 자체와 혼동 금지).
CSIDL_DRIVES 0011h Workspace PC에 모든 것을 포함하는 가상 폴더: 기억 매체(Storage media), 프린터, 제어판.
CSIDL_FONTS 0014h Fonts 폰트를 포함하는 가상 폴더.
CSIDL_NETHOOD 0013h Network 네트워크 환경(network neighbourhood)에 객체를 포함하는 디렉터리.
CSIDL_NETWORK 0012h Network environment 네트워크 계층구조 최상위 계층을 나타내는 가상 폴더.
CSIDL_PERSONAL 0005h Personal directory 문서의 공통 보관 장소인 파일 시스템의 디렉터리.
CSIDL_PRINTERS 0004h Printer folder 설치된 프린터를 보유하는 가상 디렉터리.
CSIDL_PROGRAMS 0002h Programs 사용자의 프로그램 그룹을 소유하는 디렉터리.
CSIDL_RECENT 0008h Recent 사용자가 가장 최근 사용한 문서의 디렉터리.
CSIDL_SENDTO 0009h Send to "Send to" 메뉴의 엔트리를 보유하는 디렉터리.
CSIDL_STARTMENU 000Bh Start menu Start 메뉴에 엔트리를 보유하는 디렉터리.
CSIDL_STARTUP 0007h Autostart 사용자의 프로그램 그룹 Startup에 일치하는 파일 시스템 내 디렉터리.
CSIDL_TEMPLATES 0015h Common files 문서 템플릿에 대한 공통 보관 장소로 사용되는 파일 시스템의 디렉터리.
표 8.4: SHGetSpecialFolderLocation 함수 내 nFolder 파라미터에 가능한 값


선언에서 볼 수 있듯이 결과로부터 경로를 얻는 SHGetPathFromDList 와 SHBrowseForFolder 에 대한 기반을 생성하기 위해 SHGetSpecialFolderLocation 가 사용한 함수들은 라자루스 라이브러리에 이미 선언되어왔다. 반면 SHGetMalloc 은 (메모리 예약에 사용되는) 라자루스 라이브러리에서 publish되지 않으므로 가져오기(import)를 할 필요가 있다.


예제 리스트는 SHBrowseForFolder 함수가 어떻게 SHGetSpecialFolderLocation 다음에만 사용되는지를 보여준다. 이 함수는 LCL용으로 해석되는데, 거기서 아래와 같이 선언된다:

function SHGetSpecialFolderLocation(hwndOwner:HWND; nFolder: Integer;
                                    var ppidl: PItemIDList): HResult;


nFolder 파라미터는 매우 중요하다. 표 8.4의 상수 중 하나가 전달될 것이다. 호출 이후에 PItemIDList 는 SHGetSpecialFolderLocation 에 필요한 값으로 채워질 것이다.


위의 예제에서 SHGetPathFromIDList 로의 호출은 좀 더 간단한데, 파일 시스템 내 특수 폴더의 절대 경로만 리턴한다. 프로그램 그룹의 경우 프로그램 그룹의 절대 경로가 된다. 이 정보는 프로그램 그룹으로 링크를 추가할 수 있는 프로그램을 설치할 때 필요하다. 이러한 함수는 우리가 이미 알고 있는 ItemlDList 구조를 재사용하고, PChar 로 선언된 버퍼 내 완전한 이름을 문자열로서 리턴한다. 레코드는 struct.inc 파일 내 LCL에서 정의된다:

SHITEMID = record
  cb : USHORT;
  abID : array[0..0] of BYTE;
end;
LPSHITEMID  = ^SHITEMID;
LPCSHITEMID = ^SHITEMID;
_SHITEMID   = SHITEMID;
TSHITEMID   = SHITEMID;
PSHITEMID   = ^SHITEMID;

ITEMIDLIST = record
  mkid : SHITEMID;
end;

LPITEMIDLIST  = ^ITEMIDLIST;
LPCITEMIDLIST = ^ITEMIDLIST;
_ITEMIDLIST   = ITEMIDLIST;
TITEMIDLIST   = ITEMIDLIST;
PITEMIDLIST   = ^ITEMIDLIST;


여태까지 설명한 셸 함수는 SHGetMalloc 이다. 이는 셸 객체를 위해 메모리를 예약하고 초기화하는 데 사용된다. 라자루스는 shellapi.pp 파일에 필요한 인터페이스를 정의한다:

type
{ IMalloc interface }
IMalloc = interface(IUnknown)
  ['{00000002-0000-0000-C000-000000000046}']
  function Alloc(cb: Longint): Pointer;
  function Realloc(pv: Pointer; cb: Longint): Pointer;
  procedure Free(pv: Pointer);
  function GetSize(pv: Pointer): Longint;
  function DidAlloc(pv: Pointer): Longint;
  procedure HeapMinimize;
end;
LPMALLOC = ^IMalloc;
PMALLOC  = ^IMalloc;


가장 쉬운 초기화 방법을 위의 리스트에 표시하였다. Free 메소드는 메모리를 다시 해제(release)하는 데 사용된다.

그림 8.1: 윈도우에서 API 함수로 작업 시, 문자 집합 변환이 매우 중요하다. 제목에 텍스트 오류가 발생하더라도 (상단 우측) 하단 우측의 결과 대화창에 표시되는 잘못된 경로 리턴 값만큼 나쁘진 않다 (메시지 대화창의 3번째 줄 참고).


작은 예제에서 볼 수 있듯이 윈도우 셸 API, 그 폴더 및 대화창으로 작업할 경우 많은 일을 필요로 한다. LCL 내 정의는 VCL의 정의와 항상 완전히 호환되는 것은 아니므로, 델파이에서 라자루스로 코드 변환 시에는 윈도우에 대한 내부 지식이 필요하다. 물론 이러한 루틴을 사용하는 프로그램은 추후 다른 플랫폼으로 전달될 수 없음을 유념해야 한다.


안타깝게도 윈도우에서 파일 및 디렉터리를 작업해야 하는 경우 셸의 사용이 불가피하다.


라자루스는 UTF8 문자 집합과 내부적으로 작업하지만 윈도우는 그렇지 않다는 사실도 기억한다. 이로 인해 문자열을 Windows API로 전달하거나 그로부터 문자열을 빼낼 때마다 문자 집합(character set) 변환이 필요하다. LCL은 SysToUTF8 과 UTF8ToSys 함수를 이용해 이를 돕는다. 그림 8.1은 문자 집합 변환 루틴의 유무에 따른 위 예제의 출력 대화창을 보여준다.

아래 예제를 보자:

lpszTitle := PChar(UTF8ToSys('Select a program group'));

때로는 대화창에 텍스트를 올바르게 표시하기 위해 끔찍한 타입 변환이 필요하다. 더 중요한 건 타입변환은 움라우트 문자를 포함한 디렉터리 경로의 올바른 처리와 관련된다.