반응형

난이도 : 초급

McCoy David, Writer, Independent

2007 년 4 월 17 일

코너에 갇히거나 원하는 이동 방향에서 너무 많이 벗어나지 않으면서, 로봇과 벽 사이의 간격을 유지하는 알고리즘은 간단히 만들 수 없는 것 같습니다. 한 가지 간단한 솔루션으로, Factored wall avoidance가 있습니다. 이 글에서, David McCoy가 이를 구현하는 방법을 설명합니다.

With a few additions to the bot we built in "상대편의 움직임 추적하기"에서 구현했던 로봇에 몇 가지를 더 추가하여, 기존의 움직임 알고리즘 또는 문제가 많은 움직임 알고리즘에 Factored Wall Avoidance를 추가할 수 있다. Factored Wall Avoidance는 자신의 로봇과 벽의 근접성에 따라서 안전한 방향 설정(heading)으로 원하는 방향을 팩토링 함으로써 최상의 방향을 찾는 것이다.

일반 수학적 계산에 헬퍼 메소드 추가하기

우선 자주 사용되는 수학적 알고리즘에 헬퍼 메소드를 로봇에 추가한다.

calculateBearingToXYRadians() 메소드는 java.lang.Math 메소드 atan2()를 사용하여 sourceX,sourceY에서 targetX,targetY까지 절대 위치(absolute bearing)를 계산한 다음, 이 값을 sourceHeading에 관련된 위치로 변환한다.

그리고 normalizeAbsoluteAngleRadians() 메소드와 normalizeRelativeAngleRadians() 메소드도 필요하다.


Listing 1. 수학 헬퍼 메소드
                

private static final double DOUBLE_PI = (Math.PI * 2);
private static final double HALF_PI = (Math.PI / 2);

public double calculateBearingToXYRadians(double sourceX, double sourceY,
    double sourceHeading, double targetX, double targetY) {
        return normalizeRelativeAngleRadians(
           Math.atan2((targetX - sourceX), (targetY - sourceY)) -
               sourceHeading);
    }

public double normalizeAbsoluteAngleRadians(double angle) {
   if (angle < 0) {
        return (DOUBLE_PI + (angle % DOUBLE_PI));
    } else {
        return (angle % DOUBLE_PI);
    }
}

public static double normalizeRelativeAngleRadians(double angle) {
    double trimmedAngle = (angle % DOUBLE_PI);
    if (trimmedAngle > Math.PI) {
        return -(Math.PI - (trimmedAngle % Math.PI));
    } else if (trimmedAngle < -Math.PI) {
        return (Math.PI + (trimmedAngle % Math.PI));
    } else {
        return trimmedAngle;
    }
}




위로


AdvancedRobot을 back-as-front 기능으로 확장하기

다음으로, 로봇을 반대로 이동시키기 위한 back-as-front 기능을 제공하기 위해 AdvancedRobot 클래스 기능을 몇 가지 헬퍼 메소드로 확장할 필요가 있다.

  • getRelativeHeading() 메소드는 로봇의 현재 위치와 관련하여 정확한 방향을 계산한다.

  • reverseDirection() 메소드는 매우 단순하다. direction 인스턴스 변수를 토글링(toggle) 하고 로봇의 방향을 바꾼다. 감속할 때에는 시간이 걸리기 때문에 로봇은 속도에 따라서, 방향을 바꾸기 전에 최대 네 개의 프레임까지 같은 방향으로 움직일 수도 있다.

  • setAhead()setBack() 메소드는 AdvancedRobot 클래스에서 같은 이름의 메소드를 오버라이드 한다. 현재 방향에 대한 로봇의 속도를 설정하고, direction 인스턴스 변수를 필요에 따라 조정한다. 비례 연산은 로봇이 현재 움직이고 있는 방향과 관련이 있다.

  • setTurnLeftRadiansOptimal()setTurnRightRadiansOptimal() 메소드는 (Math.PI / 2) 보다 크게 회전하여 로봇의 방향을 바꾼다. adjustHeadingForWalls 메소드를 사용할 때 이러한 메소드들을 사용해야 하는데, 나중에 설명하겠다.

주: getter와 setter 메소드를 사용하는 대신 direction 인스턴스 변수에 직접 액세스 한다. 이것은 좋은 방법은 아니지만, 나는 내 로봇 코드에 이를 수행하여 데이터 액세스 속도를 높이곤 한다.


Listing 2. 로봇 헬퍼 메소드
                


public double getRelativeHeadingRadians() {
    double relativeHeading = getHeadingRadians();
    if (direction < 1) {
        relativeHeading =
                normalizeAbsoluteAngleRadians(relativeHeading + Math.PI);
    }
    return relativeHeading;
}

public void reverseDirection() {
    double distance = (getDistanceRemaining() * direction);
    direction *= -1;
    setAhead(distance);
}

public void setAhead(double distance) {
    double relativeDistance = (distance * direction);
    super.setAhead(relativeDistance);
    if (distance < 0) {
        direction *= -1;
    }
}

public void setBack(double distance) {
    double relativeDistance = (distance * direction);
    super.setBack(relativeDistance);
    if (distance > 0) {
        direction *= -1;
    }
}

public void setTurnLeftRadiansOptimal(double angle) {
    double turn = normalizeRelativeAngleRadians(angle);
    if (Math.abs(turn) > HALF_PI) {
        reverseDirection();
        if (turn < 0) {
            turn = (HALF_PI + (turn % HALF_PI));
        } else if (turn > 0) {
            turn = -(HALF_PI - (turn % HALF_PI));
        }
    }
    setTurnLeftRadians(turn);
}

public void setTurnRightRadiansOptimal(double angle) {
    double turn = normalizeRelativeAngleRadians(angle);
    if (Math.abs(turn) > HALF_PI) {
        reverseDirection();
        if (turn < 0) {
            turn = (HALF_PI + (turn % HALF_PI));
        } else if (turn > 0) {
            turn = -(HALF_PI - (turn % HALF_PI));
        }
    }
        setTurnRightRadians(turn);
}




위로


Factored Wall Avoidance 추가하기

소셜 북마크

mar.gar.in mar.gar.in
digg Digg
del.icio.us del.icio.us
Slashdot Slashdot

우리가 추가할 마지막 메소드는 adjustHeadingForWalls()이다.

이 메소드의 초반부는 벽과의 근접성에 기반하여 안전한 x,y 위치를 선택한다. (이것은 로봇의 현재 x 또는 y 좌표 또는 로봇이 벽에 가까이 있을 경우 중심점이 될 것이다.) 이 메소드의 후반부는 "안전한" 방향을 계산하고 로봇이 벽에 얼마나 근접해 있는가에 비례하여 원하는 방향으로 이를 팩토링 한다.

로봇이 벽으로 나아가는 정도는 WALL_AVOID_INTERVALWALL_AVOID_FACTORS 상수를 사용하여 조정될 수 있다.


Listing 3. 벽 피하기 메소드
                

private static final double WALL_AVOID_INTERVAL = 10;
private static final double WALL_AVOID_FACTORS = 20;
private static final double WALL_AVOID_DISTANCE =
        (WALL_AVOID_INTERVAL * WALL_AVOID_FACTORS);

private double adjustHeadingForWalls(double heading) {
    double fieldHeight = getBattleFieldHeight();
    double fieldWidth = getBattleFieldWidth();
    double centerX = (fieldWidth / 2);
    double centerY = (fieldHeight / 2);
    double currentHeading = getRelativeHeadingRadians();
    double x = getX();
    double y = getY();
    boolean nearWall = false;
    double desiredX;
    double desiredY;

    // If we are too close to a wall, calculate a course toward 
    // the center of the battlefield.
    if ((y < WALL_AVOID_DISTANCE) ||
            ((fieldHeight - y) < WALL_AVOID_DISTANCE)) {
        desiredY = centerY;
        nearWall = true;
    } else {
        desiredY = y;
    }
    if ((x < WALL_AVOID_DISTANCE) ||
            ((fieldWidth - x) < WALL_AVOID_DISTANCE)) {
        desiredX = centerX;
        nearWall = true;
    } else {
        desiredX = x;
    }

    // Determine the safe heading and factor it in with the desired 
    // heading if the bot is near a wall
    if (nearWall) {
        double desiredBearing = 
           calculateBearingToXYRadians(x, 
                                       y, 
                                       currentHeading, 
                                       desiredX, 
                                       desiredY);
        double distanceToWall = Math.min(
                Math.min(x, (fieldWidth - x)),
                Math.min(y, (fieldHeight - y)));
        int wallFactor =
                (int)Math.min((distanceToWall / WALL_AVOID_INTERVAL),
                              WALL_AVOID_FACTORS);
        return ((((WALL_AVOID_FACTORS - wallFactor) * desiredBearing) +
                 (wallFactor * heading)) / WALL_AVOID_FACTORS);
    } else {
        return heading;
    }
}




