컴포넌트 제작의 깊은 곳

Similar documents
PowerPoint Template

歯제작.PDF

Microsoft PowerPoint - chap02-C프로그램시작하기.pptx

JAVA 프로그래밍실습 실습 1) 실습목표 - 메소드개념이해하기 - 매개변수이해하기 - 새메소드만들기 - Math 클래스의기존메소드이용하기 ( ) 문제 - 직사각형모양의땅이있다. 이땅의둘레, 면적과대각

chap 5: Trees

학습목표 함수프로시저, 서브프로시저의의미를안다. 매개변수전달방식을학습한다. 함수를이용한프로그래밍한다. 2

제이쿼리 (JQuery) 정의 자바스크립트함수를쉽게사용하기위해만든자바스크립트라이브러리. 웹페이지를즉석에서변경하는기능에특화된자바스크립트라이브러리. 사용법 $( 제이쿼리객체 ) 혹은 $( 엘리먼트 ) 참고 ) $() 이기호를제이쿼리래퍼라고한다. 즉, 제이쿼리를호출하는기호

var answer = confirm(" 확인이나취소를누르세요."); // 확인창은사용자의의사를묻는데사용합니다. if(answer == true){ document.write(" 확인을눌렀습니다."); else { document.write(" 취소를눌렀습니다.");

API - Notification 메크로를통하여어느특정상황이되었을때 SolidWorks 및보낸경로를통하여알림메시지를보낼수있습니다. 이번기술자료에서는메크로에서이벤트처리기를통하여진행할예정이며, 메크로에서작업을수행하는데유용할것입니다. 알림이벤트핸들러는응용프로그램구현하는데있어

PowerPoint Presentation

C# Programming Guide - Types

歯처리.PDF

기본적인 컴포넌트의 제작

<4D F736F F F696E74202D20C1A63038C0E520C5ACB7A1BDBABFCD20B0B4C3BC4928B0ADC0C729205BC8A3C8AF20B8F0B5E55D>

Javascript

Microsoft PowerPoint - CSharp-10-예외처리

제8장 자바 GUI 프로그래밍 II

쉽게 풀어쓴 C 프로그래밊

JAVA PROGRAMMING 실습 08.다형성

UI TASK & KEY EVENT

PowerPoint Presentation

<4D F736F F F696E74202D20C1A63139C0E520B9E8C4A120B0FCB8AEC0DA28B0ADC0C729205BC8A3C8AF20B8F0B5E55D>

gnu-lee-oop-kor-lec06-3-chap7

(Humphery Kim) RAD Studio : h=p://tech.devgear.co.kr/ : h=p://blog.hjf.pe.kr/ Facebook : h=p://d.com/hjfactory :

다른 JSP 페이지호출 forward() 메서드 - 하나의 JSP 페이지실행이끝나고다른 JSP 페이지를호출할때사용한다. 예 ) <% RequestDispatcher dispatcher = request.getrequestdispatcher(" 실행할페이지.jsp");

Microsoft Word - ntasFrameBuilderInstallGuide2.5.doc

Visual Basic 반복문

C++ Programming

Microsoft PowerPoint - 04-UDP Programming.ppt

트레이 아이콘 어플리케이션의 제작

PowerPoint 프레젠테이션

API 매뉴얼

Microsoft PowerPoint - e pptx

제11장 프로세스와 쓰레드

A Hierarchical Approach to Interactive Motion Editing for Human-like Figures

- JPA를사용하는경우의스프링설정파일에다음을기술한다. <bean id="entitymanagerfactory" class="org.springframework.orm.jpa.localentitymanagerfactorybean" p:persistenceunitname=

[ 그림 8-1] XML 을이용한옵션메뉴설정방법 <menu> <item 항목ID" android:title=" 항목제목 "/> </menu> public boolean oncreateoptionsmenu(menu menu) { getme

Microsoft PowerPoint - java1-lab5-ImageProcessorTestOOP.pptx

DLL(Dynamic Linked Library)

어댑터뷰

금오공대 컴퓨터공학전공 강의자료

SOFTBASE XFRAME DEVELOPMENT GUIDE SERIES ActiveX 컴포넌트가이드 서울특별시구로구구로 3 동한신 IT 타워 1215 호 Phone Fax

Microsoft PowerPoint - chap06-2pointer.ppt

Microsoft PowerPoint - 09-CE-5-윈도우 핸들

PowerPoint Presentation

q 이장에서다룰내용 1 객체지향프로그래밍의이해 2 객체지향언어 : 자바 2

Microsoft PowerPoint UI-Event.Notification(1.5h).pptx

Windows 8에서 BioStar 1 설치하기

<4D F736F F F696E74202D20C1A63034B0AD202D20C7C1B7B9C0D3B8AEBDBAB3CABFCD20B9ABB9F6C6DBC0D4B7C2>

쉽게 풀어쓴 C 프로그래밍

PowerPoint 프레젠테이션

. 스레드 (Thread) 란? 스레드를설명하기전에이글에서언급되는용어들에대하여알아보도록하겠습니다. - 응용프로그램 ( Application ) 사용자에게특정서비스를제공할목적으로구현된응용프로그램을말합니다. - 컴포넌트 ( component ) 어플리케이션을구성하는기능별요

학습목차 2.1 다차원배열이란 차원배열의주소와값의참조

歯MDI.PDF

PowerPoint Presentation

목차 1. 시스템요구사항 암호및힌트설정 ( 윈도우 ) JetFlash Vault 시작하기 ( 윈도우 ) JetFlash Vault 옵션 ( 윈도우 )... 9 JetFlash Vault 설정... 9 JetFlash Vault

PowerPoint 프레젠테이션

<4D F736F F F696E74202D20C1A63236C0E520BED6C7C3B8B428B0ADC0C729205BC8A3C8AF20B8F0B5E55D>

