WindowsAPI - 실습 그림판 구현하기 종합 (마지막 / 정리)

컴퓨터/Win32-API

728x90
반응형

 

서론

WindowsAPI(Win32, GDI)를 이용해서 간단하게 그림판의 기능을 모사해보는 실습을 했습니다. 

지난 과정들을 정리하고 스스로 평가하는 시간을 가져보도록 하겠습니다.

 

지난 포스트 보기

더보기

전체 소스코드 보기 with GitHub

더보기

 

 

파일 구조 및 기능 분할

해당 프로젝트는 총 4가지의 디렉터리 구조로 되어있습니다.

각각의 영역에 대해 살펴보면 다음과 같습니다.

 

  • 0.Mysource
    • 기능 구현 부분들에 대한 함수 형태의 소스코드 구현 부분
  • Header
    • 자동 생성된 Header파일 영역으로 필요한 라이브러리 및 리소스 제어 정의를 위한 부분
  • Reource
    • 자원의 형태로 사용할 이미지 등을 관리하는 부분
  • Source
    • WinMain함수의 구동 부분으로서 WindowsAPI 구조에서 메시지 흐름을 처리하는 부분

주로 제어하기 위한 영역은 MySource 영역과 Source 영역입니다.

구분을 지은 이유는 다음과 같습니다.

  • Paint.cpp에서 WM 메세지에 대한 흐름을 쉽게 파악하기 위해서 
  • MySource에 각각의 기능들을 구분적으로 나누어 향후 유지보수 및 반복된 코드 사용을 위해서

0.MySource 영역 소스코드 상세

해당 디렉터리에서는 각각의 함수를 기능별로 분할하여 구현하도록 만들었습니다.

해당 소스코드는 크게 3가지 부분으로 나누어집니다.

  • 시작 설정
    • 메인 윈도우 기본정보 적용
    • 버튼 생성
    • 버튼 이미지 추가
    • 색상표 생성
  • 주요 기능 
    • 색상 선택 
    • 그리기
    • 지우기
  • 제어
    • 색상표 색 설정하기 및 설정된 값 받아오기
    • 그리기 또는 지우기 기능 선택 및 값 받아오기

 

  • 시작 설정 함수 및 설명
//윈도우 창 설정
HWND InitMainWindowSet(HINSTANCE hInstance, WNDPROC WndProc,const WCHAR* name)
{
    //윈도우 창 구조체 설정 및 적용하기
    WNDCLASSEXW wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = NULL;
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = name;
    wcex.hIconSm = NULL;
    RegisterClassExW(&wcex);

    //해당 윈도우 창을 가지고와서 윈도우 창 생성하기
    return CreateWindowW(name, name, WS_MAXIMIZE| WS_SYSMENU | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
        100, 100, 750, 750, nullptr, nullptr, hInstance, nullptr);
}

HWND를 반환하는 함수로서, 메인 윈도의 기본적인 설정 및 이름을 정의해 줍니다.

 

  • 버튼 생성 및 이미지 생성