위로


결론

나머지는 쉽다. 현재의 네비게이션 알고리즘을 사용하고 adjustHeadingForWalls() 메소드를 통해 그 결과를 제공함으로써 벽을 피할 수 있다.

단순하게 하기 위해 로봇 예제(다운로드)는 방향 변경에 0을 요청하여 직선 라인으로 움직일 것이다.


Listing 4. 벽 피하기 메소드
                


public void run() {
    while(true) {
        setTurnRightRadiansOptimal(adjustHeadingForWalls(0));
        setAhead(100);
        execute();
    }
}

이제 다 되었다. 간단하지만, 효과적이다.
<출처: http://www.ibm.com/developerworks/kr/library/j-fwa/>

반응형
사용자 삽입 이미지

IBM dw 1기 모니터 요원으로 활동한지 반년이 지나갔다.

그렇게 1기의 활동 기간이 끝나고 2기가 선발 되고 1기 해체식과 2기 킥오프가

로보코드 행사와 같이 진행 되었다.

반년동안의 짧은 시간동안 나는 모니터 요원으로서 활동을 곰곰히 되짚어 보았다.

이곳 dw에서 만난 사람들과 같이 했던 일들 그리고 회의 등....

그리고 모니터 요원으로서 교내에서의 홍보 활동등...

그 마지막 장식은 로보코드 16강과 함께 하게 되었다.  로보코드 행사에 참석 후..

학과 후배이며 내가 동아리장으로 있었던 "CASTERLAB"의 후배인 석재의 모습이 보였다.

로보코드 16강 진출! 그렇게 하여 학교가 아닌 IBM에서 보게 되었다. 정말 반가웠다.

그리고 내가 활동함에 있어 작은 보람이라고 생각했다. 로보코드를 홍보하면서 교내에서

관심을 갖길 바랬었는데 후배가 16강에 진출하여 IBM에서 만나게 되었으니 말이다.

8강 까지 진출하였지만 안타깝게 떨어지고 말았지만 내가 홍보한후 처음 알게되서

8강까지 진출한 쾌거를 이룬것이 대견했다.^^ 그리고 나도 1기 모니터 요원의 활동기간이 끝나고

우수요원으로 선발되어 더욱 뜻깊었다. 그렇게 나의 1기 모니터 요원으로서 활동은 끝이 났고

로보코드 결승까지 치루고 16강 진출자들과 2기 모니터 요원들 그리고 1기 모니터요원들과

같이 비어파티를 갖게 되었다. 1기 활동의 마지막이라 생각하니 조금 아쉬운 감이 있었다.

이곳에서 많나 활동하면서 무엇보다 하나 중요한 것이 남는게 있었다면 이곳에서 만난

사람들과 소중한 친구들이 아닐까 싶다. 
반응형
:: 2007년 08월 08일 ::

  Weekly Highlight
IBM developerWorks가 dW Review Blogger를 모집합니다.
오늘 오후 3시부터 벌어지는 로보코드 코리아컵 2007이 미투데이로 실시간 현장중계될 예정입니다. 미투데이와 함께 긴장감 넘치는 결선대회를 함께하세요.
IBM developerworks에서 2007년 하반기 활동할 'developerWorks 대학생 모니터 요원 2기'를 발표합니다.
   Local Contents
“검색으로 인간을 이롭게 한다” - 그루터 대표, 권영길 (dW Interview)
해커 문화의 뿌리를 찾아서 Part 5: Fixed Point 계산과 고차 함수 - 안윤호 (Special Issue)
이클립스 발전의 원동력은 커뮤니티, 에릭 롱 (dW Interview)
시맨틱 소셜 네트워크를 향해, Part 1: 시맨틱 소셜 네트워크와 FOAF -김학래 (Special Issue)
   최신 기술자료 (한글)
SOA 복합 비즈니스 서비스 구현하기, Part 8: WebSphere Portlet Factory 동적 프로파일을 사용하여 다중 소유 포틀릿 구현하기 [SOA와 웹서비스]
WebSphere Portlet Factory를 사용하여 동적 프로파일을 사용함으로써 표현 레이어 내에서 설정 가능성을 이룩하는 방법을 배워봅시다.
업데이트: IBM Lotus Sametime V7.5.1의 새로운 기능 [Lotus]
point-to-point 비디오, 데스크탑 생산성 애플리케이션들과의 통합 등, IBM Lotus Sametime V7.5.1의 아키텍처 및 디자인 변화에 대해 살펴봅니다.
XML과 자바: 저급 또는 고급 XML API? [XML]
어느 정도의 XML 컨트롤을 원하십니까? Brett McLaughlin이 핵심적인 XML API를 설명하고, 개발자들이 자바와 XML 프로그래밍을 최대한 활용하고 있는지를 조명합니다.
리눅스 커널 해부 [리눅스]
커널은 서브시스템과 레이어로 나뉠 수 있습니다. 리눅스 소스를 더욱 잘 이해할 수 있도록 아키텍처를 설명합니다.
Eclipse RAVEN 방식으로 GUI 접근성 테스트 하기 [오픈 소스]
GUI의 접근성을 점검하기란 어려운 일입니다. IBM Rule-based Accessibility Validation Environment Eclipse 플러그인으로 런타임 접근성 체크가 빨라집니다.
SOA에 레거시 시스템 적용하기 [SOA와 웹서비스]
서비스 지향 아키텍처를 사용하여 기존 IT 자산들을 변형할 때의 주요 이점에 대해 알아봅시다. 여러분은 변화하는 비즈니스 조건에도 빠르고 유연하게 대처할 수 있습니다.
   기획 기사
최고의 매시업 -- 웹 서비스와 시맨틱 웹
이번 기획기사를 통해 시맨틱 기술을 이용하여 서비스를 교환하거나 데이터를 선택하는 방식으로 자신만의 매시업을 만들어내는 기법을 배움으로써 웹 창시자와 선구자들이 꿈꾸었던 비전에 다가갈 수 있는 힌트를 얻을 수 있을 것입니다.
   최신 튜토리얼 (한글)
PHP를 사용하여 인터랙티브한 제작 방식의 위키 만들기, Part 4: 작업 관리 [오픈 소스]
PHP를 사용하여 인터랙티브한 제작 방식의 위키 만들기" 튜토리얼 연재에서는 제작 과정 추적에 유용한 각종 기능이 있는 위키를 PHP를 사용해 처음부터 만듭니다. Part 3에서는 누가 무엇을 할 수 있는지에 대한 컨트롤을 추가했습니다. 이제 작업 관리를 추가할 차례입니다.
구글 가젯 만들기, Part1: 구글 가젯 기본요소 [웹 개발]
이번 새 웹 개발 연재에서는 구글 가젯을 만드는 법을 배웁니다. 가젯은 작은 애플리케이션인데 동적이고 품질 좋은 콘텐츠를 제공하는 수단으로서 대부분의 웹 페이지에 추가할 수 있습니다.
   최신 튜토리얼 (영문)
Lotus Component Designer를 사용하여 WebSphere Portal 컴포넌트 생성 및 전개하기
   최신 SW 다운로드
온라인 시험판: Rational Build Forge V7.0
WebSphere Application Server Community Edition V1.1.0.2 (무료 제품: 업데이트)

  마이크로소프트웨어(www.imaso.co.kr) 8월호 주요 목차

[COVER STORY] 프로젝트 생산성을 높여주는 협업 노하우
커뮤니케이션과 협업 / 위키를 활용한 협업 노하우 / BTS 활용 노하우 / Jira와 Mylyn 활용 전략 / CVS/SVN을 이용한 버전 관리 / Visual Studio Team System과 협업

[SPECIAL REPORT] 리눅스 대중화의 꿈, 아시아눅스 데스크톱 3
데스크톱용 리눅스, 어디까지 왔나? / 64비트 지원과 3D 강화, 그리고 라이브 CD / 리눅스 애플리케이션의 세계 / 아시아눅스 대중화 전략

[STEP BY STEP] PHP를 사용한 상호교환 방식의 위키 만들기 Part 4: 작업 관리 from IBM 디벨로퍼웍스

top

더이상 구독을 원치 않으시는 분은 developerWorks에서 뉴스레터 [구독 취소]를 선택하여 주시기 바랍니다.
developerWorks 운영자에게 메일 보내기
Copyright ⓒ IBM Korea, Inc. All rights Reserved.

