본문 바로가기
GD's IT Lectures : 기초부터 시리즈/C, C++ 기초부터 ~

[C/C++ 프로그래밍 : 중급] 15. GUI 프로그래밍

by GDNGY 2023. 6. 16.

Chapter 15. GUI 프로그래밍

GUI(Graphical User Interface) 프로그래밍은 사용자 친화적인 인터페이스를 만드는 데 필수적입니다. 이 장에서는 C/C++을 이용한 GUI 프로그래밍의 중요성을 배우고, 윈도우와 컨트롤의 생성 및 관리를 알아봅니다. WinAPI, MFC, 그리고 Qt라는 세 가지 주요 GUI 라이브러리를 사용해 GUI 프로그래밍을 실습하고, 다양한 GUI 컨트롤과 2D/3D 그래픽스 프로그래밍을 통해 고급 GUI 프로그래밍에 대해 배워보겠습니다. 

 

반응형

 


[Chapter 15. GUI 프로그래밍]

 

15.1. GUI 프로그래밍 이해하기

15.1.1. GUI 프로그래밍의 필요성

15.1.2. GUI 프로그래밍의 기본 원리

15.1.3. C/C++에서의 GUI 라이브러리 선택

 

15.2. 윈도우와 컨트롤 만들기

15.2.1. 윈도우 생성 및 관리

15.2.2. 컨트롤 생성 및 관리

15.2.3. 메시지 처리와 이벤트 핸들링

 

15.3. WinAPI를 이용한 GUI 프로그래밍

15.3.1. WinAPI의 이해

15.3.2. WinAPI를 이용한 윈도우 생성

15.3.3. WinAPI를 이용한 이벤트 처리

 

15.4. MFC를 이용한 GUI 프로그래밍

15.4.1. MFC의 이해

15.4.2. MFC를 이용한 윈도우 생성

15.4.3. MFC를 이용한 이벤트 처리

 

15.5. Qt를 이용한 GUI 프로그래밍

15.5.1. Qt의 이해

15.5.2. Qt를 이용한 윈도우 생성

15.5.3. Qt를 이용한 이벤트 처리

 

15.6. 고급 GUI 프로그래밍

15.6.1. 다양한 GUI 컨트롤 이용하기

15.6.2. 2D 그래픽스 프로그래밍

15.6.3. 3D 그래픽스 프로그래밍


15.1. GUI 프로그래밍 이해하기

GUI 프로그래밍은 사용자가 프로그램과 쉽게 상호작용할 수 있게 해주는 중요한 개념입니다. 이 섹션에서는 GUI 프로그래밍이 왜 필요한지, 그 기본 원리는 무엇인지, 그리고 C/C++에서 사용할 수 있는 GUI 라이브러리는 어떻게 선택하는지에 대해 알아봅니다. 이해를 돕기 위해 간단하고 직관적인 예제 코드를 준비했으니, GUI 프로그래밍의 핵심을 이해하는 데 도움이 되길 바랍니다. 이 과정을 통해 사용자 친화적인 프로그램을 만드는 능력을 향상할 수 있을 것입니다. 

15.1.1. GUI 프로그래밍의 필요성

GUI(Graphical User Interface) 프로그래밍은 컴퓨터 프로그램과 사용자 간의 인터랙션을 효과적으로 관리하며, 프로그램이 사용자 친화적이고 직관적이게 만드는데 매우 중요한 역할을 합니다. 그렇다면 왜 GUI 프로그래밍이 중요한 것일까요? 이것에 대해 알아봅시다.

 

컴퓨터의 초기 모델에서는 대부분의 상호작용이 텍스트 기반의 CLI(Command Line Interface)를 통해 이루어졌습니다. 이는 사용자가 명령어를 입력하여 프로그램과 상호작용하게 되는 방식인데, 이 방식은 프로그래밍 기술에 익숙한 사람들에게는 강력한 도구일 수 있지만, 일반 사용자들에게는 진입장벽이 될 수 있습니다.

 

이에 반해 GUI는 비주얼 요소를 활용하여 사용자와 상호작용하게 만듭니다. 예를 들어, 버튼을 클릭하거나, 드롭다운 메뉴에서 옵션을 선택하거나, 슬라이더를 움직이는 등의 동작을 통해 사용자가 프로그램을 쉽게 제어할 수 있습니다. 이런 방식은 사용자에게 더욱 직관적이며 사용하기 쉽습니다.

 

즉, GUI 프로그래밍은 사용자 경험(UX)를 향상하는데 중요한 역할을 하는 것입니다. 사용자는 복잡한 명령어를 외우거나 입력할 필요 없이, 비주얼 요소를 통해 쉽게 프로그램을 제어하고, 원하는 작업을 수행할 수 있게 됩니다. 이는 결국 더 많은 사용자가 프로그램을 사용하게 만들며, 그로 인해 프로그램의 성공 가능성을 높이는 요인이 됩니다.

 

GUI 프로그래밍의 필요성을 간단한 예제로 설명해 보겠습니다. 아래는 C++로 작성된 간단한 CLI 프로그램의 예제입니다.  

[예제]

#include <iostream>
#include <string>  

int main() {
    std::string name;
    std::cout << "이름을 입력하세요: ";
    std::cin >> name;
    std::cout << "안녕하세요, " << name << "님!" << std::endl;
    return 0;
}

 

이 프로그램은 사용자에게 이름을 입력하라는 메시지를 출력하고, 사용자가 이름을 입력하면 해당 이름에 대한 인사말을 출력합니다. 이 코드는 간단하고, 작동하는데 문제가 없지만, CLI 방식이므로 사용자에게 일정한 프로그래밍 지식을 요구합니다. 사용자가 콘솔 화면을 열고, 프로그램을 실행하고, 텍스트 입력에 익숙해야 합니다.

 

이와 같은 프로그램을 GUI로 변환하면 사용자가 더 쉽게 상호작용할 수 있습니다. 예를 들어, 이름을 입력하는 필드와 "확인" 버튼을 포함하는 간단한 창을 제공하고, 사용자가 버튼을 클릭하면 이름에 대한 인사말을 표시하는 방식으로 변경할 수 있습니다.

 

다음은 C++로 작성된 간단한 GUI 프로그래밍 예제입니다. 이 예제는 Qt 라이브러리를 사용하며, 사용자로부터 이름을 입력받아 화면에 출력하는 간단한 프로그램입니다.

 

[예제]

#include <QApplication>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>  

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);  

    QLineEdit* nameField = new QLineEdit;
    QLabel* greeting = new QLabel;
    QPushButton* button = new QPushButton("확인");  

    QObject::connect(button, &QPushButton::clicked, [=]() {
        QString name = nameField->text();
        greeting->setText("안녕하세요, " + name + "님!");
    });  

    QVBoxLayout* layout = new QVBoxLayout;
    layout->addWidget(nameField);
    layout->addWidget(button);
    layout->addWidget(greeting);  

    QWidget window;
    window.setLayout(layout);
    window.show();  

    return app.exec();
}

 

이 프로그램은 사용자에게 텍스트 입력 필드와 "확인" 버튼을 제공하고, 버튼을 클릭하면 입력된 이름에 대한 인사말을 출력합니다. 이런 식으로 GUI 프로그래밍은 사용자 친화적인 인터페이스를 제공하여 프로그램의 사용성을 높이는 데 기여합니다. 

 

GUI 프로그래밍은 프로그램을 사용하는 사람들에게 많은 이점을 제공합니다. 복잡한 명령어 입력이나 코드 이해 없이도 사용자가 원하는 작업을 수행할 수 있도록 도와주며, 그 결과 프로그램의 접근성이 높아집니다. 이러한 이유로, 사용자 경험을 중요시하는 현대의 소프트웨어 개발 환경에서는 GUI 프로그래밍이 필수적인 스킬이 되었습니다. 

 

15.1.2. GUI 프로그래밍의 기본 원리  

GUI 프로그래밍의 기본 원리를 이해하려면 먼저 이벤트 드리븐 프로그래밍에 대해 이해해야 합니다. 이벤트 드리븐 프로그래밍이란 사용자의 입력(키보드 입력, 마우스 클릭 등)이나 시스템 이벤트(타이머 만료, 네트워크 통신 등)와 같은 이벤트를 기반으로 프로그램이 동작하는 프로그래밍 방식을 말합니다. 

 

기본적으로 GUI 프로그램은 다음과 같은 흐름으로 동작합니다. 

  • 프로그램이 시작되면, 프로그램은 사용자로부터의 입력을 기다립니다. 이를 이벤트 루프라고 부릅니다.
  • 사용자가 어떤 동작을 하면(예: 버튼을 클릭하면), 해당 동작에 연관된 이벤트가 발생합니다.
  • 이벤트 루프는 이 이벤트를 감지하고, 이벤트를 처리하기 위해 등록된 함수(이벤트 핸들러)를 호출합니다.
  • 이벤트 핸들러는 이벤트를 처리한 후, 제어를 다시 이벤트 루프에게 반환합니다.
  • 이벤트 루프는 다시 사용자로부터의 입력을 기다리게 됩니다.
    즉, GUI 프로그램은 사용자의 입력에 반응하는 형태로 동작하며, 이를 통해 사용자와 상호작용하는 인터페이스를 제공합니다.  

다음은 C++과 Qt 라이브러리를 사용하여 이벤트 드리븐 프로그래밍의 원리를 보여주는 간단한 예제입니다. 

 

[예제]

#include <QApplication>
#include <QPushButton>  

int main(int argc, char **argv) {
    QApplication app(argc, argv);  

    QPushButton button("Quit");
    QObject::connect(&button, &QPushButton::clicked, &QApplication::quit);  

    button.show();
    return app.exec();
}

 

위의 코드는 "Quit"라는 텍스트를 가진 버튼을 생성하고, 이 버튼이 클릭되면 프로그램이 종료되도록 연결하는 예제입니다. 'QObject::connect' 함수를 통해 'clicked' 이벤트가 발생했을 때 호출될 함수('QApplication::quit')를 지정하고 있습니다.

 

이렇게 GUI 프로그래밍은 이벤트와 이벤트 핸들러, 이벤트 루프의 개념을 중심으로 동작합니다. 이를 이해하는 것은 GUI 프로그래밍의 중요한 첫걸음입니다.

 

다양한 GUI 라이브러리가 존재하지만, 가장 널리 사용되는 것들 중 몇 가지는 WinAPI, MFC, Qt, GTK+, wxWidgets 등이 있습니다. 각각의 라이브러리는 다양한 GUI 컴포넌트를 제공하며, 사용 방법이나 제공하는 기능도 조금씩 다릅니다. 따라서 GUI 프로그램을 개발할 때는 어떤 라이브러리를 선택할지, 그리고 그 라이브러리를 어떻게 사용할지에 대해 고려해야 합니다.

 

C++로 작성된 간단한 Qt GUI 프로그램 예제를 통해 이벤트 핸들링을 살펴봅시다.

 

[예제]

#include <QApplication>
#include <QMainWindow>
#include <QPushButton>  

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);  

    QMainWindow mainWindow;
    QPushButton *button = new QPushButton("Hello, World!", &mainWindow);
    QObject::connect(button, &QPushButton::clicked, &app, &QApplication::quit);  

    mainWindow.setCentralWidget(button);
    mainWindow.show();  

    return app.exec();
}

 

위 예제는 "Hello, World!" 버튼을 가진 QMainWindow를 생성합니다. QPushButton의 clicked 이벤트가 발생하면, 즉 사용자가 버튼을 클릭하면 QApplication의 quit 메서드가 호출되어 응용 프로그램이 종료됩니다.

 

이와 같이, GUI 프로그래밍의 기본 원리를 이해하고, GUI 라이브러리를 올바르게 사용하는 방법을 숙지하는 것은 중요합니다. GUI는 사용자에게 직관적인 인터페이스를 제공하며, 사용자의 편의성을 향상하는 중요한 요소입니다. 따라서 GUI 프로그래밍은 매우 중요하며, 이를 위해 이벤트 드리븐 프로그래밍을 이해하고 올바르게 사용하는 것은 필수적입니다.

 

코드의 이해를 돕기 위해 간단히 설명드리겠습니다.

 

[예제]

QApplication app(argc, argv);


위의 코드는 QApplication 객체를 생성하는 것으로, 모든 Qt GUI 애플리케이션은 QApplication 객체를 필요로 합니다.  

 

[예제]

QMainWindow mainWindow;
QPushButton *button = new QPushButton("Hello, World!", &mainWindow);


다음으로, "Hello, World!" 라는 텍스트를 가진 QPushButton 객체를 생성하고 이를 mainWindow에 붙입니다.  

 

[예제]

QObject::connect(button, &QPushButton::clicked, &app, &QApplication::quit);


이 부분은 Qt의 시그널-슬롯 메커니즘에 해당합니다. 여기서는 button의 clicked 시그널(이벤트)이 발생하면, app의 quit 슬롯(메서드)이 호출되도록 연결하였습니다.

 

[예제]

mainWindow.setCentralWidget(button);
mainWindow.show();


마지막으로, mainWindow의 중앙 위젯으로 button을 설정하고, mainWindow를 보여줍니다. 이는 사용자에게 윈도우를 보여주는 역할을 합니다.

 

이렇게 GUI 프로그래밍의 원리를 이해하고, 실제로 코드를 작성해 보는 과정을 통해 GUI 프로그래밍의 기본적인 흐름과 구조를 이해하는 것이 중요합니다. GUI 프로그래밍은 사용자와의 상호작용을 위한 필수적인 요소이며, 다양한 GUI 라이브러리를 활용하면 더욱 효율적이고 사용자 친화적인 프로그램을 개발할 수 있습니다.

 

이제 Qt가 어떻게 시그널-슬롯 메커니즘을 활용하는지에 대해 좀 더 깊게 들어가 볼까요?

 

Qt의 핵심 기능 중 하나인 시그널-슬롯 메커니즘은 객체 간의 통신을 위한 방법입니다. 이는 객체지향 프로그래밍에서 이벤트가 발생할 때마다 특정 함수(슬롯)를 호출하도록 예약하는 이벤트 처리 방식입니다.

 

예를 들어, GUI 애플리케이션에서 버튼을 클릭하는 이벤트를 처리하려면 '버튼이 클릭되었을 때 무엇을 할 것인가?'에 대한 명령을 작성해야 합니다. 이때 사용되는 것이 바로 시그널-슬롯 메커니즘입니다.

 

아래는 Qt의 시그널-슬롯 메커니즘을 사용한 예제 코드입니다.

 

[예제]

#include <QApplication>
#include <QPushButton>
#include <QMessageBox>  

int main(int argc, char **argv)
{
    QApplication app(argc, argv);  

    QPushButton button("Hello, World!");
    QObject::connect(&button, &QPushButton::clicked, [&](){
        QMessageBox::information(nullptr, "Clicked!", "You clicked the button!");
    });
    button.show();  

    return app.exec();
}

 

이 코드에서, QPushButton 객체 button의 clicked 시그널이 발생하면, 람다 함수가 호출되도록 설정하였습니다. 이 람다 함수는 QMessageBox::information을 호출하여 메시지 박스를 띄우는 작업을 합니다. 즉, 사용자가 버튼을 클릭하면 "Clicked! You clicked the button!"라는 메시지가 나타나게 됩니다.

 

이처럼 시그널-슬롯 메커니즘을 이해하고 활용하면, 사용자의 이벤트에 따라 동적으로 코드를 실행할 수 있습니다. 이는 사용자와의 상호작용을 위한 GUI 프로그래밍의 중요한 원리 중 하나입니다.

 

이벤트 드리븐 프로그래밍은 사용자의 액션이나 시스템에서 생성된 이벤트에 따라 프로그램이 동작하는 방식을 의미합니다. 즉, 프로그램의 흐름이 사용자의 행동(예를 들어 버튼 클릭, 키보드 입력 등)에 의해 결정됩니다. 이러한 이유로 GUI 프로그래밍은 대표적인 이벤트 드리븐 프로그래밍 모델을 따르며, 그래서 이벤트를 발생시키는 '이벤트 소스'와 이벤트를 처리하는 '이벤트 핸들러'의 개념을 꼭 이해해야 합니다.

 

아래는 간단한 이벤트 드리븐 프로그래밍을 구현한 C++ 예제입니다.

 

[예제]

#include <iostream>
#include <functional>  

class Button {
public:
    void onClick(std::function<void()> f) { onClick_ = f; }
    void click() { if(onClick_) onClick_(); }
private:
    std::function<void()> onClick_;
};  

int main() {
    Button button;  

    button.onClick([]() { std::cout << "Button clicked!" << std::endl; });
    button.click();  // prints "Button clicked!"  

    return 0;
}

 

이 예제에서, Button 클래스는 click 이벤트를 발생시키고, onClick 이벤트 핸들러를 등록하는 메서드를 제공합니다. main 함수에서는 람다 함수를 이용해 onClick 이벤트 핸들러를 등록하고, click 메서드를 호출하여 이벤트를 발생시킵니다. 이에 따라 "Button clicked!"라는 메시지가 출력됩니다.

 

이렇게 이벤트 드리븐 프로그래밍을 이해하고 활용하면, 동적인 GUI 애플리케이션을 개발할 수 있습니다. 이제 C++로 GUI를 개발하려면 이벤트 소스와 이벤트 핸들러를 어떻게 설계하고 연결하는지, 그리고 이벤트 루프가 어떻게 동작하는지에 대한 이해가 필요합니다.

 

이벤트 소스는 사용자 입력(예: 마우스 클릭, 키보드 키 누름), 시스템 메시지(예: 타이머 알림, 네트워크 메시지) 등 다양한 이벤트를 생성할 수 있는 객체입니다. 이벤트 핸들러는 이러한 이벤트에 반응하여 특정 동작을 수행하는 코드입니다.

 

이벤트 소스와 이벤트 핸들러는 '이벤트 연결' 또는 '이벤트 바인딩'을 통해 연결됩니다. 이렇게 연결된 이벤트 핸들러는 이벤트 소스로부터 이벤트가 발생하면 자동으로 호출되어 동작을 수행합니다. 

 

C++에서는 이벤트 바인딩을 위해 일반적으로 콜백 함수나 델리게이트를 사용합니다. 콜백 함수는 함수를 인자로 받아 이벤트 발생 시 해당 함수를 호출하는 방식입니다. 델리게이트는 콜백 함수를 더 발전시킨 개념으로, 하나 이상의 함수를 참조할 수 있는 객체입니다.

 

다음은 C++에서 람다 함수를 이용해 콜백 함수를 구현한 예제입니다.

 

[예제]

#include <iostream>
#include <functional>  

class EventSource {
public:
    void setCallback(std::function<void()> cb) { callback_ = cb; }
    void triggerEvent() { if(callback_) callback_(); }
private:
    std::function<void()> callback_;
};  

int main() {
    EventSource es;  

    es.setCallback([]() { std::cout << "Event triggered!" << std::endl; });
    es.triggerEvent();  // prints "Event triggered!"  

    return 0;
}

 

이 예제에서, EventSource 클래스는 이벤트를 트리거할 수 있는 triggerEvent 메서드와 콜백 함수를 등록할 수 있는 setCallback 메서드를 제공합니다. main 함수에서는 람다 함수를 이용해 콜백 함수를 등록하고, triggerEvent 메서드를 호출하여 이벤트를 트리거합니다. 이에 따라 "Event triggered!"라는 메시지가 출력됩니다.

 

이런 식으로 이벤트 소스와 이벤트 핸들러를 연결하면, 동적인 이벤트 처리가 가능합니다. 이제 이벤트 루프에 대해 알아보겠습니다. 

 

이벤트 루프는 애플리케이션의 핵심 구성 요소 중 하나입니다. 이벤트 루프는 사용자 입력이나 시스템 메시지 같은 이벤트를 지속적으로 감지하고, 해당 이벤트를 적절한 이벤트 핸들러로 전달하는 역할을 수행합니다. 이벤트 루프는 특정 이벤트가 발생할 때까지 애플리케이션을 블로킹 상태로 유지하고, 이벤트가 발생하면 연결된 이벤트 핸들러를 호출합니다. 이런 메커니즘이 있기 때문에 사용자는 마우스 클릭이나 키보드 입력 같은 이벤트를 발생시킬 때까지 애플리케이션은 아무런 반응을 보이지 않습니다.

 

아래는 C++에서 이벤트 루프를 구현하는 간단한 예제입니다.  

 

[예제]

#include <iostream>
#include <functional>
#include <queue>  

class EventLoop {
public:
    void addEvent(std::function<void()> event) { events_.push(event); }
    void run() {
        while (!events_.empty()) {
            auto event = events_.front();
            event();
            events_.pop();
        }
    }
private:
    std::queue<std::function<void()>> events_;
};  

int main() {
    EventLoop el;  

    el.addEvent([]() { std::cout << "Event 1" << std::endl; });
    el.addEvent([]() { std::cout << "Event 2" << std::endl; });
    el.addEvent([]() { std::cout << "Event 3" << std::endl; });  

    el.run();  // prints "Event 1", "Event 2", "Event 3"  

    return 0;
}

 

이 예제에서, EventLoop 클래스는 이벤트를 큐에 추가하는 addEvent 메서드와 큐에 있는 모든 이벤트를 처리하는 run 메서드를 제공합니다. main 함수에서는 람다 함수를 이용해 여러 이벤트를 추가하고, run 메서드를 호출하여 이벤트 루프를 시작합니다.

 

이제 우리는 GUI 프로그래밍의 핵심 개념을 이해했습니다. 다음 섹션에서는 C/C++에서 사용할 수 있는 다양한 GUI 라이브러리를 살펴볼 것입니다. 이 라이브러리들은 위에서 설명한 이벤트 소스, 이벤트 핸들러, 이벤트 루프와 같은 기본 메커니즘을 제공하며, 윈도우 생성, 컨트롤 생성, 이벤트 처리 등 GUI 프로그래밍에 필요한 다양한 기능을 제공합니다.

 

나아가, 이벤트 핸들러는 이벤트의 유형과 함께 이벤트가 발생한 위젯이나 컨트롤의 정보를 포함하는 이벤트 객체를 매개변수로 받습니다. 이벤트 핸들러는 이벤트 객체를 분석하여 어떤 작업을 수행할지 결정합니다. 예를 들어, 버튼 클릭 이벤트 핸들러는 이벤트 객체에서 클릭된 버튼의 ID를 가져와서 해당 ID에 연결된 작업을 수행할 수 있습니다.

 

다음은 C++로 작성된 이벤트 핸들러의 간단한 예시입니다. 

 

[예제]

void MyButtonClickedEventHandler(const Event& event) {
    // Event 객체에서 클릭된 버튼의 ID를 가져옵니다.
    int buttonId = event.GetSourceId();  

    // ID에 따라 다른 작업을 수행합니다.
    if (buttonId == MY_OK_BUTTON_ID) {
        std::cout << "OK 버튼이 클릭되었습니다." << std::endl;
    }
    else if (buttonId == MY_CANCEL_BUTTON_ID) {
        std::cout << "Cancel 버튼이 클릭되었습니다." << std::endl;
    }
}

 

