자바 GUI (그래픽 유저 인터페이스) 프로그래밍 [1]

에 이어서 조금은 더 고급(?) 스러운 예제를 다뤄 볼까 합니다.

앞서 다룬 예제는 솔직히.. 예제랄것도 없었죠?

그냥 프레임 클래스와 패널 클래스등을 사용하여 단순히 GUI 윈도우를 띄어보는 수준에 그쳤는데요.

이번 글에서는 조금 더 나아가서 버튼을 사용하고, 버튼을 눌러서 상호작용하는 아주 간단한 예제까지 다뤄보고자 합니다.


Content Pane과 Panel에 대한 설명이 이전 포스팅에서 다 되었기 때문에 기초 지식은 다 안다고 생각합니다.

따라서, 바로 예제 코드를 보는 것이 더 좋을 것 같네요


import javax.swing.*;

import java.awt.Color;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;


public class ButtonDemo implements ActionListener {

int redScoreAmount = 0;

int blueScoreAmount = 0;

JPanel titlePanel, scorePanel, buttonPanel;

JLabel redLabel, blueLabel, redScore, blueScore;

JButton redButton, blueButton, resetButton;

public JPanel createContentPane() {

/* total panel */

JPanel totalGUI = new JPanel();

totalGUI.setLayout(null);

/* titel panel */

titlePanel = new JPanel();

titlePanel.setLayout(null);

titlePanel.setLocation(10, 0);

titlePanel.setSize(250, 30);

totalGUI.add(titlePanel);

redLabel = new JLabel("Red Team");

redLabel.setLocation(0, 0);

redLabel.setSize(100, 30);

redLabel.setHorizontalAlignment(0);

redLabel.setForeground(Color.red);

titlePanel.add(redLabel);

blueLabel = new JLabel("Blue Team");

blueLabel.setLocation(120, 0);

blueLabel.setSize(100, 30);

blueLabel.setHorizontalAlignment(0);

blueLabel.setForeground(Color.blue);

titlePanel.add(blueLabel);

/* score panel */

scorePanel = new JPanel();

scorePanel.setLayout(null);

scorePanel.setLocation(10, 40);

scorePanel.setSize(250, 30);

totalGUI.add(scorePanel);

redScore = new JLabel("0");

redScore.setLocation(0, 0);

redScore.setSize(100, 30);

redScore.setHorizontalAlignment(0);

scorePanel.add(redScore);

blueScore = new JLabel("0");

blueScore.setLocation(120, 0);

blueScore.setSize(100, 30);

blueScore.setHorizontalAlignment(0);

scorePanel.add(blueScore);

/* button panel */

buttonPanel = new JPanel();

buttonPanel.setLayout(null);

buttonPanel.setLocation(10, 80);

buttonPanel.setSize(250, 70);

totalGUI.add(buttonPanel);

redButton = new JButton("Red Score!");

redButton.setLocation(0, 0);

redButton.setSize(100, 30);

redButton.addActionListener(this);

buttonPanel.add(redButton);

blueButton = new JButton("Blue Score!");

blueButton.setLocation(120, 0);

blueButton.setSize(100, 30);

blueButton.addActionListener(this);

buttonPanel.add(blueButton);

resetButton = new JButton("Reset Score");

resetButton.setLocation(0, 40);

resetButton.setSize(220, 30);

resetButton.addActionListener(this);

buttonPanel.add(resetButton);

totalGUI.setOpaque(true);

return totalGUI;

}


private static void createAndShowGUI() {

JFrame.setDefaultLookAndFeelDecorated(true);

JFrame frame = new JFrame("[=] JButton Score! [=]");

ButtonDemo demo = new ButtonDemo();

frame.setContentPane(demo.createContentPane());

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setSize(250, 190);

frame.setVisible(true);

}

public static void main(String[] args) {

SwingUtilities.invokeLater(new Runnable() {


@Override

public void run() {

// TODO Auto-generated method stub

createAndShowGUI();

}

});

}


@Override

public void actionPerformed(ActionEvent e) {

// TODO Auto-generated method stub

Object obj = e.getSource();

if (obj == redButton) {

++redScoreAmount;

redScore.setText("" + redScoreAmount);

} else if (obj == blueButton) {

++blueScoreAmount;

blueScore.setText("" + blueScoreAmount);

} else if (obj == resetButton) {

redScoreAmount = 0;

blueScoreAmount = 0;

redScore.setText("" + redScoreAmount);

blueScore.setText("" + blueScoreAmount);

}

}

}