반응형
IBM developerworks에서 2007년 하반기 활동할 'developerWorks 대학생 모니터 요원 2기'를 아래와 같이 발표합니다.

오는 8월 8일 수요일, 로보코드 코리아컵 2007 결승대회와 제2기 developerWorks 대학생 모니터요원 임명식이 함께 진행되오니 선정되신 분들은 꼭 참석해주셨으면 합니다.
행사에 대한 보다 자세한 안내사항은 추후 개별 통지해 드리겠습니다.

developerWorks에 많은 관심을 보여주신 대학생 여러분들께 진심으로 깊은 감사 드립니다.

• 선정자 명단 (15명)
성명         학교명
김동철       호남대학교
김주호       서울대학교
김현경       성신여자대학교
남상균       인하대학교 대학원
민창현       경북대학교
박성일       서울산업대학교
박영식       숭실대학교
박지용       고려대학교 대학원
설혜미       한신대학교
신형기       전북대학교
유용빈       명지대학교
이국진       인천대학교
장영석       세종대학교
전민지       서울여자대학교
한성현       숭실대학교 대학원

IBM developerWorks 2기 모니터 요원 선정 되었네요. ^^

뽑히신 분들 축하드리고요.. 다음 모임에서 뵈었으면 좋겠네요.^^
반응형
:: 2007년 07월 25일 ::

  Weekly Highlight
7월 23일 진행된 '이클립스 - 개발도구를 넘어서 범용 플랫폼으로' 세미나의 발표 자료와 행사 스케치가 업데이트 되었습니다.
지난 5월 29일 진행된 소프트웨어 품질 최적화를 위한 Rational 솔루션 세미나의 발표 동영상이 등록 되었습니다.
   Local Contents
기계 세상, Part 1 - 김성우 (dW Column)
웹 스크래핑으로 웹 컨텐츠 입맛대로 꾸미기 - 박지인 (Open dW)
해커 문화의 뿌리를 찾아서 Part 4: 액터와 람다 - 안윤호 (Special Issue)
블랙박스 탐험과 다양한 공부 엮어내기 - 서광열 | 노매드커넥션 CTO (dW Interview)
   최신 기술자료 (한글)
Rails 애플리케이션 개발에 IBM_DB 어댑터와 드라이버 설정하기 [Informationl Mgmt]
강력한 Rails 프레임웍과 결합한 Ruby 언어의 등장으로 웹 솔루션 개발에 많은 기회들이 생겨났습니다. Part 1에서는 Rails 기반 DB2용 Starter Toolkit을 소개하고, 설치와 마이그레이션을 설명합니다.
Real-time Java, Part 4: 실시간 가비지 컬렉션 [자바]
Metronome 가비지 컬렉션을 사용하여 자바 언어로 RT 애플리케이션을 작성해 봅시다.
Eclipse 방식으로 단위 테스트하기 [오픈 소스]
애플리케이션에서 단위 테스트를 지원하기 위해 커스텀 mock 객체들을 만들어 내는 어려움을 피하려면, jMock과 잘 작동하도록 RMock을 조정하여 좋은 결과를 얻을 수 있습니다.
Eclipse 마법사를 사용한 빠른 개발[오픈 소스]
프로젝트에 추가 될 엔터프라이즈 스팩의 코드를 만드는 마법사를 구현해 봅시다. 기존의 Eclipse 클래스의 기본 기능들을 확장합니다.
엔드투엔드 Ajax 애플리케이션 개발, Part 1: Ajax 환경 설정하기 [오픈 소스]
깨끗하고 고급스러운 Ajax 애플리케이션을 개발 및 전개하는 방법을 배워봅시다.
SOA용 엔지니어링 패러다임 [Rational]
IBM Rational Unified Process (RUP) 프레임웍과 Model Driven Systems Development (MDSD)를 함께 사용하여 서비스 지향 아키텍처(SOA) 컴포넌트의 개발 시 위험 요소를 줄일 수 있는 방법을 소개합니다.
   최신 튜토리얼 (한글)
XForms를 사용하여 회계 도구 만들기, Part 4: 자산 관리와 리포팅 더 알아보기 [오픈 소스]
본 Part 4에서는 이전 연재의 주문 리뷰 폼과 자산 관리 폼 예제에서 사용한 기술과 함께 조달 책임자(procurement user)를 위한 특권(privilege)을 함께 다루는 법에 대해 보여줍니다. 또한 실제 이슈를 다룰 수 있는 새 기술에 대해 소개합니다.
최고의 매시업 -- 웹 서비스와 시맨틱 웹, Part 6: 사용자에게 제어 능력 주기 [XML]
본 튜토리얼은 매시업 애플리케이션을 만드는 방법을 다루는 연재의 마지막 튜토리얼입니다. 사용자가 서비스 유형과 웹 서비스에서 뽑아낸 데이터, 그 데이터의 프레젠테이션을 선택할 수 있도록 사용자에게 제어 능력을 주는 방법을 다룰 것입니다.
   최신 SW 다운로드
WebSphere Extended Deployment Data Grid V6.1 (UPDATE)
top
반응형

[Flash] http://play.tagstory.com/player/tagStory_Player.swf?vid=V000069450&player_type=0&agent=IE&distributor=&feed=TS



[Flash] http://play.tagstory.com/player/tagStory_Player.swf?vid=V000069452&player_type=0&agent=IE&distributor=&feed=TS


이번 로보코드 코리아컵에 대한 홍보 영상...

DW 모니터요원들의 정례모임에서 항상 맛있는 식사를 위해..

대치동 주변의 맛집을 찾아주시는 계실장님.. 너무 멋져용..^^
반응형

요약

이번 튜토리얼에서는 Jigloo를 사용하여 기본적인 GUI를 생성하는 방법을 살펴보았다. 몇 가지 컨테이너와 컨트롤들을 사용해 봤다. 이벤트를 사용하여 각각의 컨트롤을 비즈니스 로직을 구현해 놓은 모델과 엮는 방법도 살펴보았다. 이제부터 할 수 있는 것이 많아졌을 것이다. Jigloo의 좀 더 어려운 기능들도 사용할 준비가 되었다. Jigloo를 사용하여 다음과 같은 일들을 할 수 있다.

  • 폼에서 재사용 가능하고 분리된 컴포넌트들을 추출
  • 이런 컴포넌트들을 다른 애플리케이션에서 사용할 수 있으며 심지어 비주얼 상속을 사용하여 확장(extend)할 수도 있다.
  • 스윙 컴포넌트 추가
  • 전체 애플리케이션을 스윙으로 변환

Jigloo 구조상 좀 더 다양한 특징을 제공하기 때문에 폼을 좀 더 복잡하게 만들 수 있다. 날짜 선택 컨트롤과 이런 데이터 요소를 위한 콤보 컨트롤을 사용할 수 있다.

애플리케이션에서 Jigloo 사용에 대해서는 참고자료에 있는 추가 문서들과 개념을 참조하기 바란다.

기사의 원문보기

<출처 :https://www.ibm.com/developerworks/kr/library/tutorial/os-eclipse-jigloo/section5.html>

다운로드 하십시오

설명 이름 크기 다운로드 방식
Part 4 source code os-eclipse-jigloo.workflow.zip 18KB HTTP
다운로드 방식에 대한 정보

<출처 :https://www.ibm.com/developerworks/kr/library/tutorial/os-eclipse-jigloo/section6.html>

참고자료

교육

제품 및 기술 얻기

토론
  • 이클립스 플랫폼 뉴스그룹에서 이클립스 관련 궁금증을 해결할 수 있을 것이다(링크를 클릭하면 기본 유즈넷 뉴스 리더 애플리케이션을 실행하고 eclipse.platform을 자동으로 열 것이다).

  • The 이클립스 뉴스그룹에는 이클립스를 확장하고 사용하는 것에 관심 있는 사람들을 위한 많은 참고자료가 있다.

  • 이클립스 플랫폼 SWT 메일링 리스트를 구독하여 최신 이클립스 소식을 얻을 수 있다.

  • developerWorks 블로그와 developerWorks 커뮤니티에 참여할 수 있다.

    <출처 :https://www.ibm.com/developerworks/kr/library/tutorial/os-eclipse-jigloo/section7.html>
반응형

애플리케이션에서 사용할 데이터 모델 만들기