[ 마이크로프로세서 1] 2 주차 3 차시. 포인터와구조체 2 주차 3 차시포인터와구조체 학습목표 1. C 언어에서가장어려운포인터와구조체를설명할수있다. 2. Call By Value 와 Call By Reference 를구분할수있다. 학습내용 1 : 함수 (Functi

UI TASK & KEY EVENT

 메소드 오버로딩

윈도우즈프로그래밍(1)

델파이 4 프로그래밍의 이해 (Understandings of Delphi 4 Programming)

4장기본프로그래밍2

View Licenses and Services (customer)

gnu-lee-oop-kor-lec10-1-chap10

PowerPoint Presentation

JUNIT 실습및발표

쉽게 풀어쓴 C 프로그래밍

PowerPoint Presentation

Design Issues

이도경, 최덕재 Dokyeong Lee, Deokjai Choi 1. 서론

Microsoft PowerPoint - ÀÚ¹Ù08Àå-1.ppt

예제 2) Test.java class A intvar= 10; void method() class B extends A intvar= 20; 1"); void method() 2"); void method1() public class Test 3"); args) A

9 차시고급위젯다루기 1 학습목표 날짜 / 시간과관련된위젯을배운다. 웹뷰를사용하여간단한웹브라우저기능을구현한다. 매니패스트파일의설정법을배운다. 2 확인해볼까? 3 날짜 / 시간위젯 1) 활동하기 활동개요

아이콘의 정의 본 사용자 설명서에서는 다음 아이콘을 사용합니다. 참고 참고는 발생할 수 있는 상황에 대처하는 방법을 알려 주거나 다른 기능과 함께 작동하는 방법에 대한 요령을 제공합니다. 상표 Brother 로고는 Brother Industries, Ltd.의 등록 상

윈도우 프로그래밍의 개념

슬라이드 제목 없음

슬라이드 1

Microsoft PowerPoint - ch09 - 연결형리스트, Stack, Queue와 응용 pm0100

경우 1) 80GB( 원본 ) => 2TB( 복사본 ), 원본 80GB 는 MBR 로디스크초기화하고 NTFS 로포맷한경우 복사본 HDD 도 MBR 로디스크초기화되고 80GB 만큼포맷되고나머지영역 (80GB~ 나머지부분 ) 은할당되지않음 으로나온다. A. Window P

OCW_C언어 기초

DBMS & SQL Server Installation Database Laboratory

오버라이딩 (Overriding)

Microsoft Word - src.doc

구조화 저장소 기법

2) 활동하기 활동개요 활동과정 [ 예제 10-1]main.xml 1 <LinearLayout xmlns:android=" 2 xmlns:tools="

제 1장 C#의 개요

PowerPoint Presentation

윈도우시스템프로그래밍

로거 자료실

PowerPoint Presentation

MVVM 패턴의 이해

Microsoft PowerPoint - chap06-1Array.ppt

<4D F736F F F696E74202D203137C0E55FBFACBDC0B9AEC1A6BCD6B7E7BCC72E707074>

슬라이드 1

Microsoft PowerPoint 장강의노트.ppt

PowerPoint Presentation

Lab 3. 실습문제 (Single linked list)_해답.hwp

11장 포인터

Transcription:

컴포넌트제작의깊은곳 (Advanced Components Writing Techniques) 그리드컴포넌트의제작 비교적복잡하면서도유용하게사용할수있는컴포넌트가그리드컴포넌트들이다. 델파이는그리드를작성하기쉽도록 TCustomGrid 라는기초컴포넌트를제공하고있다. 그러면, 이컴포넌트를바탕으로해서달력컴포넌트를하나만들어보도록하자. TCustomGrid 컴포넌트를상속하도록하고, 컴포넌트의이름을 TSampleCalendar 라고하자. 이컴포넌트역시 Samples 페이지에등록하도록한다. 상속된프로퍼티 publish 와초기값변경 TCustomGrid 는추상적인그리드컴포넌트이기때문에, 많은수의 protected 프로퍼티를제공한다. 그러므로, 다음과같이필요한프로퍼티를 published 섹션에선언해주도록한다. published property Align; property BorderStyle; property Color; property Ctl3D; property Font; property GridLineWidth; property ParentColor; property ParentFont; property OnClick; property OnDblClick; property OnDragDrop; property OnDragOver; property OnEndDrag; property OnKeyDown; property OnKeyPress; property OnKeyUp;

달력은기본적으로행과열의수가고정되어있기때문에, ColCount 나 RowCount 같은프 로퍼티를 publish 할필요가없다. 그렇지만, 이런초기값을 constructor 에서설정할필요 가있다. constructor TSampleCalendar.Create(AOwner: TComponent); inherited Create(AOwner); ColCount := 7; RowCount := 7; // 머릿글포함 FixedCols := 0; FixedRows := 1; // 요일표시하는행 ScrollBars := ssnone; Options := Options - [gorangeselect] + [godrawfocusselected]; // 범위선택을할수없다. 셀크기조절과내부채우기 사용자나어플리케이션이컨트롤의크기를변경하면, 윈도우는 WM_SIZE 메시지를받게된다. 컴포넌트는이메시지에맞추어이미지를다시그릴필요가있다. 이를위해서 WM_SIZE 메시지에반응하는메시지처리메소드를추가할필요가있다. 먼저 protected 섹션에다음과같이메시지처리메소드를선언한다. procedure WMSize(var Message: TWMSize); message WM_SIZE; 그리고, 다음과같이구현한다. procedure TSampleCalendar.WMSize(var Message: TWMSize); var GridLines: Integer; GridLines := 6 * GridLineWidth; DefaultColWidth := (Message.Width - GridLines) div 7; DefaultRowHeight := (Message.Height - GridLines) div 7; // 전체줄의크기 // 새로운셀의폭 // 새로운셀의높이