//버튼생성하기 
void CreateButton(const WCHAR* name,LONG x,LONG y,LONG width ,LONG height, HMENU id, HWND hWnd, HINSTANCE hInst)
{
    CreateWindowW(L"button", name, WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BS_PUSHLIKE | BS_ICON, x,y,width,height, hWnd, id, hInst,NULL);
}
void CreateButtonImg(const WCHAR* buttonName,int imgName, HWND hWnd, HINSTANCE hInst)
{
    HICON hIcon = LoadIcon(hInst, MAKEINTRESOURCE(imgName));
    SendMessageW(FindWindowExW(hWnd, NULL, L"button", buttonName), BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
}

버튼 컨트롤을 생성하는 함수와 이미지를 덮을 수 있는 함수입니다.

 

 

  • 메모리 비트맵 생성

더블 버퍼링 기법을 사용하기 위한 함수입니다.

//메모리 DC 및 비트맵에 메모리 할당 하기
void CreateBackPage(HWND hWnd, HINSTANCE hInst, HDC* memDC , HBITMAP* memBitmap)
{
    HDC hdc = GetDC(hWnd);
    *memDC = CreateCompatibleDC(hdc);
    RECT rect;
    GetClientRect(hWnd, &rect);
    *memBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
    (HBITMAP)SelectObject(*memDC, *memBitmap);
    FillRect(*memDC, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
    ReleaseDC(hWnd, hdc);
}

반환 값은 없으나, 이차포인터를 이용하여 주소의 형태로 인자에서 사용합니다.

 

  • 색상표 만들기
//색상 선택 도구 만들기
void CreateRGBTable(const WCHAR* name, LONG left, LONG top, LONG right, LONG bottom, HMENU id, HWND hWnd, HINSTANCE hInst)
{
    
    //RGB의 스크롤 바 생성 및 범위 설정
    CreateWindowW(L"scrollbar", L"R", WS_CHILD | WS_VISIBLE | SBS_HORZ, right+20, top+15, right + 30, 15, hWnd, id, hInst, NULL);
    SetScrollRange(FindWindowExW(hWnd, NULL, L"scrollbar", L"R"), SB_CTL, 0, 255, TRUE);
    CreateWindowW(L"scrollbar", L"G", WS_CHILD | WS_VISIBLE | SBS_HORZ, right+20, top+40, right + 30, 15, hWnd, id+1, hInst, NULL);
    SetScrollRange(FindWindowExW(hWnd, NULL, L"scrollbar", L"G"), SB_CTL, 0, 255, TRUE);
    CreateWindowW(L"scrollbar", L"B", WS_CHILD | WS_VISIBLE | SBS_HORZ, right+20, top+65, right + 30, 15, hWnd, id+2, hInst, NULL);
    SetScrollRange(FindWindowExW(hWnd, NULL, L"scrollbar", L"B"), SB_CTL, 0, 255, TRUE);
    //SetColor(hWnd);
}

해당 함수에서 색상표에 대한 스크롤 바를 생성해 줍니다.

 

 

  • 주요 기능
  • 색상 선택
//색상 선택
void SetColor(HWND hWnd,HDC memdc,HBITMAP memBitmap)
{
    //스크롤 Bar의 값 받아오기
    int R = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"R"), SB_CTL);
    int G = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"G"), SB_CTL);
    int B = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"B"), SB_CTL);

    HBRUSH newBrush = CreateSolidBrush(RGB(R, G, B));
    HBRUSH OldBrush = (HBRUSH)SelectObject(memdc, newBrush);

    //선택된 색상 보여주기
    Rectangle(memdc, 80, 30, 150, 120);


    //스크롤 Bar의 값 표시해주기
    WCHAR text[20];
    wsprintf(text, L"R : %d    ", R );
    TextOutW(memdc, 360, 40, text, lstrlenW(text));
    wsprintf(text, L"G : %d     ", G );
    TextOutW(memdc, 360, 70, text, lstrlenW(text));
    wsprintf(text, L"B : %d     ", B );
    TextOutW(memdc, 360, 100, text, lstrlenW(text));


    SelectObject(memdc, OldBrush);
    DeleteObject(newBrush);

}

생성된 스크롤 바에서 값을 받아와 색상을 표시해 줍니다.

 

  • 그리기 AND 지우기
//그리기
POINT Draw(HWND hWnd, HDC hdc, WPARAM wParam, LPARAM lParam,POINT stPos) 
{

    //스크롤 Bar의 값 받아오기
    int R = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"R"), SB_CTL);
    int G = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"G"), SB_CTL);
    int B = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"B"), SB_CTL);

    HPEN newPen = CreatePen(PS_SOLID, 10, RGB(R, G, B));
    HPEN oldPen = (HPEN)SelectObject(hdc, newPen);

    POINT pos;
    pos.x = GET_X_LPARAM(lParam);
    pos.y = GET_Y_LPARAM(lParam);

    MoveToEx(hdc, stPos.x, stPos.y, NULL);
    LineTo(hdc, pos.x, pos.y);
    SelectObject(hdc, oldPen);
    DeleteObject(newPen);

    stPos.x = pos.x;
    stPos.y = pos.y;
    return stPos;
}
//지우기
POINT Eraser(HWND hWnd, HDC hdc, WPARAM wParam, LPARAM lParam, POINT stPos)
{
    HPEN newPen = CreatePen(PS_SOLID, 10, RGB(255, 255, 255));
    HPEN oldPen = (HPEN)SelectObject(hdc, newPen);

    POINT pos;
    pos.x = GET_X_LPARAM(lParam);
    pos.y = GET_Y_LPARAM(lParam);

    MoveToEx(hdc, stPos.x, stPos.y, NULL);
    LineTo(hdc, pos.x, pos.y);
    SelectObject(hdc, oldPen);
    DeleteObject(newPen);

    stPos.x = pos.x;
    stPos.y = pos.y;
    return stPos;
}