이제 GUI 프로그래밍의 기본 원리에 대해 알아보았습니다. GUI 프로그래밍은 이벤트 소스, 이벤트 핸들러, 이벤트 루프라는 세 가지 핵심 요소를 기반으로 합니다. 이벤트 소스는 이벤트를 발생시키는 원인이 되는 요소(예: 사용자 입력, 시스템 메시지)이고, 이벤트 핸들러는 발생한 이벤트를 처리하는 코드, 이벤트 루프는 이벤트를 지속적으로 감지하고 해당 이벤트를 이벤트 핸들러로 전달하는 구조입니다.

 

15.1.3. C/C++에서의 GUI 라이브러리 선택  

프로그래밍 언어에 관계없이 GUI 프로그래밍에는 여러 가지 선택사항이 있습니다. C/C++에서도 여러 가지 GUI 라이브러리를 사용할 수 있습니다. 주요한 라이브러리로는 WinAPI, MFC, Qt, wxWidgets, GTK+ 등이 있습니다. 이들 각각은 자신의 특성과 장단점을 가지고 있으므로, 프로젝트의 요구 사항에 맞는 라이브러리를 선택하는 것이 중요합니다.

  • WinAPI: Windows 운영 체제를 위한 GUI 프로그래밍의 기본적인 API입니다. WinAPI를 사용하면 Windows의 모든 기능을 최대한 활용할 수 있습니다. 그러나 WinAPI는 복잡하고 낮은 수준의 API이므로, 사용하기 어렵고 코드가 길어지는 단점이 있습니다.
  • MFC (Microsoft Foundation Classes): WinAPI 위에 빌드된 클래스 라이브러리로, WinAPI를 좀 더 쉽게 사용할 수 있게 해 줍니다. MFC는 WinAPI보다 사용하기 쉽지만, 여전히 복잡하고 오래된 코드 스타일이라는 단점이 있습니다. 
  • Qt: 크로스 플랫폼 GUI 라이브러리로, Windows, macOS, Linux 등 다양한 운영 체제에서 동일한 코드로 GUI 프로그램을 작성할 수 있습니다. Qt는 사용하기 쉽고, 현대적인 C++ 코드 스타일을 사용합니다. 또한 풍부한 기능과 좋은 문서를 제공하며, 활발한 커뮤니티를 가지고 있습니다. 
  • wxWidgets와 GTK+: 이 두 라이브러리도 크로스 플랫폼 GUI 라이브러리로, Qt와 비슷한 장점을 가지고 있습니다. wxWidgets는 C++에 초점을 맞추고 있으며, GTK+는 C 언어에 초점을 맞추고 있습니다.

이러한 라이브러리들 중에서 어떤 것을 선택해야 할까요? 이에 대한 결정은 여러 가지 요인에 따라 달라집니다. 먼저, 개발하려는 애플리케이션의 특성과 필요한 기능을 고려해야 합니다. 예를 들어, 특정 운영 체제의 고유한 기능을 최대한 활용해야 한다면 WinAPI나 MFC가 적합할 수 있습니다. 크로스 플랫폼 어플리케이션을 개발하려면 Qt, wxWidgets, GTK+ 등이 더 적합합니다. 이와 같이 개발하려는 어플리케이션의 특성과 필요한 기능에 따라 GUI 라이브러리를 선택해야 합니다.  

또한, 여러분이 이미 어떤 라이브러리에 익숙하다면 그 라이브러리를 선택하는 것이 생산성을 높일 수 있습니다. 새로운 라이브러리를 배우는 것은 시간과 노력이 들기 때문입니다. 하지만, 새로운 라이브러리를 배우는 것이 더 좋은 결과를 가져올 수 있다면 그 노력을 감수해야 할 수도 있습니다. 

 

마지막으로, 프로젝트의 기간, 예산, 팀원의 기술 수준 등도 고려해야 합니다. 어떤 라이브러리를 선택하든, 그 라이브러리를 충분히 이해하고 효율적으로 사용할 수 있어야 합니다. 

 

이처럼 여러 가지 요인을 고려하여 GUI 라이브러리를 선택하는 것은 중요한 결정이므로, 신중하게 선택해야 합니다. 다음의 예제코드는 각 라이브러리에서 "Hello, World!"를 출력하는 간단한 창을 만드는 코드입니다. 각 라이브러리의 사용 방법과 스타일을 약간이나마 이해할 수 있게 해 줄 것입니다. 이해가 어려운 부분은 걱정하지 마세요. 앞으로의 섹션에서 각 라이브러리에 대해 더 자세히 알아볼 것입니다. 

 

먼저, WinAPI를 사용하는 예제입니다. 

 

[예제]

#include <windows.h> 
LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}  

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow)
{
    const char CLASS_NAME[] = "Sample Window Class";  

    WNDCLASS wc = { };  

    wc.lpfnWndProc = WindowProcedure;
    wc.hInstance = hInst;
    wc.lpszClassName = CLASS_NAME;  

    if (!RegisterClass(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }  

    HWND hwnd = CreateWindow(CLASS_NAME, "Hello, World!", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 100, NULL, NULL, hInst, NULL);  

    if (hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }  

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);  

    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }  

    return 0;
}

 

위 코드는 WinAPI를 사용하여 "Hello, World!"라는 메시지를 출력하는 간단한 창을 만드는 코드입니다. 코드가 복잡하게 느껴질 수도 있지만, 이는 WinAPI의 복잡성을 잘 보여줍니다. 

 

다음은 MFC를 사용하는 예제입니다. MFC는 클래스 기반으로 작성되므로, 코드가 WinAPI보다는 조금 더 간결해집니다. 

 

[예제]

#include <afxwin.h>  

class CMainFrame : public CFrameWnd
{
public:
    CMainFrame()
    {
        Create(NULL, _T("Hello, World!"));
    }
};  

class CApp : public CWinApp
{
public:
    BOOL InitInstance()
    {
        m_pMainWnd = new CMainFrame();
        m_pMainWnd->ShowWindow(m_nCmdShow);
        m_pMainWnd->UpdateWindow();
        return TRUE;
    }
};  

CApp theApp;

 

이 코드는 MFC를 사용하여 "Hello, World!"라는 메시지를 출력하는 간단한 창을 만듭니다. 코드가 WinAPI보다는 간결하지만, 여전히 복잡성이 존재합니다. 

 

마지막으로, Qt를 사용하는 예제입니다. Qt는 현대적인 C++ 코드 스타일을 사용하며, 사용하기 쉽습니다.  

 

[예제]

#include <QApplication>
#include <QMessageBox>  

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMessageBox::information(nullptr, "Hello, World!", "Hello, World!");
    return a.exec();
}

 

위 코드는 Qt를 사용하여 "Hello, World!"라는 메시지를 출력하는 간단한 창을 만드는 코드입니다. 코드는 매우 간결하며, 가독성이 높습니다. 

 

이 세 가지 예제를 통해 각 라이브러리의 스타일과 사용 방법을 살펴보았습니다. 여러분이 어떤 라이브러리를 선택할지는 여러분의 상황과 필요에 따라 달라질 것입니다. 다음 섹션에서는 각 라이브러리를 더 깊게 살펴보고, 윈도우와 컨트롤을 만드는 방법에 대해 알아보겠습니다. 

 

만약 기존의 코드베이스가 C로 작성되어 있다면, WinAPI가 좋은 선택일 수 있습니다. WinAPI는 C와 잘 호환되며, 또한 Windows 운영체제에서 가장 기본적인 API입니다. 이는 Windows 운영체제의 모든 기능을 완벽하게 제어할 수 있다는 것을 의미합니다. 그러나 WinAPI의 단점은 복잡성입니다. 간단한 GUI를 만드는 것조차도 상당한 노력을 필요로 합니다. 

 

반면에 MFC(Microsoft Foundation Class)는 C++를 기반으로 한 프레임워크입니다. MFC는 객체 지향 프로그래밍의 이점을 제공하고, WinAPI보다 높은 수준의 추상화를 제공합니다. 이는 GUI 프로그래밍을 더 쉽게 만듭니다. 그러나 MFC는 오래된 라이브러리이고, 현대 C++ 프로그래밍의 일부 기능을 지원하지 않을 수 있습니다. 

 

Qt는 현대적인 C++를 기반으로 하는 크로스 플랫폼 라이브러리입니다. Qt는 사용하기 쉽고, 강력하며, 다양한 기능을 제공합니다. 또한, Qt는 크로스 플랫폼을 지원하므로, 단일 코드베이스로 여러 운영체제에서 동작하는 애플리케이션을 개발할 수 있습니다. Qt의 단점은 상업적인 사용을 위해서는 라이선스 비용이 발생한다는 것입니다. 그러나 오픈 소스 프로젝트의 경우 무료로 사용할 수 있습니다. 

 

이 외에도 FLTK, wxWidgets, GTK+ 등 다양한 GUI 라이브러리가 존재합니다. 이들 각각은 자신만의 장단점을 가지고 있으며, 프로젝트의 요구 사항과 개발자의 선호도에 따라 적절한 선택이 달라질 수 있습니다. 이제, 이러한 GUI 라이브러리들을 사용하여 실제로 윈도우와 컨트롤을 만드는 방법에 대해 살펴보겠습니다. 

 

다음은 간단한 WinAPI 예제입니다. 이 코드는 새 창을 생성하고, 메시지 루프를 실행합니다.  

 

[예제]

#include <windows.h>  

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CLOSE:
        PostQuitMessage(0);
        return 0;
    }  

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}  

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
    const wchar_t CLASS_NAME[]  = L"Sample Window Class";  

    WNDCLASS wc = { };  

    wc.lpfnWndProc   = WindowProc;
    wc.hInstance     = hInstance;
    wc.lpszClassName = CLASS_NAME;  

    RegisterClass(&wc);  

    HWND hwnd = CreateWindowEx(
        0,                              
        CLASS_NAME,                     
        L"Sample Window",                    
        WS_OVERLAPPEDWINDOW,              

        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,  

        NULL,       
        NULL,       
        hInstance,  
        NULL        
    );  

    if (hwnd == NULL)
    {
        return 0;
    }  

    ShowWindow(hwnd, nCmdShow);  

    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }  

    return 0;
}

 

이것이 WinAPI로 GUI 프로그래밍을 하는 기본적인 방법입니다. 이 코드에서는 윈도우를 생성하고, 메시지 루프를 돌면서 사용자의 입력을 처리합니다. 이 코드는 복잡해 보일 수 있지만, 이것이 WinAPI의 기본적인 구조입니다. WinAPI는 매우 낮은 수준의 라이브러리이므로, 간단한 작업조차도 많은 코드를 필요로 합니다.

 

다음으로 MFC를 사용한 예제를 살펴봅시다. MFC는 객체 지향 프로그래밍을 이용하여 WinAPI를 더 쉽게 사용할 수 있도록 만들어졌습니다. MFC로 작성한 코드는 WinAPI로 작성한 코드보다 훨씬 간결하고 이해하기 쉽습니다. 그러나 MFC는 C++로만 작성할 수 있으므로, C로 작성된 코드베이스와는 호환되지 않습니다.

 

[예제]

#include <afxwin.h>  

class CMyFrame : public CFrameWnd
{
public:
    CMyFrame()
    {
        Create(NULL, _T("MFC Application Tutorial"));
    }
};  

class CMyApp : public CWinApp
{
public:
    BOOL InitInstance()
    {
        m_pMainWnd = new CMyFrame();
        m_pMainWnd->ShowWindow(SW_NORMAL);
        m_pMainWnd->UpdateWindow();
        return TRUE;
    }
};  

CMyApp theApp;

 

이것이 MFC로 GUI 프로그래밍을 하는 기본적인 방법입니다. 이 코드에서는 CFrameWnd 클래스의 인스턴스를 생성하여 창을 만들고, CWinApp 클래스의 인스턴스를 통해 애플리케이션을 실행합니다. MFC는 객체 지향 프로그래밍의 이점을 제공하므로, 이 코드는 WinAPI로 작성한 코드보다 훨씬 간결하고 이해하기 쉽습니다.

 

마지막으로, Qt를 사용한 예제를 살펴봅시다. Qt는 현대적인 C++를 기반으로 하며, 크로스 플랫폼을 지원하는 강력한 GUI 라이브러리입니다. Qt로 작성한 코드는 MFC로 작성한 코드와 비슷하게 간결하고 이해하기 쉽습니다.

 

[예제]

#include <QApplication>
#include <QPushButton>  

int main(int argc, char **argv)
{
    QApplication app(argc, argv);  

    QPushButton button("Hello, World!");
    button.show();  

    return app.exec();
}

 

이것이 Qt로 GUI 프로그래밍을 하는 기본적인 방법입니다. 이 코드에서는 QPushButton 객체를 생성하여 버튼을 만들고, QApplication 객체를 통해 애플리케이션을 실행합니다. Qt는 사용하기 쉽고, 강력하며, 다양한 기능을 제공하기 때문에, 많은 C++ 개발자들이 Qt를 선호합니다. 그러나 Qt는 라이선스 비용이 발생할 수 있으므로, 라이선스에 대한 이해가 필요합니다.

 

이 외에도 FLTK, wxWidgets, GTK+ 등 다양한 GUI 라이브러리가 있으며, 이들 각각은 자신만의 특징과 장단점을 가지고 있습니다. 따라서 프로젝트의 요구 사항과 개발자의 선호도에 따라 적절한 GUI 라이브러리를 선택해야 합니다. 이제 GUI 프로그래밍에 대한 기본적인 이해를 바탕으로, 실제로 GUI 애플리케이션을 개발해 보실 수 있을 것입니다.

 

위에서 언급한 대로, 여러 GUI 라이브러리들은 모두 자신만의 특징과 장단점을 가지고 있습니다. 이제 각 라이브러리의 특징에 대해 좀 더 자세히 살펴보겠습니다.

  • WinAPI: WinAPI는 원시적인 라이브러리로, Windows의 낮은 수준의 API에 직접 접근합니다. WinAPI를 사용하면 매우 세밀한 제어가 가능하지만, 코드는 복잡해지고 유지 관리가 어려울 수 있습니다. 그러나 WinAPI를 이해하면 Windows 운영체제의 작동 원리를 더 잘 이해할 수 있으므로, 시스템 프로그래밍이나 게임 개발과 같은 낮은 수준의 프로그래밍을 할 때 유용합니다.
  • MFC: MFC는 Microsoft에서 제공하는 C++ 기반의 라이브러리로, WinAPI를 객체 지향적으로 사용할 수 있게 해 줍니다. MFC를 사용하면 WinAPI보다 코드를 간결하게 작성할 수 있지만, 여전히 낮은 수준의 제어가 가능합니다. 그러나 MFC는 오래된 라이브러리이며, 현대적인 C++ 기능을 제대로 활용하지 못하는 단점이 있습니다.
  • Qt: Qt는 현대적인 C++ 기반의 크로스 플랫폼 라이브러리로, 강력하고 사용하기 쉬운 API를 제공합니다. Qt를 사용하면 높은 수준의 제어와 세밀한 디자인이 가능하며, 다양한 기능을 쉽게 사용할 수 있습니다. 그러나 Qt는 오픈 소스 버전과 상업적인 라이선스 버전 두 가지가 있으며, 상업적인 사용을 위해서는 라이선스 비용이 발생합니다.
  • wxWidgets, GTK+, FLTK: 이들 라이브러리도 모두 크로스 플랫폼을 지원하며, 자신만의 특징과 장단점을 가지고 있습니다. 예를 들어, wxWidgets는 Qt와 비슷한 기능을 제공하지만, 라이선스 비용이 없는 것이 특징입니다. GTK+는 GIMP, GNOME 등 많은 유명한 프로젝트에서 사용되며, C 기반으로 되어 있습니다. FLTK는 가벼운 라이브러리로, 간단한 GUI 애플리케이션에 적합합니다. 

따라서 GUI 라이브러리를 선택할 때는 프로젝트의 요구 사항, 개발자의 선호도, 사용 환경 등을 고려해야 합니다. 이를 통해 GUI 프로그래밍의 세계를 더욱 풍부하게 이해하고, 효과적인 프로그래밍 경험을 쌓을 수 있습니다.

 

특정 GUI 라이브러리를 선택하기 위해 고려해야 할 몇 가지 중요한 요소들이 있습니다.

  • 플랫폼 호환성: 개발하려는 애플리케이션은 어떤 운영체제에서 동작해야 할까요? 일부 라이브러리는 오직 특정 운영체제에서만 동작하기 때문에, 이 부분은 선택 과정에서 매우 중요합니다. 만약 애플리케이션이 여러 플랫폼에서 동작해야 한다면 크로스 플랫폼 라이브러리를 고려해야 할 것입니다.
  • 라이선스: 라이브러리의 라이선스도 선택에 중요한 부분입니다. 어떤 라이브러리는 상업적인 용도로 사용하려면 비용을 지불해야 할 수 있습니다. 따라서 프로젝트의 예산과 목표를 고려하여 라이선스를 잘 살펴보아야 합니다.
  • API의 용이성 및 문서화: 라이브러리의 API가 사용하기 쉽고 잘 문서화되어 있어야 합니다. API가 직관적이고 잘 설계되어 있다면 개발 과정을 더욱 쉽게 만들어 줄 것입니다. 또한, 풍부한 예제와 튜토리얼, 활발한 커뮤니티도 이러한 부분에서 중요한 역할을 합니다.
  • 기능성: 프로젝트에 필요한 기능을 제공하는지 검토해야 합니다. 일부 라이브러리는 다른 것보다 특정 기능에 초점을 맞추고 있을 수 있습니다.

이와 같은 요소들을 통해, C/C++에서 GUI 라이브러리를 선택하는 데 필요한 기본적인 이해를 갖추게 되었습니다. 선택의 과정은 항상 쉽지 않지만, 이번 섹션에서 배운 지식을 바탕으로 적절한 결정을 내릴 수 있을 것입니다. 이제 다음 섹션으로 넘어가, 실제 윈도우와 컨트롤을 생성하고 관리하는 방법에 대해 알아보겠습니다.

 

이제 각 라이브러리의 특징과 예제 코드를 살펴보겠습니다. 이를 통해 적절한 GUI 라이브러리를 선택하는 데 도움이 될 것입니다.

 

  • WinAPI: 이것은 Windows 운영 체제에서 GUI를 작성하는 가장 기본적인 방법입니다. WinAPI는 상당히 낮은 수준의 라이브러리로, 다른 라이브러리들보다 좀 더 복잡할 수 있습니다. 그러나 이를 통해 Windows와 가장 가까운 수준에서 작동하며, 따라서 제어력이 매우 높습니다. WinAPI는 오직 Windows에서만 작동합니다.

 

[예제]

#include <windows.h>  

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
    MessageBox(NULL, TEXT("Hello, World!"), TEXT("Hello"), MB_OK);
    return 0;
}

 

  • MFC(Microsoft Foundation Classes): MFC는 Microsoft에서 제공하는 C++ 라이브러리로, WinAPI를 더 쉽게 사용할 수 있게 해 줍니다. 이것은 클래스 기반의 인터페이스를 제공하며, 이를 통해 윈도우와 컨트롤을 생성하고 관리할 수 있습니다. 하지만 이 라이브러리는 오직 Windows에서만 작동하며, 상용 프로젝트에서 사용할 경우 라이선스 비용이 발생할 수 있습니다.
  • Qt: Qt는 가장 인기 있는 크로스 플랫폼 GUI 라이브러리 중 하나입니다. 그것은 C++로 작성되었지만, 다른 여러 언어와도 호환됩니다. Qt는 사용하기 쉽고, 잘 문서화되어 있으며, 큰 커뮤니티를 가지고 있습니다. 또한 많은 고급 기능들을 제공하며, 이를 통해 복잡한 GUI 애플리케이션을 쉽게 개발할 수 있습니다. Qt는 다양한 플랫폼에서 작동하지만, 상용 프로젝트에서 사용할 경우 라이선스 비용이 발생할 수 있습니다.

 

[예제]

#include <QApplication>
#include <QMessageBox>  

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QMessageBox::information(0, "Hello", "Hello, World!");
    return 0;
}

 

이 세 가지 라이브러리 외에도 많은 GUI 라이브러리들이 있습니다. 필요에 따라 적합한 라이브러리를 선택하는 것이 중요합니다. 다음 섹션에서는 실제로 윈도우와 컨트롤을 만들고 관리하는 방법에 대해 살펴보겠습니다.

 

시작하는 가장 쉬운 방법은 그래픽 사용자 인터페이스를 제공하는 C++ 라이브러리를 사용하는 것입니다. 이러한 라이브러리에는 다양한 유형이 있지만, 여기에서는 wxWidgets, FLTK 및 JUCE를 살펴보겠습니다.

  • wxWidgets: 이 라이브러리는 크로스 플랫폼 개발에 적합하며, C++로 작성되었습니다. wxWidgets는 사용하기 쉽고, 풍부한 기능을 제공하며, 다양한 운영 체제에서 네이티브 룩 앤 필을 지원합니다. 또한, 이 라이브러리는 완전히 무료이며, 오픈소스입니다.
  • FLTK (Fast Light Toolkit): FLTK는 가볍고 콤팩트한 C++ GUI 라이브러리로, 빠르게 GUI를 만들고, 실행할 수 있습니다. 크로스 플랫폼 호환성을 가지고 있으며, OpenGL과 함께 사용하기 쉽습니다. 하지만, UI 디자인이 다소 시대에 뒤떨어져 보일 수 있습니다.
  • JUCE: 이 라이브러리는 오디오와 관련된 애플리케이션을 만들기에 특히 적합합니다. JUCE는 크로스 플랫폼 호환성을 가지고 있으며, 풍부한 기능을 제공합니다. 하지만, 상용 프로젝트에서 사용할 경우 라이선스 비용이 발생할 수 있습니다.

각 라이브러리는 그들만의 특성과 이점을 가지고 있습니다. 어떤 라이브러리를 선택할지는 프로젝트의 요구사항, 개발 팀의 능력, 그리고 사용 가능한 리소스에 따라 달라집니다. 예를 들어, 윈도우 전용 애플리케이션을 개발한다면 WinAPI나 MFC를 선택하는 것이 좋을 수 있습니다. 반면에, 여러 플랫폼에서 동작하는 애플리케이션을 개발한다면 Qt, wxWidgets 등의 크로스 플랫폼 라이브러리를 선택하는 것이 더 좋을 수 있습니다. 

 

그러나 중요한 것은, 모든 라이브러리가 같은 기본적인 GUI 프로그래밍 원리를 따른다는 것입니다. 이는 화면에 요소를 그리고, 사용자의 입력을 받아 처리하며, 그 결과를 사용자에게 피드백하는 것입니다. 이러한 원리를 이해하고 있으면, 새로운 라이브러리나 프레임워크를 학습하는 데 있어서도 큰 도움이 됩니다. 

 