그리드컨트롤은셀단위로내부를채우게된다. 그러므로, 달력컴포넌트라면각각의셀마다날짜를계산해서그려야한다. 그리드셀은 DrawCell 가상메소드를호출하여그리게되므로, 이메소드를오버라이드해야한다. 먼저첫번째행의요일을해당되는열에그리도록한다. protected 섹션에 DrawCell 메소드를선언하고, 이를다음과같이구현한다. procedure DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); override; procedure TSampleCalendar.DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); if ARow = 0 then Canvas.TextOut(ARect.Left, ARect.Top, ShortDayNames[ACol + 1]); 날짜계산 달력컨트롤을만들때중요한것은사용자나어플리케이션이연, 월, 일을설정할수있는기전을제공해야한다는것이다. 델파이는날짜와시간은 TDateTime 데이터형의변수에저장한다. 그러므로, 날짜자체는 TDateTime 데이터형변수에저장하되 Day, Month, Year 프로퍼티를제공하여쉽게날짜를조정할수있도록해야한다. 1. 날짜의저장 날짜를저장하기위해서는먼저 TDateTime 데이터형의필드변수를하나선언하고, 이를 constructor 에서현재의날짜를얻어서저장한다. private FDate: TDateTime; constructor TSampleCalendar.Create(AOwner: TComponent); inherited Create(AOwner); FDate := Date;

그리고, 런타임에서접근할수있는프로퍼티를하나선언한다. TSampleCalendar = class(tcustomgrid) private procedure SetCalendarDate(Value: TDateTime); public property CalendarDate: TDateTime read FDate write SetCalendarDate; procedure TSampleCalendar.SetCalendarDate(Value: TDateTime); FDate := Value; Refresh; // 화면을업데이트 2. Day, Month, Year 프로퍼티선언 먼저다음과같이프로퍼티를선언한다. 특이하게보일지도모르겠지만하나의날짜에의해서이들프로퍼티는동시에변경되고, 설정되므로이들을각각의 Get, Set 메소드로구현할필요가없다. 이렇게중복되는부분을같은접근메소드 (access method) 를이용해서사용할때에는 index 를사용하면유용하다. public property Day: Integer index 3 read GetDateElement write SetDateElement; property Month: Integer index 2 read GetDateElement write SetDateElement; property Year: Integer index 1 read GetDateElement write SetDateElement; 그러면 GetDateElement, SetDateElement 메소드를다음과같이선언하고구현하면된다. type TSampleCalendar = class(tcustomgrid) private function GetDateElement(Index: Integer): Integer

procedure SetDateElement(Index: Integer; Value: Integer); function TSampleCalendar.GetDateElement(Index: Integer): Integer; var AYear, AMonth, ADay: Word; DecodeDate(FDate, AYear, AMonth, ADay); case Index of 1: Result := AYear; 2: Result := AMonth; 3: Result := ADay; else Result := -1; procedure TSampleCalendar.SetDateElement(Index: Integer; Value: Integer); var AYear, AMonth, ADay: Word; if Value > 0 then DecodeDate(FDate, AYear, AMonth, ADay); case Index of 1: AYear := Value; 2: AMonth := Value; 3: ADay := Value; else Exit; FDate := EncodeDate(AYear, AMonth, ADay); Refresh; 그렇게어려운코드는아니다. EncodeDate 와 DecodeDate 를이용하면 TDateTime 데이 터형과 Year, Month, Day 의정수형사이를자유롭게변환할수있다는것을알아두면된

다. 3. 날짜표시하기 날짜를달력에그릴때에는달마다일수가다르고, 주어진연도가윤년인지여부를고려해야한다. IsLeapYear 함수를이용하면해당연도가윤년인지알아볼수있으며, SysUtils.pas 유닛의 MonthDays 배열을이용하면해당달의날짜수를알아낼수있다. 일단윤년정보와그달의날짜수를알게되면, 그리드에서각날짜의위치를계산할수있게된다. 먼저그달에서첫번째날짜가적당한요일에위치하는지를계산할때사용할오프셋을저장할필드와필드값을업데이트할메소드를선언하고이를다음과같이구현한다. type TSampleCalendar = class(tcustomgrid) private FMonthOffset: Integer; protected procedure UpdateCalendar; virtual; procedure TSampleCalendar.UpdateCalendar; var AYear, AMonth, ADay: Word; FirstDate: TDateTime; // 그달의첫번째날짜 if FDate <> 0 then DecodeDate(FDate, AYear, AMonth, ADay); FirstDate := EncodeDate(AYear, AMonth, 1); FMonthOffset := 2 - DayOfWeek(FirstDate); // 오프셋을초기화한다. Refresh; // 달력을다시그린다.

그리고, constructor 와 SetCalendarDate, SetDateElement 메소드에 UpdateCalendar 메소 드를호출하도록수정한다. constructor TSampleCalendar.Create(AOwner: TComponent); inherited Create(AOwner); UpdateCalendar; procedure TSampleCalendar.SetCalendarDate(Value: TDateTime); FDate := Value; UpdateCalendar; procedure TSampleCalendar.SetDateElement(Index: Integer; Value: Integer); FDate := EncodeDate(AYear, AMonth, ADay); UpdateCalendar; 이제는셀의행과열의번호를넘겨주면, 이위치가그달의몇번째날인지계산하는메 소드를다음과같이구현한다. function TSampleCalendar.DayNum(ACol, ARow: Integer): Integer; Result := FMonthOffset + ACol + (ARow - 1) * 7; if (Result < 1) or (Result > MonthDays[IsLeapYear(Year), Month]) then Result := -1; // 셀의위치의날짜가유효하지않다. 이들메소드를이용하면, 날짜의위치를알수있다. 같이구현하여날짜를표시하도록한다. 그러면, DrawCell 메소드를다음과