그리기와 지우기 기능을 구현해서 POINT 측, 윈도 좌표 값을 반환해 줍니다.

 

  • 제어 영역
  • 기능 선택

기능 선택의 경우 그리기 or 지우기 하나만 선택을 해야 합니다. 따라서 메시지를 보고 판단하고 제어합니다.

//토글 값 셋팅
void SetFunction(WPARAM wParam, LPARAM lParam,HWND hWnd)
{
    if (HIWORD(wParam) == BN_CLICKED)
    {
        switch (LOWORD(wParam))
        {
        case 1:
        {
            if (SendMessageW((HWND)lParam, BM_GETCHECK, 0, 0) == BST_UNCHECKED) {
                SendMessageW((HWND)lParam, BM_SETCHECK, BST_CHECKED, 0);
                SendMessageW(FindWindowExW(hWnd, NULL, L"button", L"Erase"), BM_SETCHECK, BST_UNCHECKED, 0);
                return ;
            }
            else {
                SendMessageW((HWND)lParam, BM_SETCHECK, BST_UNCHECKED, 0);

            }
        }
        case 2:
        {
            if (SendMessageW((HWND)lParam, BM_GETCHECK, 0, 0) == BST_UNCHECKED) {
                SendMessageW((HWND)lParam, BM_SETCHECK, BST_CHECKED, 0);
                SendMessageW(FindWindowExW(hWnd, NULL, L"button", L"Pen"), BM_SETCHECK, BST_UNCHECKED, 0);
                return ;
            }
            else {
                SendMessageW((HWND)lParam, BM_SETCHECK, BST_UNCHECKED, 0);
            }
        }

        } // 스위치문 종료

    }
    return ;
}
  • 선택된 기능 값 받아오기
//토글 값 받아오기
int GetFunction(WPARAM wParam, LPARAM lParam, HWND hWnd)
{
    // 1 == 그리기 2 == 지우기 없을 시 0
    if (SendMessageW(FindWindowExW(hWnd, NULL, L"button", L"Pen"), BM_GETCHECK, BST_CHECKED, 0) == BST_CHECKED) return 1;
    if (SendMessageW(FindWindowExW(hWnd, NULL, L"button", L"Erase"), BM_GETCHECK, BST_CHECKED, 0) == BST_CHECKED) return 2;
    else return 0;

}

//스크롤 동작
void SetScrollFunction(WPARAM wParam, LPARAM lParam)
{

    switch (LOWORD(wParam))
    {
    case SB_LINELEFT:
        SetScrollPos((HWND)lParam, SB_CTL, max(0,GetScrollPos((HWND)lParam,SB_CTL)-1), TRUE);
        break;
    case SB_PAGELEFT:
        SetScrollPos((HWND)lParam, SB_CTL, max(0, GetScrollPos((HWND)lParam, SB_CTL) - 5), TRUE);
        break;
    case SB_LINERIGHT:
        SetScrollPos((HWND)lParam, SB_CTL, min(255, GetScrollPos((HWND)lParam, SB_CTL) + 1), TRUE);
        break;
    case SB_PAGERIGHT:
        SetScrollPos((HWND)lParam, SB_CTL, min(255, GetScrollPos((HWND)lParam, SB_CTL) + 5), TRUE);
        break;
    case SB_THUMBTRACK:
        SetScrollPos((HWND)lParam, SB_CTL, HIWORD(wParam), TRUE);
        break;
    }
}

 

Paint.cpp

이후 동작을 위해 해당 소스코드에서 함수들을 적재적소에 호출하여 구현해줍시다.

// Paint.cpp : Defines the entry point for the application.
//

#include "framework.h"
#include "Paint.h"

//함수 원형 선언 부분
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

// 전역변수
HINSTANCE hInst;                               

HDC memDC;
HBITMAP memBitmap;
POINT stPos;


