간단하게 swing으로 구현? 이 말이 부끄러워진다..


예전에 간단하게 awt gui 기반에서 text 필드에 사용자가 디비 드라이버 정보, 접속url, id,pw 등을 입력하고 버튼을 
누르면, 모든 컴포넌트의 값을 읽어와 루트element에 setproperty 하는 형식으로 값들을 넣고.. 그걸 documnet에 넣어서
dbinfo.xml로 출력해주는 간단한 코드였다.

사실 awt의 기능들을 충분히 사용해보지 않은 상태로(예전엔 awt 클래스들을 디자인 패턴의 관점에서 분석만 했었고..)

현재 제대로 된 dbConnect정보,ddl,dml,dql 을 사용자가 입력하고, 실제 db와 연동시키려는 툴을 만들기 위해
툴의 디자인을 고민했었다. 

1.처음의 기본 디자인은.. 각각의 입력(커넥션,ddl,dml,dql) 페이지를 JPanel로 구분해서.. 메인 툴 프레임에 
예전에 했던대로 gridLayout을 설정해 위에서부터 순서대로 판넬을 포함 시키는 방법. add(component)

(여기서 문제가 발생. 메인 프레임이 800,800 사이즈라.. 각각의 서브 판넬이 800,200 식으로 나눠 가졌으나.. 
개별적 작업 공간이 크기도 부족했을 뿐더러.. 작업 공간이 섞여있어서 직관적이지 못했다.)

2.두번째 디자인은 메인 툴 프레임에  <-  -> 버튼을 추가해서 해당 페이지로 이동하고 작업하는 방식이었다.

첫번째 문제가 현재 겹쳐있는(위치값 동일) 패널에 대해 ->버튼을 클릭시 위치를 이동시켜서 화면 밖으로 
빼고, 다음 패널이 사용자의 입력을 받을 수 있게끔 하는 것이었는데.. 포커스 문제,위치 이동과 복귀등 생각보다 
복잡했다.(물론 시간 투자해 억지로 매달리면 구현은 가능했을테지만..)

두번째 문제가 툴 사용자가 실제 db정보를 입력하고 그것이 제대로 작동하는지 피드백을 받아볼 필요가 있었고
ex) "127.0.0.1 oracal xxx 에 접속 성공"  "create memberInfo( id number(3), name vchar2(20), address ...)
memberInfo 테이블 생성 성공"  "selete * memberInfo 성공" "결과 001,가길동,강북  002,나길동,강남" ...

이 처리를 위해서 이클립스의 콘솔창의 개념이 필요했다. (현재까지 총 5개의 구분되는 컨테이너 컴포넌트=판넬 필요)
+ 콘솔용 판넬은 db커넥션 정보 입력용 패널, ddl 입력용 패널, dml 입력용, dql 입력용 패널에서 공유되어야할 필요가
있었다.

3.그 다음 디자인이.. 메인 프레임 안에.. 각각 독립적인 정보입력용 프레임 4개 + 결과 출력용 프레임 1개를 넣는 방식
이었다. mainFrame.add(new MyFrame("ddl")); 요런 식으로.. (물론... 실행을 시키면 프로그램 창(프레임)이 2개나 
띄워졌지만.. 뻑=익셉션이났다.. 디버깅을 안해서 정확한 이유는 모른다. OTL)

나는 여전히 3번 방식이 객체 지향적이며 무엇보다 내가 원했던.. 사용자가 작업을 하면서 명확해질수 있게끔 돕는 것이
라 생각해.. 이 아이디어를 포기 할수 없었다. 그렇기에 정보를 찾아봤다.

4.LayerdPane 개념 습득. 
swing에 관하여 조금 더 인터넷 서치를 하던중..(내실 없이 아주 거지같은 광고용 블로그에도 당하고.. 욕나옴 -_-;)
알게된 개념으로서.. 아주 오래전 2d 슈팅or액션 게임 만들던때 사용하던 2d화면에 배경별(멀고,가깝고),오브젝트별,캐릭터별 
층을 구분하고 아래부터 먼저 draw 해주는 개념이었다. 