procedure TSampleCalendar.DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); var TheText: string; TempDay: Integer; if ARow = 0 then // 헤더이면 TheText := ShortDayNames[ACol + 1] // 요일을표시한다. else TheText := ''; // 일단셀을비운다. TempDay := DayNum(ACol, ARow); // 셀의위치에따른날짜를구한다. if TempDay <> -1 then TheText := IntToStr(TempDay); // 결과가유효할때만사용! with ARect, Canvas do TextRect(ARect, Left + (Right - Left - TextWidth(TheText)) div 2, Top + (Bottom - Top - TextHeight(TheText)) div 2, TheText); // 셀의중앙에날짜표시 4. 현재날짜의선택 이제선택된날짜를표시하도록해보자. 이를위해서는 UpdateCalendar 메소드를 Refresh 메소드를호출하기전에 Row, Column 프로퍼티를설정하도록수정해야한다. procedure TSampleCalendar.UpdateCalendar; if FDate <> 0 then Row := (ADay - FMonthOffset) div 7 + 1; Col := (ADay - FMonthOffset) mod 7; Refresh; 날짜변경

날짜사이를이동할때에는화살표키를이용하여이동할수도있고, 마우스를클릭할수도있다. 이들을모두처리해주어야하는것이중요하다. 선택된날짜를변경해주어야할것이다. 기본적으로그리드는화살표키를누르거나, 마우스를클릭할때선택된셀을옮기는것을처리하게되어있다. 그렇지만, 달력의경우약간의수정이필요하다. 이를위해, 그리드의 Click 메소드를오버라이드해야한다. procedure TSampleCalendar.Click; var TempDay: Integer; inherited Click; TempDay := DayNum(Col, Row); if TempDay <> -1 then Day := TempDay; 날짜가변경되었을때이벤트를발생시킬수있다면좋을것이다. 그러면, OnChange 이벤 트를추가하도록하자. 먼저다음과같이프로시저형을선언하고이벤트를추가하자. type TSampleCalendar = class(tcustomgrid) private FOnChange: TNotifyEvent; protected procedure Change; dynamic; published property OnChange: TNotifyEvent read FOnChange write FOnChange; 그리고, Change 메소드를다음과같이구현한다. procedure TSampleCalendar.Change; if Assigned(FOnChange) then FOnChange(Self);

그리고, SetCalendarDate 와 SetDateElement 메소드에 Change 메소드를호출하는부분을 추가한다. procedure TSampleCalendar.SetCalendarDate(Value: TDateTime); FDate := Value; UpdateCalendar; Change; procedure TSampleCalendar.SetDateElement(Index: Integer; Value: Integer); FDate := EncodeDate(AYear, AMonth, ADay); UpdateCalendar; Change; 이렇게만든달력컴포넌트는비어있는셀이존재하게되는데, 날짜가찍혀있지않은셀을선택할경우선택이되지않도록해야할것이다. 이를위해서는 SelectCell 메소드를오버라이드하여구현해야한다. function TSampleCalendar.SelectCell(ACol, ARow: Longint): Boolean; if DayNum(ACol, ARow) = -1 then Result := False else Result := inherited SelectCell(ACol, ARow); 이것으로간단한달력컴포넌트를그리드를이용해서작성해보았다. 비록볼품도없고, 단순한컴포넌트이지만, 그리드를이용하여여러가지컴포넌트를개발하는데에는참고가 될만할것이다.

데이터인식컨트롤의제작 델파이는데이터소스와연결해서데이터를보여주고, 편집할수있는여러가지컨트롤들을제공한다. 이런데이터인식컨트롤을제작하기위해서는몇가지고려해야할공통적인것들이있다. 단순한단일필드와연결된데이터인식컨트롤을만들수도있고, 여러레코드를관리하는컨트롤을만들수도있다. 컨트롤과데이터베이스를연결하는것은데이터링크클래스를통해서다루어진다. 데이터링크클래스중에서단일필드와연결할때에는보통 TFieldDataLink 클래스를사용한다. 물론전체테이블과연결할수있는데이터링크클래스도있다. 여기에서는필자가작성한 TDBTree 컴포넌트를바탕으로설명하겠지만, 지면관계상 570 라인가까이에이르는전체소스를설명할수는없고데이터인식컨트롤을구현하는방법을중심으로설명하도록한다. 그밖에도소스를분석하면객체를저장하고, 이를활용하는등의여러가지테크닉을배울수있을것이다. TFieldDataLink 클래스 TFieldDataLink 클래스는컨트롤과데이터소스사이의연결을구성한다. 그러므로, 데이터인식컨트롤을만들때에는이클래스에대한이해가필수적이라고할수있다. 주요메소드와프로퍼티를소개하면다음과같다. 메소드 / 프로퍼티 Edit 내용 편집가능한레코드를편집모드로만든다. 데이터소스가읽기전용이면 False 를반환한다. Modified 컨트롤이데이터를변경할때호출해야한다. 데이터링크는레코드가바뀌지 않았던데이터소스를제공하며, 변경된데이터를요구할때 OnUpdateData 이 벤트핸들러를호출한다. Reset 데이터소스의데이터를변경하지않거나, 새로운데이터를검색할때호출한다. CanModify 읽기전용으로필드의값을수정할수있는지여부를결정한다. Control 읽기전용으로연결된데이터컨트롤을가리킨다. 데이터링크가입력포커스를 받는컨트롤을결정하기위해 Control 속성을사용한다. Editing 읽기전용으로데이터소스의 State 속성이 dsedit, dsinsert, dssetkey 중하나 인지나타낸다. Field 읽기전용으로연결된필드클래스를, 연결된것이없으면 nil 을반환한다. FieldName 데이터베이스필드이름을지정한다. OnActiveChange 데이터소스의 Active 프로퍼티가변경될때호출되는이벤트이다. OnDataChange 데이터레코드가변한후에호출되는이벤트이다. 모든데이터인식컨트롤은