결과물



코드가 갑자기 너무 방대해졌나요? 실은 큰 의미가 있는 코드들은 그닥 길지 않습니다. 찬찬히 읽어보시면 충분히 이해가 갈겁니다.

일단, 당연히 프로그램이 실행되면 main 메서드가 호출이 될 것이고, 

createAndShowGUI() 메서드를 호출하는 구문을 볼 수 있을 것입니다.

createAndShowGUI에서는 이 프레임의 ContentPane을 createContentPane 메서드로 반환하도록 하였는데요. 

그 메서드가 바로 맨 위에 길다란 메소드 구문입니다.

또, createContentPane 메서드에서 버튼 구성요소들에게는 addActionListener(this) 메서드로 액션 리스너를 설정하였는데요.

액션 리스너는 그 버튼이 클릭될 경우 actionPerformed 메서드를 호출하도록 하는 리스너입니다.


디스플레이 구성요소에는 크게 세 부분이 있는데요. 타이틀 패널, 스코어 패널, 버튼 패널이 있습니다.




많은 개발자분들이 자바 언어를 사용하시는 경우가 많을 것이라고 생각하는데요.

자바 언어를 활용하여 콘솔 창에서 다양한 프로그램을 작성해본 경험은 있어도 GUI 프로그래밍을 해본적은 없는 분들을 위해서 간단한 GUI 프로그래밍 방법에 대해서 소개해드리고자 합니다. 자바 언어를 아주 간단히 사용해본 적이 있으신 분들에 대해서 읽어보시면 좋을 듯합니다.


자바는 GUI 프로그래밍을 위해 2가지 패키지를 제공하고 있습니다.

- abstract windows kit (AWT) 

- swing toolkit

AWT가 기존의 것이고, swing toolkit이 더 새로운 것입니다. Swing 패키지에서 제공하는 프레임들은 기존 AWT가 제공하는 프레임들과 구별하기 위해서 JFrame과 같은 클래스 이름을 사용하고 있습니다. (AWT에서 제공하는 프레임 클래스는 Frame) 즉, 맨 앞에 J를 붙여줌으로써 Swing 패키지에서 제공하고 있음을 병시하는 것입니다. 


표시가능한 프레임들 JWindows, JDialog, JApplet과 같은 최상 수준의 컨테이너들이고,

표시되지 않는 컨텐츠 틀들 JPanel, JoptionsPane, JScrollPane, JSplitPane들과 같은 중간 수준의 컨테이너들입니다.

(최상 수준, 중간 수준이라는 말은 고수준-저수준의 용어의 맥락에서 생각하시면 편합니다. 수준이 높을수록 좋은게 아니고, 사람과 더 가까이 있다는 것을 의미합니다. 즉, 눈에 보이는 컨테이너들이 최상 수준이고, 눈에 보이지는 않지만 작동하고 있는 것들이 중간 수준, 혹은 저수준이라고 말할 수 있는 것이지요)


여기서 컨테이너란 위젯들을 말하거나 다른 위젯들을 관리하고 처리하는 GUI 컨트롤들을 말합니다. 

위젯들은 간단히 말해 텍스트 박스, 체크 박스, 버튼 같은 GUi 컴포넌트를 말하는 것이구요. 

(두 개가 큰 차이가 있다기 보단 위젯이 더 작은 구성요소, 그것을 그룹화하고 관리하는 것이 컨테이너라고 말할 수 있습니다.)


아래는 자바를 관리하는 썬에서 제공하는 그림입니다.



