어떻게 하면 컬의 장점을 최대한 보여줄 수 있을지 고민 끝에 ‘웹 고객 관리’라는 프로젝트를 생각해냈다. 우선 고객 등록 화면이 있고, 그 고객을 그룹(조직)별로 배치하는 화면을 구축하는 것이 이번 강좌에서 다룰 내용이다. 아마 머리 속에 간단한 폼 하나와 몇 개의 리스트 박스, 그리고 버튼들이 떠오를 것이다. 여기서는 기존의 웹 화면에서는 구현하기 어려웠던 기능들이 컬을 통해 얼마나 쉽고 재미있게 구현되는지 소개한다.
준비 작업
강좌에 사용된 컬은 3.0.x대 버전이며 컬의 설치나 자세한 내용은 컬커뮤니티(fullsource.net)의 강좌를 참조하기 바란다. 서버 쪽은 APM(Aapche, PHP, MySQL)을 사용한다. 혹시 PHP를 모르더라도 단순히 SQL 쿼리만 실행하는 것이니 어렵지는 않을 것이다. 준비가 끝난 독자는 작업에 사용할 ‘maso’라는 데이터베이스와 maso.sql 파일을 이용해서 테이블을 생성한다. 이번 강좌에 필요한 파일과 소스는 여기를 클릭하면 다운로드할 수 있다.
프로그램 개요와 구성
첨부된 소스 파일을 웹 루트의 maso라는 폴더에 압축을 해제한 후 project.cprj 파일을 더블클릭해 컬 IDE를 실행한다. IDE의 왼쪽 위에 있는 파일 리스트에서 start.curl은 프로젝트를 실행했을 때 처음 실행되는 파일이다. 나머지 트리 형태로 되어 있는 파일 중 CURL.COMMON.MASO는 공통 라이브러리가 들어 있는 패키지이고 CURL.MASO.LECTURE1은 고객 등록 화면, CURL.MASO.LECTURE2는 고객 그룹 관리 화면이 들어 있는 패키지이다.
프로그램 개요와 구성
첨부된 소스 파일을 웹 루트의 maso라는 폴더에 압축을 해제한 후 project.cprj 파일을 더블클릭해 컬 IDE를 실행한다. IDE의 왼쪽 위에 있는 파일 리스트에서 start.curl은 프로젝트를 실행했을 때 처음 실행되는 파일이다. 나머지 트리 형태로 되어 있는 파일 중 CURL.COMMON.MASO는 공통 라이브러리가 들어 있는 패키지이고 CURL.MASO.LECTURE1은 고객 등록 화면, CURL.MASO.LECTURE2는 고객 그룹 관리 화면이 들어 있는 패키지이다.
이 프로젝트를 실행하면 <화면 1>과 같은 초기 실행 화면이 브라우저에서 실행된다(반드시 http://localhost/maso/start.curl과 같이 localhost로 실행해야 한다). 전체 레이아웃은 <그림 1>과 같다. 가장 상위에 HBox가 있고 그 안에 VBox가 두 개 포함되어 있다. ![]() 첫 번째 VBox는 메뉴를 구성하는 것으로 색이 칠해진 사각형은 Frame이다. 메뉴가 두 개이므로 Frame을 두개 두었다. 두 번째 VBox는 메뉴에 따라 바뀌는 화면으로 왼쪽에서 메뉴 Frame을 클릭하면 오른쪽의 두 개의 Frame이 바뀌게 된다. 실제로는 Frame 자체가 바뀌는 것이 아니라 Frame의 child가 바뀌는 것이다. 위쪽의 Frame은 타이틀이 표시되는 것이고, 아래 Frame은 실제 내용이 표시되는 부분이다. ![]() ![]() ![]() ![]() | |
<리스트 1>은 start.curl의 내용 중 일부이다. <그림 1>과 같은 화면을 구성하기 위해서 HBox 안에 VBox 두 개를 넣고, 그 안에 미리 정의된 title, body 등을 넣었다. 메뉴를 구성하기 위해 TMenuItem을 사용했는데 이 클래스는 Frame에서 상속을 받은 것이다. 그리고 실제 메뉴가 클릭된 경우 body에 내용을 표시해야 되는데 방법은 body 의 child를 표시하고자 하는 객체로 바꾸는 것이다. 즉 body가 바뀌는 게 아니라 body의 child가 바뀐다. 고객 관리 메뉴 왼쪽 메뉴에서 [고객 관리]를 클릭한 경우 TManageCustomer 클래스 객체를 생성해서 body의 child를 바꿔준다. 바로 이 TManageCustomer 클래스가 고객 관리 화면을 구성하는 클래스이다. 여기서 TManageCustomer 클래스는 VBox에서 상속받았다. 상단에는 사용자 정보를 표시하기 위한 RecordGrid가 있다. RecordGrid는 RecordSet을 Grid 형식으로 제어하기 위한 클래스이며 자체적으로 sorting, filtering, editing을 지원한다. 그리고 하단의 HBox는 RecordGrid에서 선택된 사용자의 상세 정보를 보여주는 폼이다. 왼쪽의 VBox에 CommandButton이 4개 있고 오른쪽에 TWriteForm 클래스 객체가 들어가게 된다. TWriteForm은 고객 정보를 수정, 조회할 수 있는 폼 클래스이다. ![]() 동작 방식은 TManageCustomer가 생성될 때 서버로부터 사용자 정보를 받아서 RecordGrid에 넣는다. 그리고 사용자를 선택하고 TWriteForm에 사용자 정보를 넣는다. 기타 다른 작업들은 왼쪽의 CommandButton에 의해 이루어지며 삭제, 수정, 다시읽기 기능을 수행한다. 고객 추가 기능은 TWriteForm 객체를 하나 더 생성해서 팝업 창으로 띄운다. 그리고 추가하게 되면 팝업 창의 TWriteForm에서 데이터를 받아 서버에 사용자를 추가하게 되고 성공하면 RecordGrid에 추가한다. ![]() ![]() ![]() ![]() | |
<리스트 2>를 보면 TWriteForm을 생성하고 팝업 창을 표시하기 위해서 Dialog를 생성하여 {dlg.show}로 팝업 창을 띄운 후, 팝업 창의 결과를 받아 처리한다. 자세한 내용은 실제 소스를 보기 바란다. TWriteForm 화면에서 중요한 부분은 사용자 정보를 입력할 때 처음에는 필수 입력 화면만 나오고 작은 버튼을 누르면 추가 입력 화면이 나온다는 것이다. TWriteForm을 한번 살펴보자. ![]() 레이아웃 그림 없이 <화면 2>를 보고 TWriteForm의 레이아웃이 어떤 식으로 구성됐는지 대충이라도 떠오른다면 이미 컬을 절반 이상 이해한 것이다. {HBox {필수입력 VBox}, {작은 버튼}, {추가입력 Frame} } 간단하게 컬 식으로 표현하면 이와 같다. 여기서 {작은 버튼}을 누르면 {추가입력 Frame}의 child로 {추가입력폼인 VBox}를 넣고, 다시 누르면 {추가입력 Frame}의 child를 없앤다. 간단하게 Frame의 child를 조작함으로써 토글 되는 입력창을 만든 것이다. 실제 화면도 아주 잘 작동한다. 쉽고 간단한 기능이지만 여러 분야에 응용하면 효과적인 화면을 만들 수 있다. 이번엔 독자들이 가장 궁금해 하는 것 중 하나인 서버와 통신하는 법을 알아보자. 소스의 CURL.COMMON.MASO 패키지 안에 lib.scurl 파일을 열어보면 서버와 통신할 수 있는 두 개의 메쏘드가 정의되어 있다. gfGetMethod 메쏘드는 GET 방식으로 서버의 스크립트(CGI, PHP, ...)를 호출할 때 사용한다. 메쏘드 이름에 나와 있는 것처럼 gfPostMethod 메쏘드는 POST 방식을 구현한 것이다. TManageCustomer 클래스 소스를 보면 {get-recordset-all}이라는 메쏘드가 존재한다. 이 메쏘드는 사용자 정보를 서버로부터 가져오는 작업을 한다. ![]() ![]() ![]() | |
GET 방식은 HTML에서 A 태그의 href에 링크를 지정할 때처럼 서버의 실행 스크립트 URL 뒤에 물음표(?)로 연결한 후 name=value쌍 형태로 구현한다. 그리고 그 결과를 result로 받아온다. 그럼 서버 쪽이 이것을 어떻게 처리했는지 보자. ![]() ![]() | |
<리스트 4>는 users.php의 소스 중 <리스트 3>의 GET 요청을 받아 처리하는 부분이다. “select * from users” 쿼리를 MySQL에 보내고 결과를 리턴한다. echo 함수를 사용해서 결과를 리턴하게 되면 리턴 문자열이 <리스트 3> 의 result에 들어가게 된다. 그럼 컬 쪽에서는 result를 적절하게 이용하면 되는 것이다. ![]() ![]() | |
<리스트 5>는 gfPostMethod 사용한 소스의 일부분으로, 수정된 정보를 POST 방식으로 서버 스크립트에 보내는 것이다. GET과 마찬가지로 보내고자 하는 데이터를 HttpFormStringParam의 형태로 gfPostMethod에 넘긴다. 마찬가지로 결과는 result에 들어오게 되고 그 값을 비교해서 실행 결과를 판단하는 방식이다. 그럼 서버 쪽을 확인해 보자. ![]() ![]() | |
<리스트 6>은 insert 문장을 수행하는 php 소스이다. 눈여겨 봐야할 것은 마지막의 실행 결과를 가지고 echo 문을 사용한 것이다. 컬에서는 “fail”이나 “ok”를 받게 될 것이다. 그룹 관리 메뉴 이번에는 두 번째 메뉴인 [그룹 관리] 메뉴를 살펴보자. [그룹 관리]를 구현한 클래스는 TManageGroup 클래스이며 HBox에서 상속받았다. 전체 레이아웃을 보면 두 개의 VBox 안에 TCustomerTable과 버튼, TTreeControl과 버튼, 콤보 박스로 되어있다. 두 개의 복잡한 레이아웃은 클래스로 만들어서 TCustomerTable 자체의 레이아웃은 단순해졌다. <화면 3>에서 보듯이 왼쪽은 고객의 정보를 보여주는 테이블이다. 간단하게 RecordGrid를 사용해도 되겠지만 개발자나 사용자의 요구는 다양하기 때문에 완전히 새롭게 테이블로 구성했다. 그리고 오른쪽은 트리 컨트롤로 컬에서 기본적으로 제공하는 것은 아니고, 간단한 작업을 통해 직접 만든 것이다. 물론 새로 만드는 것이 다른 언어처럼 어렵다면 컬에서 제공하는 것을 썼겠지만 기쁘게도 컬로는 매우 쉽게 구현할 수 있다. <화면 3>의 동작 방식은 왼쪽에서 사용자를 드래그해서 오른쪽의 트리 컨트롤의 폴더에 드롭하면 그대로 추가된다. 그리고 오른쪽의 트리 컨트롤에서는 새로운 하위 그룹을 만들거나 삭제, 이름 변경 등을 할 수 있도록 팝업 메뉴가 지원된다. DHTML과 자바스크립트만 가지고는 결코 쉽지 않을 작업이지만, 소스를 보면 쉽게 구현되었음을 알 수 있을 것이다. TCustomerTable 왼쪽 테이블을 구성하는 TCustomerTable 클래스는 TBaseTable 테이블에서 상속받았다. 테이블의 컬럼은 TTableColumn 클래스이며 테이블의 각 셀은 TTableCell 클래스이다. 가장 핵심인 TBaseTable은 ScrollBox에서 상속받았다. <그림 4>와 같이 ScrollBox 안에 VBox를 이용해서 header와 body 테이블을 따로 구성했다. 자세한 내용은 소스를 참조하기 바란다. 이제 실제 테이블을 구성한 TCustomerTable 클래스를 살펴보도록 하자. 이 클래스는 TBaseTable에서 상속받았기 때문에 수정 없이 그냥 사용한다면 전달된 RecordSet의 모든 필드를 다 표시한다. 그래서 RecordSet 중에서 특정 필드만 보여주기 위해 {get-colums} 메쏘드를 오버라이딩(overriding)했다. 그리고 {get-body-table-cell} 메쏘드를 오버라이딩하여 각 셀(TTableCell)의 모양을 원하는 형태로 정의했다. 상세한 메쏘드의 구현은 소스를 보면 쉽게 이해할 수 있을 것이다. 트리 컨트롤 이번에는 그룹 관리의 핵심인 오른쪽의 TTreeConrol을 살펴보자. TTreeControl의 핵심은 mtree(VBox)로 각 트리의 한 행을 child로 갖는다. 그 child는 HBox로 <그림 5>에서 화살표가 가리키는 오른쪽 레이아웃이다. 처음에는 Fill 객체로 트리 레벨에 따라 들여쓰기를 해주고 그 다음은 +/- 그림이고 마지막으로 TTreeControlItem이 들어간다. TTreeControlItem은 HBox에서 상속을 받았으며 폴더 이미지와 TTreeItem을 child로 갖는다. 트리 컨트롤에서 TTreeItem을 드래그해서 TTreeControlItem 위에 드롭할 수 있도록 하기 위해 TTreeControlItem에는 Drop 이벤트를 주고 TTreeItem은 드래그 이벤트를 주었다. 물론 TTreeControlItem은 TCustomerTable의 셀인 TTableCell 객체도 받을 수 있다. TTreeControl에 넘겨질 트리 정보는 TTreeNode로 구성되어 있다. TTreeNode는 child를 배열로 관리하고 있으며 status 필드는 현재 노드의 상태를 나타내는데 +/-는 폴더인 경우 펼쳐진 것인지, 아닌지를 나타내며 *인 경우는 폴더가 아닌 아이템, 즉 고객을 말한다. 이번엔 TTreeControl에서 TTreeNode 객체를 받아서 어떻게 트리를 구성하는지 알아보자. TTreeControl은 한 개의 TTreeNode를 받는다. 물론 TTreeNode는 루트 노드로 하위 노드들을 child로 갖고 있다. 이 루트 노드를 써서 트리 구성을 하는 메쏘드가 {TTreeControl.refresh-controls} 메쏘드이다. ![]() ![]() | |
<리스트 7>을 보면 mtree(VBox)를 클리어한 후에 {make-tree} 메쏘드를 호출한다. {make-tree} 메쏘드가 하는 일은 현재 넘겨진 TTreeNode를 레벨에 맞게 mtree에 추가해 주는 것이다. 그리고 TTreeNode.status가 -인 경우는 펼쳐진 경우이므로 하위 노드를 {make-tree} 메쏘드에 넘겨 재귀 호출을 한다. +는 하위 노드를 그릴 필요가 없으므로 넘어가고 *인 경우는 폴더가 아니기 때문에 역시 넘어간다. {make-tree}가 전체 TreeNode를 따라 실행되면 마지막에 mtree에는 화면에 표시돼야 할 트리 노드 모두가 child로 추가된다. 아주 간단한 방식으로 트리를 생성하지만 제대로 동작한다. 그럼 각 노드에서 +/-가 눌린 경우는 어떻게 해야 될까? 만약 이벤트가 발생한다면 {TTreeControl.refresh-controls}를 다시 호출해서 트리를 다시 생성한다. 물론 이벤트가 발생한 노드의 status는 변경을 해줘야 한다. 다음은 TManageGroup 클래스에서 TTreeControl 생성하는 과정을 보여주고 있다. set self.ftn-rs = {self.get-recordset-group 0} set self.ftn = {self.recordset-to-treenode self.ftn-rs} set self.ftreecontrol = {TTreeControl root = self.ftn, showroot?=true} 처음에 {TManageGroup.get-recordset-group} 메쏘드를 이용해서 서버로부터 트리 데이터를 받는다. 트리 데이터는 다음과 같이 서버에서 php가 <레벨, 이름(아이디), 상태>의 형태로 컬에 넘겨주게 된다(꼭 이렇게 해야 하는 것은 아니고 서버 측과 원하는 방식대로 정의하면 된다). 1,서울시,- 2,강남구,- 3,vidkid,* 3,50bo100bo,* 1,수원시,- 2,장안구,- 3,ddurami,* 2,권선구,- 그럼 컬 쪽에서는 그것을 파싱해서 RecordSet에 넣으면 된다. 그리고 그 RecordSet을 {TManageGroup.recordset-to-treenode} 메쏘드를 이용해서 TTreeNode 객체로 만들어 준다. 마지막으로 TTreeControl를 생성한다. 이번엔 컬의 가장 큰 장점 중의 하나인 드래그 앤드 드롭 부분을 살펴보자. 위에서 설명했듯이 드롭 이벤트를 받을 수 있는 클래스는 TTreeControlItem이고 드래그될 수 있는 클래스는 TTreeItem과 TTableCell 클래스이다. 드래그 이벤트는 간단히 드래그할 객체에 다음과 같이 해주면 된다. set TTreeItem.dragee = {ImageDragee} 그럼 핵심 부분인 드롭 이벤트를 받기 위한 부분을 보자. <리스트 8>에서 {on-drag-over} 메쏘드는 드래그 상태에서 TTreeControlItem 위로 마우스 커서가 들어온 경우 드롭할 수 있는지 아닌지를 결정하기 위해서 실행되며 {on-drop} 메쏘드는 드롭이 된 경우에 실행된다. TTreeControlItem 클래스의 경우는 드롭된 객체의 클래스 Type을 보고 결정하게 되는데 TTableCell인 경우는 추가가 되고 TTreeItem인 경우는 이동을 하게 된다. ![]() ![]() | |
이것으로 TTreeControl의 핵심 부분을 모두 설명했다. 이 트리 컨트롤의 단점은 표시할 데이터가 굉장히 많아지면 느려질 수 있다는 것이다. 속도를 중요시해야 한다면 좀 더 낮은 레벨 프로그래밍을 해야 되는데 그 부분에 대해서는 기회가 된다면 다뤄보도록 하겠다. 대부분의 웹에서 동작하는 데이터를 표시하는 데에는 위의 두 클래스로도 충분히 잘 동작할 것이다. 뛰어난 유연성으로 개발 생산성 향상 이번 연재를 통해서 독자들에게 전달하고자 했던 내용은 기존 웹 방식의 업무 틀을 벗어날 수 있다는 점과 컬을 사용하면 개발자가 만들고자 하는 컨트롤을 쉽게 만들 수 있다는 점이다. 우선 드래그 앤드 드롭과 트리 컨트롤의 사용은 분명 기존의 웹 페이지 기획 방식의 한계를 넘어섰다. 윈도우용 CS 프로그램에서는 한 화면으로 구현 가능하던 것을 억지로 2~3화면으로 나눌 필요 없이 그대로 한 화면으로 구성 가능하다는 것이다. 두 번째로 트리 컨트롤을 구성하는데, 컬만으로 쉽게 구현했으며 어떠한 고객의 요구라도 확장이 쉽다는 것이다. 만약 각 트리 노드에 그림을 3개씩 넣는다거나 두 줄로 만든다거나 아니면 메모장 같은 에디터를 넣는 경우가 생긴다면 컬은 아주 쉽게 확장할 수 있다. 컬의 도입 효과는 단지 ‘화면이 예쁘다’뿐 아니라 웹 화면을 기획하는 데 더 많은 유연성을 제공할 것이다. 그리고 웹 프로그래밍에 지친 개발자에게는 컴포넌트를 만드는 재미도 느끼게 해줄 것이다. 컴포넌트가 늘어감에 따라 개발 생산성의 비약적인 향상을 기대할 수 있다. 이번 연재에서는 아쉽게도 소스의 양은 많고 지면은 한정되어 있어 소스의 핵심 부분과 동작 방식, 레이아웃만을 설명했다. 소스를 보는 것만으로 이해하기 어려운 부분은 www.fullsource.net에 질문하거나 자료를 참조하기 바란다. @ 지금 뜨는 기사이시각 헤드라인ZDNet Power Center![]() |