여기에대한핸들러를구현해야한다. 이핸들러가없으면컨트롤이필드로부터 데이터를검색할때알수있는방법이없다. OnEditingChange 데이터소스가편집모드로들어가거나나올때호출되는이벤트이다. OnUpdateData 컨트롤로부터변경된데이터를소스가요구할때호출되는이벤트이다. 여기에 대한핸들러는연결된필드구성요소의데이터를저장한다. UpdateRecord 데이터소스가데이터컨트롤에의해수정된데이터를가진현재레코드의복사 본을수정하려할때호출된다. 데이터컨트롤에서포커스를잃을때호출된다. BufferCount 사용가능한레코드버퍼의개수를반환한다. 그리드와같은다중레코드컨트롤 을구현할때에이를이용하여사용자가한번에많은수의값을편집할때성능을향상시킬수있다. 디폴트값은 1 이다. DataSet 읽기전용으로데이터소스로연결된데이터세트를되돌려준다. DataSource 연결된데이터소스를지정한다. RecordCout 현재데이터링크에의해버퍼되어진레코드수를반환한다. 데이터인식컨트롤은자체적인데이터링크클래스를가지고있으며, 컨트롤이데이터링 크객체를생성하고파괴하는책임을가지고있다. type TDBTree = class(tcustomtreeview) private FDataTitleLink: TFieldDataLink; 모든데이터인식컨트롤은데이터를컨트롤에제공하는데이터소스를지정하는 DataSource 프로퍼티를가지고있다. 또한, 연결할데이터소스의필드를지정하는 DataField 프로퍼티를가지고있다. 물론, 보통의컨트롤은하나의필드와데이터컨트롤을연결하게되므로이렇게 DataField 프로퍼티로연결하면되지만, 컨트롤에따라서는여러개의필드를설정해야하는경우도있을것이다. 보통의경우에는 DataField 프로퍼티를하나만가지면되지만, 트리구조를데이터베이스에저장하기위해서는자신의 ID 와 Parent, Index 를저장할 3 개의필드와타이틀의내용을저장할필드가필요하다. 물론대표적인필드는 Title 을저장할필드를하나만지정하도록 published 프로퍼티로지정하고이를 DataField 프로퍼티로사용하고, 나머지는런타임에서접근할수있도록 public 섹션에선언해서사용하도록한다. 이들프로퍼티를접근하고, 설정할접근메소드를설정하고구현할때에는데이터링크클래스를이용한다.

TDBTree = class(tcustomtreeview) private FIDFieldName: string; FParentFieldName: string; FIndexFieldName: string; function GetDataTitleField: string; function GetDataSource: TDataSource; function GetTitleField: TField; function GetIDField: TField; function GetParentField: TField; function GetIndexField: TField; function GetDataSet: TDataSet; procedure SetDataTitleField(const Value: string); procedure SetDataSource(Value: TDataSource); public property TreeDataSet: TDataSet read GetDataSet; property IDField: TField read GetIDField; property ParentField: TField read GetParentField; property IndexField: TField read GetIndexField; property TitleField: TField read GetTitleField; published property DataSource: TDataSource read GetDataSource write SetDataSource; property DataField: string read GetDataTitleField write SetDataTitleField; function TDBTree.GetDataSource: TDataSource; Result := FDataTitleLink.DataSource; procedure TDBTree.SetDataSource(Value: TDataSource); FDataTitleLink.DataSource := Value;

if Value <> nil then Value.FreeNotification(Self); function TDBTree.GetDataSet: TDataSet; Result := FDataTitleLink.DataSet; function TDBTree.GetDataTitleField: string; Result := FDataTitleLink.FieldName; procedure TDBTree.SetDataTitleField(const Value: string); FDataTitleLink.FieldName := Value; procedure TDBTree.SetIDField(FieldName: string); if TreeDataSet.FindField(FieldName) <> nil then FIDFieldName := FieldName; procedure TDBTree.SetParentField(FieldName: string); if TreeDataSet.FindField(FieldName) <> nil then FParentFieldName := FieldName; procedure TDBTree.SetIndexField(FieldName: string); if TreeDataSet.FindField(FieldName) <> nil then FIndexFieldName := FieldName;

function TDBTree.GetTitleField: TField; Result := FDataTitleLink.Field; function TDBTree.GetIDField: TField; if TreeDataSet.FindField(FIDFieldName) <> nil then Result := TreeDataSet.FieldByName(FIDFieldName) else MessageDlg(' 해당되는 ID 필드가없습니다!', mterror, [mbok], 0); function TDBTree.GetParentField: TField; if TreeDataSet.FindField(FParentFieldName) <> nil then Result := TreeDataSet.FieldByName(FParentFieldName) else MessageDlg(' 해당되는 Parent 필드가없습니다!', mterror, [mbok], 0); function TDBTree.GetIndexField: TField; if TreeDataSet.FindField(FIndexFieldName) <> nil then Result := TreeDataSet.FieldByName(FIndexFieldName) else MessageDlg(' 해당되는 Index 필드가없습니다!', mterror, [mbok], 0); 그리고, constructor 와 destructor 에서데이터링크객체를생성하고, 파괴하는것이중요 하다. public constructor Create(AOwner: TComponent); override; destructor Destroy; override; constructor TDBTree.Create(AOwner: TComponent);