여기서 중요한 것은 위젯을 추가하여 GUI 컨트롤을 보여주려면, 그것을 Content Pane에 추가해야 한다는 것입니다.


충분히 기본적인 지식은 다 쌓았다고 생각이 듭니다.

자, 이제 다음 가장 간단한 GUI 예제를 보시면 되겠습니다.


import java.awt.*; import java.awt.event.*; import javax.swing.*;

public class Frame1 extends JFrame { JPanel pane = new JPanel(); Frame1() { super("Empty Simple Frmae"); 

setBounds(100,100,300,100); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Container con = this.getContentPane(); con.add(pane); // add the panel to content pane

/* customize panel here */

setVisible(true); }

public static void main(String args[])

{

Frame1 frame = new Frame1();

} }


중간 수준에서 위젯들을 그룹화하고 처리할 (물론 이 예제에서는 딱히 처리할 위젯이나 GUI 컨트롤이 없긴 하지만) JPanel 클래스를 만들어줍니다. 그리고 이 Panel을 Content Pane에 추가합니다. 그 이후, 패널을 자유자재로 설정해주시고, setVisible(true)를 호출하면 간단한 GUI 프레임이 등장하는 것을 보실 수 있으실 겁니다.

(프로젝트 개요)


현재 pintos는 시스템 콜 핸들러가 제대로 구현되어 있지 않다. 시스템 콜이 발생해도 system call!! 이라는 출력문 하나만 달랑 뿌리고, 그대로 스레드가 종료된다.(thread_exit()) 따라서, 이번 프로젝트 2는 핀토스의 시스템 콜 핸들러 함수를 구현하고, 간단한 시스템 콜(halt, exit, create, remove)를 구현해본다.



| 시스템 콜?

사용자 모드 수준에서의 프로그램이 커널 기능을 사용할 수 있도록 해주는 인터페이스.

시스템 콜의 기능들은 커널 모드에서 실행되고, 처리 후 사용자 모드로 복귀됨.



(프로젝트 해결 방법 개요)


우선, 2가지 함수를 구현해야 함. 


1. 특정 포인터(주소값)가 유저 영역에 존재하는지 확인하는 함수.

void check_address(void * address);


2. 유저 스택에 들어 있는 인자들의 주소를 저장하는 함수.

void get_argument(void *esp, void *arg, int count);


1. 유저 영역은 0x08048000 에서 0xc0000000 사이에 (경계값 제외) 존재하는 주소값이면 유저 영역인 것이다. check_address를 구현할 때 이 바깥 범위의 값이면 exit (-1)을 호출해 프로그램을 종료시켜야 한다. (유저 영역이 아닌 커널 영역을 건드리는 프로그램은 프로그램을 종료시키는 것이 적절한 운영체제의 반응이다.)


2. get_argument 의 함수는 esp 인자에서 인자들을 4바이트씩 긁어온 후, arg라는 변수에 저장하는 행동을 한다. 참고로, arg에 저장하는 값들은 인자의 주소값이다. (이 부분을 정확히 이해하라.) arg에 주소값을 저장하지 않고 인자값 자체를 저장하면 안된다. 왜냐하면, 시스템 콜의 인자들이 전부 4바이트로 표현할 수 있다는 보장이 없기 떄문이다. (가령 문자열이 전달되어 온다면 어떻게 할것인가..? 해결방법은 문자열 자체의 주소값을 저장하는 것이다.)


따라서, arg에는 반드시 인자의 주.소.값이 저장되어야 함을 무한 강조!!

f->esp 의 첫 4바이트는 시스템 콜 넘버이므로, 그 다음 4바이트씩 count 횟수만큼 긁어와서 arg에 저장하면 된다.



============

시스템 콜 핸들러 함수

static void syscall_handler (struct intr_frame *f UNUSED) { /* 유저 스택에 저장되어 있는 시스템 콜 넘버를 이용해 시스템 콜 핸들러 구현 */ /* 스택 포인터가 유저 영역인지 확인 */ /* 저장된 인자 값이 포인터일 경우 유저 영역의 주소인지 확인*/

}