이걸 이용한다면.. 콘솔창의 문제를 제외하곤 애초에 생각했던 2번문제.. <- -> 버튼으로 작업공간 이동이 쉽게 풀릴듯 하여.. 작업을 시작하려 하였으나..

5.Intenal(내부)Frame 개념 습득..
요 클래스가 내가 3번에서 뻑이 났던.. 문제를 해결해주는 개념이었다.. (나처럼 메인 프레임 안에.. 독립적인 프레임을
만들길 원하는 수요가 있어서 java가 만들어 줬나보다. 

멋도 모르고 JFrame을 상속받은 내 메인 프레임에 ddl = new JInternalFrame(); mainFrame.add(ddl); 
요런식으로 해봤더니.. 원하던대로.. 뻑도 안나고 내부의 독립적인 프레임이 생성이 되었다. (이때의 감동이란 ㅠㅠ)

6.샘플 소스코드 입수
... 그렇다.. 이미 java의 고급 개발자들이 우리에게 은혜를 베푸시어.. core api 외에도 샘플 소스코드를
만들어주셨는데.. 왜 이생각을 못했을까? (예전에 한번, 콘솔용 겁나 복잡해진 사용자 컨트롤 처리 로또 프로젝트를 실패
하고, 하루만에 접은 gui 기반 동물 농장 프로젝트를 통해.. "다음에는 샘플 코드부터 작업을 시작하자고 생각했었는데.."

채팅이나, nio 채팅의 경우도.. 거의 맨땅에서 작업하느라 효율이 안좋았는데.. 왜 그랬을까?.. 왜그랬을까..?

에서.. internalFrame 샘플 코드를 받아본 나는.. 다시 한번 새로운 충격에 빠졌다..


7.방황하는 시간..

아래처럼 구현하는 것이.. 스윙 패키지의 기능을 90% 이상 뽑아내는 것이리라..
(내가 구현했던 것들이.. 아주 먼지처럼 느껴졌다.. 내가 한건.. gui 패키지 사용의 겉핥기..)

import javax.swing.JInternalFrame;
import javax.swing.JDesktopPane;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JMenuBar;
import javax.swing.JFrame;
import javax.swing.KeyStroke;

import java.awt.event.*;
import java.awt.*;

/*
 * InternalFrameDemo.java requires:
 *   MyInternalFrame.java
 */
public class InternalFrameDemo extends JFrame
                               implements ActionListener {
    //내경우 JFrame을 상속받아.. add 처리만 하려 했는데.. 아래의 JDesktopPane 이란 관리용 클래스...
    //내가 구현하려던 mainFrame 클래스는 각각의 서브 프레임(db입력용,dml,ddl,dql,콘솔창용)에 대해
    //중앙 컨트롤(사용자의 개별적 입력 이벤트를 전달받아.. 처리후 각각의 서브 프레임에게 명령 전달 하는 
    //메디에이터 클래스의 개념.. ) 한마디로 씬or 페이지 매니저를 만들려고 했었는데.. 이미 만들어져 있었다.
    //즉.. 메인 프레임에서 컨테이너 관리적인 측면을 또 다른 객체에게 위임한 상태..
    //글고 중요한건 아니지만.. 내경운 지금까지 해당 컴포넌트에 대한 리스너를 component.addXXListerner( new
    // xxxAdapter(){익명 클래스 기법으로 재정의}; ) 식으로 처리했는데.. 위처럼 implements를 시키는 것에 대해
    // 고민이 필요할듯 하다.
/*
<명세>

멀티 문서 인터페이스 또는 가상 데스크탑을 생성하는 컨테이너입니다. JInternalFrame 객체를 생성해,JDesktopPane 에 추가합니다. JDesktopPane  JLayeredPane 를 확장해, 오버랩의 가능성이 있는 내부 프레임을 관리합니다. 또, 현재의 Look & Feel (L&F)에 대해서 UI 클래스에서 설정된 DesktopManager 의 인스턴스에의 참조도 유지합니다. JDesktopPane 는 경계를 지원하지 않습니다.  

이 클래스는 일반적으로,JInternalFrames 의 부모로서 사용되어 플러그 인 가능한 DesktopManager 객체를JInternalFrames 에 제공합니다. L&F 별로 구현되는 installUI 로,desktopManager 변수의 적절한 설정을 실시합니다. JInternalFrame 의 부모가 JDesktopPane 의 경우, 클로즈나 사이즈 변경등의 동작의 대부분을 desktopManager에 위양 합니다.  

상세와 사용예에 대해서는, 「The Java Tutorial」의「How to Use Internal Framesy」를 참조해 주세요.  

*/

    JDesktopPane desktop; //요것을 멤버 필드로 보관하는 이유는 좀 있다가..

    public InternalFrameDemo() {
        super("InternalFrameDemo");

        //Make the big window be indented 50 pixels from each edge
        //of the screen.
        int inset = 50;
       //내경운 일단 빠른 구현을 위해서 setSize인가로 800,600을 떄려 넣었다. 아래가 정석이다.ㅠㅠ
       //툴킷의 경운 .. 사용자의 native한 gui 데스크탑 환경이라 생각하면 된다.(os)
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 
        setBounds(inset, inset,
                  screenSize.width  - inset*2,
                  screenSize.height - inset*2);

        //Set up the GUI.
        desktop = new JDesktopPane(); //a specialized layered pane
        //메인프레임을 클래스 멤버필드로 관리하기에.. 아래처럼 서브프레임을 만드는 행위를 메서드로 뺄수 있게 되었다.
        //서브프레임을 동적으로 넣었다, 뺐다 할수 있다.  미리 고정된 .. mainFrame.add(subFrame1); ... 요럴 필요가 없음.
        //생성하려는 서브 프레임이 동일하다면.. 아래처럼 쓰면 될테고.. dml,dql,,ddl,console,dbinfo처럼 제각각 특징이 
        //있다면 상속받아서.. 다형성을 이용해야 한다.
        createFrame(); //create first "window" 
        
        //메디에이터? 관리용 객체(JDesktopPane)을 만들었으니 메인 Frame에 세팅하는 작업이 필요.
        setContentPane(desktop);
        //아래의 경운.. 내가 만들툴의 디자인상 각각의 서브 프레임이 사용자의 입력 데이터를 개별적으로 save/load
        //하는데 사용이 가능할듯. JMenuBar crateMenuBar()로서 메서드 단위로 개념을 분리시켰다.
        setJMenuBar(createMenuBar());

        //Make dragging a little faster but perhaps uglier.
        desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
    }

    protected JMenuBar createMenuBar() {
        //이처럼 모듈별로 쪼개는것은 중요하다. 내가 구상한 디자인 경우에도.. ddl 프레임에서 setColumn() 이란 메서드로서
       // 반복적이고 몇개가 될지 모르는 테이블의 컬럼 정의를 메서드로 추출하려 했었으니까..
       //ex)사용자가 테이블에 대해 하나의 컬럼 정의를 완료하고 add 버튼을 누르면 다음 컬럼 정의할수 있고
       //또는 delete를 누르면 이미 정의된 컬럼 날아가고.. confirm 누르면 확정되어 데이터를 실제로 조립하고...
        JMenuBar menuBar = new JMenuBar();

        //Set up the lone menu.
        //정말..멋진 디자인이다. 메뉴바 객체에 메뉴들을 추가한다.. 컨테이너의 개념...
        JMenu menu = new JMenu("Document");
        
       //놀라울 따름이다.. 해당 메뉴객체에 키보드 alt+D 이벤트를 연결하는 작업이 이처럼 간단하다.
        menu.setMnemonic(KeyEvent.VK_D);
        menuBar.add(menu);

        //Set up the first menu item.
        //마찬가지이다. 하나의 메뉴객체는 또한 내부에 이것 저것 아이템을 가지고 있을테니.. 나는 정말로
        //자바가 제공하는 gui 프레임 워크를 1%도 이해를 못하고선.. 혼자서 대충 만들수 있다느니..하고 자만했다.
        //얼마나 직관적이고 일관성있는 인터페이스 인가?
        JMenuItem menuItem = new JMenuItem("New");
        menuItem.setMnemonic(KeyEvent.VK_N);
        //일반적인 키 이벤트야 비트마스크로 추출하는건 알고 있으니 넘어가고..
        menuItem.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.VK_N, ActionEvent.ALT_MASK));

        //아래의 menuItem.setActionCommand("new"); menuItem.addActionListener(this);... 화룡점정이다.
        //내가 만들던 파일매니저의 서브시스템중 워커 팩토리를 구현할때 다형적인 워커와 클래스를 해쉬맵으로 
        //key,value로 묶어서 문자열로 매핑한 것을 상기하자.  

/* 리마인드용 내가 구현했던 코드 조각
workerMap = new HashMap<String,Class<?>>();
Properties pro = new Properties();
//TODO:경로까지 지정을 할까;;
pro.loadFromXML(new FileInputStream("worker.xml"));
for(Entry<Object,Object> entry : pro.entrySet())
{
try
{
workerMap.put((String)entry.getKey(), Class.forName((String)entry.getValue()));
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}


public FileWorkerImpl getWorker(String workerName)
{
assert workerName != null : "workerName 뷁:" + workerName;
assert !" ".equals(workerName): "workerName 뷁:" + workerName;
FileWorkerImpl worker = null;
try
{
worker = (FileWorkerImpl)workerMap.get(workerName).newInstance();
}
catch (InstantiationException | IllegalAccessException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
return worker;
}
*/
        //다시 생각해봐도 정말 아름다운 디자인으로, 커맨드 패턴 + 옵저버 패턴(이벤트 디스패처+리스너)의 멋진 활용
        //내가 전에 최고 수준의 코딩 기법을 따라하고 싶다고 노래를 불렀었는데.. 자바 샘플 소스중.. swing 포함 gui
        //부분.. 정말로 멋지다. 내가 아래처럼 설계하고 구현할수 있게끔 훈련된다면 얼마나 좋을까 ㅠㅠ 샘플 소스 코드
        //매일 하나씩 분석해야겠다.
        menuItem.setActionCommand("new");
        menuItem.addActionListener(this);
        menu.add(menuItem);

        //Set up the second menu item.
        //중복 됨으로 분석 생략
        menuItem = new JMenuItem("Quit");
        menuItem.setMnemonic(KeyEvent.VK_Q);
        menuItem.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.VK_Q, ActionEvent.ALT_MASK));
        menuItem.setActionCommand("quit");
        menuItem.addActionListener(this);
        menu.add(menuItem);

        //메뉴바 란.. 하나의 프레임 속에선 서브 컴퍼넌트이지만.. 그 자신도 내부에 메뉴들과 그에 속한 아이템들을
        //갖는 객체.. 이 메뉴바란 객체를 구성하는걸 메서드 단위로 뺀 모듈화.. 배워야한다..ㅠㅠ 
        return menuBar;
    }

    //React to menu selections.
    //위에서 작업해준.. 액션이벤트란 커맨드를 문자열로 매핑하고, 액션 리스너 클래스의 actionPerformed() 메서드를
    //재정의 하여.. 아래처럼 간단하게 구현이 가능하다. 명령에 따른 처리를 메서드 단위로 추출한 것도 포인트.
    //사실.. 아래의 작업을 더 추상화하면.. 내가 파일시스템에 구현했듯.. 팩토리 + 워커를 상속계층으로 만들어
    //동일한 인터페이스인 work()로서 추상화하면.. 더 간단해진다. 
    //ex) Worker worker =  workerFactory.getInstance().getWorker("copy");
    //      Work work = workFactory.getInstance().getWork("file", srcPath, destPath);
    //      worker.setWork(work);  한마디로.. 디자인 패턴에 빠져.. 워커도 추상화, 워크도 다형적으로 추상화 해버렸다..
    //      worker.work();
    // 근데 실제로 위처럼 구현하게 되면.. 코드는 이뻐지지만, 명시적이지 않다. 예전에 코드컴플리트에서도 지적했듯
    // switch() case 문 또는 if else() 문으로 아래처럼 명시적으로 처리가 되면.. 추상화할 필요가 없다는데.. 동의한다.
    // 아래는 참으로 명시적이다.
    public void actionPerformed(ActionEvent e) {
        if ("new".equals(e.getActionCommand())) { //new
            createFrame();
        } else { //quit
            quit();
        }
    }

    //Create a new internal frame.
    protected void createFrame() {
        MyInternalFrame frame = new MyInternalFrame();
        frame.setVisible(true); //necessary as of 1.3
        //아까 JDeskTopPane(서브 프레임들의 메디에이터)을 멤버 필드로 보관한 효과가 여기서 나타남..
        desktop.add(frame);
        try {
            frame.setSelected(true); //요건 정확힌 모르겠으나.. 해당 프레임에 포커스 이동시키는 걸테지..
        } catch (java.beans.PropertyVetoException e) {} //요 빈 어쩌구 예외 클래스는 당황스럽다.. 지금은 걍 패스
    }

    //Quit the application.
    protected void quit() {
        System.exit(0);
    }

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread. 
       위의 의미는 정확히 이해는 못하겠다.
       단지 이벤트 디스패팅 스레드란 단어를 보니.. 예전에 컴포넌트에 이벤트가 발생햇다는 것을 감지하는 이벤트 디스패처
       (내가 awt 분석떄 자주본 놈.. eventmulitcaster였나?)이 떠오른다. 
       대충 해석을 하면.. 멀티스레딩에 안전하게 gui객체를 생성하기 위해서 이벤트 감시자에게 불려가는 콜백 메서드를
       정의하는것 같은데.. 생소할 따름이다.
     */
    private static void createAndShowGUI() {
        //Make sure we have nice window decorations.
        JFrame.setDefaultLookAndFeelDecorated(true);

        //Create and set up the window.
        InternalFrameDemo frame = new InternalFrameDemo();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Display the window.
        frame.setVisible(true);
    }