다음 장에서는 이러한 GUI 라이브러리들을 이용하여 실제로 윈도우를 생성하고 이벤트를 처리하는 방법을 살펴보겠습니다. 

 

C/C++로 GUI 프로그래밍을 시작하려면 다음 라이브러리 중 하나를 선택하는 것이 좋습니다. 

  • WinAPI: 윈도우 운영 체제에서 가장 기본적인 GUI 라이브러리로, 윈도우와 컨트롤을 생성하고 이벤트를 처리하는 데 필요한 모든 기능을 제공합니다. C로 작성된 WinAPI는 윈도우 전용 애플리케이션을 개발하는 데 가장 직접적인 방법이지만, 복잡성 때문에 초보자에게는 약간 어려울 수 있습니다. 
  • MFC (Microsoft Foundation Class): MFC는 C++로 작성된 클래스 라이브러리로, WinAPI의 복잡성을 추상화하여 GUI 프로그래밍을 좀 더 쉽게 만들어줍니다. MFC는 윈도우 전용 애플리케이션을 개발하려는 개발자에게 유용하지만, 역시 다소 난이도가 있습니다. 
  • Qt: Qt는 C++로 작성된 크로스 플랫폼 라이브러리로, 다양한 운영 체제에서 실행되는 GUI 애플리케이션을 만들 수 있습니다. Qt는 강력한 기능과 풍부한 도큐먼트를 제공하며, 디자인과 코드를 분리할 수 있는 QML 같은 고급 기능도 제공합니다.  

각각의 라이브러리는 장단점과 특성을 가지고 있으며, 어떤 프로젝트를 하느냐, 어떤 환경에서 작업을 하느냐에 따라 선택할 라이브러리가 달라질 수 있습니다. 따라서 개발자로서 가장 중요한 것은 이러한 다양한 도구들을 이해하고, 자신의 필요에 가장 잘 맞는 도구를 선택하는 능력을 키우는 것입니다.


15.2. 윈도우와 컨트롤 만들기  

윈도우와 컨트롤 생성은 GUI 프로그래밍의 핵심적인 요소입니다. 이번 장에서는 이 두 개념을 깊이 이해하고, C/C++로 실제 윈도우와 컨트롤을 어떻게 만들 수 있는지 알아볼 것입니다. 윈도우는 사용자가 직접 상호작용하는 프레임을 제공하고, 컨트롤은 버튼, 텍스트 박스, 체크 박스와 같은 사용자 인터페이스 요소를 가리킵니다. 각 컨트롤은 특정한 메시지를 보내고 받아 사용자의 입력을 프로그램에 전달하며, 이를 통해 프로그램은 사용자의 요구에 따라 동작하게 됩니다.

15.2.1. 윈도우 생성 및 관리  

윈도우를 생성하고 관리하는 것은 GUI 프로그래밍의 가장 중요한 요소 중 하나입니다. 윈도우는 사용자가 애플리케이션과 상호작용하는 주요 표면을 제공합니다. 따라서 윈도우를 효과적으로 생성하고 관리하는 방법을 배우는 것은 매우 중요합니다. 

 

윈도우를 생성하려면 먼저 윈도우 클래스를 등록해야 합니다. 윈도우 클래스는 윈도우의 모양과 동작을 결정하는 속성을 정의합니다. 이를 위해 WNDCLASS 구조체를 사용하며, 이 구조체에는 윈도우 클래스의 여러 속성을 설정하는 필드가 있습니다. 

 

[예제]

WNDCLASS wc = { 0 };
wc.lpfnWndProc   = WindowProc;
wc.hInstance     = hInstance;
wc.lpszClassName = L"Sample Window Class";


이 예제에서는 WNDCLASS 구조체를 초기화하고, 윈도우 프로시저(메시지 처리 함수)의 주소, 인스턴스 핸들, 그리고 윈도우 클래스의 이름을 설정합니다. 이 구조체를 설정한 후에는 RegisterClass 함수를 사용하여 윈도우 클래스를 등록합니다.

 

윈도우 클래스를 등록한 후에는 CreateWindow 함수를 사용하여 윈도우를 생성할 수 있습니다. 이 함수는 윈도우 클래스의 이름, 윈도우의 제목, 윈도우 스타일, 그리고 윈도우의 초기 위치와 크기 등을 인수로 받아 윈도우를 생성하고, 생성된 윈도우의 핸들을 반환합니다.

 

[예제]

HWND hwnd = CreateWindowEx(
    0,                              // Optional window styles.
    L"Sample Window Class",         // Window class
    L"Sample Window Title",         // Window text
    WS_OVERLAPPEDWINDOW,            // Window style
    // Size and position
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    NULL,       // Parent window    
    NULL,       // Menu
    hInstance,  // Instance handle
    NULL        // Additional application data
);


윈도우를 성공적으로 생성한 후에는 ShowWindow 함수를 사용하여 윈도우를 표시해야 합니다. 이 함수는 윈도우를 화면에 표시하고, 필요한 경우 윈도우를 업데이트합니다.

 

윈도우를 관리하는 것은 애플리케이션의 생명 주기 동안 계속됩니다. 이는 주로 메시지 루프를 통해 수행되며, 메시지 루프는 윈도우로 보내진 메시지를 계속해서 처리하는 역할을 합니다. 주요 메시지는 WM_PAINT (윈도우를 다시 그려야 할 때), WM_SIZE (윈도우의 크기가 변경될 때), 그리고 WM_DESTROY (윈도우가 파괴되어야 할 때) 등이 있습니다.

 

WM_DESTROY 메시지는 윈도우가 닫힐 때 처리해야 합니다. 이 메시지를 처리하면 애플리케이션은 윈도우를 제거하고 메시지 루프를 종료할 수 있습니다.

 

[예제]

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


위의 예제에서는 WM_DESTROY 메시지를 처리하는 윈도우 프로시저를 보여줍니다. PostQuitMessage 함수를 호출하여 메시지 루프를 종료시키고, 애플리케이션이 종료될 수 있도록 합니다.

 

윈도우 생성 및 관리는 C/C++ GUI 프로그래밍의 기본적인 부분이며, 강력한 사용자 인터페이스를 만드는 데 필수적인 요소입니다. 이러한 원리를 이해하면 사용자와 상호 작용하는 다양한 애플리케이션을 만드는 데 큰 도움이 될 것입니다.

 

이제, 생성한 윈도우에 컨트롤을 추가하는 방법에 대해 알아보겠습니다. 컨트롤은 사용자가 애플리케이션과 상호작용하는 데 사용하는 요소(예: 버튼, 텍스트 상자, 체크 박스 등)입니다. 컨트롤은 CreateWindow 또는 CreateWindowEx 함수를 사용하여 윈도우와 마찬가지로 생성할 수 있습니다. 컨트롤의 윈도우 클래스 이름은 컨트롤의 종류에 따라 다릅니다 (예: "Button", "Edit" 등).

 

다음은 윈도우에 버튼 컨트롤을 추가하는 예제입니다.  

 

[예제]

HWND hwndButton = CreateWindow(
    L"Button",   // Predefined class; Unicode assumed 
    L"OK",       // Button text 
    WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,  // Styles 
    10,         // x position 
    10,         // y position 
    100,        // Button width
    100,        // Button height
    hwnd,     // Parent window
    NULL,       // No menu.
    (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE), 
    NULL);      // Pointer not needed.


이 예제에서는 "Button" 클래스의 윈도우(버튼 컨트롤)를 생성합니다. 생성된 버튼은 부모 윈도우에 자식 윈도우로 추가됩니다. 윈도우 생성 함수의 나머지 인수는 버튼의 위치, 크기, 스타일 등을 결정합니다.

 

윈도우 생성 및 관리에 대한 이해는 GUI 프로그래밍의 기본을 이해하는 데 중요합니다. 이런 기본적인 원리를 바탕으로 다양한 사용자 인터페이스를 만드는 데 활용할 수 있습니다. 다음 섹션에서는 이러한 컨트롤을 더욱 효과적으로 관리하는 방법에 대해 알아보겠습니다.

 

15.2.2. 컨트롤 생성 및 관리  

컨트롤은 GUI에서 사용자 입력을 처리하는 주요 방법입니다. 텍스트 상자, 버튼, 체크 박스, 라디오 버튼, 리스트 박스, 콤보 박스 등과 같은 다양한 유형의 컨트롤이 있습니다. 이러한 컨트롤은 사용자와 상호 작용하고 사용자 입력을 수집하는 데 사용됩니다.

 

각 컨트롤은 고유한 윈도우입니다. 윈도우를 생성하는 것과 유사하게, CreateWindow 또는 CreateWindowEx 함수를 사용하여 컨트롤을 생성합니다. 컨트롤을 생성할 때는 컨트롤 유형에 따라 다른 윈도우 클래스 이름을 사용해야 합니다. 예를 들어, 텍스트 상자 컨트롤을 생성하려면 "Edit"을, 버튼 컨트롤을 생성하려면 "Button"을 윈도우 클래스 이름으로 사용해야 합니다.

 

각 컨트롤은 메시지를 보내고 받을 수 있으며, 이 메시지를 사용하여 컨트롤의 상태를 관리하고 사용자의 상호 작용을 처리합니다. 예를 들어, 버튼 컨트롤에 WM_COMMAND 메시지를 보내면 버튼 클릭을 시뮬레이션할 수 있습니다.

 

아래에 C로 작성된 코드는 "Edit" 클래스의 윈도우(텍스트 상자)를 생성하는 예입니다.

 

[예제]

HWND hwndEdit = CreateWindowEx(
    0,          // Optional window styles.
    L"EDIT",         // Standard-class name.
    NULL,       // Window text.
    WS_CHILD | WS_VISIBLE | ES_LEFT | ES_AUTOHSCROLL,   // Window style
    // Size and position settings
    100, 100, 200, 30,     // x, y, width, height
    hwnd,       // Parent window    
    NULL,       // Menu.
    (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE), // Instance handle
    NULL        // Additional application data
);


이 예제에서는 "Edit" 클래스의 윈도우(텍스트 상자)를 생성합니다. 생성된 텍스트 상자는 부모 윈도우에 자식 윈도우로 추가됩니다. 윈도우 생성 함수의 나머지 인수는 텍스트 상자의 위치, 크기, 스타일 등을 결정합니다.

 

컨트롤의 내용을 설정하거나 가져오려면, WM_SETTEXT 및 WM_GETTEXT 메시지를 사용할 수 있습니다. 이 메시지들은 SendMessage 함수를 통해 컨트롤에 전달됩니다. 예를 들어, 위에서 생성한 텍스트 상자에 텍스트를 설정하려면 다음 코드를 사용할 수 있습니다.

 

[예제]

SendMessage(hwndEdit, WM_SETTEXT, 0, (LPARAM)L"My Text");


이 코드는 "My Text"라는 문자열을 텍스트 상자에 설정합니다. 비슷하게, 텍스트 상자에서 텍스트를 가져오려면 다음 코드를 사용할 수 있습니다.

 

[예제]

TCHAR buffer[256];
SendMessage(hwndEdit, WM_GETTEXT, sizeof(buffer)/sizeof(TCHAR), (LPARAM)buffer);


이 코드는 텍스트 상자에서 텍스트를 가져와서 buffer에 저장합니다.

 

컨트롤은 그들의 부모 윈도우에 의해 관리됩니다. 부모 윈도우가 파괴되면 자동으로 모든 자식 컨트롤도 파괴됩니다. 그러나 필요한 경우 DestroyWindow 함수를 사용하여 개별 컨트롤을 수동으로 파괴할 수도 있습니다.

 

다음으로, 각 컨트롤의 고유한 기능과 특성을 이용하기 위한 메시지들이 있습니다. 예를 들어, 체크 박스 컨트롤의 경우 BM_GETCHECK 및 BM_SETCHECK 메시지를 사용하여 체크 박스의 상태를 가져오거나 설정할 수 있습니다. 리스트 박스 컨트롤의 경우 LB_ADDSTRING 및 LB_DELETESTRING 메시지를 사용하여 항목을 추가하거나 삭제할 수 있습니다.

 

아래는 C++로 작성된 코드는 체크 박스의 상태를 확인하고 변경하는 예입니다.

 

[예제]

// 체크 상태 확인
LRESULT checked = SendMessage(hwndCheckbox, BM_GETCHECK, 0, 0);  

if (checked == BST_CHECKED) {
    // 체크 박스가 이미 체크되어 있다면 체크를 해제
    SendMessage(hwndCheckbox, BM_SETCHECK, BST_UNCHECKED, 0);
} else {
    // 체크 박스가 체크되어 있지 않다면 체크
    SendMessage(hwndCheckbox, BM_SETCHECK, BST_CHECKED, 0);
}

 

이 코드는 체크 박스 컨트롤의 상태를 BM_GETCHECK 메시지를 사용하여 확인하고, 상태에 따라 BM_SETCHECK 메시지를 사용하여 체크 박스의 상태를 변경합니다.

 

컨트롤을 사용하면 사용자에게 입력을 받거나 정보를 제공하는 등의 다양한 상호작용을 GUI 애플리케이션에서 구현할 수 있습니다. 이렇게 함으로써 프로그램은 사용자 친화적이며 직관적인 인터페이스를 제공하게 됩니다.

 

이외에도 각기 다른 컨트롤들은 서로 다른 특성과 기능을 가지고 있으며, 이에 대한 자세한 내용은 해당 컨트롤의 공식 문서를 참조하시는 것이 좋습니다. 다음 섹션에서는 윈도우와 컨트롤의 이벤트를 처리하는 방법에 대해 설명하겠습니다. 이 부분은 GUI 프로그래밍에서 중요한 부분이므로 주의 깊게 살펴보시기 바랍니다.

 

15.2.3. 메시지 처리와 이벤트 핸들링  

GUI 프로그래밍에서 메시지 처리와 이벤트 핸들링은 매우 중요한 부분입니다. 사용자의 액션(키보드 입력, 마우스 클릭 등)이나 시스템 이벤트(윈도우 사이즈 변경, 윈도우 닫기 등)를 처리하는 메커니즘이기 때문입니다.

 

메시지 처리는 윈도우가 수신하는 메시지를 처리하는 과정을 말합니다. 윈도우 메시지는 시스템 또는 응용 프로그램에서 발생하는 다양한 이벤트를 표현하며, 윈도우는 이러한 메시지를 받아 처리해야 합니다. 메시지 처리 과정은 메시지 큐에서 메시지를 가져와 적절한 처리를 수행하는 것으로 이루어집니다.

 

이벤트 핸들링은 사용자의 입력(키보드, 마우스 등)이나 시스템 이벤트(윈도우 생성, 파괴 등)를 처리하는 것을 말합니다. 이벤트 핸들러는 특정 이벤트가 발생했을 때 호출되는 함수나 메서드를 말하며, 이벤트 핸들러는 해당 이벤트를 처리하는 코드를 포함하고 있습니다.

 

다음은 C++에서 메시지 처리와 이벤트 핸들링을 하는 예제 코드입니다. 이 코드는 윈도우 프로시저를 정의하여 각각의 윈도우 메시지에 따라 처리를 수행합니다.

 

[예제]

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
    case WM_CLOSE:
        PostQuitMessage(0);
        return 0;
    case WM_COMMAND:
        if (LOWORD(wParam) == IDC_MYBUTTON) {
            // 버튼 클릭 이벤트 처리
            MessageBox(hwnd, "버튼이 클릭되었습니다.", "버튼 클릭", MB_OK);
        }
        return 0;
    }  

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

 

이 코드에서 WindowProc 함수는 윈도우 프로시저로, 윈도우가 받는 모든 메시지를 처리하는 역할을 합니다. WM_CLOSE 메시지는 윈도우가 닫힐 때, WM_COMMAND 메시지는 컨트롤(버튼, 메뉴 등)에서 이벤트가 발생할 때 발생하는 메시지입니다.

 

이렇게 각 메시지에 대한 처리를 정의함으로써 우리는 사용자의 액션에 따라 프로그램이 어떻게 반응할지를 정의할 수 있습니다. 이는 GUI 프로그래밍에서 매우 중요한 부분이며, 사용자와 프로그램 간의 상호작용을 가능하게 합니다.

 

이제 실제로 C++로 작성된 코드를 살펴보겠습니다. 아래의 예제는 간단한 GUI 애플리케이션을 만드는 C++ 코드입니다.  

 

[예제]

#include <windows.h>  

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
    switch(msg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        case WM_COMMAND:
            if (LOWORD(wparam) == 1) {
                MessageBox(NULL, "버튼이 클릭되었습니다!", "알림", MB_OK);
            }
            break;
        default:
            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    return 0;
}  

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow) {
    const char CLASS_NAME[] = "Sample Window Class";  

    WNDCLASS wc = {};  

    wc.lpfnWndProc = WindowProcedure;
    wc.hInstance = hInst;
    wc.lpszClassName = CLASS_NAME;  

    RegisterClass(&wc);  

    HWND hwnd = CreateWindow(CLASS_NAME, "Sample Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, NULL, NULL, hInst, NULL);  

    if (hwnd == NULL) {
        return 0;
    }  

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);  

    MSG msg = {};  

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }  

    return 0;
}

 

이 코드는 'Sample Window'라는 제목의 윈도우를 생성하고, 사용자가 윈도우를 닫으려 할 때 WM_DESTROY 메시지를 처리하여 어플리케이션을 종료하게 합니다. 또한, '알림' 메시지 박스를 띄우는 이벤트 핸들러도 추가되어 있습니다.

 

주요 함수들에 대해 간단히 설명하겠습니다. 

  • RegisterClass: 윈도우 클래스를 등록하는 함수입니다. 윈도우 클래스는 윈도우의 특성(배경 색상, 커서 모양, 메뉴 유형 등)을 정의합니다. 
  • CreateWindow: 실제 윈도우를 생성하는 함수입니다. 생성된 윈도우는 아직 화면에 보이지 않습니다. 
  • ShowWindow와 UpdateWindow: 이 함수들은 윈도우를 화면에 표시하고 업데이트하는 데 사용됩니다.  
  • GetMessage, TranslateMessage, DispatchMessage: 이 함수들은 메시지 루프를 구현하는 데 사용되며, 윈도우가 메시지를 수신하고 처리할 수 있게 합니다. 

이 예제는 간단하지만, GUI 프로그래밍의 핵심 원리를 잘 보여줍니다. 사용자의 이벤트를 처리하고 그에 따른 반응을 구현하는 것, 바로 이것이 GUI 프로그래밍의 본질입니다. 

 

다음으로, 코드에 컨트롤을 추가하고 이벤트를 처리해 보겠습니다. 컨트롤은 버튼이나 텍스트 박스와 같이 사용자와 상호작용하는 GUI 요소를 말합니다. 우리는 버튼을 윈도우에 추가하고 클릭 이벤트를 처리하는 방법을 배워볼 것입니다.

 

아래에 표시된 코드를 살펴보십시오. 이 코드는 CreateWindow 함수를 이용하여 "Click me!" 라는 버튼을 생성하고, 사용자가 버튼을 클릭하면 "Button clicked!" 라는 메시지가 출력되는 간단한 프로그램입니다. 

 

[예제]

#include <windows.h>  

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
    switch(msg) {
        case WM_CREATE: {
            CreateWindow("button", "Click me!", WS_VISIBLE | WS_CHILD, 10, 10, 100, 30, hwnd, (HMENU) 1, NULL, NULL);
            break;
        }
        case WM_COMMAND: {
            if (LOWORD(wparam) == 1) {
                MessageBox(NULL, "Button clicked!", "Notification", MB_OK);
            }
            break;
        }
        case WM_DESTROY: {
            PostQuitMessage(0);
            break;
        }
        default:
            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    return 0;
}  

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow) {
    // 이전의 코드는 생략...
}

 

기존의 코드에서 다음 사항들을 주목해 주세요.

 

WM_CREATE 메시지 핸들러가 추가되었습니다. 이 메시지는 윈도우가 생성되는 시점에 발생합니다. 이 시점에 버튼을 생성합니다.

 

CreateWindow 함수를 사용하여 버튼을 생성합니다. 여기서 WS_VISIBLE | WS_CHILD 스타일은 버튼이 화면에 보이도록 하고, 부모 윈도우 (여기서는 메인 윈도우) 내에 포함되도록 합니다.

 

WM_COMMAND 메시지는 컨트롤에서 이벤트가 발생할 때 처리됩니다. LOWORD(wparam) == 1 조건은 버튼 클릭 이벤트를 확인합니다.

 

이 예제를 통해, 윈도우와 컨트롤을 생성하고 이벤트를 처리하는 방법을 배웠습니다. 이런 기본적인 기법들을 바탕으로, 더 복잡한 GUI 어플리케이션을 개발할 수 있게 될 것입니다. 이번 섹션에서는 그런 개념들을 계속해서 살펴보도록 하겠습니다.

 

추가로, 메시지 루프에서 일어나는 일을 더 깊게 이해하려면, 우리가 관리하는 윈도우가 여러 가지의 메시지를 어떻게 처리하는지 살펴보는 것이 중요합니다. 예를 들어, 우리는 키보드 입력, 마우스 클릭, 윈도우 크기 변경 등 다양한 이벤트에 대해 메시지를 받고 처리합니다. 이 모든 이벤트는 메시지 큐에 추가되고, 메시지 루프는 이 큐에서 메시지를 꺼내어 처리합니다.

 

다음은 키보드 입력 이벤트에 대한 메시지 처리를 추가한 예제입니다.  

 

[예제]

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
    switch(msg) {
        case WM_KEYDOWN: {
            if(wparam == VK_RETURN) {
                MessageBox(NULL, "Enter key pressed!", "Notification", MB_OK);
            }
            break;
        }
        // 기존의 코드는 생략...
    }
    return 0;
}


여기서, WM_KEYDOWN 메시지는 키보드 키가 눌릴 때 발생하고, wparam은 눌린 키의 가상 키 코드를 담고 있습니다. VK_RETURN는 엔터 키의 가상 키 코드입니다.

 

마찬가지로, 마우스 클릭 이벤트에 대한 메시지 처리를 추가할 수도 있습니다.

 

[예제]

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
    switch(msg) {
        case WM_LBUTTONDOWN: {
            MessageBox(NULL, "Mouse left button clicked!", "Notification", MB_OK);
            break;
        }
        // 기존의 코드는 생략...
    }
    return 0;
}


WM_LBUTTONDOWN 메시지는 마우스 왼쪽 버튼이 클릭될 때 발생합니다.

 

GUI 프로그래밍에서는 이렇게 다양한 유형의 이벤트에 대해 메시지 처리를 추가하여, 사용자의 입력에 동적으로 반응하는 프로그램을 만들 수 있습니다. 이벤트 핸들링이 GUI 프로그래밍의 핵심 요소 중 하나라는 것을 이해하시길 바랍니다.

 