f->esp (유저 스택)에는 4바이트씩 인자들이 저장되어 있다. f->esp의 첫 번째 4바이트에는 시스템 콜의 넘버가 저장되어 있고, 그 뒤로 4바이트씩 시스템 콜의 인자가 저장되어 있다.


예를 들어, create 시스템 콜은 인자로 파일 이름과 파일의 첫 크기를 인자로 받는다. 그러면, create 시스템 콜이 발생할 때 유저 스택 포인터는

다음과 같은 구조를 띈다.


f->esp

(4바이트) 4

(4바이트) 0x100

(4바이트) 0x104

....

0x100은 파일 이름 문자열의 주소값이고, 0x104는 파일의 첫 크기의 주소값이다. 물론, 파일의 첫 크기값은 정수일테지만, 정수값 자체를 유저 스택에 저장하지 않는다. 유저 스택에는 '주소값'이 저장되어 있음을 무조건 기억하라. 따라서, 나중에 시스템 콜 핸들러에서 create 시스템 콜을 처리할 때 다음과 같이 처리해야 한다.


    case SYS_CREATE:/* 4 */
      f->eax = create((char *)*(int *)arg[0], (unsigned)*(int *)arg[1]);
    break;

현재 arg에는 인자들의 '주소값'이 저장되어 있다. 즉, arg[0], arg[1],...등은 전부 '주소값'이다. 따라서, *arg[0]의 의미는, arg의 첫 번째 값의 내용물을 찾아오는 것이다. 따라서, 첫 번째 인자의 주소값의 내용물을 찾아와서 그들을 시스템 콜 함수의 인자로 넘겨줘야 하는 것이다. 


| 문제


입력(표준 입력)

각각의 줄에는 최대 3개의 정수들을 포함하고 있어야 한다.

3개의 입력 정수들 중,

첫 번쨰 숫자가 0이면 결과를 출력하고 프로그램을 종료한다. (terminate)

첫 번째 숫자가 1이면 두 번째 숫자를 삽입한다. (insert)

첫 번째 숫자가 2이면 가장 큰 숫자를 추출한다. (extract)

첫 번째 숫자가 3이면, 두 번째 숫자 인덱스의 원소를 세 번째 숫자로 바꾼다.

우선순위 큐의 인덱스는 1로 시작한다고 가정하자.

우선순위 큐의 최대 인덱스 값은 100000이다.


출력(표준 출력)

첫 번째 줄에, 추출된 원소들을 출력한다.

두 번째 줄에, 현재 힙을 출력한다.


example)


입력 

출력 

1 16

1 15

1 10

1 14

1 7

1 9

1 3

1 2

1 8

1 1

3 2 4

2

2

0

16 14

10 8 9 4 7 1 3 2 


| 소스 코드

#include <stdio.h>
#include <stdlib.h>

#define minus_inf -32767

int PARENT(int index){ return index/2; }
int LCHILD(int index){ return index*2; }
int RCHILD(int index){ return index*2+1; }

void array_swap(int arr[], int a, int b)
{
	int temp;
	temp = arr[a];
	arr[a] = arr[b];
	arr[b] = temp;
}

void Heapify(int arr[], int current, int length)
{
	int left, right, largest;
	left=LCHILD(current);
	right=RCHILD(current);

	if((right<=length) && (arr[right]>arr[current]))
		largest = right;
	else
		largest = current;

	if((left<=length) && (arr[left]>arr[largest]))
		largest = left;

	if(largest != current)
	{
		array_swap(arr, current, largest);
		Heapify(arr, largest, length);
	}
}

void PRINT(int arr[], int length)
{
	int i=0;
	for(i=1; i<=length; i++)
		printf("%d ", arr[i]);
}

int EXTRACT_MAX(int arr[], int length)
{
	if(length < 1)		// "heap underflow"
		return minus_inf;
	int max = arr[1];
	arr[1] = arr[length];
	length --;
	Heapify(arr, 1, length);
	return max;
}