inherited Create(AOwner); inherited ReadOnly := True; ControlStyle := ControlStyle + [csreplicatable]; FDataTitleLink := TFieldDataLink.Create; FDataTitleLink.Control := Self; FIDFieldName := 'ID'; FParentFieldName := 'Parent'; FIndexFieldName := 'Index'; FMemorySaving := True; destructor TDBTree.Destroy; var i: integer; i := 0; FDataTitleLink.Free; inherited Destroy; 데이터링크를이용하여필드와데이터소스를설정하는것까지했으면, 절반은끝난셈이다. 이제는데이터의변화를컨트롤에반영하도록구현하는것이중요하다. 보통의경우이때에는데이터링크클래스의 OnDataChange 이벤트를이용한다. 데이터소스에서데이터의변화를감지하면, 데이터링크객체는 OnDataChange 이벤트핸들러를호출하게된다. 그러므로, DataChange 메소드를구현하고, 이메소드를 OnDataChange 이벤트에연결하는것이중요하다. 그러나, DBTree 에서는이를이용하지않고따로 public 메소드들을제공하여구현하였다. 표준적인방법이아니기때문에따로설명하지는않는다. 대신일반적인데이터인식컨트롤을구현할때에는앞에서설명한방법을사용하는데, 여기에서는 TDBEdit 컨트롤이어떻게구현되었는지소개한다. TDBEdit = class(tcustommaskedit) private procedure DataChange(Sender: TObject); procedure TDBEdit.DataChange(Sender: TObject);

if FDataLink.Field <> nil then if FAlignment <> FDataLink.Field.Alignment then EditText := ''; {forces update} FAlignment := FDataLink.Field.Alignment; EditMask := FDataLink.Field.EditMask; if not (csdesigning in ComponentState) then if (FDataLink.Field.DataType = ftstring) and (MaxLength = 0) then MaxLength := FDataLink.Field.Size; if FFocused and FDataLink.CanModify then Text := FDataLink.Field.Text else EditText := FDataLink.Field.DisplayText; if FDataLink.Editing and FDataLink.FModified then Modified := True; end else FAlignment := taleftjustify; EditMask := ''; if csdesigning in ComponentState then EditText := Name else EditText := ''; 데이터인식컨트롤의내용을편집했을때에는필드데이터링크객체를업데이트해야데이터세트에변화를반영할수있다. 데이터가변했을때에는 OnDataChange 이벤트에서처리하듯이, 데이터컨트롤이변경되었을때에는 OnUpdateData 이벤트에서처리해야한다. 그러므로, UpdateData 메소드를구현하고 OnUpdateData 이벤트에 UpdateData 메소

드를연결한다. TDBEdit 컨트롤은다음과같이구현되어있다. TDBEdit = class(tcustommaskedit) private procedure UpdateData(Sender: TObject); procedure TFieldDataLink.UpdateData; if FModified then if (Field <> nil) and Assigned(FOnUpdateData) then FOnUpdateData(Self); FModified := False; DataChange, UpdateData 메소드에서중요한것은이렇게구현된메소드를 constructor 에 서이벤트핸들러를대입하는코드를추가해야한다는것이다. constructor TDBEdit.Create(AOwner: TComponent); inherited Create(AOwner); inherited ReadOnly := True; ControlStyle := ControlStyle + [csreplicatable]; FDataLink := TFieldDataLink.Create; FDataLink.Control := Self; FDataLink.OnDataChange := DataChange; FDataLink.OnEditingChange := EditingChange; FDataLink.OnUpdateData := UpdateData; 데이터인식컨트롤의 Change 메소드는새로운값이설정될때마다호출된다. Change 메소드는 OnChage 이벤트핸들러가존재하면이를호출한다. 그러므로, 컴포넌트사용자는 OnChage 이벤트핸들러를이용하여데이터변화에대한여러가지처리를하는코드를입력할수있다. 설정값이변경되면연결된데이터세트는반드시변화에대해반응하도록해야하는데, 이를위해서는 Change 메소드를오버라이드하여야한다. DBTree 의경우다

음과같이구현한다. TDBTree = class(tcustomtreeview) protected procedure Change(Node: TTreeNode); override; procedure TDBTree.Change(Node: TTreeNode); inherited Change(Node); GotoID(Node); procedure TDBTree.GotoID(ANode: TTreeNode); if ANode <> nil then TreeDataSet.Locate(FIDFieldName, PID(ANode.Data)^, []); 여기서 Change 메소드는사용자가트리노드를선택하여변경한경우이므로해당트리노드에해당되는레코드로옮겨가야한다. 이를처리하는메소드가 GotoID 이며, GotoID 메소드는 DataSet 의 Locate 메소드를이용하여구현한다. 이것으로데이터인식컨트롤의가장핵심적인부분에대한구현방법에대해서소개하였다. 예제로보여준 DBTree 의경우일반적인데이터인식컨트롤과는달리여러개의레코드를보여주고, 이들사이를옮겨다니는처리를해야하기때문에다소복잡하다. 그래서, 사실가장전형적인예제로서는부적절한면이있지만, 어떤식으로구현하는지에대해서는감을잡았을것으로믿는다. 델파이소스중에서 DBCtrls.pas 유닛에보면좋은예제가많으므로이들을참고로할것을권하고싶다. 이번장에서같이제공하는 DBTree 컴포넌트는인터넷에서구할수있는여러가지 DBTreeView 컴포넌트와조금은다른방식으로간단하게구현한컴포넌트이다. 물론인터넷에서구한컴포넌트보다미약한부분도많지만, 사용하기에는더쉽고간단하다. 컴포넌트를제작할때아쉬운점은필자가 DBTree 컴포넌트를처음만들때에는인터넷에서없었기때문에 96 년가을에사용을위해서제작한것인데, 몇달지나지않아서비슷한컴포넌트들이공개되었다. 이처럼기다리면비슷한것들이나오는것을보면누구나생각하고필요로하는내용은비슷한가보다 (^^;). DBTree 컴포넌트의소스와함께제공되는