모델-뷰-컨트롤(MVC) 아키텍처에 익숙하다면 지금까지 만든 것이 애플리케이션의 뷰에 해당한다는 것을 알아차렸을 것이다. 이 부분이야 말로 Jigloo가 진가를 발휘하는 부분이다. 이제는 모델을 만들어야 한다. 다시 말하면 시스템에서 사용할 데이터를 가져오고 사용할 자바 코드를 만들어야 함을 의미한다. Jigloo는 여기서도 역시 빛을 발한다. Jigloo는 단순한 이클립스 플러그인으로 Jigloo를 사용할 때도 직접 이클립스의 강력한 기능들을 힘들이지 않고 바로 사용할 수 있다. 이클립스는 자바 코드 작성과 데이터를 다루는 데 매우 훌륭한 도구이기 때문에 Jigloo 역시 이런 작업을 하기에 적합하다.

스키마 만들기

언급했다시피, XML 형식으로 데이터를 저장하고 JAXB를 사용하여 XML을 읽고 쓰는 작업을 할 것이다. 따라서 스키마를 작성할 필요가 있다.


Listing 1. 워크플로우 XML 스키마
                    
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" 
  targetNamespace="org:developerworks:workflow" 
  xmlns:dw="org:developerworks:workflow">

     <element name="workflow" type="dw:workflow"/>
     <complexType name="workflow">
          <sequence>
               <element name="user" type="dw:user" minOccurs="0"
maxOccurs="unbounded"/>
               <element name="po" type="dw:purchaseOrder"
minOccurs="0" maxOccurs="unbounded"/>
          </sequence>
     </complexType>
     
     <complexType name="user">
          <sequence>
               <element name="username" type="string"/>
               <element name="role" type="dw:role"/>
          </sequence>
          <attribute name="id" type="integer" use="required"/>
     </complexType>
     <simpleType name="role">
          <restriction base="string">
               <enumeration value="worker"/>
               <enumeration value="manager"/>
          </restriction>
     </simpleType>
     
     <complexType name="purchaseOrder">
          <sequence>
               <element name="priority" type="dw:priority"/>
               <element name="dateRequested" type="date"/>
               <element name="dateNeeded" type="date" minOccurs="0"/>
               <element name="itemName" type="string"/>
               <element name="itemDescription" type="string" minOccurs="0"/>
               <element name="quantityRequested" type="integer"/>
               <element name="url" type="anyURI" minOccurs="0"/>
               <element name="price" type="decimal"/>
               <element name="status" type="dw:orderStatus"/>
               <element name="submittedBy" type="integer"/>
               <element name="processedBy" type="integer" minOccurs="0"/>
          </sequence>
          <attribute name="id" type="integer" use="required"/>
     </complexType>
     
     <simpleType name="priority">
          <restriction base="string">
               <enumeration value="normal"/>
               <enumeration value="high"/>
          </restriction>
     </simpleType>
     
     <simpleType name="orderStatus">
          <restriction base="string">
               <enumeration value="pending"/>
               <enumeration value="approved"/>
               <enumeration value="rejected"/>
          </restriction>
     </simpleType>
</schema>

스키마에 해당하는 XML 파일에 바인딩할 자바 클래스를 만들기 위해 JAXB를 사용할 수 있다. 자바 6을 사용한다면 JAXB가 내장되어 있다. 자바 5를 사용한다면 썬에서 JAXB를 다운로드해야 한다. 여러분은 명령행 도구인 xjc를 사용하길 원할 수도 있다. 예를 들어 xjc workflow.xsd와 같이 사용할 수 있다. 이 명령어를 사용하여 workflow.xsd를 스키마 컴파일러가 파싱하고 클래스를 만들어낼 것이다. 그런 다음 클래스 파일들을 프로젝트에 복사하여 사용할 수 있다. 프로젝트에 XML 디렉터리를 만들고 그 안에 스키마 파일도 복사한다. 이제 예제로 사용할 XML 파일을 만들자.


Listing 2. 초기 XML 데이터
                    
<?xml version="1.0" encoding="UTF-8"?>
<dw:workflow xmlns:dw="org:developerworks:workflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="org:developerworks:workflow
workflow.xsd ">
  <user id="0">
    <username>homer</username>
    <role>worker</role>
  </user>
  <user id="1">
    <username>bart</username>
    <role>manager</role>
  </user>
  <po id="0">
    <priority>normal</priority>
    <dateRequested>2001-01-01</dateRequested>
    <dateNeeded>2001-01-01</dateNeeded>
    <itemName>stapler</itemName>
    <itemDescription>A great stapler</itemDescription>
    <quantityRequested>2</quantityRequested>
    <url>http://www.thinkgeek.com/homeoffice/gear/61b7/</url>
    <price>21.99</price>
    <status>pending</status>
    <submittedBy>0</submittedBy>
  </po>
</dw:workflow>

모든 요소들을 추가한 뒤 Package Explorer는 그림 26처럼 보일 것이다(필요하다면 JAXB jar 파일들을 자바 빌드 패스에 추가해야 한다).


그림 26. JAXB 클래스와 XML 파일이 추가된 Package explorer
JAXB 클래스와 XML 파일이 추가된 Package explorer




위로


데이터 접근

Package Explorer에 몇 가지 파일들이 추가된 것을 확인할 수 있을 것이다. 바로 WorkflowDao와 XmlWorkflow다. WorkflowDao는 데이터를 사용할 때 필요로 하는 작업들을 정의한 인터페이스다(Listing 3).


Listing 3. WorkflowDao 인터페이스
                    
package org.developerworks.workflow;

import java.util.List;

public interface WorkflowDao {
     public List<User> getUsers();
     public List<PurchaseOrder> getAllOrders();
     public List<PurchaseOrder> getAllPendingOrders();
     public List<PurchaseOrder> getOrdersForUser(int userId);
     public void saveOrder(PurchaseOrder order);
     public void setOrderStatus(int orderId, OrderStatus status);
}

고전적인 데이터 접근 객체(Data Access Object) 패턴을 사용하고 있다. 간단하게 인터페이스를 정의하고 이 인터페이스에 대한 애플리케이션 코드를 작성한다. XML을 사용하여 JAXB 기반 구현을 할 것이다. 하지만 이 디자인을 사용하여 손쉽게 데이터베이스 기반 구현 같은 기타 구현으로 변경할 수 있다. 이 인터페이스의 구현체가 XmlWorkFlow다.


Listing 4. XmlWorkflow 인터페이스 구현
                    