void INCREASE_KEY(int arr[], int index, int key)
{
	int tmp=0;
	if(key < arr[index])		// not increasing, then fail.
		return;

	arr[index] = key;
	while(index>1 && arr[PARENT(index)]<arr[index])
	{
		/* EXCHANGE CURRENT WITH PARENT */
		tmp = arr[index];
		arr[index] = arr[index/2];
		arr[index/2] = tmp;

		index = PARENT(index);
	}
}

void INSERT(int arr[], int length, int key)
{
	arr[length] = minus_inf;
	INCREASE_KEY(arr, length, key);
}

int main(void)
{
	int arr[100000];
	int ext[100000];
	int length=0, extlength=0;

	int op, tmp, i;
	int index, num;

	int quit=0;

	//INSERT(arr, length, 1); length++;
	//INSERT(arr, length, 2); length++;

	while(quit==0)
	{
		scanf("%d", &op);
		switch(op)
		{
		case 0:// print
			quit=1;		// terminate this program.
			break;

		case 1:// insert
			scanf("%d", &num);
			length++;
			arr[length] = num;
			INCREASE_KEY(arr, length, num);
			break;

		case 2:// extract
			extlength++;
			ext[extlength] = arr[1];

			tmp = arr[1];
			arr[1] = arr[length];
			arr[length] = tmp;

			length--;
			Heapify(arr, 1, length);
			break;

		case 3:// replace
			scanf("%d %d", &index, &num);
			arr[index] = num;
			if(index != 1 && arr[index] > arr[PARENT(index)])
				INCREASE_KEY(arr, index, num);
			else
				Heapify(arr, index, length);
			break;
	
		default:
			break;
		}
	}

	for(i=1; i<=extlength; i++)
		printf("%d ", ext[i]);

	printf("\n");
	for(i=1; i<=length; i++)
		printf("%d ", arr[i]);

	return 0;
}


| 문제


방향성 그래프 G에서 모든 간선들의 타입을 출력해내는 DFS(깊이 우선 탐색)을 구현하여라. DFS 과정을 진행하는 동안, 작은 숫자의 꼭짓점부터 방문하도록 설계하라. 


입력(표준 입력)

첫 번째 줄에, 꼭짓점의 개수 N을 입력한다.

두번 째 줄부터, 그래프 G의 인접 리스트가 x,y 두 정수로 표현이 된다. 이것은 x로 출발해서 y로 도착하는 간선이 있음을 의미한다.


출력(표준 출력)

G의 각각의 간선 타입들과 함께 간선들을 출력해내라. 간선들의 타입 표현은 다음과 같다.

1: 트리 엣지

2: 백 엣지

3: 포워드 엣지

4: 크로스 엣지

각각의 줄에는 세 개의 정수 x,y,z가 출력되어야 한다. 이것들은 x에서 출발하여 y로 도착하는 간선이 있으며 그 간선이 z의 타입을 가진다는 것을 의미한다.



입력 

출력 

6

1 2

1 4

2 5

3 5

3 6

4 2

5 4

6 6

1 2 1

1 4 3

2 5 1

3 5 4

3 6 1

4 2 2

5 4 1

6 6 2 


| 소스 코드

#include <stdio.h>
#include <stdlib.h>

#define WHITE			0
#define GRAY			1
#define BLACK			2

#define TREE_EDGE		1
#define BACK_EDGE		2
#define FORWARD_EDGE	3
#define CROSS_EDGE	4

unsigned int time = 0;
unsigned int numV = 0;

typedef struct _vertex
{
	int color;
	int d;
	int f;
	int pred;
} Vertex;

// 구조체 멤버 중 자기 구조체를 타입으로 하는 포인터 next을 사용하는 이유는,
// 리스트 형태로 연결하고자 하기 위함.
typedef struct _edge
{
	int toV;
	int type;
	struct _edge * next;
} Edge;

// 각 Vertxt 마다 Adjacency list 구조체가 하나씩 배당되는 것으로 생각
Vertex SetV[1000];
Edge * adjlist[1000]={NULL,};