또한 GUI 프로그래밍에서 중요한 부분은 이벤트 핸들링과 관련된 비동기 프로그래밍 개념입니다. 비동기 프로그래밍이란, 여러 가지 작업이 동시에 수행되는 프로그래밍 패러다임을 말합니다. 이는 GUI 프로그래밍에서 특히 중요한데, 그 이유는 사용자의 입력(이벤트)이 언제 발생할지 알 수 없기 때문입니다. 따라서, 프로그램은 동시에 여러 가지 일을 처리할 수 있어야 합니다.

 

메시지 루프는 이 비동기 행동의 핵심입니다. 메시지 루프는 메시지를 받아들이고, 각 메시지에 대한 처리를 시작한 후, 다음 메시지를 받아들이는 동안 이전 메시지의 처리가 계속 진행됩니다. 이런 방식으로, 프로그램은 여러 가지 작업을 동시에 처리할 수 있습니다.

 

이와 같은 원칙을 이해하면, 다양한 이벤트에 대해 응답하고, 복잡한 사용자 인터페이스를 구성하고, 여러 가지 작업을 동시에 처리하는 등의 복잡한 GUI 애플리케이션을 개발할 수 있습니다.

 

마지막으로, 이 섹션에서는 메시지 처리와 이벤트 핸들링에 대해 살펴보았습니다. 이는 GUI 프로그래밍의 핵심적인 부분으로, 윈도우나 컨트롤과 같은 객체의 동작을 제어하는 데 필요한 기본적인 원리입니다. GUI 프로그래밍을 배울 때는 이러한 원리를 반드시 이해해야 합니다.


15.3. WinAPI를 이용한 GUI 프로그래밍  

'15.3. WinAPI를 이용한 GUI 프로그래밍'에서는 C/C++로 Windows Application Programming Interface (WinAPI)를 이용한 GUI 개발 방법에 대해 학습합니다. WinAPI는 윈도우즈 운영 체제에서 네이티브 애플리케이션 개발을 위한 핵심 API로, 윈도우 생성, 메시지 처리, 컨트롤 관리 등의 GUI 관련 작업을 수행합니다. 이 장에서는 WinAPI의 기본 개념을 이해하고, 간단한 윈도우와 컨트롤을 만들어보는 실습을 진행하게 됩니다.

15.3.1. WinAPI의 이해  

WinAPI(Windows Application Programming Interface)는 윈도우 운영 체제에서 사용되는, 응용 프로그램 개발을 위한 함수들의 모음입니다. 이를 통해 운영 체제가 제공하는 기능들을 사용하고, GUI 애플리케이션을 만들 수 있습니다. WinAPI는 대략 2000개 이상의 함수로 구성되어 있으며, 이들은 파일 관리, 장치 관리, 메모리 관리, 프로세스와 스레드 관리, 네트워크 통신, 그래픽 처리, 사용자 인터페이스 등 다양한 분야에서 사용됩니다. 

 

WinAPI를 사용하면 직접 윈도우를 생성하고 컨트롤하며, 메시지를 처리할 수 있습니다. 이를 이용해 GUI 프로그래밍을 할 수 있으며, 이는 운영 체제와의 상호작용이 필요한 다른 모든 작업에도 필요합니다. 

 

WinAPI는 C언어로 작성되었으며, 대부분의 함수들이 C 언어 스타일로 작성되어 있습니다. 따라서, C/C++에서 직접 WinAPI 함수를 호출할 수 있습니다. 

 

간단한 예제를 통해 살펴보겠습니다. 아래는 WinAPI를 이용해 메시지 박스를 생성하는 예제 코드입니다. 

 

[예제]

#include <windows.h>  

int main()
{
    MessageBox(NULL, "Hello, World!", "Hello Message", MB_OK);
    return 0;
}

 

위 코드를 컴파일하고 실행하면, "Hello, World!"라는 메시지와 "OK" 버튼이 있는 메시지 박스가 표시됩니다. 이 예제는 가장 기본적인 WinAPI 함수 중 하나인 MessageBox를 사용합니다. 이 함수는 사용자에게 메시지를 표시하고 사용자의 응답을 기다리는 간단한 대화상자를 생성합니다.

 

본격적으로 WinAPI를 배우기 위해서는 기본적인 C/C++ 프로그래밍 지식이 필요하며, 특히 포인터와 구조체에 대한 이해가 필요합니다. 그리고 WinAPI의 주요 개념인 메시지 루프, 윈도우 프로시저 등에 대한 이해가 필요합니다.

 

앞서 언급한 윈도우 프로시저와 메시지 루프에 대해 더 자세히 설명하겠습니다. 윈도우 프로시저는 윈도우에서 발생하는 다양한 이벤트나 메시지를 처리하는 함수입니다. 이는 운영 체제로부터 메시지를 받아 적절한 동작을 수행하며, 이를 통해 사용자와 애플리케이션 간의 상호작용이 이루어집니다.

 

다음은 윈도우 프로시저의 기본적인 형태입니다.

 

[예제]

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}


이 윈도우 프로시저는 WM_CLOSE와 WM_DESTROY라는 두 가지 메시지를 처리합니다. WM_CLOSE 메시지는 윈도우 닫기 버튼이 클릭되었을 때 발생하며, 이 경우 DestroyWindow 함수를 호출하여 윈도우를 파괴합니다. WM_DESTROY 메시지는 윈도우가 파괴되었을 때 발생하며, 이 경우 PostQuitMessage 함수를 호출하여 프로그램을 종료합니다.

 

한편, 메시지 루프는 애플리케이션에서 계속 실행되는 루프로, 여기서 윈도우 프로시저에 전달할 메시지를 가져옵니다. 다음은 메시지 루프의 기본적인 형태입니다.

 

[예제]

MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}


GetMessage 함수는 메시지 큐에서 메시지를 가져오며, TranslateMessage와 DispatchMessage 함수는 해당 메시지를 윈도우 프로시저로 전달합니다.

 

이렇게 윈도우 프로시저와 메시지 루프를 이해하고 활용하면, 사용자의 입력을 받아 처리하고, 필요에 따라 애플리케이션의 상태를 변경하거나 응답을 제공할 수 있습니다. 이는 WinAPI를 이용한 GUI 프로그래밍의 핵심 원리입니다.

 

위에서 설명한 원리를 토대로 하여, 우리는 실제로 C++을 이용하여 WinAPI를 사용해 GUI 애플리케이션을 만들어 볼 수 있습니다. 다음의 예제 코드는 가장 간단한 윈도우를 생성하는 기본적인 WinAPI 프로그램입니다.

 

[예제]

#include <windows.h>  

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}  

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow)
{
    const char CLASS_NAME[] = "Sample Window Class";  

    WNDCLASS wc = { };  

    wc.lpfnWndProc = WindowProcedure;
    wc.hInstance = hInst;
    wc.lpszClassName = CLASS_NAME;  

    if (!RegisterClass(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }  

    HWND hwnd = CreateWindow(CLASS_NAME, "Sample Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 300, NULL, NULL, hInst, NULL);  

    if (hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }  

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);  

    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }  

    return 0;
}

 

위 코드에서 WinMain 함수는 애플리케이션의 진입점입니다. 이 함수는 윈도우 클래스를 등록하고, 윈도우를 생성하고, 메시지 루프를 실행합니다. WindowProcedure 함수는 앞서 설명한 대로 윈도우에서 발생하는 메시지를 처리합니다. 

 

이 예제는 매우 간단하지만, WinAPI를 사용하여 GUI 애플리케이션을 만드는 기본적인 틀을 제공합니다. 이를 통해 각종 컨트롤을 추가하고, 유저 인터랙션을 처리하고, 복잡한 UI를 구성하는 등의 작업을 수행할 수 있습니다. 

 

참고로, WinAPI는 그 자체로 꽤 복잡하고 낮은 수준의 인터페이스를 제공하므로, 실제 애플리케이션 개발에서는 더 높은 수준의 라이브러리나 프레임워크를 사용하는 것이 일반적입니다. 하지만, WinAPI를 이해하는 것은 윈도우 운영 체제에서 GUI 프로그래밍의 기본 원리를 이해하는 데 매우 중요합니다. 

 

WinAPI는 그 이름에서 알 수 있듯이, 윈도우를 위한 API(Application Programming Interface)입니다. 이는 운영 체제에서 제공하는 기능들을 개발자가 사용할 수 있도록 돕는 역할을 합니다. WinAPI는 파일 시스템, 네트워크, 디바이스, 프로세스, 스레드 등을 조작할 수 있는 방법을 제공하며, 이 중에서도 GUI 관련 API는 특히 중요한 부분을 차지합니다. 

 

WinAPI에 대한 깊은 이해는 윈도우 애플리케이션 개발에 있어 필수적인 요소입니다. GUI 프로그래밍에 있어서는 WinAPI를 이해하는 것이 매우 중요한데, 이는 윈도우의 메시지 기반 아키텍처와 직접적으로 연관되어 있기 때문입니다. 윈도우는 사용자의 키보드 입력, 마우스 클릭 등의 동작을 메시지로 변환하여 해당 애플리케이션에 전달하고, 애플리케이션은 이 메시지를 해석하여 적절한 동작을 수행합니다. 이와 같이 메시지 기반 아키텍처를 이해하는 것은 WinAPI를 이용한 GUI 프로그래밍에 있어 핵심적인 요소입니다. 

 

위에서 다룬 간단한 윈도우 생성 예제를 통해, 우리는 WinAPI가 어떻게 GUI를 생성하고, 이벤트를 처리하는지에 대한 간략한 이해를 얻을 수 있습니다. 이러한 기본적인 개념을 토대로, 우리는 더욱 복잡한 GUI 애플리케이션을 만들어 나갈 수 있습니다. 하지만, 복잡한 GUI 애플리케이션을 개발하기 위해서는 WinAPI 외에도 여러 가지 추가적인 지식이 필요합니다. 이는 다음 섹션에서 더욱 자세히 다루도록 하겠습니다. 

 

초보자가 처음으로 WinAPI를 접하면, 간단한 기능을 구현하는 데에도 수많은 함수와 매개변수들을 기억해야 하며, 이로 인해 상당히 복잡해 보일 수 있습니다. 하지만, 이러한 복잡성을 극복하고 나면, WinAPI는 운영 체제의 많은 기능에 접근할 수 있는 강력한 도구가 됩니다. 

 

다음은 WinAPI를 사용하여 간단한 윈도우를 생성하고, 그 위에 버튼을 추가하는 코드 예제입니다. 

 

[예제]

#include <windows.h>  

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            PostQuitMessage(0);
            break;
        case WM_COMMAND:
            if (LOWORD(wParam) == 1) // 버튼 ID
            {
                MessageBox(hwnd, "버튼 클릭!", "알림", MB_OK);
            }
            break;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}  

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow)
{
    const char CLASS_NAME[] = "Sample Window Class";
    WNDCLASS wc = { };  

    wc.lpfnWndProc = WindowProcedure;
    wc.hInstance = hInst;
    wc.lpszClassName = CLASS_NAME;  

    RegisterClass(&wc);  

    HWND hwnd = CreateWindow(CLASS_NAME, "샘플 윈도우", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInst, NULL);
    if (hwnd == NULL) return 0;  

    ShowWindow(hwnd, nCmdShow);  

    HWND hButton = CreateWindow("BUTTON", "클릭!", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
        50, 50, 100, 25, hwnd, (HMENU)1, NULL, NULL);  

    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}

 

이 코드는 "샘플 윈도우"라는 제목의 윈도우를 생성하고, 그 위에 "클릭!"이라는 버튼을 추가합니다. 버튼을 클릭하면 "버튼 클릭!"이라는 메시지 박스가 표시됩니다. 여기서 중요한 것은 WindowProcedure 함수입니다. 이 함수는 윈도우가 받은 메시지를 처리하는 역할을 합니다. 메시지는 WM_CLOSE, WM_COMMAND 등과 같이 다양하며, 각 메시지에 대한 처리 코드를 작성하면 됩니다. 

 

이제 이 예제를 통해 WinAPI를 어떻게 사용하는지 대략적으로 알아보았습니다. 다음 섹션에서는 더욱 복잡한 GUI 프로그래밍을 위해 필요한 WinAPI의 더 깊은 이해에 대해 살펴보겠습니다. 

 

WinAPI는 윈도우 운영 체제와 상호 작용하도록 설계된 광범위한 함수들로 구성되어 있습니다. 이들 함수들은 그래픽 출력, 파일 및 네트워크 I/O, 프로세스 관리 등을 포함한 다양한 작업을 지원합니다. 

 

C++과 같은 언어에서 WinAPI를 사용하면, 운영 체제 수준에서 필요한 기능을 사용하는 강력한 프로그램을 작성할 수 있습니다. WinAPI 함수는 대부분 C언어 인터페이스를 사용하므로, 이를 C++에서 호출하는 것은 쉽습니다. 다만, C++ 프로그램에서 WinAPI를 사용할 때에는 클래스와 객체 지향 프로그래밍의 개념과 운영 체제의 기본 원칙을 모두 이해해야 한다는 점에 유의해야 합니다. 

 

WinAPI는 Microsoft Windows의 버전마다 다르게 작동할 수 있으며, 새로운 기능이 추가되거나 이전 기능이 폐기되는 등 변화가 많습니다. 따라서, WinAPI를 사용하려면 Microsoft에서 제공하는 공식 문서를 참조하는 것이 좋습니다. 이 문서에서는 각 함수의 기능, 필요한 매개변수, 반환 값 등에 대한 자세한 정보를 제공합니다. 

 

WinAPI를 이해하고 올바르게 사용하는 것은 쉬운 일이 아닙니다. 하지만 이 과정을 통해 운영 체제가 어떻게 작동하는지, 프로그램이 어떻게 운영 체제와 상호 작용하는지에 대한 깊은 이해를 얻을 수 있습니다. 이러한 이해는 프로그래밍 능력을 향상하는 데 매우 중요하므로, WinAPI를 학습하는 것을 강력히 권장합니다. 

 

15.3.2. WinAPI를 이용한 윈도우 생성  

WinAPI를 이용해 윈도우를 생성하는 것은 매우 중요한 과정입니다. 이 섹션에서는, 그 과정을 단계별로 살펴보겠습니다.  

 

  • 윈도우 클래스 등록: 우선, 우리가 만들 윈도우의 '형태'를 정의하는 WNDCLASS 구조체를 작성하고 이를 등록해야 합니다. WNDCLASS는 윈도우의 배경색, 커서, 아이콘, 메뉴 등을 지정하는 필드들을 포함합니다

[예제]

WNDCLASS wc = { 0 };
wc.lpfnWndProc = WindowProc; // 윈도우 프로시저 함수 지정
wc.hInstance = hInstance; // 인스턴스 핸들 지정
wc.lpszClassName = "MyWindowClass"; // 클래스 이름 지정  

if (!RegisterClass(&wc)) // 클래스 등록
{
    MessageBox(NULL, "윈도우 클래스 등록 실패", "오류", MB_OK);
    return 0;
}

 

  • 윈도우 생성: RegisterClass 함수로 윈도우 클래스를 등록한 후에는, CreateWindow 함수를 이용해 실제 윈도우를 만듭니다.

[예제]

HWND hwnd = CreateWindow(
    "MyWindowClass", // 등록한 클래스 이름
    "My Window", // 윈도우 타이틀
    WS_OVERLAPPEDWINDOW, // 윈도우 스타일
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, // 윈도우 위치 및 크기
    NULL, NULL, // 부모 윈도우와 메뉴 핸들
    hInstance, // 인스턴스 핸들
    NULL); // 추가 파라미터  

if (!hwnd) // 윈도우 생성 확인
{
    MessageBox(NULL, "윈도우 생성 실패", "오류", MB_OK);
    return 0;
}

 

  • 윈도우 표시: 이제 ShowWindow 함수를 이용해 생성한 윈도우를 화면에 보여줄 수 있습니다.

[예제]

ShowWindow(hwnd, SW_SHOW);

 

  • 메시지 루프: 마지막으로, GetMessage-TranslateMessage-DispatchMessage 함수를 이용한 메시지 루프를 만들어 윈도우가 사용자의 입력 등을 처리할 수 있도록 합니다.

[예제]

MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0)) // 메시지 가져오기
{
    TranslateMessage(&msg); // 메시지 번역
    DispatchMessage(&msg); // 메시지 전달
}


이상이 WinAPI를 이용한 윈도우 생성의 기본적인 흐름입니다. 윈도우 프로그래밍은 복잡할 수 있지만, 이러한 기본적인 구조를 이해하면 좋은 출발점이 될 것입니다. 다음 섹션에서는 이 윈도우에 컨트롤을 추가하는 방법을 살펴보겠습니다.   

 

15.3.2. WinAPI를 이용한 윈도우 생성  

WinAPI를 이용해 윈도우를 생성하는 것은 매우 중요한 과정입니다. 이 섹션에서는, 그 과정을 단계별로 살펴보겠습니다. 

 

  • 윈도우 클래스 등록: 우선, 우리가 만들 윈도우의 '형태'를 정의하는 WNDCLASS 구조체를 작성하고 이를 등록해야 합니다. WNDCLASS는 윈도우의 배경색, 커서, 아이콘, 메뉴 등을 지정하는 필드들을 포함합니다. 

[예제]

WNDCLASS wc = { 0 };
wc.lpfnWndProc = WindowProc; // 윈도우 프로시저 함수 지정
wc.hInstance = hInstance; // 인스턴스 핸들 지정
wc.lpszClassName = "MyWindowClass"; // 클래스 이름 지정  

if (!RegisterClass(&wc)) // 클래스 등록
{
    MessageBox(NULL, "윈도우 클래스 등록 실패", "오류", MB_OK);
    return 0;
}

 

  • 윈도우 생성: RegisterClass 함수로 윈도우 클래스를 등록한 후에는, CreateWindow 함수를 이용해 실제 윈도우를 만듭니다.

[예제]

HWND hwnd = CreateWindow(
    "MyWindowClass", // 등록한 클래스 이름
    "My Window", // 윈도우 타이틀
    WS_OVERLAPPEDWINDOW, // 윈도우 스타일
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, // 윈도우 위치 및 크기
    NULL, NULL, // 부모 윈도우와 메뉴 핸들
    hInstance, // 인스턴스 핸들
    NULL); // 추가 파라미터  

if (!hwnd) // 윈도우 생성 확인
{
    MessageBox(NULL, "윈도우 생성 실패", "오류", MB_OK);
    return 0;
}

 

  • 윈도우 표시: 이제 ShowWindow 함수를 이용해 생성한 윈도우를 화면에 보여줄 수 있습니다.

[예제]

ShowWindow(hwnd, SW_SHOW);

 

  • 메시지 루프: 마지막으로, GetMessage-TranslateMessage-DispatchMessage 함수를 이용한 메시지 루프를 만들어 윈도우가 사용자의 입력 등을 처리할 수 있도록 합니다.

[예제]

MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0)) // 메시지 가져오기
{
    TranslateMessage(&msg); // 메시지 번역
    DispatchMessage(&msg); // 메시지 전달
}


이상이 WinAPI를 이용한 윈도우 생성의 기본적인 흐름입니다. 윈도우 프로그래밍은 복잡할 수 있지만, 이러한 기본적인 구조를 이해하면 좋은 출발점이 될 것입니다. 다음 섹션에서는 이 윈도우에 컨트롤을 추가하는 방법을 살펴보겠습니다.   

 

추가적으로 윈도우에 컨트롤을 추가하는 것은 CreateWindow 또는 CreateWindowEx 함수를 이용하면 됩니다. 여기에서는 버튼을 추가하는 방법을 간단히 살펴보겠습니다. 

 

[예제]

HWND hwndButton = CreateWindow(
    "BUTTON",  // 컨트롤 클래스 이름
    "Click me",  // 버튼 텍스트
    WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,  // 스타일 지정
    10, 10, 100, 30,  // 버튼 위치 및 크기
    hwnd,  // 부모 윈도우 핸들
    NULL,  // 메뉴 핸들
    (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE),  // 인스턴스 핸들
    NULL);  // 추가 파라미터


위의 코드를 통해 "Click me"라는 텍스트를 가진 버튼이 생성되고 윈도우에 추가됩니다. 이와 같은 방식으로 다양한 컨트롤(텍스트 박스, 리스트 박스, 체크 박스 등)을 윈도우에 추가할 수 있습니다. 

 

이러한 컨트롤은 사용자와의 상호작용을 돕는 중요한 요소입니다. 예를 들어, 버튼은 사용자가 클릭함으로써 특정 동작을 실행할 수 있도록 합니다. 이런 동작은 버튼에 연결된 이벤트 핸들러에서 처리합니다. 

 

이벤트 핸들러는 메시지 처리를 담당하는 윈도우 프로시저 함수 내에서 설정합니다. 버튼 클릭 이벤트는 WM_COMMAND 메시지로 전달되며, 이 메시지를 처리함으로써 클릭에 대한 동작을 정의할 수 있습니다. 

 

[예제]

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_COMMAND:
        if (LOWORD(wParam) == ID_BUTTON)  // 버튼의 ID가 ID_BUTTON인 경우
        {
            MessageBox(hwnd, "버튼이 클릭되었습니다.", "정보", MB_OK);
        }
        break;
    // ...
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


이처럼 WinAPI를 이용한 윈도우 생성과 관리는 조금 복잡해 보일 수 있지만, 기본적인 원리를 이해하고 나면 다양한 GUI 애플리케이션을 만드는 데 도움이 될 것입니다. 다음 섹션에서는 이에 더해 다양한 WinAPI 함수와 기능에 대해 더 깊게 다루어 보겠습니다. 

 

또한 WinAPI는 다양한 컨트롤과 위젯을 제공합니다. 이러한 컨트롤들은 사용자와의 상호작용을 통해 프로그램에 입력을 제공하거나 프로그램의 상태를 표시하는 데 사용됩니다. 예를 들어, 텍스트 박스는 사용자로부터 텍스트 입력을 받거나 텍스트 정보를 표시하는 데 사용되며, 체크 박스는 선택 사항을 제공하거나 설정을 토글 하는 데 사용됩니다. 

 

컨트롤을 윈도우에 추가하려면 먼저 컨트롤을 생성하고, 컨트롤의 핸들을 사용하여 부모 윈도우에 추가해야 합니다. 이 작업은 CreateWindow 함수를 사용하여 수행할 수 있습니다. 

 

[예제]

HWND hwndEdit = CreateWindowEx(
    WS_EX_CLIENTEDGE,  // 테두리 스타일 지정
    "EDIT",  // 텍스트 박스 컨트롤 클래스
    "",  // 초기 텍스트 (없음)
    WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,  // 스타일 지정
    10, 10, 200, 100,  // 위치 및 크기
    hwnd,  // 부모 윈도우 핸들
    (HMENU)IDC_MAIN_EDIT,  // 컨트롤 ID
    GetModuleHandle(NULL),  // 인스턴스 핸들
    NULL);  // 추가 파라미터