package org.developerworks.workflow;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class XmlWorkflow implements WorkflowDao {
     private static final String DATA_FILE = "data.xml";
     private static XmlWorkflow instance;
     
     private Workflow workflow;
     
     private XmlWorkflow() {
          try {
               JAXBContext ctx = this.getContext();
               Unmarshaller unm = ctx.createUnmarshaller();
               File dataFile = this.getDataFile();
               InputStream inputStream;
               if (dataFile.exists() && dataFile.length() > 0){
                    inputStream = new FileInputStream(dataFile);
               } else {
                    inputStream = 
Thread.currentThread().getContextClassLoader().getResourceAsStream("xml/"+DATA_FILE);
               }
               JAXBElement element = (JAXBElement) unm.unmarshal(inputStream);
               this.workflow = (Workflow) element.getValue();
          } catch (JAXBException e) {
               e.printStackTrace();
               throw new RuntimeException("Failed to read data file",e);
          } catch (FileNotFoundException e) {
               e.printStackTrace();
               throw new RuntimeException("Could not open data file", e);
          }
     }
     
     public static XmlWorkflow getInstance(){
          if (instance == null){
               instance = new XmlWorkflow();
          }
          return instance;
     }

     public List<PurchaseOrder> getAllOrders() {
          return this.workflow.getPo();
     }

     public List<PurchaseOrder> getAllPendingOrders() {
          List<PurchaseOrder> allOrders = this.getAllOrders();
          List<PurchaseOrder> pending = new ArrayList<PurchaseOrder>();
          for (PurchaseOrder order : allOrders){
               if (order.getStatus().equals(OrderStatus.PENDING)){
                    pending.add(order);
               }
          }
          return pending;
     }

     public List<PurchaseOrder> getOrdersForUser(int userId) {
          List<PurchaseOrder> allOrders = this.getAllOrders();
          List<PurchaseOrder> userOrders = new ArrayList<PurchaseOrder>();
          for (PurchaseOrder order : allOrders){
               if (order.getSubmittedBy().intValue() == userId){
                    userOrders.add(order);
               }
          }
          return userOrders;
     }

     public List<User> getUsers() {
          return this.workflow.getUser();
     }

     public void saveOrder(PurchaseOrder order) {
          int index = 0;
          for (PurchaseOrder po : this.workflow.getPo()){
               if (po.getId().intValue() == order.getId().intValue()){
                    this.workflow.getPo().set(index, order);
                    this.saveData();
                    return;
               }
               index++;
          }
          // add new order
          order.setId(new BigInteger(Integer.toString(this.workflow.getPo().size())));
          this.workflow.getPo().add(order);
          this.saveData();
     }
     
     public void setOrderStatus(int orderId, OrderStatus status) {
          for (PurchaseOrder po : this.workflow.getPo()){
               if (po.getId().intValue() == orderId){
                    po.setStatus(status);
                    this.saveData();
                    return;
               }
          }
     }

     private void saveData(){
          File dataFile = this.getDataFile();
          try {
               JAXBContext ctx = this.getContext();
               Marshaller marshaller = ctx.createMarshaller();
               FileOutputStream stream = new FileOutputStream(dataFile);
               marshaller.marshal(this.workflow, stream);
          } catch (JAXBException e) {
               e.printStackTrace();
               throw new RuntimeException("Exception serializing data file",e);
          } catch (FileNotFoundException e) {
               e.printStackTrace();
               throw new RuntimeException("Exception opening data file");
          }
     }
     
     private File getDataFile() {
          String tempDir = System.getProperty("java.io.tmpdir");
          File dataFile = new File(tempDir + File.separatorChar + DATA_FILE);
          return dataFile;
     }

     private JAXBContext getContext() throws JAXBException {
          JAXBContext ctx = JAXBContext.newInstance("org.developerworks.workflow");
          return ctx;
     }
     
     public static void main(String[] args){
          XmlWorkflow dao = XmlWorkflow.getInstance();
          List<User> users = dao.getUsers();
          assert(users.size() == 2);
          for (User user : users){
               System.out.println("User: " + user.getUsername() + " ID:" + user.getId());
          }
          List<PurchaseOrder> orders = dao.getAllOrders();
          assert(orders.size() == 1);
          for (PurchaseOrder order : orders){
               System.out.println("Order:" + order.getItemName() + "
ID:" + order.getId() + " Status:" + order.getStatus());
          }
          PurchaseOrder order = orders.get(0);
          order.setStatus(OrderStatus.APPROVED);
          order.setProcessedBy(new BigInteger("1"));
          dao.saveOrder(order);
     }
}

위에서 만들었던 예제 파일을 초기에 읽어 들이는 것을 확인할 수 있지만 시스템 임시 폴더에 data.xml로 변경사항을 저장한다. 데이터를 저장하기에 가장 안전한 장소는 아니지만 예제 애플리케이션에 사용하기에는 적절하다. 이 클래스 파일 안에 간단한 main 메서드가 있는 것 또한 볼 수 있다. JAXB가 동작하는 것을 간단하게 단위 테스트하기 위한 용도로 만들었다. 만약에 자바 5를 사용한다면 JAXB jar 파일을 프로젝트의 클래스패스에 추가해야 한다. 계속해서 작업하려면 그 파일들을 복사하여 프로젝트에 추가하거나 프로젝트 외부에 그 파일들이 위치한 장소를 참조할 수 있도록 해야 한다.




위로


애플리케이션 초기화하기

애플리케이션과 인터랙트하기 전에 모든 요소를 초기화해야 한다. 먼저 애플리케이션에서 사용할 모델 객체를 몇 개 선언해야 한다. Listing 5에 있는 코드를 추가하여 WorkflowMain 멤버 변수를 추가한다.


Listing 5. 모델 객체 선언
                    
     // Data Model Objects
     private java.util.List<User> users;
     private User user;
     
     // Service Object
     private WorkflowDao dao = XmlWorkflow.getInstance();

코드에 접근하려면 Workflow.java 파일을 마우스 오른쪽 클릭을 하고 Open With > Java Editor를 선택한다.

애플리케이션의 initGUI() 메서드 코드를 수정한다. 사용자 명단을 초기화하기 위해 private 메서드를 만들겠다.


Listing 6. 사용자 메서드 만들기
                    
     private void initUserList(){
          this.users = dao.getUsers();
          for (User u : users){
               this.userListCombo.add(u.getUsername());
          }
     }

userListCombo를 정의한 후 initGUI()에서 이 메서드를 호출한다.


Listing 7. 사용자 메서드 호출
                    
                {
                    userListCombo = new Combo(this, SWT.NONE);
                    userListCombo.setText("Users");
                    userListCombo.setBounds(28, 35, 105, 21);
                    this.initUserList();
               }




위로


이벤트를 사용하여 뷰와 모델을 엮기

뷰와 모델을 만들었다. 이제 그 둘을 엮어 보자. 컨트롤러가 필요하다. SWT(와 스윙)는 모든 UI 프레임워크에서 사용하는 간단한 기술을 사용한다. 바로 이벤트 주도 시스템이라는 것이다. 이벤트를 사용하여 언제 모델에 있는 작업을 호출하고 뷰를 수정할지 알려줄 수 있다.

이제 다시 비주얼 디자이너로 돌아가자. 모델과 엮기를 원하는 첫 번째 UI 이벤트는 사용자 콤보 리스트에서 사용자를 선택했을 때다. 콤보 컨트롤을 선택하고 GUI 속성 뷰에서 Event 탭으로 변경하면 그림 27에 보이는 것과 같은 화면을 확인할 수 있을 것이다.


그림 27. 콤보 컨트롤 이벤트에 접근하기
콤보 컨트롤 이벤트에 접근하기

몇몇 리스너들을 콤보 컨트롤에서 볼 수 있다. SelectionListener를 선택한다. 이 리스너는 콤보 컨트롤에서 무언가를 선택할 때마다 SelectionEvent를 발생시킨다. 이 이벤트를 다룰 것을 익명 메서드로 그것을 인라인에서 다룰 수도 있고이 이벤트를 다룰 메서드를 정의할 수도 있다. 후자를 선택하겠다. 이렇게 하면 userListComboWidgetSelected라는 메서드가 생성된다. 이벤트를 다루기 위한 코드는 Listing 8에 나와있다.


Listing 8. 사용자 콤보 리스트 선택 코드
                    
     private void userListComboWidgetSelected(SelectionEvent evt) {
          int index = this.userListCombo.getSelectionIndex();
          if (index >= 0){
               this.user = this.users.get(index);
               System.out.println("User selected="+this.user.getUsername());
               purchaseOrderTable.removeAll();
               java.util.List<PurchaseOrder> orders;
               boolean isManager = this.user.getRole().equals(Role.MANAGER);
               if (isManager){
                    orders = dao.getAllPendingOrders();
               } else {
                    orders = dao.getOrdersForUser(this.user.getId().intValue());
               }
               this.approveButton.setVisible(isManager);
               this.rejectButton.setVisible(isManager);
               for (PurchaseOrder order : orders){
                    displayPurchaseOrder(order);
               }
          }
     }

여기서 살펴봐야 할 것들이 많다. 먼저 사용자가 관리자인지 확인한다. 관리자가 아니면 사용자의 모든 구매 주문 목록을 보여준다. 관리자가 맞다면 대기중인 주문 목록만을 보여준다. 다음으로 관리자가 아니면 승인/취소 버튼이 보이지 않도록 한다. 마지막으로 purchaseOrderTable에 있는 모든 주문 데이터들을 데이터 접근 객체를 사용해 가져와 보여준다.

이제 Approve 버튼에 이벤트를 추가한다. 비주얼 디자이너에 보이는 Approve 버튼을 선택하고 GUI 속성 창에 있는 이벤트 탭으로 이동한다. 버튼을 선택했을 때 실행되어야 하기 때문에 그림 28에 보이는 것처럼 여기서도 selection event를 사용할 것이다.


그림 28. Approve 버튼 선택 이벤트 설정
Approve 버튼 선택 이벤트 설정

그리고 나서 이 이벤트를 다룰 코드를 추가한다.


Listing 9. Approve 버튼 선택 이벤트 코드
                    
     private void approveButtonWidgetSelected(SelectionEvent evt) {
          TableItem[] selected = this.purchaseOrderTable.getSelection();
          if (selected != null){
               for (TableItem item : selected){
	this.dao.setOrderStatus(Integer.parseInt(item.getText(4)), OrderStatus.APPROVED);
                    item.setText(3, OrderStatus.APPROVED.toString());
               }
          }
     }

reject 버튼도 이와 매우 비슷하게 할 수 있다. 선택 이벤트 리스너를 추가하고 비슷한 코드를 실행한다. 유일한 차이가 있다면 주문 상태를 APPROVED가 아닌 REJECTED로 바꾼다는 것이다.


Listing 10. Reject 버튼 코드
                    
     private void rejectButtonWidgetSelected(SelectionEvent evt) {
          TableItem[] selected = this.purchaseOrderTable.getSelection();
          if (selected != null){
               for (TableItem item : selected){
	this.dao.setOrderStatus(Integer.parseInt(item.getText(4)), OrderStatus.REJECTED);
                    item.setText(3, OrderStatus.REJECTED.toString());
               }
          }
     }

이제 남은 건 Submit 버튼이다. 이 버튼을 사용하여 새로운 구매 주문을 추가할 수 있어야 한다. 이 버튼 역시 다른 버튼들과 유사하다. 선택 이벤트에 대한 이벤트 핸들러를 그림 29처럼 추가한다.


그림 29. PO 버튼 선택 리스너 추가
PO 버튼 선택 리스너 추가

이벤트를 다룰 코드를 추가한다.


Listing 11. PO 버튼 이벤트 핸들러 코드 추가
                    
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.GregorianCalendar;

import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

...

     private void addButtonWidgetSelected(SelectionEvent evt) {
          try {
               this.addPurchaseOrder();
          } catch (Exception e) {
               throw new RuntimeException("Exception adding purchase order",e);
          }
          this.formItemText.clearSelection();
          this.formPriceText.clearSelection();
          this.formQuantityText.clearSelection();
     }
     
     private void addPurchaseOrder() throws Exception{
          String item = this.formItemText.getText();
          String priceString = this.formPriceText.getText();
          String quantityString = this.formQuantityText.getText();
          BigDecimal price = new BigDecimal(priceString);
          BigInteger quantity = new BigInteger(quantityString);
          PurchaseOrder po = new PurchaseOrder();
          int num = this.dao.getAllOrders().size();
          String numString = Integer.toString(num);
          BigInteger newId = new BigInteger(numString);
          po.setId(newId);
          po.setItemName(item);
          po.setPrice(price);
          po.setQuantityRequested(quantity);
          po.setPriority(Priority.NORMAL);
          po.setStatus(OrderStatus.PENDING);
          po.setSubmittedBy(this.user.getId());
          GregorianCalendar cal = (GregorianCalendar) GregorianCalendar.getInstance();
          DatatypeFactory factory = DatatypeFactory.newInstance();
          XMLGregorianCalendar now = factory.newXMLGregorianCalendar(cal);
          po.setDateRequested(now);
          this.dao.saveOrder(po);
          this.displayPurchaseOrder(po);
     }

	private void displayPurchaseOrder(PurchaseOrder order) {
		String[] row = new String[] 
		         {order.getItemName(), order.getPrice().toString(), 
				order.getQuantityRequested().toString(),
order.getStatus().toString(), order.getId().toString()};
		TableItem tableItem = new TableItem(purchaseOrderTable,0);
		  tableItem.setText(row);
		  this.purchaseOrderTable.showItem(tableItem);
	}




위로


GUI 테스트

GUI를 테스트해야 할 시간이다. 그림 30처럼 클래스를 오른쪽 클릭을 한 후 Run As > SWT Application을 선택한다.


그림 30. 애플리케이션 실행
애플리케이션 실행

이렇게 하면 애플리케이션이 실행된다.


그림 31. 워크플로우 애플리케이션
워크플로우 애플리케이션

기본 데이터로 대기중인 구매 주문을 넣어뒀기 때문에 사용자 bart를 선택한 뒤 구매 주문을 승인할 수 있다.


그림 32. 주문 승인
주문 승인

사용자를 homer로 변경한 뒤 구매 주문의 상태를 확인해 보자.


그림 33. 사용자가 자기 PO 보기
사용자가 자기 PO 보기

새로운 주문을 추가할 수도 있다.


그림 34. PO 추가
PO 추가

Add PO를 클릭하여 테이블에 구매 주문을 추가할 수 있다.


그림 35. 새 PO 추가
새 PO 추가

다시 bart로 변경하여 승인 또는 취소할 수 있다.


그림 36. 업데이트된 관리자 화면
업데이트된 관리자 화면 
반응형
:: 2007년 7월 13일 ::


로보코드 코리아컵 2007

자바 프로그램 경진대회인 로보코드 코리아컵 2007의 접수 마감이 얼마 남지 않았습니다. 7월 22일(금) 로보코드의 예선 접수가 마감되오니 아직 제작 중이거나 준비 중이신 분들은 서둘러 접수를 해주시기 바랍니다. 재미있는 게임을 통해 자바 실력도 늘리고 푸짐한 상품도 꼭 받아가세요. 참가만 하셔도 참가상을 드리니 여러분의 많은 성원 바랍니다.



제2기 대학생 모니터요원 모집

IBM developerWorks에서 2007년 하반기 동안 활동할 "제2기 developerWorks 대학생 모니터 요원" 모집이 7월 20일 마감됩니다. 선정된 모니터 요원에게는 임명장과 함께 각종 행사에 우선 참여할 수 있는 기회가 주어집니다. 우수한 활동을 펼친 분께는 IBM의 추천서도 제공되오니 재기 넘치는 대학생 여러분의 많은 참여 바랍니다.



오픈 소스 세미나

IBM이 후원하는 기묘 세미나, 성공적인 오픈소스 프로젝트 방법론이 오는 19일(목),역삼동 포스틸 타워 3층 이벤트홀에서 열립니다!  이번 세미나에서는 오픈소스 프로젝트를 성공으로 이끄는 방법론을 중심으로 오픈소스의 커스터마이징과 확장 전략을 세워볼 수 있는 계기가 될 것입니다. 선착순으로 사전등록이 마감되오니 관심 있으신 분들은 서둘러 주세요.

반응형

GUI 설정하기

시스템에 대한 기본적인 설계를 했기 때문에 이제는 코드를 작성할 차례다. GUI와 Jigloo 사용법에 집중하도록 하겠다.

워크플로우 애플리케이션 GUI 디자인

워크플로우 애플리케이션 GUI 설계를 시작해 보자. 먼저, 워크플로우 애플리케이션에서 사용할 패키지를 만들자. Package Explorer 창에서 프로젝트를 마우스 오른쪽 클릭을 한 후 New > Package를 선택한다. 기본 패키지를 org.developerworks.workflow로 하겠다. Finish를 클릭한다.

이제 이 패키지에 애플리케이션을 작성해보자. 패키지에서 오른쪽 클릭한 후 New > Other를 선택한다. 그럼 “Select a wizard” 화면을 볼 수 있을 것이다. 모든 Jigloo 마법사들이 이 화면에 있는 GUI Form 폴더에 있을 것이다. 그림 3과 같이 그 폴더를 열고 나서 SWT 폴더를 열고 SWT Main Application 옵션을 선택한다.


그림 3. 마법사 선택
마법사 선택

Next를 선택하여 New SWTApp 마법사를 시작한다. 그림 4처럼 main 클래스를 WorkflowMain으로 하겠다.


그림 4. 새 SWTApp 마법사
새 SWTApp 마법사

나머지는 그대로 두고 Finish를 클릭한다.

Jigloo 비주얼 디자이너 뷰

이제 여러분의 이클립스 워크스페이스가 그림 5처럼 보일 것이다.


그림 5. WorkflowMain을 보여주는 이클립스
WorkflowMain을 보여주는 이클립스

여기서 주목해야 할 것들이 여러 가지 있다. 먼저 메인 창은 Jigloo에서 사용하는 커스텀 뷰를 보여준다. 이 뷰는 몇 개의 화면으로 나뉘어 있다. 맨 위에는 Jigloo 비주얼 디자이너가 있다. GUI에서 드래그 앤 드롭을 사용하여 만들 수 있기 때문에 이 화면을 가장 자주 사용하게 될 것이다. 이 뷰에 대해서는 좀 더 자세히 알아보겠다. 나뉘어져 있는 화면 맨 아래에는 나중에 자세히 살펴 볼 애플리케이션의 자바 코드가 있다. Jigloo가 어떻게 코드를 만들어 놨는지 살펴보길 바란다.

메인 창 바로 아래에는 GUI 속성 창이 있다. 이 뷰에서는 비주얼 다자이너 창에서 선택된 비주얼 컴포넌트의 속성들을 자세히 보여준다. 이 뷰 역시 자주 사용할 것이다. 마지막으로 Package Explorer 뷰에 com.cloudscape.resource패키지가 만들어진 것을 볼 수 있다. 이 패키지는 자동으로 만들어진 SWTResourceManager클래스를 포함하고 있다. 클래스의 이름이 암시하듯이 이 클래스는 글꼴, 색, 이미지와 같은 SWT 자원을 관리한다. Jigloo와 같은 비주얼 디자이너 도구를 사용해본 경험이 있다면, 이런 클래스를 본 적이 있을 것이다. 보통 이런 종류의 클래스는 절대로 수정하지 않도록 권하고 있다. 이 클래스를 수정할 경우 에러를 발생할 가능성이 많으며 아마도 수정하면 예외를 던지거나 수정을 가하더라도 아마 재정의될 것이다. 그러나 SWTResourceManager의 경우에는 그렇지 않다. 이 클래스를 수정하여 Jigloo와 애플리케이션에서 사용할 기본 글꼴과 같은 이 클래스가 관리할 자원을 변경할 수 있다. 그리고 Jigloo가 그 변경사항들을 무시한 채 덮어쓰지도 않을 것이며 에러를 발생시키지도 않을 것이다.

비주얼 디자이너를 사용해 보자. 만약 이클립스를 사용하는 데 익숙하다면, 창 제목 막대를 더블 클릭하면 창을 최대화할 수 있음을 알고 있을 것이다. 그림 6처럼 WorkflowMain 창을 최대화하자.


그림 6. WorkflowMain 최대화
WorkflowMain 최대화

이렇게 함으로써 비주얼 디자이너로 작업할 때 좀 더 예측하기가 수월하며 오른쪽 아래에 있는 GUI 속성에 접근하기도 편리해진다. 또한 우상단에서 GUI를 계층적으로 보여준다. 현재는 매우 단순하지만 GUI에 컴포넌트를 추가하면 그것들이 계층(hierarchy)에 추가되는 것을 확인할 수 있다. 그뿐만 아니라 애플리케이션을 미리 보고 테스트할 수 있는 유용한 기능을 제공한다. 이제 비주얼 디자이너에 조금 익숙해졌을 것이다. 이것을 사용하여 애플리케이션을 만들어 보자.




위로


비주얼 디자이너로 개발하기

스윙이나 SWT 애플리케이션을 개발해 본 적이 있다면, 다양한 종류의 레이아웃 관리자에 익숙할 것이다. 레이아웃 관리자는 자바 GUI 애플리케이션의 강력한 기능 중 하나다. GUI의 레이아웃을 구성할 때 다양한 종류의 알고리즘을 적용할 수 있다. 자바의 모토는 항상 “한 번 만들면 거의 어디서든 동작한다”였고 이러한 레이아웃 알고리즘 역시 이 모토와 일맥상통한다. 이것들을 사용하면 GUI 애플리케이션의 레이아웃이 GUI를 보여주는 창이나 화면에 놓이는 도형들의 크기를 고려하지 않아도 된다.

이런 레이아웃 알고리즘의 유일한 단점은 개발자들이 학습을 해야 한다는 것이며 특히 구성요소들을 절대 좌표에 위치시켜야 하는 플랫폼을 사용하던 개발자들에겐 더욱 심하다. 절대 좌표를 사용하면 컴포넌트들을 화면에서 원하는 위치에 놓을 수 있고 그럼 구성요소들은 그 곳에 놓인다. 이런 레이아웃의 문제점은 창의 크기가 자유롭게 변하는 경우 “깨짐” 현상이 나타난다는 것이다.

예제 애플리케이션은 매우 간단한 프로그램이기 때문에 절대 좌표 레이아웃을 사용할 것이다. 설정은 GUI 속성에서 할 수 있다. 그림 7처럼 Layout 탭을 클릭하고 Layout 값을 Absolute로 변경한다.


그림 7. 레이아웃 설정
레이아웃 설정

다음으로 애플리케이션 크기를 좀 더 크게 해보자. 그렇게 하려면 메인 창의 오른쪽 아래 구석을 마우스로 클릭하여 잡은 다음에 원하는 크기만큼 잡아당겨 늘이면 된다.

이제 애플리케이션에 컴포넌트를 추가할 준비가 끝났다. 여러 사용자 계정 중에 로그인할 사용자 계정을 선택할 수 있어야 한다. 또한 로그인 화면을 만들어 사용자 이름과 비밀번호를 입력할 수 있어야 하지만 최대한 단순하게 만들자. drop-down 리스트를 사용하여 로그인할 사용자를 선택하자.

비주얼 디자이너 위에 툴바가 있고 그곳에서 여러 컴포넌트들을 선택하여 애플리케이션에 추가할 수 있다. 이것은 탭으로 되어 있는 툴바다. 컨트롤을 추가해야 되기 때문에 컨트롤 탭을 선택한다. 각각의 컨트롤은 직관적인 아이콘으로 보이고 있지만 각 아이콘 위에 마우스를 가져가면 이름을 확인할 수 있다. combo 컨트롤을 선택하고 메인 창 위에 놓는다. 그렇게 하면 그림 8처럼 속성 편집 창에 새로운 컨트롤에 대한 정보가 표시된다.


그림 8. 콤보 컨트롤용 속성 편집창
콤보 컨트롤용 속성 편집창

컨포넌트의 이름을 좀 더 직관적인 이름인 userListCombo로 수정하고 Users의 기본 텍스트를 준다. OK를 클릭하면 애플리케이션에서 볼 수 있다.

이제 사용자를 선택하고 바꿀 수 있는 방법이 생겼다. 사용자를 선택했을 때 그 사용자들이 진짜로 원하는 것이 무엇일까? 작업자들은 그들이 입력했던 구매 주문 데이터와 주문 상태를 보고 싶을 것이다. 관리자들은 어떤 주문들이 승인 또는 취소를 기다리고 있는지 보고 싶을 것이다. 구매 주문을 보여주기 위한 테이블을 만들어 보자.

테이블을 추가하기 위해 툴바 탭에서 컨테이너 탭으로 바꾼다. 테이블처럼 보이는 아이콘을 찾아 선택한다. 테이블을 처음 선택하면 마우스를 사용하여 원하는 위치에 그것을 놓을 수 있다. 위에서 추가했던 콤보 리스트 바로 아래에 놓는다. 위치를 정하면 테이블 이름을 묻는 대화창이 나타날 것이다. 콤보 리스트의 이름으로 직관적인 purchaseOrderTable을 입력하자. OK를 클릭하면 테이블이 비주얼 디자이너에 보일 것이다.

테이블 크기도 오른쪽 아래 코너를 잡아 좀 더 크게 만들 수 있다. 이 방법으로 좀 더 테이블을 크게 설정하자. 테이블 속성은 이클립스 오른쪽 아래에 있는 GUI 속성 뷰에 보일 것이다. Properties 탭을 클릭하고 속성 목록 중에서 Expert를 펼친다. 그림 9에 보이는 것처럼 체크박스를 클릭하여 lines-visible 속성을 true로 설정하자.


그림 9. 테이블 속성 편집
테이블 속성 편집

테이블에 칼럼 몇 개를 추가해 보자. 툴바에 있는 컨테이너 탭에서 이전에 선택했던 테이블 아이콘 바로 오른쪽에 있는 Add TableColumn to Table 아이콘을 선택한다. 이제 TableColumn을 purchaseOrderTable에 내려놓으면 TableColumn 속성 편집기가 보일 것이다. 주문한 물건의 이름을 보여줄 것이 때문에 poItemNameColumn이라는 적절한 이름으로 설정한다. 이 값은 테이블의 칼럼 이름으로 사용할 것이기 때문에 Item에 입력하고 OK를 클릭한다.

이것을 추가한 뒤에 비주얼 디자이너는 그림 10처럼 보일 것이다.


그림 10. 칼럼 하나를 추가한 후의 비주얼 편집기
칼럼 하나를 추가한 후의 비주얼 편집기

그런데 지금, 테이블에서 칼럼 이름을 볼 수 없을지도 모른다. 그렇다면 이것은 테이블의 Expert 속성을 변경하여 보이게 할 수 있다. 테이블을 클릭하고 GUI 속성 창으로 다시 이동한다. Expert 섹션을 열고 headerVisible 속성을 Figure 11처럼 설정한다.


그림 11. 테이블 속성 편집
테이블 속성 편집

속성 값은 체크 박스를 사용하여 true나 false로 설정할 수 있다. true로 바꾸고 나면 테이블에서 header를 볼 수 있고 첫 번째 칼럼의 item 레이블을 볼 수 있을 것이다.

계속해서 같은 방법으로 네 개의 칼럼(Price, Quantity, Status, ID)를 추가하자. 모두 추가했으면 그림 12와 같이 보일 것이다.


그림 12. 테이블에 칼럼 전부 추가
모든 칼럼을 테이블에 추가

필요한 주문을 전부 보여줄 수 있다. 여기에서 주문을 승인할지 취소할지 선택할 수 있는 방법이 필요하다. 그렇게 할 수 있도록 각각의 기능을 할 수 있는 버튼들을 추가하자. 버튼을 추가하려면 툴바에 있는 컨트롤 탭으로 이동하여 버튼 컨트롤 아이콘을 선택한다. 이 버튼 역시 원하는 위치에 놓을 수 있다. 테이블 바로 아래에 위치시킨다. 버튼을 위치시키면 버튼의 속성 편집창이 나타날 것이다. 첫 번째 버튼은 구매 주문을 승인하기 위한 버튼으로 사용할 것이다. 따라서 이름은 approveButton이고 텍스트 레이블은 Approve로 하자. OK를 클릭하면 비주얼 편집기에 새로 추가한 버튼이 보일 것이다.

간단하게 Reject 버튼을 애플리케이션에 추가할 것이다. 이 모든 과정을 마친 뒤 비주얼 디자이너는 그림 13처럼 보일 것이다.


그림 13. 버튼 둘 다 추가
버튼 둘 다 추가

추가한 두 개의 버튼이 줄이 맞지 않다는 것을 위 그림에서 알아차렸을 것이다. 맨 눈으로 그런 일을 하는 것은 쉽지 않지만 다행히도 Jigloo가 컴포넌트들의 줄을 맞추는 쉬운 방법을 제공한다. 간단하게 두 개의 컴포넌트를 선택하고(이 경우 버튼 두 개) 비주얼 디자이너 왼쪽에 있는 스타일링 툴바를 사용하면 된다. Align tops of selected elements 버튼을 사용하면 그림 14처럼 보일 것이다.


그림 14. 버튼 정렬
버튼 정렬

버튼들의 모양이 보기 좋게 자리를 잡았다. Jigloo를 사용하면 GUI 모양을 매우 전문적으로 보이게 하는 것이 쉽다.

이제 사용자를 선택할 수 있고 구매 주문 내역을 볼 수 있으며 구매 주문을 승인 또는 거절할 수 있다. 이제 남은 건 구매 주문을 추가하는 기능이다. 새로운 구매 주문을 만들기 위한 데이터를 입력할 때 사용할 폼을 만들 것이다. 폼에는 구매 주문 모델을 만드는 데 필요한 다양한 데이터 유형에 해당하는 여러 가지 컴포넌트과 관련되어 있다. 이 컴포넌트들을 그룹화해 만들어 보자. 모든 컴포넌트를 그룹으로 처리하는 것은 매우 유용하다. 예를 들어, 특정 사용자만 새로운 구매 주문을 추가할 수 있도록 할 수 있다. 그런 권한이 있는 사용자에게만 폼이 보이도록 설정할 수 있다. 이 때 모든 컴포넌트가 그룹으로 되어 있을 때 더 쉽게 할 수 있다.

그룹을 만들려면 Composite 컴포넌트를 애플리케이션에 추가한다. 툴바의 컨테이너 탭으로 이동하고 Figure 15처럼 Composite 아이콘을 선택한다.


그림 15. 툴바를 사용해 컴포지트 추가
툴바를 사용해 컴포지트 추가

원하는 위치에 놓는다. 오른쪽 공간이 많이 비어 있으므로 그곳에 놓자. 그림 16처럼 Composite 속성 편집 창이 나오는 것에 익숙해졌을 것이다.


그림 16. 컴포지트 속성 편집창
컴포지트 속성 편집창

여기서 추가한 Composite이 하는 일의 의도에 적당한 이름인 itemForm을 입력한다. 레이아웃으로 절대 좌표를 사용하는 것에 유의하자. Composite도 자신의 레이아웃 관리자를 사용할 수 있기 때문이다. OK를 클릭한 후 그림 17과 같이 비주얼 디자이너에 보일 것이다.


그림 17. 비주얼 디자이너의 컴포지트
비주얼 디자이너의 컴포지트

오른쪽 아래 코너를 잡아 당겨 Composite의 크기를 조정할 수 있다. 폼 요소들이 모두 자리 잡을 수 있을 만큼 넉넉하게 크기를 확장시키자.

이제 폼을 디자인해 보자. 폼은 텍스트 박스와 레이블을 주로 사용한다. 툴바를 사용하여 이런 요소들을 추가한다. Control 탭으로 이동하여 레이블 아이콘을 선택한다. 예상했듯이 원하는 위치에 내려 놓을 수 있다. 폼 컨테이너 왼쪽 위에 놓도록 하자. 이번에는 레이블 컨트롤에 대한 속성 편집창이 그림 18처럼 나타날 것이다.


그림 18. 라벨 컨트롤 속성 편집창
라벨 컨트롤 속성 편집창

이제 비주얼 디자이너에서 레이블을 볼 수 있을 것이다. 다시 오른쪽 아래 코너를 사용하여 원하는 크기로 조정하자.

레이블 바로 오른쪽에 텍스트 박스를 추가해 사용자가 구매 주문할 물건의 이름을 적을 수 있도록 만든다. 툴바에서 Text Control을 선택한다. 이제 원하는 위치에 놓으면 그림 19와 같은 속성 편집창이 나타난다.


그림 19. 텍스트 컨트롤 속성 편집창
텍스트 컨틀롤 속성 편집창

formItemText라는 이름을 주자. Text 값에는 아무것도 입력하지 않는다. 기본 값을 줄 수도 있지만 그럴 필요는 없다. 이제 비주얼 편집기에서 텍스트 박스를 볼 수 있고 크기를 조정할 수 있다.

계속해서 두 개의 필드 Price와 Quantity를 이와 같은 방법으로 추가한다. 각각 레이블과 텍스트 박스를 사용한다. 모두 추가하면 비주얼 디자이너는 그림 20처럼 보일 것이다.


그림 20. 가격 및 수량 필드 추가
가격 및 수량 필드 추가

Quantity의 기본 값을 1로 설정한 것에 주의하기 바란다. 구매 주문을 할 때 필요로 하는 기본적으로 필요로 하는 요소들이다. 이제 폼을 처리하기 위해 보내는 방법이 필요하다. 이런 기능을 할 버튼을 추가하자. 툴바에서 Button 컨트롤을 선택하여 추가하는 작업은 이전에 살펴보았다. 이번에는 다른 방법으로 해보자. Composite 안에서 마우스 오른쪽 버튼을 클릭하고 그림 21처럼 Add SWT Object > Button을 선택한다.


그림 21. 컨텍스트 메뉴를 사용해 버튼 추가
컨텍스트 메뉴를 사용해 버튼 추가

이제 많이 익숙한 속성 편집창이 뜰 것이다. 컴포넌트의 이름을 addButton으로 하고 텍스트는 Add PO로 한다. OK를 클릭하면 비주얼 디자이너에 그림 22처럼 새로운 버튼이 추가된 것을 볼 수 있다.


그림 22. 버튼 추가
버튼 추가

이 방법과 툴바를 사용하는 방법의 가장 큰 차이는 위치를 조정하고 다른 컨트롤들과의 배열을 맞출 필요가 있다는 것이다. 이 방법을 사용하면 다른 배열 컨트롤을 사용할 수 있는 기회가 생긴다. 세 개의 텍스트 박스와 추가 버튼을 선택하고(shift 키를 사용해 마우스를 클릭하면 여러 컨트롤을 선택할 수 있다) 그림 23처럼 Space selected elements evenly vertically"를 클릭한다.


그림 23. 요소 간 위아래 간격을 똑같게 조정하기
요소 간 위아래 간격을 똑같게 조정하기

다른 정렬 컨트롤러들도 자유롭게 사용할 수 있다. 모든 작업을 완료하면 그림 24처럼 보일 것이다.


그림 24. 최종 UI 디자인
최종 UI 디자인




위로


GUI 미리보기

이제 GUI를 보여주는 멋진 그림을 얻었다. 하지만 아직 어떤 데이터도 연결되지 않았기 때문에 기능적으로 동작하지는 않는다. 간단하게 추가할 수 있지만 그전에 Jigloo의 멋진 기능 중 하나인 미리보기를 사용하기에 적절한 시점이다. 창의 오른쪽 위에 있는 Preview를 클릭한다. 그림 25에서 볼 수 있듯이 GUI의 미리보기가 제공될 것이다.


그림 25. 워크플로우 GUI 미리보기
워크플로우 GUI 미리보기

우리가 마지막으로 만들었던 애플리케이션의 모양과 정확히 일치한다. Windows® 애플리케이션과 모양이 똑같다는 것에 주목하기 바란다. 윈도우에서 SWT를 사용하고 있기 때문이다. 언급했다시피 SWT는 네이티브 위젯을 사용하기 때문이다.
<출처 :https://www.ibm.com/developerworks/kr/library/tutorial/os-eclipse-jigloo/section3.html>

+ Recent posts