void addEdge(int fromV, int toV)
{
	Edge * newEdge;
	Edge * temp;
	temp = adjlist[fromV];

	newEdge = (Edge*)malloc(sizeof(Edge));
	newEdge->toV = toV;
	newEdge->next = NULL;

	if(adjlist[fromV] == NULL)
	{
		adjlist[fromV] = newEdge;
	}
	else
	{
		while(temp->next != NULL)
		{
			temp = temp->next;
		}
		temp->next = newEdge;
	}
}

void DFS_Visit(int fromV)
{
	Edge * edge_fromV;
	edge_fromV = adjlist[fromV];

	time ++;
	/* DISCOVERY */
	SetV[fromV].color = GRAY;
	SetV[fromV].d = time;

	while(edge_fromV != NULL)
	{
		int toV;
		toV = edge_fromV->toV;

		switch(SetV[toV].color)
		{
		case WHITE:
			edge_fromV->type = TREE_EDGE;		// 1
			SetV[toV].pred = fromV;
			DFS_Visit(toV);
			break;

		case GRAY:
			edge_fromV->type = BACK_EDGE;		// 2
			break;

		case BLACK:
			if(SetV[fromV].d < SetV[toV].d)
				edge_fromV->type = FORWARD_EDGE;// 3
			else if(SetV[fromV].d > SetV[toV].d)
				edge_fromV->type = CROSS_EDGE;	// 4
			break;

		default:
			break;
		}

		edge_fromV = edge_fromV->next;
	}

	/* FINISHED */
	SetV[fromV].color = BLACK;
	time ++;
	SetV[fromV].f = time;
}

void DFS(void)
{
	int i;
	for(i=1; i<=numV; i++)
	{
		SetV[i].color = WHITE;
		SetV[i].pred = NULL;
	}
	time = 0;
	for(i=1; i<=numV; i++)
	{
		if(SetV[i].color == WHITE)
		{
			DFS_Visit(i);
		}
	}
}

int main(void)
{
	int i=0;
	int fromV, toV;
	Edge * temp;

	scanf("%d", &numV);
	while(scanf("%d %d", &fromV, &toV) != EOF)
	{
		addEdge(fromV, toV);
	}

	DFS();

	for(i=1; i<=numV; i++)
	{
		temp = adjlist[i];
		while(temp != NULL)
		{
			printf("%d %d %d \n", i, temp->toV, temp->type);
			temp = temp->next;
		}
	}
	return 0;
}


C++ Pointers and Memory – Free Coding Tutorials


포인터의 사용법의 구문과 의미는 C 표준 문서(http://bit.ly/173cDxJ)에 매우 자세히 설명되어 있다. 그러나 표준 문서가 포인터의 동작을 명확히 정의하지 못하는 경우가 있다. 이러 떄 표준 문서는 포인터의 동작을 다음과 같이 정의한다.


- 구현 방법에 따라 정의된 행동(Implementation-defined behavior)

 동작에 대한 문서화된 구현을 제공한다. 구현 방법에 따라 정의된 행동의 예로, 정수에 대한 오른쪽 시피트 연산에서 상위 비트의 확장 방법이 있다.


- 명시되지 않은 행동(Unspecified behavior)

 동작에 대한 구현을 제공하지만 문서화하지 않는다. malloc함수에 인자로 0을 주고, 실핼할 때 메모리가 얼마나 할당되는가 하는 것이 명시되지 않은 행동의 예가 될 수 있다. 명시되지 않은 행동의 목록을 CERT Secure Coding Appendix DD에서 볼 수 있다.


- 정의되지 않은 행동(Undefined behavior)

 포인터의 동작에 대해 어떠한 것도 강요하지 않으므로 어떠한 동작도 발생할 수 있다. 이 경우의 예로, free 함수에 의해 해제된 포인터의 값이 있다. (C 표준은 해제된 포인터에 어떤 값이 들어 있는지 정의하지 않는다.) 정의되지 않은 행동의 목록은 CERT Secure Coding Appendix CC(http://bit.ly/16msOVK)에서 찾을 수 있다.

+ Recent posts