이러한 방식으로 다양한 컨트롤을 생성하고 윈도우에 추가할 수 있습니다. 하지만 이것만으로는 충분하지 않습니다. 사용자와의 상호작용을 가능하게 하려면 각 컨트롤에서 발생하는 이벤트를 처리해야 합니다. 이벤트는 메시지의 형태로 전달되며, 윈도우 프로시저 함수에서 처리합니다. 

 

[예제]

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_COMMAND:
        if (HIWORD(wParam) == EN_CHANGE && LOWORD(wParam) == IDC_MAIN_EDIT)  // 텍스트 박스의 내용이 변경된 경우
        {
            // 변경된 내용을 처리합니다.
        }
        break;
    // ...
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


이처럼 WinAPI를 이용한 윈도우 및 컨트롤 생성, 관리, 이벤트 처리는 복잡해 보일 수 있지만, 한 번 익혀두면 강력한 GUI 애플리케이션을 만드는 데 도움이 될 것입니다. 다음 섹션에서는 이러한 기본적인 원리를 바탕으로 더 복잡한 GUI 프로그래밍에 대해 알아보겠습니다. 

 

15.3.3. WinAPI를 이용한 이벤트 처리  

GUI 프로그래밍에서 이벤트 처리는 핵심적인 부분입니다. 사용자의 동작, 예를 들면 버튼 클릭이나 키보드 입력 등을 프로그램이 감지하고 그에 따라 적절하게 반응하는 과정을 말합니다. 이는 '메시지 기반'으로 이루어집니다. 사용자의 동작이나 시스템의 변경 사항들이 '메시지'로 변환되고, 우리의 프로그램은 이 메시지를 해석하고 처리합니다. 이 섹션에서는 WinAPI를 사용하여 이런 이벤트 처리를 어떻게 구현하는지 살펴보겠습니다. 

 

이벤트 처리는 보통 '메시지 루프' 내에서 이루어집니다. 메시지 루프는 프로그램이 실행되는 동안 계속해서 메시지를 체크하고, 있으면 해당 메시지를 처리하는 구조입니다. 아래의 코드는 메시지 루프의 기본적인 형태를 보여줍니다.

 

[예제]

MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}


이 코드에서 GetMessage 함수는 메시지 큐에서 메시지를 가져옵니다. 메시지가 없으면 함수는 대기 상태로 전환되어 새 메시지가 도착할 때까지 기다립니다. TranslateMessage 함수는 키보드 입력과 관련된 특정 메시지를 변환하며, DispatchMessage 함수는 메시지를 해당 윈도우의 '윈도우 프로시저'로 전달합니다.

 

윈도우 프로시저는 우리가 정의한 함수로, 윈도우가 받는 메시지를 어떻게 처리할지를 결정합니다. 아래는 윈도우 프로시저의 기본적인 형태입니다.

 

[예제]

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}


이 함수는 윈도우 핸들, 메시지, 그리고 추가 정보를 인자로 받습니다. switch 문을 사용하여 메시지의 종류에 따라 다른 작업을 수행합니다. 여기서는 WM_DESTROY 메시지만 처리하고 있으며, 이 메시지가 들어오면 PostQuitMessage 함수를 호출하여 프로그램을 종료합니다. 그 외의 메시지는 DefWindowProc 함수에 의해 기본적인 처리를 받습니다.

 

이러한 방식을 통해, WinAPI를 이용하면 다양한 이벤트 처리와 메시지 핸들링이 가능합니다. 이는 사용자의 행동에 따라 동적으로 반응하는 GUI 프로그램을 작성하는데 필수적인 요소입니다.

 

아래 코드는 간단한 버튼 클릭 이벤트를 처리하는 예제입니다. 버튼을 클릭하면 메시지 박스가 출력되는 프로그램입니다. 이를 통해 WinAPI에서 이벤트 처리가 어떻게 이루어지는지 간략하게 알아볼 수 있습니다.

 

[예제]

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    static HWND hwndButton;
    switch (msg) {
    case WM_CREATE:
        hwndButton = CreateWindow(TEXT("button"), TEXT("Click me"),
                                  WS_VISIBLE | WS_CHILD,
                                  10, 10, 80, 25,
                                  hwnd, (HMENU) 1, NULL, NULL);
        break;
    case WM_COMMAND:
        if (LOWORD(wParam) == 1) {
            MessageBox(hwnd, TEXT("Button clicked!"), TEXT("Notification"), MB_OK);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}


이 프로그램에서 WM_CREATE 메시지는 윈도우가 처음 생성될 때 발생합니다. 이 메시지가 발생하면 버튼을 생성합니다. WM_COMMAND 메시지는 버튼이 클릭되었을 때 발생하며, 이때 메시지 박스를 출력합니다. 이런 방식으로 각각의 메시지에 대해 적절한 처리를 수행할 수 있습니다.

 

이처럼, 이벤트 처리는 GUI 프로그래밍의 중요한 부분입니다. 사용자의 동작을 적절히 인식하고, 그에 따른 반응을 프로그래밍하는 것이 필요합니다. WinAPI를 이용하면 이런 작업을 효과적으로 수행할 수 있습니다.

 

더 자세히 설명하자면, 각 컨트롤은 고유의 메시지를 가지며, 이를 이용해서 특정 동작에 반응하도록 프로그램을 작성할 수 있습니다. 예를 들어, 텍스트 박스는 사용자가 텍스트를 입력하면 WM_CHAR 메시지를 발생시키고, 체크 박스는 체크 상태가 변경되면 WM_COMMAND 메시지와 함께 BN_CLICKED 노티피케이션 코드를 발생시킵니다.

 

다음은 '텍스트 박스에 입력된 텍스트를 버튼 클릭 시 메시지 박스로 출력하는' 프로그램의 예시입니다.

 

[예제]

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    static HWND hwndEdit, hwndButton;
    static TCHAR szText[100];
    switch (msg) {
    case WM_CREATE:
        hwndEdit = CreateWindow(TEXT("edit"), NULL,
                                WS_VISIBLE | WS_CHILD | WS_BORDER |
                                ES_AUTOHSCROLL,
                                10, 10, 180, 25,
                                hwnd, (HMENU) 1, NULL, NULL);
        hwndButton = CreateWindow(TEXT("button"), TEXT("Show"),
                                  WS_VISIBLE | WS_CHILD,
                                  10, 40, 80, 25,
                                  hwnd, (HMENU) 2, NULL, NULL);
        break;
    case WM_COMMAND:
        if (LOWORD(wParam) == 2) {
            GetWindowText(hwndEdit, szText, 100);
            MessageBox(hwnd, szText, TEXT("Your text"), MB_OK);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}


이 코드에서 WM_CREATE 메시지가 발생하면 텍스트 박스와 버튼을 생성합니다. 버튼이 클릭되면 WM_COMMAND 메시지가 발생하며, 이때 텍스트 박스에서 텍스트를 가져와 메시지 박스로 출력합니다. 이런 방식으로 각 컨트롤의 특성에 따른 이벤트 처리를 구현할 수 있습니다.

 

WinAPI에서 제공하는 다양한 컨트롤들은 각각 고유한 메시지와 동작을 가지며, 이를 이용하여 다양한 GUI 프로그램을 작성할 수 있습니다. 이벤트 처리는 이런 동작들을 제어하는 핵심적인 부분이므로, 각 메시지와 그에 따른 동작을 잘 이해하고 있어야 합니다.

 

위에서 언급한 것처럼, 각 컨트롤에는 고유한 메시지가 있고, 이 메시지들은 사용자의 입력이나 다른 윈도우와의 상호작용에 따라 시스템에 의해 발생합니다. 메시지가 발생하면 WinAPI는 애플리케이션의 메시지 큐에 메시지를 저장하고, 메시지 루프는 큐에 저장된 메시지를 하나씩 가져와 처리합니다. 메시지 루프는 일반적으로 다음과 같이 구현됩니다.

 

[예제]

MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}


GetMessage 함수는 메시지 큐에서 메시지를 하나씩 가져오며, 메시지 큐가 비어있으면 프로그램이 블로킹됩니다. 가져온 메시지는 TranslateMessage와 DispatchMessage 함수를 통해 처리되며, DispatchMessage 함수는 해당 메시지를 처리할 윈도우 프로시저로 메시지를 전달합니다.

 

윈도우 프로시저는 앞서 설명한 것처럼 메시지에 따라 다른 동작을 수행하며, 이를 이용해서 윈도우와 컨트롤의 동작을 제어합니다. 윈도우 프로시저는 애플리케이션에 따라 다르게 구현할 수 있지만, 기본적으로는 모든 메시지를 처리할 수 있는 형태로 구현되어야 합니다.

 

이제 WinAPI를 이용한 GUI 프로그래밍에 대한 기본적인 이해를 가지셨다면, 다음 단계는 이러한 지식을 바탕으로 실제 애플리케이션을 만들어 보는 것입니다. 실제로 코드를 작성하면서 여러 가지 문제와 도전을 경험하게 되면, 이론적인 지식을 실제 문제 해결에 활용하는 방법을 배울 수 있습니다. 그러므로, 가능하다면 실제로 간단한 GUI 애플리케이션을 만들어 보는 것을 추천드립니다. 이를 통해 이 섹션에서 배운 지식을 실제 코드에 적용하고, 코드가 어떻게 동작하는지 이해하는 데 도움이 될 것입니다. 

 

다음은 WinAPI를 사용하여 버튼 클릭 이벤트를 처리하는 예입니다.

 

[예제]

// 메시지 처리 함수
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_COMMAND:
            if (LOWORD(wParam) == IDC_BUTTON1) {
                MessageBox(hwnd, "버튼 클릭!", "이벤트", MB_OK);
            }
            break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}


위의 코드에서는 WM_COMMAND 메시지를 통해 버튼 클릭 이벤트를 처리하고 있습니다. wParam의 하위 단어(LOWORD)가 버튼1의 ID와 일치할 때, 메시지 박스를 띄워 "버튼 클릭!"이라는 메시지를 표시합니다.

 

이러한 이벤트 처리를 통해 우리는 사용자의 액션에 따라 다양한 동작을 수행할 수 있게 됩니다. 또한 이벤트 처리를 통해 우리는 유저 인터페이스를 통해 사용자와 애플리케이션 간의 상호작용을 가능하게 합니다. 

 

이 예제를 통해, WinAPI를 사용하여 GUI 프로그래밍을 하면서 이벤트를 처리하는 방법에 대해 알아보았습니다. 이론적인 지식과 실제 코드를 비교하면서 이해를 깊게 하시는 것이 중요합니다. 이런 방식으로 학습을 진행하면, WinAPI와 GUI 프로그래밍에 대한 깊은 이해를 얻을 수 있습니다. 

 

여러 가지 컨트롤(버튼, 체크박스, 라디오 버튼 등)에 대한 다양한 이벤트를 처리하는 방법을 자세히 살펴볼 것입니다. 특히 컨트롤마다 고유한 이벤트가 존재하며, 이를 어떻게 처리하는지 이해하는 것이 중요합니다.  

 

예를 들어, 체크박스는 BM_GETCHECK 메시지를 사용하여 체크 상태를 가져올 수 있고, 라디오 버튼은 그룹 내에서 선택된 버튼을 확인하기 위해 BM_GETCHECK와 IsDlgButtonChecked 함수를 사용할 수 있습니다. 이와 같이 WinAPI는 다양한 컨트롤에 대응하는 이벤트 처리 메커니즘을 제공하고 있습니다.  

 

[예제]

case WM_COMMAND:
    switch (LOWORD(wParam)) {
        case IDC_BUTTON1:
            MessageBox(hwnd, "버튼 클릭!", "이벤트", MB_OK);
            break;
        case IDC_CHECKBOX1:
            if (SendMessage(GetDlgItem(hwnd, IDC_CHECKBOX1), BM_GETCHECK, 0, 0) == BST_CHECKED) {
                MessageBox(hwnd, "체크박스 체크!", "이벤트", MB_OK);
            }
            break;
        case IDC_RADIO1:
            if (IsDlgButtonChecked(hwnd, IDC_RADIO1)) {
                MessageBox(hwnd, "라디오 버튼 선택!", "이벤트", MB_OK);
            }
            break;
    }
    break;


위 코드는 버튼, 체크박스, 라디오 버튼 클릭 시 각각 다른 동작을 수행하는 예제입니다. 각 컨트롤에 대한 클릭 이벤트를 WM_COMMAND 메시지 내에서 LOWORD(wParam)을 통해 구분하고 있습니다. 

 

이러한 방식으로 WinAPI는 다양한 컨트롤에 대해 굉장히 유연한 이벤트 처리를 가능하게 합니다. 앞으로의 학습에서는 이외에도 다양한 컨트롤들과 그에 대한 이벤트 처리 방법을 살펴보면서, 복잡한 윈도우 애플리케이션 개발에 필요한 기본기를 키워 나갈 것입니다. 그러므로 꼭 기억해 두시고, 다음 섹션에 이어서 학습해 나가시기 바랍니다. 

 

윈도우 메시지 처리에서 중요한 개념인 메시지 큐에 대해 이해하는 것이 중요합니다. 윈도우 OS는 사용자의 마우스 이동, 클릭, 키보드 입력 등을 메시지로 변환하여 메시지 큐에 저장하고, 각 애플리케이션은 이 메시지 큐를 순서대로 처리하면서 사용자의 입력에 반응합니다.  

 

이벤트 처리의 핵심은 GetMessage 함수를 이용하여 메시지 큐에서 메시지를 가져오고, TranslateMessage 및 DispatchMessage 함수를 이용하여 메시지를 처리하는 것입니다. 아래의 코드는 메시지 루프를 생성하는 기본적인 방법을 보여줍니다.

 

[예제]

MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}


GetMessage 함수는 메시지 큐에서 메시지를 가져오며, 메시지 큐에 메시지가 없을 경우 대기합니다. 가져온 메시지는 TranslateMessage 함수를 통해 키보드 입력에 대한 추가 처리를 하고, DispatchMessage 함수를 통해 해당 윈도우의 윈도우 프로시저로 메시지를 보냅니다. 

 

이렇게 처리된 메시지는 윈도우 프로시저 내부의 switch 문에서 처리되며, 이곳에서 사용자가 정의한 이벤트 핸들러가 호출됩니다. 이는 앞서 우리가 본 WM_COMMAND 메시지 처리가 대표적인 예입니다. 

 

이처럼, 메시지 처리는 GUI 프로그래밍의 핵심적인 요소이며, 이를 통해 사용자의 입력에 따른 다양한 이벤트를 처리할 수 있습니다. 이후의 강좌에서는 이러한 메시지 처리를 통해 다양한 GUI 컴포넌트를 동작시키는 방법에 대해 자세히 알아보겠습니다. 

 

이처럼, 윈도우 메시지 처리는 사용자 인터페이스를 구현하는데 필수적인 요소입니다. 각각의 메시지는 윈도우와 상호작용하는 사용자의 동작을 나타내며, 이를 적절히 처리함으로써 사용자가 의도한 동작을 프로그램에 반영할 수 있습니다. 

 

그러나 이런 방식으로 메시지를 처리하는 것은 다소 번거롭게 느껴질 수 있습니다. 특히, 복잡한 GUI 애플리케이션을 개발하는 경우 수백, 수천 가지의 메시지를 모두 직접 처리하는 것은 매우 큰 작업이 될 것입니다. 이런 이유로, 실제로는 다양한 GUI 라이브러리와 프레임워크를 사용하여 메시지 처리를 보다 편리하게 합니다. 

 

예를 들어, MFC(Microsoft Foundation Class) 라이브러리는 메시지 맵을 도입하여 메시지 처리 코드를 메서드 단위로 분리하고, 메시지 종류에 따라 적절한 메서드가 자동으로 호출되도록 구현하였습니다. 이렇게 하면, 개발자는 각 메시지를 처리하는 코드를 보다 명확하게 작성할 수 있습니다. 

 

[예제]

BEGIN_MESSAGE_MAP(CMyWindow, CWnd)
    ON_WM_PAINT()
    ON_WM_SIZE()
    // ...
END_MESSAGE_MAP()


위의 코드는 MFC를 사용한 메시지 처리의 예입니다. BEGIN_MESSAGE_MAP과 END_MESSAGE_MAP 사이에 필요한 메시지 처리를 메서드 형태로 정의하면, 윈도우 프로시저는 이를 알아서 처리해줍니다. 

 

마지막으로, 메시지 처리와 관련된 세부적인 내용, 특히 메시지의 종류와 각 메시지를 어떻게 처리해야 하는지는 MSDN의 문서를 참조하는 것이 좋습니다. 이 문서는 모든 윈도우 메시지에 대한 상세한 정보와 예제 코드를 제공하며, 필요한 정보를 찾는데 매우 유용합니다. 


15.4. MFC를 이용한 GUI 프로그래밍  

MFC(Microsoft Foundation Classes)는 Microsoft에서 제공하는 C++ 클래스 라이브러리로, Windows GUI 프로그래밍을 쉽게 만들어 줍니다. MFC는 동적으로 메모리를 할당하고 해제하는 등의 일반적인 프로그래밍 작업을 단순화하는 데 도움이 되지만, 주로 윈도우 및 대화상자, 버튼, 텍스트 상자 등의 GUI 요소를 생성하고 관리하는데 사용됩니다. MFC를 사용하면 개발자는 윈도우 메시지 처리와 같은 복잡한 작업을 직접 할 필요 없이, 상대적으로 간단한 코드로 복잡한 GUI 애플리케이션을 만들 수 있습니다.  

15.4.1. MFC의 이해  

MFC(Microsoft Foundation Classes)는 마이크로소프트에서 개발한 C++ 클래스 라이브러리로, 개발자가 Windows GUI 애플리케이션을 보다 쉽게 만들 수 있도록 돕는 도구입니다. 

 

MFC는 많은 미리 만들어진 클래스들로 구성되어 있으며, 이들은 운영체제와 상호작용하는 데 필요한 함수를 간편하게 사용할 수 있도록 해줍니다. 예를 들어, MFC에는 윈도우를 생성하고, 메시지를 처리하고, 유저 인터페이스를 그리는 등의 일반적인 작업을 수행하는 데 필요한 클래스들이 포함되어 있습니다. 

 

[예제]

#include <afxwin.h>  

class SimpleApp : public CWinApp {
public:
  BOOL InitInstance() {
    m_pMainWnd = new CFrameWnd;
    m_pMainWnd->Create(NULL, _T("Hello MFC"));
    m_pMainWnd->ShowWindow(m_nCmdShow);
    m_pMainWnd->UpdateWindow();
    return TRUE;
  }
};  

SimpleApp theApp;

 

위 예제는 MFC를 사용하여 기본 윈도우를 만드는 코드입니다. CWinApp 클래스를 상속받은 SimpleApp 클래스를 만들고, 윈도우를 생성하고 표시하는 InitInstance 함수를 오버라이드 합니다. 이렇게 하면 운영체제가 애플리케이션을 실행하면서 InitInstance 함수를 호출하게 되고, 이 때 윈도우가 생성됩니다. 이렇게 MFC를 사용하면 윈도우를 생성하고 관리하는 작업이 간단해집니다. 

 

MFC는 이외에도 대화상자, 버튼, 메뉴 등의 GUI 요소를 만들고 관리하는 클래스를 제공하며, 이들은 CDialog, CButton, CMenu 등의 클래스를 통해 사용할 수 있습니다. MFC를 사용하면 Windows 프로그래밍에서 발생할 수 있는 복잡한 작업들을 쉽게 처리할 수 있습니다. 

 

다만, MFC는 C++을 기반으로 하므로 C++의 기본적인 개념에 익숙해져야 합니다. 또한, MFC는 Windows 전용이므로 다른 운영체제에서는 사용할 수 없습니다. 

 

MFC를 사용하는 데에는 특정한 패턴이 필요하며, 이를 문서/뷰 아키텍처라고 부릅니다. 문서/뷰 아키텍처는 유저 인터페이스와 데이터를 분리하여 프로그램의 구조를 단순화하는 디자인 패턴입니다. 

 

문서 클래스는 애플리케이션의 데이터와 관련된 로직을 담당하며, 뷰 클래스는 유저 인터페이스와 관련된 로직을 담당합니다. 이를 통해 유저 인터페이스와 데이터 로직을 분리하고, 서로 독립적으로 개발할 수 있게 됩니다. 

 

[예제]

class SimpleDoc : public CDocument {
  // 데이터 관련 로직
};  

class SimpleView : public CView {
  // 유저 인터페이스 관련 로직
};  

class SimpleApp : public CWinApp {
public:
  BOOL InitInstance() {
    AddDocTemplate(new CSingleDocTemplate(IDR_MAINFRAME,
      RUNTIME_CLASS(SimpleDoc),
      RUNTIME_CLASS(CFrameWnd),
      RUNTIME_CLASS(SimpleView)));
    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);
    if (!ProcessShellCommand(cmdInfo))
      return FALSE;
    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();
    return TRUE;
  }
};  

SimpleApp theApp;

 

위 예제는 문서/뷰 아키텍처를 사용하는 MFC 애플리케이션의 코드입니다. CDocument를 상속 받은 SimpleDoc 클래스와 CView를 상속 받은 SimpleView 클래스를 만들고, 이들을 CSingleDocTemplate을 통해 연결합니다. 

 

이렇게 MFC를 사용하면 복잡한 Windows 프로그래밍 작업을 간편하게 수행할 수 있습니다. 또한, 코드의 가독성과 유지보수성을 향상시키는 데에도 도움이 됩니다. 

 

하지만, MFC를 사용하려면 C++와 Windows 프로그래밍에 대한 이해가 필요하며, MFC 자체의 학습 곡선이 가파를 수 있습니다. 따라서 초보자가 MFC를 사용하기 전에 기본적인 C++과 Windows 프로그래밍에 대해 충분히 이해하고 있어야 합니다. 

 

이벤트 핸들링 및 메시지 맵

MFC에서는 이벤트 핸들링을 위해 '메시지 맵'이라는 개념을 도입하였습니다. 메시지 맵은 Windows 메시지와 그 메시지를 처리할 함수를 매핑하는 역할을 합니다.  

 

[예제]

BEGIN_MESSAGE_MAP(SimpleView, CView)
  ON_WM_PAINT()
END_MESSAGE_MAP()  

void SimpleView::OnPaint() {
  CPaintDC dc(this);
  // 그리기 코드
}

 

위 예제는 WM_PAINT 메시지가 SimpleView로 전달될 때 OnPaint 함수를 호출하도록 메시지 맵을 설정하는 방법을 보여줍니다. BEGIN_MESSAGE_MAP 매크로로 메시지 맵을 시작하고, ON_WM_PAINT() 매크로로 WM_PAINT 메시지를 OnPaint 함수와 연결합니다. END_MESSAGE_MAP 매크로로 메시지 맵을 끝냅니다. 

 