// WinMain함수 부분
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.

    /*윈도우 설정 및 메인 윈도우를 생성합니다.*/
    HWND hWnd = InitMainWindowSet(hInstance, &WndProc, L"Paint");
    //정상적으로 생성되었다면 윈도우를 갱신합니다.
    if (!hWnd)
    {
        return FALSE;
    }
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    MSG msg;
    // 기본 메시지 루프입니다:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}

// WndProc 함수부분
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        //윈도우 생성 메세지
    case WM_CREATE:
    {
        //버튼 생성
        CreateButton(L"Pen", 20, 20, 50, 50, (HMENU)1, hWnd, hInst);
        CreateButton(L"Erase", 20, 75, 50, 50, (HMENU)2, hWnd, hInst);
        //버튼 생성 및 이미지 씌우기
        CreateButtonImg(L"Pen", IDI_ICON2, hWnd, hInst);
        CreateButtonImg(L"Erase", IDI_ICON1, hWnd, hInst);
        //색상 테이블 생성
        CreateRGBTable(L"RGBtable", 80, 30, 150, 120, (HMENU)10, hWnd, hInst);

        //더블 버퍼링을 위한 메모리 셋팅
        CreateBackPage(hWnd, hInst, &memDC, &memBitmap);
        break;
    }

    //메인 기능 그리기 Or 지우기
    case WM_COMMAND:
    {
        //토글 버튼 처리하기
        SetFunction(wParam, lParam, hWnd);
        //각종 버튼에 따른 동작 
        break;
    }
    //스크롤 기능
    case WM_HSCROLL:
    {
        SetScrollFunction(wParam, lParam);
        InvalidateRect(hWnd, NULL, FALSE);
        UpdateWindow(hWnd);
        break;
    }


        /*각종 마우스 동작에 따른 컨트롤*/
    case WM_MOUSEMOVE:
    {
        if (wParam == MK_LBUTTON && stPos.y >= 150) {
            //그리기 모드
            if (GetFunction(wParam, lParam, hWnd) == 1)
            {
                stPos = Draw(hWnd, memDC, wParam, lParam, stPos);
                HDC hdc = GetDC(hWnd);
                RECT rect;
                GetClientRect(hWnd, &rect);
                BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
                ReleaseDC(hWnd, hdc);
            }
            //지우기 모드
            else if (GetFunction(wParam, lParam, hWnd) == 2)
            {
                stPos = Eraser(hWnd, memDC, wParam, lParam, stPos);
                HDC hdc = GetDC(hWnd);
                RECT rect;
                GetClientRect(hWnd, &rect);
                BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
                ReleaseDC(hWnd, hdc);
            }

        }
        break;
    }
    case WM_LBUTTONDOWN:
    {
        //좌 클릭시 초기 좌표 값 저장
        stPos.x = GET_X_LPARAM(lParam);
        stPos.y = GET_Y_LPARAM(lParam);
        break;
    }
    case WM_LBUTTONUP:
    {
        break;
    }
    case WM_RBUTTONDOWN:
    {
        break;
    }
    case WM_RBUTTONUP:
    {
        break;
    }
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        SetColor(hWnd,memDC,memBitmap);
        RECT rect;
        GetClientRect(hWnd, &rect);
        BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);

        EndPaint(hWnd, &ps);
        break;
    }
    case WM_DESTROY:
        DeleteObject(memBitmap);
        DeleteDC(memDC);
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

후기 및 보완점

간단하게 그림판을 구현하기 위한 실습을 해봤습니다. 

 

나름대로 각각의 함수마다 결합도를 최소화시키기 위해 노력했습니다.

결합도 최소화 (함수의 변경에 따른 다른 함수에 영향을 최소화)

추가로 보완이 돼야 하는 가장 최우선 점은 함수를 호출하는 과정에서 정상적인 동작을 하지 않았을 때의 처리가 필요합니다.

 

이후 다른 실습이나 해당 소스코드를 이용하여 추가적인 구현을 할 경우 해당 내용을 해소하는 방향으로 진행하겠습니다.

 

 

728x90
반응형

Commnet

G91개발일지

Gon91(지구일)

91년생 공학엔지니어의 개발일지

TODAY :

YESTER DAY :

TOTAL :