Readme.txt 파일을읽어보면 DBTree 컴포넌트의사용법에대해서적어놓았으므로활용 해서좋은어플리케이션을만들어보기바란다. 컴포넌트제작에유용한프로퍼티 / 메소드 지금까지언급한여러가지컴포넌트제작에대한내용이외에도조금은고급스러운컴포넌트로만들기위해서는몇가지알아야할프로퍼티와메소드가있다. 여기서이들에대해모두다룰수는없지만그래도자주쓰이는것들을중심으로소개하고자한다. 컴포넌트 State 의검사 TComponent 컴포넌트의 ComponentState 프로퍼티는컴포넌트의현재상태를파악하는데사용할수있다. 보통컴포넌트를쉐어웨어로배포할때, 디자인타임과런타임을구별해서제한을두는컴포넌트등을제작할때사용된다. ComponentState 프로퍼티값에는다음과같은것들이있다. 값 csancestor 설명 컴포넌트가조상폼에나타난경우설정된다. csdesigning 이같이설정 csdesigning 컴포넌트가디자인모드에있을경우설정된다. csdestroying 컴포넌트가파괴되려할때설정된다. csfixups 컴포넌트가다른폼의컴포넌트와연결되어있으나, 연결된컴포넌트가아직로드되 지않은경우설정된다. csloading Filer 객체에서컴포넌트를로딩하는중이면설정된다. csreading 스트림에서프로퍼티를읽어들이는도중이면설정된다. csupdating 컴포넌트의변경사항을업데이트하는도중일때. 사용된다. csancestor 가설정된경우에만 cswriting 프로퍼티값을스트림에기록하는도중이면설정된다. 예를들어, 디자인타임에서등록하라는메시지를다음과같이보여줄수있다. if (csdesigning in ComponentState) then ShowMessage( 등록하세요! ); 컨트롤의 State 프로퍼티 TControl 클래스에는런타임에서의컨트롤의상태를반영하는 ControlState 라는프로퍼티 가정의되어있다. 이프로퍼티는세트로정의되어있으며, 다음과같은값을가질수있

다. 값 cslbuttondown 의미 왼쪽마우스버튼을누르고아직놓지않은상태 csclicked 클릭이벤트가발생할때설정된다. cspalette WM_PALETTECHANGED 메시지를받았다. csreadingstate 컨트롤이 state 정보를스트림에서읽고있다. csalignmentneeded 컨트롤이재정렬될필요가있을때 csfocusing 어플리케이션이컨트롤에포커스를주려고한다. cscreating 컨트롤이생성되고있다. cspaintcopy 컨트롤이복제되어그려지고있다. 컨트롤의 Style 설정 ControlStyle 프로퍼티는컨트롤의특징을결정하는세트프로퍼티이다. 이프로퍼티는주 로컴포넌트의 constructor 에서설정하게된다. 다음과같은값들과의미를가질수있다. 값의미 csacceptscontrols 디자인타임에서컨트롤을드롭하면 parent 가될수있다. cscapturemouse 마우스를클릭했을때이벤트를캡쳐할수있다. csdesigninteractive 디자인타임에서오른쪽마우스버튼을클릭하면왼쪽버튼을클릭하는것으 로매핑하여컨트롤을다룬다. csclickevents 마우스의클릭을받아들인다. csframed 컨트롤이 3D 프레임을가진다. cssetcaption Name 프로퍼티가변경될때, 자동으로캡션이변경된다. csopaque 컨트롤이클라이언트영역을완전히채운다. csdoubleclicks 더블클릭메시지를처리한다. csfixedwidth 컨트롤의폭이변경되지않는다. csfixedheight 컨트롤의높이가변경되지않는다. csnodesignvisible 디자인타임에컨트롤이보이지않는다. csreplicatable 컨트롤을 DBCtrlGrid 에드롭할수있다. csnostdevents 마우스, 키보드, 클릭과같은표준이벤트를무시한다. Loaded 메소드

Loaded 메소드는컨트롤이생성될때프로퍼티를 DFM 파일에서읽어온뒤, 컴포넌트가보여지기전에프로퍼티값에대한생성프로세스를처리할기회를얻을수있다. 보통, 여기에서저장된프로퍼티값에대해 private, public 데이터필드의값을초기화하거나제작된컴포넌트에대한여러가지설정을할기회가된다. Notification 메소드 Notification 메소드는 TComponent 클래스에서제공되는가상메소드로, 델파이 IDE 가컴포넌트가폼에드롭되거나제거될때호출하는메소드이다. Notification 메소드는다음과같이선언되어있다. procedure Notification(AComponent: TComponent; Operation: TOperation); virtual; Notification 메소드의첫번째파라미터는폼에추가되거나삭제되는컴포넌트를나타내며, 두번째파라미터는 opinsert, opremove 라는값을가질수있다. 이메소드는다른컴포넌트가폼에추가되거나삭제될때특정작업을해야할때사용된다. 예를들어, TBatchMove 의 Notification 메소드는다음과같이구현되어있다. procedure TBatchMove.Notification(AComponent: TComponent; Operation: TOperation); inherited Notification(AComponent, Operation); if Operation = opremove then if Destination = AComponent then Destination := nil; if Source = AComponent then Source := nil; 먼저상속된 Notification 메소드를호출하고, 작업이 opremove 인경우에제거되려는컴포넌트의종류가 Source, Destination 프로퍼티에지정된컴포넌트이면이들프로퍼티의값을 nil 로설정한다. 이렇게, 다른컴포넌트와의관련이있도록만들어진경우에는컴포넌트의추가와삭제에따른처리를해주어야한다. DBTree 컴포넌트에도 Notification 메소드를다음과같이구현하고있다. procedure TDBTree.Notification(AComponent: TComponent;

Operation: TOperation); inherited Notification(AComponent, Operation); if (Operation = opremove) and (FDataTitleLink <> nil) and (AComponent = DataSource) then DataSource := nil; FreeNotification 메소드 FreeNotification 메소드는 Notification 메소드와관련되어사용되는 TComponent 의메소드이다. Notification 메소드가폼에서컴포넌트가추가되고삭제될때호출되지만, Owner 가다른컴포넌트의추가, 삭제에는반응하지못한다. 즉, 데이터인식컨트롤의경우데이터모듈에데이터소스가있고폼에컨트롤이있는경우와같이 Owner 가다른경우에는데이터소스가추가, 삭제될때 Notificaiton 메소드를호출하지못한다. 이런경우에는연결될컴포넌트를 FreeNotification 메소드를이용해서컴포넌트가삭제될때 Notification 메소드를호출하도록지정할수있다. 사소한듯하지만데이터인식컨트롤등과같이연결된컴포넌트가있어야하는컴포넌트를제작할때에는놓치기쉬운부분이다. TDBTree 컨트롤의데이터소스컨트롤을지정하는 SetDataSource 메소드의구현부분을살펴보자. procedure TDBTree.SetDataSource(Value: TDataSource); FDataTitleLink.DataSource := Value; if Value <> nil then Value.FreeNotification(Self); 이렇게데이터소스를연결할때파라미터로 Notification 을받을컴포넌트를지정하면된다. 여기서 Value 는데이터소스를가리키며, 데이터소스의 FreeNotification 메소드에 Self( 여기서는 TDBTree) 를파라미터로하여호출함으로써데이터소스가추가, 삭제될때 TDBTree 의 Notification 메소드를호출하게된다. CreateWnd, DestroyWnd 메소드 CreateWnd 메소드는윈도우컨트롤이처음생성되거나, 윈도우가프로퍼티의변경에따라 파괴되었다가재생성되어야할필요가있을때호출된다. 그러므로, CreateWnd 메소드는 윈도우가처음생성될때추가적인초기화메시지를넘겨주기위해오버라이드하여구현한