이렇게 MFC를 이용하면 복잡한 이벤트 처리 로직을 간단하게 작성할 수 있습니다. 메시지 맵은 메시지 처리 함수를 쉽게 찾을 수 있도록 하여 코드의 가독성을 높여줍니다. 

 

다이얼로그 및 컨트롤 사용

MFC는 다이얼로그와 컨트롤의 사용을 쉽게 해주는 다양한 클래스와 기능을 제공합니다. 예를 들어, 다이얼로그는 CDialog 클래스를 사용하여 생성하고 관리할 수 있습니다. 

 

[예제]

class SimpleDialog : public CDialog {
  // 다이얼로그 관련 로직
};  

SimpleDialog dlg;
dlg.DoModal();

 

위 예제는 CDialog를 상속받아 SimpleDialog 클래스를 만들고, DoModal 함수를 호출하여 다이얼로그를 표시하는 방법을 보여줍니다. 

 

또한, MFC에서는 버튼, 텍스트 박스 등의 컨트롤을 사용하기 위해 CButton, CEdit 등의 클래스를 제공합니다. 이 클래스들은 각 컨트롤의 생성, 관리, 이벤트 처리 등을 쉽게 할 수 있게 도와줍니다. 

 

MFC 클래스 계층 구조

MFC는 객체 지향 프로그래밍(OOP)의 장점을 최대한 활용하기 위해 클래스 계층 구조를 제공합니다. 이 계층 구조는 두 가지 주요한 부분으로 나뉘는데, 하나는 애플리케이션과 문서, 뷰, 프레임 등 GUI 요소를 관리하는 클래스들이고, 다른 하나는 Windows 컨트롤을 래핑하는 클래스들입니다. 

 

[예제]

class CMyApp : public CWinApp {
  // 애플리케이션 클래스의 구현
};  

class CMyFrame : public CFrameWnd {
  // 프레임 윈도우 클래스의 구현
};  

class CMyView : public CView {
  // 뷰 클래스의 구현
};

 

위 예제는 MFC 클래스 계층 구조의 일부를 보여줍니다. CMyApp, CMyFrame, CMyView 클래스는 각각 CWinApp, CFrameWnd, CView 클래스를 상속받아 만들어졌습니다. 

 

이런 방식을 사용하면, 프로그래머는 기본적인 윈도우 동작을 제공하는 MFC의 기본 클래스를 상속받아 필요한 기능만 추가하거나 수정함으로써 빠르고 쉽게 윈도우 애플리케이션을 만들 수 있습니다. 

 

MFC 애플리케이션의 생명주기

MFC 애플리케이션의 생명주기는 CWinApp 클래스를 통해 시작됩니다. 이 클래스의 객체는 애플리케이션의 시작과 종료를 관리하며, 메인 윈도우와 모든 동작을 제어합니다. 

 

다음은 CWinApp에서 애플리케이션의 시작과 종료를 관리하는 InitInstance와 ExitInstance 함수의 사용 예입니다. 

 

[예제]

class CMyApp : public CWinApp {
public:
  virtual BOOL InitInstance() {
    // 애플리케이션 초기화
    return TRUE;
  }

  virtual int ExitInstance() {
    // 애플리케이션 종료
    return CWinApp::ExitInstance();
  }
};

 

InitInstance 함수는 애플리케이션의 초기화를 담당하며, 이 함수에서는 주로 메인 윈도우를 만드는 등의 작업이 이루어집니다. ExitInstance 함수는 애플리케이션의 종료를 담당하며, 이 함수에서는 주로 생성한 리소스의 해제 등의 작업이 이루어집니다.

 

15.4.2. MFC를 이용한 윈도우 생성

MFC (Microsoft Foundation Classes) 라이브러리를 이용하면 Windows 윈도우를 손쉽게 생성하고 관리할 수 있습니다. MFC는 다양한 클래스를 제공하며, 이들은 Windows API 함수를 래핑하여 사용하기 쉽게 만들어 줍니다. 이러한 클래스 중 가장 핵심적인 것은 CWinApp와 CFrameWnd 클래스입니다. 

 

1. CWinApp 클래스

CWinApp 클래스는 MFC 애플리케이션의 시작점입니다. 애플리케이션은 CWinApp 클래스를 상속받은 클래스의 인스턴스를 생성하여 시작하며, 이 인스턴스는 애플리케이션의 메인 윈도우를 생성하고 메시지 루프를 시작하는 역할을 합니다. 

 

다음은 CWinApp 클래스를 상속받은 CMyApp 클래스의 예입니다.

 

[예제]

class CMyApp : public CWinApp {
public:
  BOOL InitInstance() override {
    m_pMainWnd = new CMyFrame;
    m_pMainWnd->ShowWindow(m_nCmdShow);
    m_pMainWnd->UpdateWindow();
    return TRUE;
  }
};

 

위 코드에서 InitInstance 메서드는 애플리케이션의 초기화를 담당합니다. 이 메서드에서 CMyFrame 클래스의 인스턴스를 생성하고 이를 메인 윈도우로 설정한 후, ShowWindow와 UpdateWindow 메서드를 호출하여 윈도우를 화면에 표시합니다. 

 

2. CFrameWnd 클래스

CFrameWnd 클래스는 윈도우 프레임을 나타내는 클래스입니다. 이 클래스를 상속받아 새 클래스를 만들고, 이 클래스의 인스턴스를 생성하여 윈도우 프레임을 만들 수 있습니다.

 

다음은 CFrameWnd 클래스를 상속받은 CMyFrame 클래스의 예입니다.

 

[예제]

class CMyFrame : public CFrameWnd {
public:
  CMyFrame() {
    Create(NULL, _T("MFC Window"));
  }
};

 

위 코드에서 Create 메서드는 윈도우 프레임을 생성하는 메서드입니다. 이 메서드는 윈도우의 이름을 인자로 받아 윈도우를 생성합니다.

 

3. MFC 윈도우 프로그램 시작하기

그렇다면, 실제로 MFC를 사용한 윈도우 프로그램은 어떻게 시작할까요? 윈도우 프로그램은 main 함수 대신 WinMain 함수에서 시작하지만, MFC 프로그램은 CWinApp 클래스의 인스턴스를 생성하여 시작합니다. MFC 프로그램의 시작점은 전역 객체로 생성된 CWinApp 파생 클래스의 인스턴스입니다. 

 

아래의 코드는 MFC를 이용하여 윈도우를 생성하는 간단한 예제입니다.

 

[예제]

class CMyFrame : public CFrameWnd {
public:
  CMyFrame() {
    Create(NULL, _T("MFC Window"));
  }
};

class CMyApp : public CWinApp {
public:
  BOOL InitInstance() override {
    m_pMainWnd = new CMyFrame;
    m_pMainWnd->ShowWindow(m_nCmdShow);
    m_pMainWnd->UpdateWindow();
    return TRUE;
  }
};

CMyApp theApp;

 

위 코드에서 마지막 줄 CMyApp theApp;은 전역 객체 theApp을 생성하며 프로그램을 시작합니다. CMyApp 클래스는 CWinApp 클래스를 상속받았으며, CWinApp 클래스의 InitInstance 메서드를 오버라이드하여 윈도우 프레임을 생성하고 표시하는 역할을 담당합니다. 

 

15.4.3. MFC를 이용한 이벤트 처리

WinAPI에서와 마찬가지로, MFC에서도 사용자의 입력이나 시스템의 변화에 반응하여 코드를 실행하는 이벤트 처리가 중요합니다. 그러나 MFC는 이벤트 처리를 더욱 간결하고 직관적으로 할 수 있도록 설계되어 있습니다.

 

MFC에서는 이벤트 처리를 위해 메시지 맵이라는 기술을 사용합니다. 메시지 맵은 클래스 내에 메시지와 이에 대응하는 처리 함수를 연결하는 매크로를 정의하는 방법입니다. 즉, 메시지를 처리할 함수를 직접 작성하고 메시지 맵에 등록함으로써 해당 메시지가 도착했을 때 자동으로 호출되도록 하는 것입니다.

 

아래 코드는 MFC를 이용하여 클릭 이벤트를 처리하는 간단한 예입니다.

 

[예제]

class CMyButton : public CButton {
public:
  DECLARE_MESSAGE_MAP() // 메시지 맵 선언
};

BEGIN_MESSAGE_MAP(CMyButton, CButton) // 메시지 맵 시작
  ON_WM_LBUTTONDOWN() // 마우스 왼쪽 버튼 클릭 이벤트
END_MESSAGE_MAP() // 메시지 맵 끝

void CMyButton::OnLButtonDown(UINT nFlags, CPoint point) { 
  // 마우스 왼쪽 버튼 클릭 처리 함수
  MessageBox(_T("Button clicked!"), _T("Event"), MB_OK);
  CButton::OnLButtonDown(nFlags, point);
}

 

위 코드에서 CMyButton 클래스는 CButton 클래스를 상속받습니다. DECLARE_MESSAGE_MAP() 매크로를 사용하여 메시지 맵을 선언하고, BEGIN_MESSAGE_MAP()과 END_MESSAGE_MAP() 매크로 사이에 이벤트와 이를 처리할 함수를 연결합니다. 이 예제에서는 마우스 왼쪽 버튼 클릭 이벤트와 이를 처리할 OnLButtonDown() 함수를 연결하였습니다. 

 

이벤트 처리를 MFC로 작성할 때, 처리해야 할 이벤트의 종류에 따라 다양한 함수를 이용할 수 있습니다. 위의 예시에서는 마우스 클릭 이벤트를 처리하는 함수 OnLButtonDown()를 사용했지만, 키보드 입력, 윈도우 크기 변경, 메뉴 선택 등 다양한 이벤트에 대응하는 다양한 함수가 존재합니다.

 

이러한 함수들은 모두 MFC 프레임워크에 의해 자동으로 호출되며, 프로그래머는 해당 함수가 호출될 때 수행해야 할 작업만을 정의하면 됩니다. 예를 들어, 사용자가 버튼을 클릭하면 메시지 박스를 표시하도록 하려면, OnLButtonDown() 함수 내에 메시지 박스를 표시하는 코드를 작성하면 됩니다.

 

이렇게 MFC를 이용하면, 이벤트 처리 로직을 쉽고 간결하게 작성할 수 있습니다. 복잡한 이벤트 처리 로직을 WinAPI로 직접 작성하는 것에 비해 훨씬 빠르게 구현할 수 있으며, 코드의 가독성도 높아집니다.

 

하지만, MFC를 통한 이벤트 처리에는 몇 가지 주의점이 있습니다. 첫째, 모든 이벤트 처리 함수는 반드시 메시지 맵 내에 정의되어야 합니다. 메시지 맵 밖에서 이벤트 처리 함수를 정의하면, 해당 함수는 호출되지 않습니다. 둘째, 이벤트 처리 함수는 대부분 MFC의 내장 함수를 오버라이딩하는 형태로 정의됩니다. 따라서, 원래 함수가 수행하던 기본 동작을 유지하려면 이벤트 처리 함수 내에서 원래 함수를 명시적으로 호출해야 합니다. 위의 예시에서 CButton::OnLButtonDown(nFlags, point)는 이에 해당하는 부분입니다.

 

이벤트 처리를 활용한 실용적인 GUI 프로그래밍에 대해 살펴봅니다. 예를 들어, 여러 종류의 컨트롤(버튼, 텍스트 박스, 리스트 박스 등)을 이용하거나, 다양한 윈도우 스타일을 적용하거나, 메뉴와 툴바를 추가하는 방법 등을 다룹니다. 이러한 기능들은 모두 MFC의 강력한 이벤트 처리 기능을 기반으로 합니다.

 

또한, MFC 프로그래밍에서는 다양한 UI 디자인 패턴을 이용할 수 있습니다. 예를 들어, Document/View 구조를 이용하면, 데이터(Model)와 그를 표시하는 인터페이스(View)를 분리하여 관리할 수 있습니다. 이는 유지보수를 쉽게 하고 코드의 가독성을 높여주는 장점이 있습니다.

 

MFC는 복잡한 GUI 애플리케이션을 쉽게 개발할 수 있도록 강력한 기능을 제공합니다. 하지만, 그만큼 높은 수준의 추상화를 제공하므로, 초기 학습 곡선은 다소 가파를 수 있습니다. 그러나 MFC를 통해 제공되는 다양한 클래스와 메서드, 이벤트 처리 메커니즘을 이해하고 나면, 훨씬 생산적이고 효율적인 GUI 프로그래밍이 가능해집니다.

 

마지막으로, MFC를 이용한 GUI 프로그래밍에서는 디버깅이 중요한 역할을 합니다. UI는 사용자의 입력에 따라 다양한 상태 변화를 가지므로, 문제가 발생했을 때 그 원인을 찾는 것이 쉽지 않을 수 있습니다. 따라서 코드의 실행 흐름을 이해하고, 예상치 못한 입력이나 상태 변화를 잘 처리할 수 있도록 충분히 테스트하고 디버깅하는 것이 중요합니다.

 

15.4.3.1. MFC의 메시지 맵

이벤트 처리를 위해 MFC에서는 메시지 맵이라는 특별한 기능을 제공합니다. 메시지 맵은 운영 체제로부터 발생하는 메시지와 이에 대응하는 함수를 연결하는 역할을 합니다. 이를 통해 개발자는 다양한 이벤트에 대응하는 코드를 간편하게 작성할 수 있습니다.

 

예를 들어, MFC를 사용하여 버튼 클릭 이벤트를 처리하는 코드는 다음과 같습니다.

 

[예제]

BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
    ON_BN_CLICKED(IDC_MYBUTTON, &CMyDialog::OnBnClickedMybutton)
END_MESSAGE_MAP()

void CMyDialog::OnBnClickedMybutton()
{
    // 버튼 클릭 시 실행할 코드
}

 

위 코드에서 ON_BN_CLICKED는 버튼 클릭 이벤트를 나타내는 메시지이고, OnBnClickedMybutton은 이 이벤트가 발생했을 때 호출될 함수입니다. 이렇게 메시지 맵을 통해 이벤트와 이에 대응하는 함수를 직관적으로 연결할 수 있습니다.

 

15.4.3.2. MFC의 메시지 처리 순서

MFC의 이벤트 처리는 아래와 같은 순서로 진행됩니다.

 

  • 운영 체제는 발생한 이벤트에 해당하는 메시지를 애플리케이션의 메시지 큐에 넣습니다.
  • MFC 프레임워크는 메시지 큐에서 메시지를 가져와 해당 메시지를 처리할 수 있는 메시지 핸들러를 찾습니다.
  • 메시지 핸들러(함수)가 메시지를 처리하고, 필요한 경우 화면을 다시 그립니다.
  • 이 과정을 통해 MFC 애플리케이션은 사용자의 입력, 시스템 이벤트 등 다양한 이벤트를 감지하고 적절하게 처리할 수 있습니다.

15.4.3.3. MFC에서 이벤트 처리하기

MFC에서 이벤트 처리는 굉장히 직관적입니다. 우선 이벤트가 발생한 컨트롤(예: 버튼)의 ID와 이벤트 핸들러 함수를 메시지 맵에 명시해야 합니다. 이벤트 핸들러는 이벤트가 발생했을 때 실행될 함수를 의미합니다. 

 

예를 들어, 버튼 클릭 이벤트를 처리하는 코드는 다음과 같습니다:

 

[예제]

BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
   ON_BN_CLICKED(IDC_BUTTON1, &CMyDialog::OnBnClickedButton1)
END_MESSAGE_MAP()

void CMyDialog::OnBnClickedButton1()
{
   // 버튼 클릭 시 실행할 코드를 이곳에 작성합니다.
   AfxMessageBox(_T("버튼이 클릭되었습니다!"));
}


이 코드는 IDC_BUTTON1 ID를 가진 버튼이 클릭되었을 때 OnBnClickedButton1 함수를 호출하도록 설정한 것입니다. OnBnClickedButton1 함수는 사용자가 버튼을 클릭했을 때 실행될 코드를 포함하고 있습니다. 이 예제에서는 메시지 박스를 표시하는 코드를 사용하였습니다. 

 

마지막으로, 알아두면 좋은 점은 MFC에서는 다양한 이벤트에 대해 미리 정의된 메시지가 있습니다. 예를 들어, ON_WM_PAINT 메시지는 윈도우의 내용을 다시 그려야 할 때, ON_WM_SIZE 메시지는 윈도우의 크기가 변경될 때 발생합니다. 이런 메시지들을 이용하면 다양한 이벤트를 쉽게 처리할 수 있습니다. 

 

MFC 프로그래밍에서 메시지 맵을 활용한 이벤트 처리는 꽤 중요한 개념입니다. MFC는 윈도우 메시지를 클래스 멤버 함수와 자동으로 연결해주는 메시지 맵이라는 기능을 제공합니다. 이는 클래스 인스턴스와 그 멤버 함수를 사용자 정의 이벤트와 맵핑함으로써 이벤트 기반 프로그래밍을 용이하게 합니다. 

 

예를 들어, MFC에서는 다음과 같이 윈도우 크기 변경 이벤트를 처리할 수 있습니다:

 

[예제]

class CMyWindow : public CWnd
{
public:
   // 클래스 선언부에 메시지 맵 매크로를 선언합니다.
   DECLARE_MESSAGE_MAP()

   afx_msg void OnSize(UINT nType, int cx, int cy);
   // 다른 멤버 함수와 변수들...
};

// cpp 파일에 메시지 맵을 구현합니다.
BEGIN_MESSAGE_MAP(CMyWindow, CWnd)
   ON_WM_SIZE()
END_MESSAGE_MAP()

void CMyWindow::OnSize(UINT nType, int cx, int cy)
{
   CWnd::OnSize(nType, cx, cy);
   // 여기에 윈도우 크기가 변경될 때 수행할 작업을 코딩합니다.
}


이 코드는 ON_WM_SIZE() 매크로를 사용하여 OnSize 멤버 함수를 WM_SIZE 윈도우 메시지에 연결하고 있습니다. 이렇게 하면 윈도우 크기가 변경될 때마다 시스템에서 자동으로 OnSize 함수를 호출합니다. 

 

WinAPI에서의 메시지 처리보다 훨씬 간결하며 읽기 쉽다는 장점이 있습니다. 이런 편의성 덕분에, MFC는 오랫동안 윈도우즈 기반의 복잡한 GUI 애플리케이션 개발을 위해 널리 사용되어 왔습니다.

 

마우스 클릭 이벤트를 처리하려면 ON_WM_LBUTTONDOWN() 매크로를 사용하고, 키보드 입력 이벤트를 처리하려면 ON_WM_KEYDOWN() 매크로를 사용합니다. 이들 매크로는 각각 OnLButtonDown, OnKeyDown 등의 멤버 함수와 연결됩니다. 

 

메시지 맵에 이벤트 핸들러를 등록하면, 그 이벤트가 발생할 때마다 MFC 프레임워크가 알아서 해당 함수를 호출해 주므로 개발자는 이벤트 처리 로직에 집중할 수 있습니다.

 

마지막으로, MFC를 이용한 이벤트 처리에는 두 가지 주의 사항이 있습니다.

  • 첫째, 메시지 맵은 반드시 cpp 파일에 구현되어야 합니다. h 파일에 구현하면 컴파일 에러가 발생합니다.
  • 둘째, afx_msg 키워드는 반드시 이벤트 핸들러 함수 선언 앞에 붙여야 합니다. 이 키워드는 MFC의 내부 메커니즘에 관련되어 있으며, 컴파일러에게 이 함수가 윈도우 메시지 핸들러임을 알려주는 역할을 합니다.

15.5. Qt를 이용한 GUI 프로그래밍

Qt는 C++ 기반의 크로스 플랫폼 GUI 툴킷으로, 깔끔한 API와 풍부한 기능으로 인해 많은 개발자들에게 사랑받고 있습니다. 이 섹션에서는 Qt의 기본적인 구조와 클래스, 위젯 시스템에 대한 이해를 돕고, Qt를 이용해 간단한 GUI 애플리케이션을 만드는 방법에 대해 알아봅니다. 특히, Qt의 이벤트 처리와 시그널-슬롯 메커니즘을 이해하는 것이 중요합니다.

15.5.1. Qt의 이해

Qt는 C++를 기반으로 한 프레임워크로, 그 특징은 고급 GUI 프로그래밍 기능을 제공하면서도 운영 체제 간의 호환성을 유지합니다. 이로 인해, Qt를 사용하면 한 번의 코드 작성으로 다양한 플랫폼에서 실행 가능한 GUI 애플리케이션을 개발할 수 있습니다. 

 

Qt의 주요 컴포넌트 중 하나는 '위젯'입니다. 위젯은 윈도우, 버튼, 슬라이더, 체크 박스 등 GUI 요소를 나타냅니다. 각 위젯은 독립적인 기능을 가지며, 여러 위젯을 결합하여 복잡한 사용자 인터페이스를 만들 수 있습니다. 

 

Qt를 사용하는 데 있어 또 다른 중요한 개념은 '시그널과 슬롯'입니다. 시그널과 슬롯은 객체 간의 통신 방법을 제공하는 Qt의 특징적인 메커니즘입니다. 예를 들어, 버튼 위젯이 클릭되면 '클릭됨' 시그널이 발생하고, 이에 연결된 슬롯이 호출되어 해당 이벤트를 처리합니다. 

 

이해를 돕기 위해 간단한 Qt 애플리케이션을 만들어 보겠습니다. 먼저, 필요한 헤더 파일을 포함시킵니다:

 

[예제]

#include <QApplication>
#include <QPushButton>


그리고 main 함수를 작성합니다:

[예제]

int main(int argc, char **argv)
{
    QApplication app (argc, argv);

    QPushButton button ("Hello, Qt!");
    button.show();

    return app.exec();
}


위의 코드는 'Hello, Qt!'라는 텍스트를 가진 버튼을 생성하고 보여줍니다. QApplication 객체는 Qt 애플리케이션의 진입점을 나타내며, 이벤트 루프를 관리합니다. 

 

이것은 단순한 예제지만, Qt의 기본적인 사용 방법을 보여줍니다. '위젯'과 '시그널-슬롯'의 개념을 이해하면, 더 복잡한 GUI 애플리케이션을 만드는 데에도 Qt를 효과적으로 사용할 수 있습니다. 

 

이제부터 Qt에서 더 복잡한 상호작용을 달성하는 방법에 대해 설명하겠습니다. 위에서 언급했던 '시그널과 슬롯'의 개념을 활용하여 버튼이 클릭될 때 어떤 동작이 발생하도록 만들어 보겠습니다. 

 

우선 헤더 파일을 포함시킵니다.

 

[예제]

#include <QApplication>
#include <QPushButton>
#include <QMessageBox>