//아래를 보니.. 이해가 된다.. 위의 creatrAndShowGUI()가 특별한 의미가 있었던건 아닌.. 걍 init()메서드
//였구나..
//그보다 더욱 중요한건.. invokerLater의 매개변수로 스레드(=Runnable() + run()재정의를 익명클래스 기법을
//통해 생성과 동시에 재정의 처리) 객체를 생성하고.. run() 내부에서 InternalFrameDemo 란 gui 객체를 생성했다는 점이다.
//만약 내가 툴 프로그램을 멀티스레딩으로 처리했다면..  JFrame을 상속받은 툴 프로그램 객체에 Runnalbe을
//implements 하고 내부에 Thread 객체를 둔 다음에.. 생성자에서 thread = new Thread(this); start(){thread.start();}
//run(){} 을 구현했을 것이다. 위와 비교해 아래는 얼마나 깔끔한가..(제대로 이해는 못했지만..)
    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}


ps.음.. 일단.. 멋진 코드를 보면서 감동도 받았지만, 현재 내 초라한 능력에 위축도 된다.. 툴 프로그래밍 진도가 예상보다 
늦어질지도 모른다.(자바가 제공하는 패키지를 백분 활용하여 작업을 한다는 것의 난이도를 느꼈기 때문이다.) 위와 같은 코딩 
스타일을 몸에 익혀야 할것이다. 

불행중 다행이라면.. 아직 완전히 이해는 못했지만.. 학원 1달차 부터.. 꾸준히 디자인 패턴 관련 글과 자료를 공부해 놓아서
여러 패턴들이 눈에 보이고... 왜 그렇게 설계를 했을지.. 약간의 짐작과 이해가 가능하다는 점이다. 그런고로 app를  oop를 
활용해 제대로 구현하기 위해선 디자인 패턴에 대한 이해는.. 선택이 아닌, 필수이다. 디자인 패턴은 개발중의 여러 복잡한 
이슈를 해결하기 위한 솔루션이다.

ps2.오늘 점심땐 중고 서점 가서 db책,swing책,멀티스레드책(+@스프링)


'자바se api > swing' 카테고리의 다른 글

스윙..  (0) 2014.01.02
스윙의 기본적인 멀티스레드 처리방식  (0) 2014.01.02
이벤트처리 스레드;;  (0) 2014.01.02
propertyEditor vs ConversionService + 토비의 스프링?  (0) 2014.01.02
by givingsheart 2014. 1. 2. 09:11