다. CreateWnd 메소드는처음에 CreateParams 메소드를호출하여윈도우생성파라미터를초기화하고, CreateWindowHandle 을호출하여컨트롤에대한윈도우핸들을생성한다. 그리고, 새로운크기의윈도우를적용하게되며 WM_SETFONT 메시지를이용해 Perform 메소드를호출하여컨트롤의폰트를설정한다. DestroyWnd 메소드는 TWinControl 에서추가한메소드로, 컴포넌트가파괴될때컨트롤의윈도우핸들과연관된디바이스컨텍스트를해제하기위한메소드이다. 이메소드는컴포넌트의 Destroy 메소드이전에호출된다. 만약 Destroy 메소드에서윈도우핸들을이용한작업을하려고하면, Window has no parent 에러메시지를보게된다. 이는윈도우핸들이없어졌기때문이다. 그러므로, 컴포넌트가파괴될때윈도우핸들을가지고디바이스컨텍스트를이용한여러가지작업을하고자할때에는 DestroyWnd 메소드를오버라이드하여구현해주어야한다. 여기서꼭생각해야할것은상속된 DestroyWnd 메소드를호출하면핸들이없어지므로, 상속된 DestoryWnd 메소드는가장나중에호출하는것이좋다. VCL 소스코드중에서비교적간단한 TCustomEdit 의예를들어설명하겠다. procedure TCustomEdit.CreateWnd; FCreating := True; try inherited CreateWnd; finally FCreating := False; DoSetMaxLength(FMaxLength); Modified := FModified; if FPasswordChar <> #0 then SendMessage(Handle, EM_SETPASSWORDCHAR, Ord(FPasswordChar), 0); UpdateHeight; 먼저상속된 CreateWnd 메소드를호출하여초기작업을한다. 그리고, FMaxLength 필드의값을이용하여길이를결정하고 Modified 프로퍼티를설정한다. 또한, FPasswordChar 의값이존재하면패스워드문자를보여주기위해메시지를넘겨주고컨트롤의높이를맞추게된다. 이와같이 CreateWnd 메소드에서는윈도우가생성될때고려해야할여러가지를설정한다. procedure TCustomEdit.DestroyWnd;

FModified := Modified; inherited DestroyWnd; function TCustomEdit.GetModified: Boolean; Result := FModified; if HandleAllocated then Result := SendMessage(Handle, EM_GETMODIFY, 0, 0) <> 0; 여기서는 FModified 필드의값을 Modified 프로퍼티의값과일치시킨다. 자동적으로접근 메소드인 GetModified 가호출되는데, 여기에서 Handle 을이용하여변경된내용을반영하 게된다. 정리 (Summary) 이것으로컴포넌트제작에대한여러가지방법들에대한설명을마치고자한다. 이번장에서제작한달력과 DBTree 컴포넌트는 Chap23.dpk 패키지파일에저장하였으니직접설치하고익혀보기바란다. 컴포넌트제작은델파이로프로그래밍을할때가장필수적인부분이고중요한부분이라고할수있다. 나름대로여러가지부분을다루었으나, 컨테이너컴포넌트를제작하는방법이나프로퍼티에디터를제작하는방법등의고급컴포넌트제작에대한내용을지면관계상모두다루지못한아쉬움이남는다. 아마도컴포넌트를제대로제작하는내용을모두담으려고하면적어도그것만가지고 1000 페이지는넘는책을써야할것이다. 델파이의소스를열심히분석해보면얻는것이많을것이라는정도로밖에여기서는말할수없는것이무척아쉽다. 제 4 부에서다룬내용은델파이에서사용하게되는델파이컴포넌트에대한개발방법에대해서알아보았다. 다음장에서부터다루게되는제 5 부의내용은델파이에한정되지않고, 다른개발도구에서도사용할수있는표준적인개발방법론인 DLL/DCOM/CORBA 에대해서알아보도록할것이다.