그리고 main 함수를 작성합니다:

[예제]

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    QPushButton button("Click me!");
    QObject::connect(&button, &QPushButton::clicked, [&]() {
        QMessageBox::information(nullptr, "Clicked", "You clicked the button!");
    });
    button.show();

    return app.exec();
}


이 예제에서는 '클릭됨' 시그널을 '메시지 박스를 보여주는' 슬롯에 연결했습니다. 따라서 사용자가 버튼을 클릭하면, 'You clicked the button!'이라는 메시지가 포함된 정보 메시지 박스가 표시됩니다. 이렇게 Qt의 시그널-슬롯 메커니즘을 활용하면 사용자의 입력에 따라 다양한 동작을 실행할 수 있습니다. 

 

시그널-슬롯 연결을 생성할 때 QObject::connect 함수를 사용합니다. 이 함수의 첫 번째 인자는 시그널을 발생시키는 객체, 두 번째 인자는 해당 객체의 시그널, 세 번째 인자는 슬롯을 실행할 함수입니다. 여기서 슬롯 함수로 람다 함수를 사용했는데, 이를 통해 코드를 간결하게 유지할 수 있습니다. 

 

Qt에서는 위젯뿐만 아니라, 시간이나 네트워크 이벤트 등 다양한 요소에 대한 시그널-슬롯 연결을 제공합니다. 이를 통해 복잡한 GUI 애플리케이션의 로직을 구성할 수 있습니다.

 

15.5.2. Qt를 이용한 윈도우 생성

Qt를 이용한 윈도우 생성은 아주 간단합니다. Qt의 핵심 클래스 중 하나인 QWidget 클래스를 사용하여 기본 윈도우를 생성할 수 있습니다. 

 

우선 가장 기본적인 윈도우를 생성하는 코드를 보겠습니다.

 

[예제]

#include <QApplication>
#include <QWidget>

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    QWidget window;
    window.resize(320, 240);
    window.setWindowTitle("Hello, World!");
    window.show();

    return app.exec();
}


이 코드에서 QApplication 객체를 생성하는 부분은 이전에 설명드린 것과 같습니다. 이 QApplication 객체는 애플리케이션의 수명 동안 유지되며, 이벤트 루프와 기타 시스템 설정을 관리합니다. 

 

다음으로, QWidget 객체인 window를 생성합니다. QWidget는 모든 유저 인터페이스 오브젝트의 기본 클래스이며, 여기에는 버튼, 레이블, 슬라이더 등이 포함됩니다. 여기에서는 QWidget를 그대로 사용하여 기본 윈도우를 생성합니다. 

 

resize 메서드를 사용하여 윈도우의 크기를 설정하고, setWindowTitle 메서드를 사용하여 윈도우의 제목을 설정합니다. 그리고 마지막으로 show 메서드를 호출하여 윈도우를 표시합니다.

 

이 예제는 Qt를 사용하여 가장 기본적인 윈도우를 생성하는 방법을 보여줍니다. 하지만 Qt는 이보다 훨씬 더 복잡한 GUI를 생성할 수 있습니다. 예를 들어, 여러 개의 윈도우를 생성하거나, 윈도우 내에 다른 위젯을 배치하거나, 레이아웃을 사용하여 위젯을 자동으로 정렬하는 것 등이 가능합니다.

 

다음은 Qt를 이용하여 버튼이 있는 윈도우를 생성하는 코드입니다.

 

[예제]

#include <QApplication>
#include <QWidget>
#include <QPushButton>

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    QWidget window;
    window.resize(320, 240);
    window.setWindowTitle("Hello, World!");

    QPushButton button(&window);
    button.setText("Click me!");

    window.show();

    return app.exec();
}


이 코드에서는 QPushButton 클래스를 사용하여 버튼을 생성합니다. QPushButton은 QWidget의 서브 클래스이므로, 이를 QWidget 윈도우에 추가할 수 있습니다. 

 

QPushButton의 생성자에 &window를 전달하여, 이 버튼이 window 위젯의 자식이 되도록 합니다. 이렇게 하면, window가 움직이거나 크기가 변할 때, 버튼도 함께 움직이거나 크기가 변합니다. 

 

버튼의 텍스트는 setText 메서드를 사용하여 설정합니다. 이 메서드는 버튼 위에 표시될 텍스트를 설정합니다.

 

이 코드를 실행하면, "Click me!"라는 텍스트가 있는 버튼이 포함된 윈도우가 생성됩니다. 버튼을 클릭하더라도 아무런 동작이 발생하지 않습니다. 왜냐하면 이 버튼에 대한 이벤트 핸들러를 아직 정의하지 않았기 때문입니다. 

 

이 코드를 통해, Qt를 이용하여 윈도우를 생성하고, 그 윈도우에 버튼을 추가하는 방법을 보았습니다. 이것은 Qt를 이용한 GUI 프로그래밍의 기본 중 하나입니다. 

 

마지막으로, 우리가 만든 버튼이 실제로 어떤 동작을 수행하도록 만들어 보겠습니다. 이렇게 하려면 버튼의 clicked 신호(signal)에 슬롯(slot)을 연결해야 합니다. 슬롯은 특정 신호가 발생했을 때 호출되는 함수 또는 메서드입니다. 이것은 이벤트 핸들링을 위한 Qt의 방식입니다. 

 

다음은 버튼이 클릭될 때 메시지 박스를 표시하는 코드입니다:

 

[예제]

#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QMessageBox>

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    QWidget window;
    window.resize(320, 240);
    window.setWindowTitle("Hello, World!");

    QPushButton button(&window);
    button.setText("Click me!");

    QObject::connect(&button, &QPushButton::clicked, [&](){
        QMessageBox::information(&window, "Clicked", "You clicked the button!");
    });

    window.show();

    return app.exec();
}


QObject::connect 함수는 QPushButton의 clicked 신호와 람다 함수를 연결합니다.

이 람다 함수는 QMessageBox::information 함수를 호출하여 메시지 박스를 표시합니다. 이 메시지 박스는 윈도우의 자식이므로, 윈도우가 움직이면 메시지 박스도 함께 움직입니다. 

 

이제 이 코드를 실행하면, 버튼을 클릭할 때마다 "You clicked the button!"이라는 메시지가 표시되는 메시지 박스가 나타납니다. 이렇게 하면, 버튼이 실제로 어떤 동작을 수행하도록 만들 수 있습니다. 

 

15.5.3. Qt를 이용한 이벤트 처리

이벤트 처리는 모든 GUI 프로그래밍에서 중요한 부분입니다. 사용자가 클릭하거나 키를 누르는 등의 동작은 모두 이벤트로 간주되며, 이 이벤트를 처리하는 코드를 작성해야 합니다. Qt에서는 이러한 이벤트를 처리하기 위해 신호와 슬롯이라는 개념을 도입했습니다. 

 

신호(signal)는 어떤 일이 발생했음을 나타내는 Qt 객체의 멤버 함수입니다. 예를 들어, QPushButton 클래스에는 clicked()라는 신호가 있습니다. 버튼이 클릭되면 이 신호가 발생(emit)합니다. 

 

슬롯(slot)은 신호가 발생했을 때 호출되어야 하는 함수 또는 메서드입니다. 즉, 슬롯은 이벤트 핸들러 역할을 합니다. 슬롯은 Qt 객체의 멤버 함수 또는 람다 함수일 수 있습니다. 

 

신호와 슬롯을 연결(connect)하는 것은 이벤트를 처리하는 방법입니다. 예를 들어, 버튼이 클릭되었을 때 메시지 박스를 표시하려면, clicked() 신호와 메시지 박스를 표시하는 슬롯을 연결해야 합니다. 이것이 이전 섹션에서 했던 것입니다. 

 

다음은 이것을 더 자세히 보여주는 예입니다:

 

[예제]

#include <QApplication>
#include <QMessageBox>
#include <QPushButton>

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    QPushButton button("Click me!");
    QObject::connect(&button, &QPushButton::clicked, [&](){
        QMessageBox::information(nullptr, "Clicked", "You clicked the button!");
    });
    button.show();

    return app.exec();
}


이 코드는 버튼을 만들고, clicked() 신호와 메시지 박스를 표시하는 람다 함수 슬롯을 연결합니다. 이제 이 버튼을 클릭하면 메시지 박스가 표시됩니다. 이렇게 하면, 버튼이 클릭되었을 때 원하는 동작을 수행할 수 있습니다. 

 

이것이 Qt에서 이벤트를 처리하는 기본적인 방법입니다. 다양한 신호와 슬롯을 사용하여 원하는 동작을 수행할 수 있습니다. 다음 섹션에서는 이를 확장하여 더 복잡한 GUI를 만드는 방법을 보여주겠습니다. 

 

Qt에서는 사용자 정의 슬롯을 만들어 신호와 연결할 수 있습니다. 이를 통해 특정 신호가 발생했을 때 실행되는 코드를 자체적으로 제어할 수 있습니다. 사용자 정의 슬롯은 일반 멤버 함수와 같지만, 특별히 Q_OBJECT 매크로를 포함한 Qt 클래스 내에 정의되어야 합니다. 

 

예를 들어, 다음과 같은 클래스를 만들 수 있습니다:

 

[예제]

#include <QMessageBox>
#include <QPushButton>
#include <QWidget>

class MyButton : public QPushButton
{
    Q_OBJECT

public:
    MyButton(const QString &text, QWidget *parent = nullptr)
        : QPushButton(text, parent)
    {
        connect(this, &QPushButton::clicked, this, &MyButton::onClicked);
    }

private slots:
    void onClicked()
    {
        QMessageBox::information(nullptr, "Clicked", "You clicked the button!");
    }
};


MyButton 클래스는 QPushButton의 하위 클래스입니다. 이 클래스에는 onClicked라는 사용자 정의 슬롯이 있습니다. onClicked 슬롯은 QMessageBox::information() 함수를 호출하여 버튼이 클릭됐음을 사용자에게 알립니다. 이 클래스의 인스턴스를 만들고, 화면에 표시하면, 사용자가 이 버튼을 클릭하면 메시지 박스가 표시됩니다. 

 

이것이 Qt의 신호와 슬롯을 이용한 이벤트 처리 방법입니다. 이 방법을 이용하면, 사용자의 동작에 대응하는 코드를 쉽게 작성할 수 있습니다. 

 

Qt에서는 여러 위젯과 이벤트를 함께 사용하여 다양한 GUI 애플리케이션을 만들 수 있습니다. 예를 들어, 사용자가 텍스트 상자에 텍스트를 입력하고 버튼을 누르면 해당 텍스트가 라벨에 표시되는 간단한 애플리케이션을 만들어보겠습니다.

 

[예제]

#include <QApplication>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QWidget>

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    MyWidget(QWidget *parent = nullptr)
        : QWidget(parent)
    {
        QVBoxLayout *layout = new QVBoxLayout(this);
        m_lineEdit = new QLineEdit(this);
        QPushButton *button = new QPushButton("Copy Text", this);
        m_label = new QLabel(this);

        layout->addWidget(m_lineEdit);
        layout->addWidget(button);
        layout->addWidget(m_label);

        connect(button, &QPushButton::clicked, this, &MyWidget::onButtonClicked);
    }

private slots:
    void onButtonClicked()
    {
        QString text = m_lineEdit->text();
        m_label->setText(text);
    }

private:
    QLineEdit *m_lineEdit;
    QLabel *m_label;
};

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    MyWidget widget;
    widget.show();

    return app.exec();
}


이 코드는 QVBoxLayout을 사용하여 QLineEdit, QPushButton, QLabel 위젯을 수직 레이아웃에 추가하는 MyWidget 클래스를 정의합니다. QPushButton의 clicked 신호는 MyWidget의 onButtonClicked 슬롯에 연결되어 있습니다. 이 슬롯은 QLineEdit에서 텍스트를 가져와 QLabel에 설정합니다. 

 

이것은 Qt를 사용하여 복잡한 GUI를 만드는 방법의 간단한 예입니다. Qt의 이벤트 처리 시스템은 신호와 슬롯 메커니즘을 사용하여 간결하고 이해하기 쉬운 코드를 작성할 수 있게 해 줍니다. 이 예제를 통해 Qt를 사용하여 사용자 인터페이스가 있는 애플리케이션을 작성하는 방법에 대해 간략하게 이해했기를 바랍니다. 다음 섹션에서는 Qt의 다른 기능에 대해 더 깊이 들어가겠습니다. 

 

이 예제에서는 우리는 QLineEdit의 텍스트를 QLabel에 복사하는 것을 확인했습니다. 그런데 사용자가 QLineEdit에서 텍스트를 입력할 때마다 텍스트가 QLabel에 즉시 복사되도록 하려면 어떻게 해야 할까요? 이것을 실현하는 것은 Qt의 신호-슬롯 메커니즘을 이용하면 매우 간단합니다. 

 

[예제]

connect(m_lineEdit, &QLineEdit::textChanged, m_label, &QLabel::setText);


이 코드는 QLineEdit의 textChanged 신호를 QLabel의 setText 슬롯에 직접 연결합니다. 이렇게 하면 사용자가 QLineEdit에 텍스트를 입력할 때마다 이 텍스트가 즉시 QLabel에 복사됩니다. 

 

그런데 사용자가 특정 키, 예를 들어 Enter 키를 눌렀을 때만 텍스트가 복사되도록 하려면 어떻게 해야 할까요? 이를 위해 우리는 Qt의 이벤트 필터링 기능을 사용할 수 있습니다. QLineEdit에 이벤트 필터를 설치하고, 키보드 이벤트를 감지하여 Enter 키가 눌렸을 때만 텍스트를 복사하도록 할 수 있습니다. 

 

[예제]

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    MyWidget(QWidget *parent = nullptr)
        : QWidget(parent)
    {
        QVBoxLayout *layout = new QVBoxLayout(this);
        m_lineEdit = new QLineEdit(this);
        m_label = new QLabel(this);

        layout->addWidget(m_lineEdit);
        layout->addWidget(m_label);

        m_lineEdit->installEventFilter(this);
    }

protected:
    bool eventFilter(QObject *obj, QEvent *event) override
    {
        if (obj == m_lineEdit && event->type() == QEvent::KeyPress) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
            if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
                m_label->setText(m_lineEdit->text());
                return true;
            }
        }
        return QWidget::eventFilter(obj, event);
    }

private:
    QLineEdit *m_lineEdit;
    QLabel *m_label;
};


이 코드는 QLineEdit에 이벤트 필터를 설치하고, 이벤트 필터 내에서 키보드 이벤트를 처리하여 Enter 키가 눌렸을 때만 텍스트를 복사합니다. 

 

위에서 제시한 예제코드는 Qt를 사용하여 GUI 프로그래밍을 하는 방법을 보여주는 것이며, Qt의 이벤트 처리 시스템이 어떻게 동작하는지를 이해하는데 도움이 됩니다. 그러나 실제로 애플리케이션을 개발할 때는 훨씬 더 복잡한 이벤트 처리가 필요할 수 있습니다.

 

예를 들어, 사용자가 어떤 버튼을 클릭했을 때 다른 창이 뜨게 하거나, 키보드의 특정 키를 눌렀을 때 특정 동작을 수행하게 하거나, 마우스로 특정 위치를 클릭하면 그 위치에 정보를 표시하게 하는 등의 동작은 Qt의 이벤트 처리 기능을 이용해서 구현할 수 있습니다. 

 

이런 심화된 이벤트 처리를 위해서는 Qt의 다양한 클래스와 메서드에 대해 더 깊이 이해할 필요가 있습니다. 예를 들어, QMouseEvent 클래스를 이용하면 마우스 이벤트를 처리할 수 있고, QKeyEvent 클래스를 이용하면 키보드 이벤트를 처리할 수 있습니다. 

 

다음은 사용자가 마우스로 창의 특정 위치를 클릭하면 그 위치의 좌표를 출력하는 예제코드입니다.

 

[예제]

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    MyWidget(QWidget *parent = nullptr)
        : QWidget(parent)
    {
    }

protected:
    void mousePressEvent(QMouseEvent *event) override
    {
        qDebug() << "Mouse clicked at" << event->pos();
    }
};


이 코드는 mousePressEvent라는 Qt 이벤트 핸들러를 재정의(override)하여 사용자가 마우스를 클릭할 때마다 그 위치의 좌표를 출력하도록 만듭니다. 

 

앞서 설명한 내용을 바탕으로, 이제는 Qt의 이벤트 처리를 활용해 키보드 이벤트를 처리하는 예제를 살펴보겠습니다.

 

[예제]

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    MyWidget(QWidget *parent = nullptr)
        : QWidget(parent)
    {
    }

protected:
    void keyPressEvent(QKeyEvent *event) override
    {
        if(event->key() == Qt::Key_Space)
        {
            qDebug() << "Space key pressed!";
        }
        else
        {
            QWidget::keyPressEvent(event);
        }
    }
};


이 예제 코드에서는 keyPressEvent라는 Qt 이벤트 핸들러를 재정의하여 사용자가 스페이스바를 눌렀을 때, "Space key pressed!"를 출력하도록 만들었습니다. 이 코드를 실행하면, Qt 애플리케이션이 스페이스바를 누를 때마다 해당 메시지를 출력합니다. 

 

Qt의 이벤트 처리 시스템은 이처럼 편리하게 GUI 애플리케이션의 동작을 사용자의 입력에 따라 제어할 수 있게 해줍니다. 특히 이벤트 핸들러를 재정의하는 방법은 간단하면서도 강력한 기능을 제공하기 때문에 다양한 상황에 유연하게 대응할 수 있습니다. 

 

이처럼 Qt 프레임워크는 GUI 프로그래밍을 위한 강력한 도구입니다. 이 도구를 잘 활용하면 사용자의 입력에 따라 반응하는 다양한 기능을 가진 애플리케이션을 만들 수 있습니다. 앞에서 설명한 내용이 Qt 프로그래밍에 대한 기본적인 이해를 돕는데 도움이 되었기를 바랍니다. 이어서 다양한 주제에 대해 더 깊이 있게 학습하면서 여러분의 GUI 프로그래밍 능력을 키워나가길 바랍니다. 

 

조금 더 구체적인 예시로, Qt를 이용해 웹 브라우저와 같은 복잡한 GUI를 생성하는 과정을 살펴보겠습니다.

 

[예제]

#include <QApplication>
#include <QWebEngineView>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWebEngineView view;
    view.load(QUrl("https://www.google.com"));
    view.show();

    return app.exec();
}

 

이 예제에서는 Qt의 QWebEngineView 클래스를 이용하여 웹 브라우저를 생성하고 있습니다. QWebEngineView는 QWidget를 상속받아 웹 페이지를 표시할 수 있도록 확장한 클래스입니다. load() 함수를 사용해 웹 주소를 로드하고, show() 함수로 화면에 표시합니다. 

 

또한 Qt의 시그널-슬롯 메커니즘이 이벤트 처리에서 중요한 역할을 합니다. 예를 들어, 버튼이 클릭되었을 때 어떤 동작을 수행하도록 하려면, 버튼의 clicked() 시그널을 원하는 슬롯에 연결하면 됩니다. 

 

[예제]

QPushButton *button = new QPushButton("Click me");
connect(button, SIGNAL(clicked()), this, SLOT(onButtonClicked()));


이 코드에서 connect() 함수는 버튼의 clicked() 시그널과 onButtonClicked() 슬롯을 연결하여, 버튼이 클릭되면 onButtonClicked() 함수가 호출되도록 합니다. 

 

Qt를 이용한 GUI 프로그래밍은 이런 식으로 다양한 위젯과 이벤트, 시그널-슬롯 메커니즘을 활용하여 직관적이고 풍부한 사용자 인터페이스를 구현할 수 있습니다. Qt에는 이 외에도 다양한 위젯과 기능이 포함되어 있으므로, 필요에 따라 적절한 도구를 선택하여 사용하면 됩니다. 

 

앞서 살펴본 예제에서 버튼 클릭 이벤트를 처리하는 onButtonClicked() 슬롯 함수를 어떻게 정의하는지 확인해 보겠습니다.

[예제]

void MyClass::onButtonClicked()
{
    qDebug() << "Button clicked!";
}


onButtonClicked() 슬롯 함수는 MyClass 내부에 정의되어 있으며, 버튼이 클릭되면 "Button clicked!" 메시지를 출력합니다. 여기서 qDebug() 함수는 Qt의 디버그 출력 함수로, 콘솔에 메시지를 출력하는 데 사용됩니다. 


다른 유형의 이벤트도 비슷한 방식으로 처리할 수 있습니다. 예를 들어, 텍스트 박스에서 텍스트가 변경되었을 때 이벤트를 처리하려면, QLineEdit의 textChanged() 시그널을 사용하면 됩니다. 


[예제]

QLineEdit *lineEdit = new QLineEdit;
connect(lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(onTextChanged(const QString &)));


여기서 onTextChanged() 슬롯 함수는 다음과 같이 정의됩니다.

[예제]

void MyClass::onTextChanged(const QString &text)
{
    qDebug() << "Text changed: " << text;
}


이와 같이 Qt는 다양한 위젯의 이벤트를 간단하게 처리할 수 있는 기능을 제공합니다. 따라서 복잡한 GUI 프로그램도 Qt를 이용하면 비교적 쉽게 구현할 수 있습니다. 또한, Qt는 크로스 플랫폼 지원을 통해 한 번 작성한 코드를 여러 운영 체제에서 재사용할 수 있으므로, 개발 시간을 크게 단축시킬 수 있습니다. 


Qt는 기본적인 버튼 클릭이나 텍스트 변경 외에도 마우스 클릭, 키보드 입력, 윈도우 크기 변경 등 다양한 이벤트를 처리할 수 있습니다. 예를 들어, 마우스 클릭 이벤트를 처리하려면 다음과 같이 QMouseEvent를 사용할 수 있습니다. 


먼저, 커스텀 위젯 클래스를 만들고 QWidget를 상속받아 마우스 이벤트를 재정의합니다. 


[예제]

class CustomWidget : public QWidget
{
    Q_OBJECT

protected:
    void mousePressEvent(QMouseEvent *event) override;
};


그리고 mousePressEvent() 함수 내에서 이벤트를 처리합니다.


[예제]

void CustomWidget::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        qDebug() << "Left button pressed";
    }
    else if(event->button() == Qt::RightButton)
    {
        qDebug() << "Right button pressed";
    }
}


이 예제에서 CustomWidget은 마우스 버튼이 눌릴 때마다 해당 버튼 정보를 콘솔에 출력합니다. Qt에서는 QMouseEvent의 button() 함수를 이용해 어떤 마우스 버튼이 눌렸는지 확인할 수 있습니다.

Qt의 이벤트 처리는 매우 유연하고 다양한 이벤트를 쉽게 처리할 수 있습니다. 따라서 GUI 프로그래밍을 할 때 Qt를 사용하면, 복잡한 이벤트 처리를 쉽게 구현할 수 있습니다. 또한, Qt는 다양한 플랫폼에서 동작하도록 지원하므로, 한 번 작성한 코드를 여러 운영체제에서 재사용할 수 있습니다. 이는 개발 시간을 크게 줄일 수 있습니다. 이렇게 해서 Qt를 이용한 GUI 프로그래밍에 대한 이해를 마무리합니다. 다음 섹션에서는 더 다양한 GUI 구성요소와 이를 활용한 애플리케이션 개발에 대해 알아보겠습니다.


15.6. 고급 GUI 프로그래밍

'고급 GUI 프로그래밍'은 사용자 정의 위젯 작성, 3D 그래픽, 애니메이션, 네트워킹과 같이 보다 복잡한 기능을 구현하는 방법에 대해 학습합니다. 또한, 멀티스레딩, 데이터베이스 통합, 성능 최적화와 같은 주제들도 다룹니다. 이 섹션을 마치고 나면, 사용자는 C++과 GUI 라이브러리를 활용하여 복잡한 프로젝트를 생성하고 유지하는 데 필요한 지식과 기술을 갖추게 됩니다. 

15.6.1. 다양한 GUI 컨트롤 이용하기

GUI 프로그래밍의 핵심은 여러 가지 컨트롤을 통해 사용자 인터페이스를 구성하는 것입니다. 컨트롤은 버튼, 텍스트 박스, 라디오 버튼, 체크 박스, 드롭다운 메뉴 등 다양한 형태가 있습니다. 이번 섹션에서는 이러한 다양한 GUI 컨트롤을 어떻게 사용하는지에 대해 알아보겠습니다. 

 

버튼

버튼은 가장 기본적인 컨트롤 중 하나입니다. 사용자가 클릭하면 동작을 수행합니다. 예를 들어, 다음은 버튼을 만드는 간단한 코드입니다.

[예제]

QPushButton *button = new QPushButton("Click me");
button->show();


이 코드는 "Click me"라는 텍스트가 쓰인 버튼을 생성하고 보여줍니다.

 

텍스트 박스

텍스트 박스는 사용자로부터 문자열 입력을 받는 컨트롤입니다. 다음 코드는 텍스트 박스를 만드는 예입니다.

 

[예제]

QLineEdit *textBox = new QLineEdit();
textBox->show();


이 코드는 빈 텍스트 박스를 생성하고 보여줍니다.

 

라디오 버튼

라디오 버튼은 여러 개 중에서 하나만 선택할 수 있는 컨트롤입니다. 아래 코드는 라디오 버튼의 예입니다.

[예제]

QRadioButton *radioButton = new QRadioButton("Option 1");
radioButton->show();


이 코드는 "Option 1"이라는 라디오 버튼을 생성하고 보여줍니다.

 

체크 박스

체크 박스는 사용자가 선택할 수 있는 옵션을 제공하는 컨트롤입니다. 사용자는 여러 체크 박스 중에서 여러 개를 선택할 수 있습니다. 아래는 체크 박스를 만드는 코드입니다.

[예제]

QCheckBox *checkBox = new QCheckBox("Option 1");
checkBox->show();


이 코드는 "Option 1"이라는 체크 박스를 생성하고 보여줍니다.

 

드롭다운 메뉴

드롭다운 메뉴는 여러 개의 옵션 중에서 하나를 선택할 수 있는 컨트롤입니다. 사용자가 드롭다운 메뉴를 클릭하면 옵션 목록이 표시되고, 사용자는 그중에서 하나를 선택할 수 있습니다. 다음 코드는 드롭다운 메뉴를 만드는 예입니다.

[예제]

QComboBox *comboBox = new QComboBox();
comboBox->addItem("Option 1");
comboBox->addItem("Option 2");
comboBox->addItem("Option 3");
comboBox->show();


이 코드는 "Option 1", "Option 2", "Option 3" 세 개의 옵션을 가진 드롭다운 메뉴를 생성하고 보여줍니다.

 

위의 예제들은 Qt를 사용하여 각 컨트롤을 생성하고 보여주는 기본적인 방법을 보여줍니다. 각 컨트롤에 대한 동작은 이벤트 처리를 통해 정의할 수 있습니다. 이에 대한 내용은 다음 섹션에서 더 자세히 다루도록 하겠습니다.

 

이번에는 앞서 소개한 각 컨트롤에 대한 이벤트 처리 방법에 대해 설명해 보겠습니다. 이벤트 처리는 사용자의 입력에 대응하는 동작을 정의하는 것을 말합니다.

 

버튼 이벤트 처리

먼저, 버튼의 클릭 이벤트를 처리하는 방법을 알아보겠습니다. 이는 Qt에서 제공하는 'clicked()' 시그널을 사용하여 가능합니다. 아래 예제를 보겠습니다.

[예제]

QPushButton *button = new QPushButton("Click me");
QObject::connect(button, &QPushButton::clicked, [](){
    qDebug() << "Button clicked!";
});
button->show();


이 코드는 사용자가 버튼을 클릭하면 콘솔에 "Button clicked!"라는 메시지를 출력합니다. QObject::connect() 함수는 버튼의 'clicked()' 시그널과 람다 함수를 연결합니다.

 

텍스트 박스 이벤트 처리

다음으로, 텍스트 박스에서 텍스트 변경 이벤트를 처리하는 방법을 알아보겠습니다. 이는 'textChanged()' 시그널을 사용하여 가능합니다.

[예제]

QLineEdit *textBox = new QLineEdit();
QObject::connect(textBox, &QLineEdit::textChanged, [](const QString &text){
    qDebug() << "Text changed: " << text;
});
textBox->show();


이 코드는 텍스트 박스의 텍스트가 변경되면 콘솔에 "Text changed: "와 함께 변경된 텍스트를 출력합니다.

 

라디오 버튼 및 체크 박스 이벤트 처리

라디오 버튼과 체크 박스는 'toggled()' 시그널을 사용하여 선택 상태 변경 이벤트를 처리할 수 있습니다.

[예제]

QCheckBox *checkBox = new QCheckBox("Option 1");
QObject::connect(checkBox, &QCheckBox::toggled, [](bool checked){
    if(checked){
        qDebug() << "CheckBox is checked.";
    } else {
        qDebug() << "CheckBox is unchecked.";
    }
});
checkBox->show();


이 코드는 체크 박스의 선택 상태가 변경되면 콘솔에 체크 박스의 현재 상태를 출력합니다.

 

드롭다운 메뉴 이벤트 처리

마지막으로, 드롭다운 메뉴에서 선택 변경 이벤트를 처리하는 방법을 알아보겠습니다. 이는 'currentIndexChanged()' 시그널을 사용하여 가능합니다.

[예제]

QComboBox *comboBox = new QComboBox();
comboBox->addItem("Option 1");
comboBox->addItem("Option 2");
comboBox->addItem("Option 3");
QObject::connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [](int index){
    qDebug() << "Selected index: " << index;
});
comboBox->show();


이 코드는 드롭다운 메뉴의 선택된 항목이 변경되면 콘솔에 선택된 인덱스를 출력합니다.

 

15.6.2. 2D 그래픽스 프로그래밍

C++의 GUI 프로그래밍에서 2D 그래픽스 프로그래밍은 매우 중요한 부분입니다. 2D 그래픽스는 차트, 그림, 게임 등 다양한 애플리케이션에서 사용됩니다. 여기서는 C++과 Qt 라이브러리를 사용하여 기본적인 2D 그래픽스 프로그래밍을 어떻게 하는지 살펴보겠습니다. 

 

Qt에서 2D 그래픽스를 그리는 가장 기본적인 방법은 QPainter 클래스를 사용하는 것입니다. QPainter는 Qt에서 그림을 그리는 기능을 제공하는 클래스로, 기본 도형, 이미지, 텍스트 등 다양한 종류의 그래픽스를 그릴 수 있습니다. 

 

아래는 QPainter를 이용하여 기본적인 도형을 그리는 예제입니다.

 

[예제]

#include <QApplication>
#include <QWidget>
#include <QPainter>

class MyWidget : public QWidget
{
protected:
    void paintEvent(QPaintEvent *event) override
    {
        QPainter painter(this);
        painter.setPen(Qt::blue);
        painter.drawRect(10, 10, 100, 100);
        
        painter.setPen(Qt::red);
        painter.drawEllipse(120, 10, 100, 100);
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MyWidget w;
    w.show();

    return app.exec();
}


이 예제는 두 개의 기본 도형을 그립니다. 첫 번째로 파란색 사각형을 그리고, 두 번째로 빨간색 원을 그립니다. QPainter의 setPen() 함수를 이용하여 도형의 선 색을 설정할 수 있습니다.

 

하지만 이런 방식으로 그림을 그리면 GUI 요소와 그래픽스 사이에 충돌이 발생할 수 있습니다. 이 문제를 해결하기 위해 Qt는 그래픽스 뷰 프레임워크를 제공합니다. 이 프레임워크를 사용하면 GUI 요소와 그래픽스를 서로 독립적으로 관리할 수 있어, 복잡한 그래픽스 애플리케이션을 더 쉽게 만들 수 있습니다.

 

아래는 Qt의 그래픽스 뷰 프레임워크를 이용하여 도형을 그리는 예제입니다.

 

[예제]

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsEllipseItem>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QGraphicsScene scene;
    QGraphicsRectItem *rect = scene.addRect(QRectF(10, 10, 100, 100));
    rect->setPen(QPen(Qt::blue));
    
    QGraphicsEllipseItem *ellipse = scene.addEllipse(QRectF(120, 10, 100, 100));
    ellipse->setPen(QPen(Qt::red));

    QGraphicsView view(&scene);
    view.show();

    return app.exec();
}


이 예제도 첫 번째 예제와 동일한 도형을 그립니다. 그러나 이번에는 QGraphicsScene, QGraphicsView, QGraphicsItem과 같은 클래스를 사용하여 도형을 그립니다. 이런 방식으로 그림을 그리면 GUI 요소와 그래픽스를 독립적으로 관리할 수 있어, 복잡한 그래픽스 애플리케이션을 더 쉽게 만들 수 있습니다. 

 

또한, QGraphicsView 클래스는 확대, 축소, 회전 등의 뷰 변환 기능을 제공하므로, 사용자 인터페이스에서 이러한 기능을 쉽게 구현할 수 있습니다.

 

[예제]

QGraphicsView view(&scene);
view.setRenderHint(QPainter::Antialiasing);
view.setRenderHint(QPainter::SmoothPixmapTransform);
view.setRenderHint(QPainter::TextAntialiasing);
view.setRenderHint(QPainter::HighQualityAntialiasing);
view.setRenderHint(QPainter::NonCosmeticDefaultPen);
view.setRenderHint(QPainter::Qt4CompatiblePainting);
view.show();


위의 코드에서는 QGraphicsView의 render hints를 설정하여 그리는 동안의 속성을 정의할 수 있습니다. 예를 들어 QPainter::Antialiasing은 그림의 가장자리를 부드럽게 하여 그림이 더 자연스럽게 보이게 합니다. 

 

Qt에서 2D 그래픽스 프로그래밍은 이보다 훨씬 더 복잡하고 다양한 기능을 제공합니다. 그러나 이 글에서는 가장 기본적인 기능만을 다루었습니다. 다음 단계로는 애니메이션, 이미지 처리, 사용자 정의 그래픽스 항목 등을 다루어 볼 수 있을 것입니다. 

 

이제 여러분은 Qt를 이용한 기본적인 2D 그래픽스 프로그래밍을 할 수 있습니다. 이 지식을 바탕으로 더 복잡한 그래픽스 애플리케이션을 만들어보세요. GUI 프로그래밍은 복잡할 수 있지만, 잘 설계된 코드와 Qt와 같은 훌륭한 도구를 사용하면 매우 효율적이고 강력한 애플리케이션을 만들 수 있습니다. 

 

그럼, 2D 그래픽 프로그래밍에 대한 논의를 마무리하며, 간단한 그림을 그리는 데 필요한 몇 가지 기본적인 개념을 간략하게 다시 살펴보겠습니다: 

  • QPainter: QPainter는 Qt에서 그래픽 출력을 위한 기본적인 도구입니다. 모든 그림은 QPainter 객체를 사용하여 그려집니다. 이 객체는 브러시, 펜, 폰트 등의 그림 도구를 설정하고, 직선, 원, 다각형 등의 기본적인 도형을 그리는 메서드를 제공합니다. 
  • QPen과 QBrush: QPen은 선의 색상, 스타일, 너비 등을 설정하는 데 사용되며, QBrush는 도형의 내부를 채우는 데 사용됩니다. 이 두 객체는 QPainter에 설정되어 그림 그리기에 사용됩니다. 
  • QPaintDevice와 QPaintEvent: QPaintDevice는 그림을 그릴 수 있는 객체를 나타내며, QWidget은 이를 상속받아 그림 그리기 기능을 가집니다. QPaintEvent는 그림을 그릴 때 발생하는 이벤트를 나타냅니다. 이 이벤트는 paintEvent() 메서드에서 처리되며, 이 메서드 내에서 QPainter 객체를 생성하고 그림을 그립니다.
  • QGraphicsScene과 QGraphicsView: 이 두 클래스는 Qt의 고급 그래픽스 기능을 제공합니다. QGraphicsScene은 그림을 그리는 2D 공간을 나타내며, QGraphicsView는 이를 보여주는 뷰를 나타냅니다.
  • Transformations: Qt는 2D 그래픽스에 대한 여러 가지 변형을 지원합니다. 이들은 스케일링(scale), 회전(rotation), 및 이동(translation)을 포함합니다. 이러한 변형은 QPainter의 setTransform() 메서드를 통해 적용됩니다.

예를 들어, QPainter 객체를 생성하고 이를 사용하여 변형을 적용하는 코드는 다음과 같습니다:

[예제]

QPainter painter(this);
QTransform transform;
transform.translate(50, 50);
transform.rotate(30);
transform.scale(2, 2);
painter.setTransform(transform);
painter.drawRect(0, 0, 100, 100);


이 코드는 (50, 50)만큼 이동한 후에 30도 회전하고, 그 후에 2배로 스케일링하는 변형을 적용합니다. 이 변형은 사각형을 그리는 데에 적용됩니다. 

 

  • Paths: QPainterPath 클래스는 복잡한 그래픽스 도형을 만들기 위한 기능을 제공합니다. 이 클래스는 moveTo(), lineTo(), quadTo(), cubicTo() 등의 메소드를 통해 도형의 경로를 지정할 수 있습니다. 


예를 들어, QPainterPath를 사용하여 복잡한 도형을 그리는 코드는 다음과 같습니다.

[예제]

QPainter painter(this);
QPainterPath path;
path.moveTo(50, 50);
path.lineTo(100, 50);
path.quadTo(150, 50, 150, 100);
path.cubicTo(200, 100, 200, 200, 50, 200);
painter.drawPath(path);


이 코드는 선, 사각형, 곡선을 포함한 복잡한 도형을 그립니다.

 

2D 그래픽 프로그래밍은 이보다 더 많은 세부 사항과 기능을 포함하며, 이에 대한 더 깊은 이해를 위해서는 Qt의 공식 문서와 튜토리얼을 참고하는 것이 좋습니다. 하지만 이번 섹션에서 설명한 기본적인 개념을 통해 GUI 프로그래밍에 대한 간단한 이해를 가질 수 있을 것입니다. 다음 섹션에서는 3D 그래픽 프로그래밍에 대해 살펴볼 것입니다.

 

15.6.3. 3D 그래픽스 프로그래밍

3D 그래픽 프로그래밍은 그 자체로 복잡하고 광범위한 주제이며, 이는 다양한 알고리즘과 수학적인 이해를 필요로 합니다. 하지만 여기서는 간단한 3D 그래픽 프로그래밍을 위해 C++와 OpenGL이 어떻게 사용되는지에 대해 초보자가 이해할 수 있도록 기초적인 수준에서 설명하겠습니다.

 

OpenGL(Open Graphics Library)은 2D와 3D 그래픽스 애플리케이션을 만들기 위한 크로스 플랫폼 API입니다. C/C++ 프로그래밍에서 가장 널리 사용되며, 대부분의 운영 체제에서 지원됩니다.

 

OpenGL 환경 설정: 먼저, OpenGL 라이브러리를 프로젝트에 포함시키는 것으로 시작해야 합니다. 다음은 OpenGL 헤더 파일을 프로그램에 포함시키는 방법을 보여주는 코드입니다:

 

[예제]

#include <GL/glut.h>

 

  • Window 생성: 다음으로, OpenGL 윈도우와 렌더링 콘텍스트를 만들어야 합니다. 이는 GLUT(Window 관리와 이벤트 처리를 위한 유틸리티 툴킷)를 사용하여 수행될 수 있습니다.

 

[예제]

int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE);
    glutInitWindowSize(500, 500);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("Hello world");
    glutMainLoop();
    return 0;
}


이 코드는 500x500 크기의 윈도우를 생성하고, 이를 표시합니다.

 

  • 그리기 함수: 그래픽을 그리기 위해서는, 우리는 특별한 그리기 함수를 정의하고 이를 GLUT에게 알려줘야 합니다. 이 함수는 화면이 갱신될 때마다 호출됩니다.

 

[예제]

void display() {
    glClear(GL_COLOR_BUFFER_BIT);
    glBegin(GL_TRIANGLES);
    glVertex2f(-0.5, -0.5);
    glVertex2f(0.5, -0.5);
    glVertex2f(0.0, 0.5);
    glEnd();
    glFlush();
}

int main(int argc, char** argv) {
    //...윈도우 생성 코드
    glutDisplayFunc(display);
    //...
}


이 코드는 화면을 지우고, 화면 중앙에 삼각형을 그린 후, 변경 사항을 화면에 적용합니다.

 

  • 변환: OpenGL은 변환(transformation)을 통해 3D 그래픽을 생성합니다. 이는 물체의 위치를 이동(translation), 회전(rotation), 또는 크기를 조절(scale)하는 데 사용됩니다. 이러한 변환은 특별한 행렬을 사용하여 수행됩니다.

 

[예제]

void display() {
    glClear(GL_COLOR_BUFFER_BIT);
    glPushMatrix();
    glTranslatef(0.5f, 0.0f, 0.0f);
    glBegin(GL_TRIANGLES);
    //...
    glEnd();
    glPopMatrix();
    glFlush();
}


이 코드는 삼각형을 오른쪽으로 0.5만큼 이동시킵니다. 'glPushMatrix'와 'glPopMatrix'는 현재 변환 매트릭스를 스택에 저장하고 복원하는 데 사용됩니다. 

 

이러한 기본적인 OpenGL 코드는 3D 그래픽스 프로그래밍의 시작점일 뿐입니다. OpenGL은 빛과 그림자, 텍스처 매핑, 투영, 버퍼링, 셰이더 등 다양한 고급 기능을 제공합니다. 

  • 빛과 그림자: 빛과 그림자는 3D 씬에서 실제감을 높이는 중요한 요소입니다. OpenGL은 다양한 유형의 빛(직접적인 빛, 점 빛, 환경 빛 등)을 지원하며, 이는 간단한 설정 변경으로 적용할 수 있습니다.

[예제]

void display() {
    //...
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);
    //...
}


이 코드는 빛을 활성화하고, 하나의 빛을 씬에 추가합니다. 빛의 위치는 월드 좌표계에 상대적입니다.

  • 텍스처 매핑: 텍스처 매핑은 3D 객체에 실제감을 더하는 또 다른 기술입니다. OpenGL은 2D 이미지(텍스처)를 3D 객체에 '붙이는' 기능을 제공합니다.

[예제]

GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);


이 코드는 텍스처를 생성하고, 이미지 데이터로 채웁니다. 이 텍스처는 이후에 3D 객체에 적용될 수 있습니다.

 

  • 셰이더: 셰이더는 GPU 상에서 실행되는 작은 프로그램으로, 빛과 그림자, 텍스처 매핑, 색상, 반사율 등을 계산하는 데 사용됩니다. OpenGL은 GLSL(OpenGL Shading Language)라는 고유의 셰이더 언어를 제공합니다.

 

[예제]

const char* vertexShaderSource = "...";
const char* fragmentShaderSource = "...";
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);


이 코드는 두 개의 셰이더(정점 셰이더와 프래그먼트 셰이더)를 컴파일하고, 이를 프로그램에 연결하여 GPU 상에서 실행할 준비를 합니다.

 

이러한 모든 기술은 복잡한 3D 그래픽을 생성하는 데 사용됩니다. 다음 섹션에서는 이러한 기술들을 조합하여 복잡한 3D 씬을 만드는 방법에 대해 알아보겠습니다.

 

  • 씬 그래프(Scene Graph): 복잡한 3D 씬을 구성하는 데 유용한 도구가 씬 그래프입니다. 씬 그래프는 3D 객체들을 트리 구조로 표현하며, 각 노드는 객체의 위치, 회전, 스케일 등의 정보를 포함합니다. 이를 통해 복잡한 씬에서 객체간의 관계를 쉽게 정의하고 관리할 수 있습니다.

 

OpenGL은 씬 그래프를 직접 지원하지는 않지만, C++의 표준 라이브러리를 이용해 씬 그래프를 구현할 수 있습니다.

[예제]

class Node {
public:
    glm::vec3 position;
    glm::quat rotation;
    glm::vec3 scale;
    std::vector<Node*> children;
    //...
};


이 코드는 간단한 씬 그래프 노드를 표현하는 클래스를 정의합니다. 각 노드는 자식 노드들을 가지고 있으며, 이를 통해 트리 구조를 형성합니다.

 

  • 애니메이션: 3D 씬을 더욱 생동감 있게 만들기 위해 애니메이션을 적용할 수 있습니다. 애니메이션은 객체의 위치, 회전, 스케일 등의 속성을 시간에 따라 변화시킵니다. 이는 간단한 수학적 함수를 이용해 구현할 수 있습니다.

 

[예제]

void animate(Node* node, float time) {
    node->position.x = sin(time);
    node->position.y = cos(time);
    //...
}


이 코드는 노드의 위치를 시간에 따라 사인과 코사인 함수로 변화시키는 간단한 애니메이션 함수를 정의합니다. 이 함수를 매 프레임마다 호출함으로써 노드를 애니메이션화할 수 있습니다.

 

 

 

2023.06.15 - [GD's IT Lectures : 기초부터 시리즈/C, C++ 기초부터 ~] - [C/C++ 프로그래밍 : 중급] 14. 네트워크 프로그래밍

 

[C/C++ 프로그래밍 : 중급] 14. 네트워크 프로그래밍

Chapter 14. 네트워크 프로그래밍 네트워크 프로그래밍은 컴퓨터들이 서로 정보를 주고받는 통신 과정을 소프트웨어로 구현하는 것입니다. C/C++ 언어를 통해 네트워크 프로그래밍의 중요성을 이해

gdngy.tistory.com

 

반응형

댓글