C++

MFC 프로그래밍 – 김성엽

 

  1. MFC 공부를 시작하려면!
  2. MFC의 기본 클래스 소개 및 실습 준비하기
  3. MFC에서 메시지 처리하기
  4. 대화상자에컨트롤사용하기
  5. Edit 컨트롤에 값을 읽고 쓰는 다양한 방법에 대하여!
  6. ListBox 컨트롤을 사용하여 채팅 인터페이스 만들기
  7. 사각형을 마우스로 클릭해서 이동하기
  8. 투명한 윈도우 만들기
  9. 대화 상자 추가하기 (정형)
  10. 여러 개의 컨트롤에 입력된 값을 쉽게 관리하는 방법
  11. MFC에서 컨트롤 변수 직접 등록해서 사용하기
  12. [Q&A] MFC에서 윈도우 배경색 변경하기
  13. [실습 영상] MFC에서 사용자 정의 윈도우 만들기
  14. [실습 영상] 서브클래싱을 사용하여 버튼 기능 확장하기
  15. [MFC] List Box를 사용하여 색상 선택 기능 구현하기
  16. 시스템 전역 단축키 사용하기
  17. [MFC] 가격 계산 프로그램 만들기
  18. Edit 컨트롤의 색상 변경하기 – Step1
  19. 대화상자에서 메뉴 사용하기
  20. 대화 상자에서 단축키 사용하기
  21. 바이너리 뷰어 만들기 (CListBox 사용)
  22. 대화상자의 컨트롤중에서 Edit 컨트롤만 찾아서 문자열 설정하기
  23. 원 모양의 윈도우를 만들고 마우스로 이동하기
  24. 팝업 메뉴 사용하기 – Step1
  25. 탐색기에서 Drag And Drop된 파일 정보 사용하기 – Step1
  26. 파일 관리하기 – Step1 (목록구성)
  27. [MFC] 마우스 휠 버튼 사용하기 – Step 1
  28. [MFC] 키보드 방향 키로 사각형 움직이기 예제 – Step 1
  29. 네트워크 프로그래밍 – CSocket으로 정숫값 전달하기

 

유튜브 강의
https://www.youtube.com/watch?v=8MQsJDk5HTk&list=PLiZvlxkcLhalUHK9UnRS_KweH9R3tgBIO&index=2

김성엽의 MFC 이야기 (네이버 블로그)
https://blog.naver.com/tipsware/221307415937

 


 

2. MFC의 기본 클래스 소개 및 실습 준비하기

 

# InitInstance() 함수 정리

// 불필요한 코드가 추가되므로 아래처럼 4줄만 남기고 모두 지운다.

BOOL CExamMFC001App::InitInstance()
{
    CWinApp::InitInstance();


    CExamMFC001Dlg dlg;
    m_pMainWnd = &dlg;
    dlg.DoModal();	
    return FALSE;
}

 

 


 

3. MFC에서 메시지 처리하기

 

# 메시지 처리 함수 WindowProc 함수 추가하기

 

메뉴->프로젝트->클래스 마법사

클래스 이름 : CExamMFC001Dlg
가상 함수 : WindowProc

 

LRESULT CExamMFC001Dlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_LBUTTONDOWN)
    {
        // MFC 권장 코드
        CClientDC dc(this);
        dc.Rectangle(10, 10, 100, 100);		
	

        // Win32
        //HDC h_dc = ::GetDC(m_hWnd);
        //Rectangle(h_dc, 10, 10, 100, 100);
        //::ReleaseDC(m_hWnd, h_dc);

        // MFC 기본 코드
        //CDC* p_dc = GetDC();
        //p_dc->Rectangle(10, 10, 100, 100);
        //ReleaseDC(p_dc);


        //CDC 자식 클래스 CClientDC (다형성)
        //CClientDC dc(this);
        //CDC* p_dc = &dc;
        //dc.Rectangle(10, 10, 100, 100);

    }

    return CDialogEx::WindowProc(message, wParam, lParam);
}

 

 

# 마우스 클릭 시 원이나 사각형 그리기

 

LRESULT CExamMFC001Dlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_LBUTTONDOWN)
    {
        CClientDC dc(this);

        int x = LOWORD(lParam);  // 하위 16비트 값 분리
        int y = HIWORD(lParam);  // 상위 16비트 값 분리

        if (wParam & MK_CONTROL) dc.Ellipse(x - 30, y - 30, x + 30, y + 30);
        else dc.Rectangle(x - 30, y - 30, x + 30, y + 30);
    }

    return CDialogEx::WindowProc(message, wParam, lParam);
}

 

// 추가한 가상 함수를 삭제하려면 CExamMFC001Dlg.h에서 함수 선언 삭제하고 CExamMFC001Dlg.cpp 에서 코드 삭제

 

 

 

# MFC 권장 방법 쓰기 (클래스 마법사에서 메시지 핸들러 추가하기)

 

메뉴->프로젝트->클래스 마법사

클래스 이름 : CExamMFC001Dlg
메시지 : WM_LBUTTONDOWN

 

// 3군데에 추가됨

1번째 (ExamMFC001Dlg.h)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

2번째 (ExamMFC001Dlg.cpp)
BEGIN_MESSAGE_MAP(CExamMFC001Dlg, CDialogEx)
    ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

3번째 (ExamMFC001Dlg.cpp)
void CExamMFC001Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{

    CDialogEx::OnLButtonDown(nFlags, point);
}

 

// 클래스 마법사에서 WM_LBUTTONDOWN 메시지를 삭제하면 주석 처리만 되고 코드는 남아있다.

// 그래서 직접 위 3군데를 찾아서 지워야 한다.

 

void CExamMFC001Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
    CClientDC dc(this);

    if (nFlags & MK_CONTROL) dc.Ellipse(point.x - 30, point.y - 30, point.x + 30, point.y + 30);
    else dc.Rectangle(point.x - 30, point.y - 30, point.x + 30, point.y + 30);

    CDialogEx::OnLButtonDown(nFlags, point);
}

 

 


 

4. 대화상자에컨트롤사용하기

 

  1. 대화 상자에 에디트 컨트롤과 버튼 배치
  2. 버튼을 더블 클릭하면 메시지 핸들러가 자동으로 만들어진다.

 

void CExamMFC001Dlg::OnBnClickedShowMsgBtn()
{
    //wchar_t str[64];
    //GetDlgItemText(IDC_INPUT_MSG_EDIT, str, 64);

    CString str, show_str;
    GetDlgItemText(IDC_INPUT_MSG_EDIT, str);
    //show_str.Format(_T("사용자가 입력한 문자열 : %s"), str);
    //show_str = _T("사용자가 입력한 문자열 : ") + str;
    AfxMessageBox(show_str);
}

 

# 리소스 ID 를 인식하지 못한다면 리소스 ID 에서 마우스 오른쪽 버튼을 누룬 후 Rescan – Rescan File 를 누룬다.

 


 

5. Edit 컨트롤에 값을 읽고 쓰는 다양한 방법에 대하여!

 

# 에디트 컨트롤->변수 추가
– 액세스 : protected
– 범주 : 값
– 이름 : m_my_string

# 읽기 버튼, 쓰기 버튼 두 개 배치
– 더블 클릭해서 메시지 처리기 생성

 

 

void CExamMFC001Dlg::OnBnClickedReadBtn()  // 읽기 버튼 메시지 핸들러
{
    UpdateData(TRUE); // 컨트롤 -> 변수
    AfxMessageBox(m_my_string);
}


void CExamMFC001Dlg::OnBnClickedWriteBtn()  // 쓰기 버튼 메시지 핸들러
{
    m_my_string = _T("안녕하세요.");
    UpdateData(FALSE);
}

 

 

 

# 에디트 컨트롤에서 변수 추가 한 것 삭제하려면 세 군데를 삭제한다.

 

### ExamMFC001Dlg.h
1.
protected:
    CString m_my_string;


### ExamMFC001Dlg.cpp

2. CExamMFC001Dlg 클래스 생성자에서 변수 초기화 삭제
m_my_string(_T(""))


3.
void CExamMFC001Dlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT2, m_my_string); // 이 부분 삭제
}

 

 

# string -> int

int value = _ttoi(m_my_value)

 

# int -> string

CString str;
str.Format(_T(“%d”), m_my_string);

 

# 에디트 컨트롤에서 int 형 변수 추가

  • – protected
    – 값
    – int
    – m_my_value

 

# 에디트 컨트롤에서 int 값 읽기

void CExamMFC001Dlg::OnBnClickedReadBtn()
{
    UpdateData(TRUE);

    CString str;
    str.Format(_T("%d"), m_my_value);
    
    AfxMessageBox(str);
}


# 에디트 컨트롤에 값 쓰기
void CExamMFC001Dlg::OnBnClickedWriteBtn()
{
    m_my_value = 5;

    UpdateData(FALSE);
}

 

 

# GetDlgItemText, SetDlgItemText

 

void CExamMFC001Dlg::OnBnClickedReadBtn()
{
    CString str;
    int len = GetDlgItemText(IDC_EDIT2, str);

    str.Format(_T("%s (%d)"), str, len);
    AfxMessageBox(str);
}


void CExamMFC001Dlg::OnBnClickedWriteBtn()
{
    SetDlgItemText(IDC_EDIT2, _T("반갑습니다."));
}

 

 

# GetDlgItemInt, SetDlgItemInt

 

void CExamMFC001Dlg::OnBnClickedReadBtn()
{
    int value = GetDlgItemInt(IDC_EDIT2);

    CString str;
    str.Format(_T("%d"), value);
    AfxMessageBox(str);
}


void CExamMFC001Dlg::OnBnClickedWriteBtn()
{
    SetDlgItemInt(IDC_EDIT2, 5);
}

 

 

# GetDlgItem, SendMessage(WM_GETTEXTLENGTH)

 

void CExamMFC001Dlg::OnBnClickedReadBtn()
{
    // CEdit 클래스 (CWnd는 부모 클래스) 다형성
    //CWnd* p = GetDlgItem(IDC_EDIT2);
    //CEdit* p_edit = (CEdit*)p;

    // 이렇게 써도 됨.
    //CEdit *p_edit = (CEdit*)GetDlgItem(IDC_EDIT2);

    // CWnd 부모 클래스 사용 가능하므로.
    CWnd* p = GetDlgItem(IDC_EDIT2);
    int len = p->SendMessage(WM_GETTEXTLENGTH);
    
    if (len > 3)
    {
        AfxMessageBox(_T("너무 길게 입력했습니다."));
    }
    else
    {
        wchar_t str[4];
        GetDlgItemText(IDC_EDIT2, str, 4);
        AfxMessageBox(str);
    }
}

 

# p->SendMessage(WM_GETTEXTLENGTH); 함수를 좀 더 편하게 쓰기

 

int len = p->GetWindowTextLength();
// 내부적으로 p->SendMessage(WM_GETTEXTLENGTH); 를 포함.

 

 

# 좀 더 짧게 줄이기.

 

int len = GetDlgItem(IDC_EDIT2)->GetWindowTextLength();

if (GetDlgItem(IDC_EDIT2)->GetWindowTextLength() > 3)

 

 

 


 

6. ListBox 컨트롤을 사용하여 채팅 인터페이스 만들기

 

컨트롤 3개 배치

리스트 컨트롤 – IDC_CHAT_LIST
에디트 컨트롤 – IDC_CHAT_EDIT
버튼 – 추가, IDC_ADD_BTN

 

# 리스트 컨트롤에서 변수 추가
– 범주 : Control, 이름 : m_chat_list

# 버튼 더블클릭해서 메시지 핸들러 코드 생성

# 에디트 버튼에서 엔터키를 적용하려면 버튼의 Default Button 을 True 로 바꾸기 (기존 Default Button은 False 로 바꾼다.)

# 에디트  컨트롤에 엔터키가 적용이 안되었다면 서식(Format) -> 탭 순서(Tab Order) 에서 추가 버튼을 1번으로 바꾼다.

 

void CExamChatDlg::OnBnClickedAddBtn()
{
    CString str;
    GetDlgItemText(IDC_CHAT_EDIT, str);
    SetDlgItemText(IDC_CHAT_EDIT, _T(""));

    //m_chat_list.AddString(str);  // 리스트 컨트롤 마지막에 추가
    //m_chat_list.InsertString(0, str);  // 인덱스 지정해서 추가
    int index = m_chat_list.InsertString(-1, str);  // -1은 마지막에 추가
    m_chat_list.SetCurSel(index);  // 추가된 아이템을 선택하고 자동 스크롤해줌.	
}

 

 

 

 


 

7. 사각형을 마우스로 클릭해서 이동하기

 

# WM_PAINT

 

void CEXamMFC02Dlg::OnPaint()
{
    CPaintDC dc(this); // else 문에도 써야하므로 여기로 빼준다.

    if (IsIconic())
    {
                // 최소화 될 때 코드...
    }
    else
    {
                // 실제 코드는 여기다가 작성

        // CRect r(10, 10, 100, 100);
        CRect r;
        r.SetRect(10, 10, 100, 100);

        dc.Rectangle(r);
        // CDialogEx::OnPaint(); // CPaintDC dc(this); 때문에 제거한다.
    }
}

 

 

# 실제 예제

 

# CEXamMFC02Dlg.h

class CEXamMFC02Dlg : public CDialogEx
{
private:
    CRect m_rect;
    CPoint m_prev_pos;
    char m_is_clicked = 0;
...


# CEXamMFC02Dlg.cpp
// 변수 초기화

CEXamMFC02Dlg::CEXamMFC02Dlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_EXAMMFC02_DIALOG, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    m_rect.SetRect(10, 10, 100, 100);
}

...

void CEXamMFC02Dlg::OnPaint()
{
    CPaintDC dc(this);

    if (IsIconic())
    {
                ...
    }
    else
    {
        dc.Rectangle(m_rect);
    }
}

...

// 클래스 마법사로 3개 메시지 핸들러 추가

void CEXamMFC02Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
    //if (point.x >= m_rect.left && point.y >= m_rect.top &&
    //	  point.x <= m_rect.right && point.y <= m_rect.bottom)
    if (m_rect.PtInRect(point))   // 간단한 함수 제공
    {
        m_is_clicked = 1;
        m_prev_pos = point;
        SetCapture(); // 마우스 포인터가 클라이언트 영역을 벗어나더라도 메시지를 받으라는 함수.
    }

    CDialogEx::OnLButtonDown(nFlags, point);
}


void CEXamMFC02Dlg::OnLButtonUp(UINT nFlags, CPoint point)
{
    if (m_is_clicked == 1)
    {
        m_is_clicked = 0;
        ReleaseCapture();  // 캡처 해제.
    }

    CDialogEx::OnLButtonUp(nFlags, point);
}


void CEXamMFC02Dlg::OnMouseMove(UINT nFlags, CPoint point)
{
    if (m_is_clicked == 1)
    {
        CPoint move_pos = point - m_prev_pos;  // 연산자 오버로딩 되어 있음
        //CPoint move_pos;
        //move_pos.x = point.x - m_prev_pos.x;
        //move_pos.y = point.y - m_prev_pos.y;

        m_rect = m_rect + move_pos;  // 연산자 오버로딩 되어 있음
        
        //m_rect.left += move_pos.x;
        //m_rect.top += move_pos.y;
        //m_rect.right += move_pos.x;
        //m_rect.bottom += move_pos.y;

        m_prev_pos = point;

        Invalidate();
    }

    CDialogEx::OnMouseMove(nFlags, point);
}

 

 

 

 

 


 

8. 투명한 윈도우 만들기

 

# 투명한 윈도우를 사용하려면 다이얼로그 속성 Layered 값에 True 를 준다.

 

BOOL CLayerExamDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // 다이얼로그 Layered 속성을 True 로 하면 반드시 아래 둘 중 하나를 적용해야한다.
    // 둘 다 안주면 완전 투명 윈도우로 나온다.


    // 알파값 100을 적용 (0 ~ 255)
    //SetLayeredWindowAttributes(0, 100, LWA_ALPHA);
    
    // 임의로 한 색을 투명색으로 지정한다.
    //SetLayeredWindowAttributes(RGB(255, 1, 7), 0, LWA_COLORKEY);

    return TRUE;  // return TRUE  unless you set the focus to a control
}




void CLayerExamDlg::OnPaint()
{
    CPaintDC dc(this);

    if (IsIconic())
    {
        ...
    }
    else
    {
        dc.FillSolidRect(10, 10, 200, 200, RGB(255, 1, 7));
        //CDialogEx::OnPaint();
    }
}




void CLayerExamDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
    // Layered 속성 값이 False 이면 Layered 속성 적용
    int wnd_style = GetWindowLong(m_hWnd, GWL_EXSTYLE);
    if (!(wnd_style & WS_EX_LAYERED))
    {
        ::SetWindowLong(m_hWnd, GWL_EXSTYLE, wnd_style | WS_EX_LAYERED);
    }

    SetLayeredWindowAttributes(RGB(255, 1, 7), 0, LWA_COLORKEY);


    // 마우스 클릭하면 투명한 사각형 만들기
    //CClientDC dc(this);
    //dc.FillSolidRect(point.x - 10, point.y - 10, 20, 20, RGB(255, 1, 7));

    CDialogEx::OnLButtonDown(nFlags, point);
}

 

 


 

9. 대화 상자 추가하기 (정형)

 

– 자식 대화상자 추가하기 –

  1. 리소스 뷰에서 다이얼로그 추가
  2. 대화상자에서 더블 클릭 -> MFC 클래스 추가 창이 뜬다. (새로 만든 대화상자는 클래스를 만들어 주어야 한다.)
  3. 클래스 이름 : NewDlg
  4. 클래스 마법사 -> 가상 함수 추가 : OninitDialog (대화상자가 생성된 후 화면에 나타나기 직전에 호출되는 함수, 초기값 세팅 작업을 한다.)

 

  • – 부모 대화상자 작업 –

  • 버튼 추가 : IDC_NEW_DLG_BTN, 대화상자 실행
  • 에디트 컨트롤 추가 : IDC_PARENT_NUM_EDIT

 

버튼 이벤트 핸들러

#include "NewDlg.h"

void CExamChatDlg::OnBnClickedNewDlgBtn()
{
    int num = GetDlgItemInt(IDC_PARENT_NUM_EDIT);

    NewDlg ins_dlg;
    ins_dlg.SetNum(num);

    // DoModal() 함수로 인해서 대화상자가 닫히면 대화상자와 그 안의 컨트롤들이 파괴됨.
    // 따라서 미리 자식의 대화상자에서 값을 저장해 놓고 부모 대화상자에서 불러와야 한다.
    int result = ins_dlg.DoModal();

    // 버튼의 ID가 반환값이 된다.
    if (IDOK == result)
    {
        num = ins_dlg.GetNum();
        SetDlgItemInt(IDC_PARENT_NUM_EDIT, num);
    }
    else if (20 == result)
    {
        SetDlgItemInt(IDC_PARENT_NUM_EDIT, 0);
    }
}

 

 

  • – 자식 대화상자 작업 –

확인 버튼 : 기존 사용
종료 버튼 추가 : IDC_EXIT_BTN
에디트 컨트롤 추가 : IDC_CHILD_NUM_EDIT

 

- NewDlg.h

class NewDlg : public CDialogEx
{
private:
    int m_num;  // 가능하면 private 으로 하라.

public:
    void SetNum(int a_num)
    {
        m_num = a_num;  // 부모 대화상자에서 사용
    }

    int GetNum()
    {
        return m_num;  // 부모 대화상자에서 사용.
    }
...
}


- NewDlg.cpp

BOOL NewDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    SetDlgItemInt(IDC_CHILD_NUM_EDIT, m_num);  // 부모 대화상자에서 넘어온 값을 에디트 값에 넘겨준다.

    return TRUE;
}

// 확인 버튼
void NewDlg::OnBnClickedOk()
{
    m_num = GetDlgItemInt(IDC_CHILD_NUM_EDIT);
    CDialogEx::OnOK(); // 내부적으로 EndDialog(IDOK);
}

// 종료 버튼
void NewDlg::OnBnClickedExitBtn()
{
    // 반환값과 함께 종료
    EndDialog(20);
}

 


 

10. 여러 개의 컨트롤에 입력된 값을 쉽게 관리하는 방법

 

컨트롤의 리소스 ID 를 순서대로 맞춰놓고 배열을 이용해서 값을 쉽게 관리한다.

 

# 에디트 컨트롤 5개

# 콤보박스 6개
Data : 1;2;3;4;5; (세미콜론으로 분리)
Type : Drop List (편집 막음)

# 버튼 1개
값 나열하기, IDC_SHOW_BTN

# 에디트 컨트롤 1개
IDC_NUM_EDIT

# Resource.h 파일
IDC_EDIT1 ~ IDC_EDIT5
IDC_COMBO1 ~ IDC_COMBO6
리소스 ID 가 순서대로 되어 있는지 확인.

 

 

원래 방법

 

void CExamChatDlg::OnBnClickedShowBtn()
{
    int num[11], i;

    for (i = 0; i < 5; i++) num[i] = GetDlgItemInt(IDC_EDIT1 + i);

    CString str, total_str;
    CComboBox* p_combo;
    int index;
    for (i = 0; i < 6; i++)
    {
        p_combo = (CComboBox*)GetDlgItem(IDC_COMBO1 + i);  // CWnd 형을 반환하지만 다형성으로 자식인 CComboBox 받을 수 있다.
        index = p_combo->GetCurSel();
        if (index != CB_ERR)
        {
            p_combo->GetLBText(index, str);
            num[5 + i] = _ttoi(str);
        }
        else
        {
            num[5 + i] = 0;
        }
    }	

    for (i = 0; i < 11; i++)
    {
        str.Format(_T("%d, "), num[i]);
        total_str = total_str + str;
    }

    SetDlgItemText(IDC_NUM_EDIT, total_str);
}

 

 

리소스 ID 를 순서대로 맞춰 놓고 쉽게 하는 방법

 

void CExamChatDlg::OnBnClickedShowBtn()
{
    int num[11], i;
    CString str, total_str;

    for (i = 0; i < 11; i++) num[i] = GetDlgItemInt(IDC_EDIT1 + i);

    for (i = 0; i < 11; i++)
    {
        str.Format(_T("%d, "), num[i]);
        total_str = total_str + str;
    }

    SetDlgItemText(IDC_NUM_EDIT, total_str);
}

 

 


 

11. MFC에서 컨트롤 변수 직접 등록해서 사용하기

 

# 클래스 마법사나 변수 추가를 사용하지 않고 관리하는 방법

1. 클래스에 컨트롤의 핸들로 Attach 하여 클래스가 컨트롤을 관리하도록 한다.
2. 직접 Attach 하는 경우 Detach 를 해서 클래스가 컨트롤을 관리하지 않도록 한다.

# WM_DESTORY
아직 윈도우는 존재하지만 대화상자가 곧 파괴되니까 마무리 작업 하라.

대화상자가 파괴될 때 대화상자도 컨트롤을 파괴하려고 하고 해당 클래스도 컨트롤을 파괴하려고 하기 때문에 충돌이 나서 문제가 된다.
반드시 Attach 를 했으면 Detach 를 해준다.

 

- NewDlg.h

private:
    CEdit m_my_edit;
    CListBox m_my_list_box;



- NewDlg.cpp

BOOL NewMyDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    m_my_edit.Attach(GetDlgItem(IDC_EDIT1)->m_hWnd);
    m_my_list_box.Attach(GetDlgItem(IDC_LIST1)->m_hWnd);

    return TRUE;
}


void NewMyDlg::OnDestroy()
{
    CDialogEx::OnDestroy();

    m_my_edit.Detach();
    m_my_list_box.Detach();
}


void NewMyDlg::OnBnClickedButton1()
{
    CString str;
    m_my_edit.GetWindowText(str);
    int index = m_my_list_box.InsertString(-1, str);
    m_my_list_box.SetCurSel(index);
}

 

 


 

[Q&A] MFC에서 윈도우 배경색 변경하기

 

# SetDialogBkColor() 함수가 더이상 지원되지 않는다.

 

BOOL CLayerExamApp::InitInstance()
{
    CWinApp::InitInstance();	

    // SetDialogBkColor(RGB(0, 200, 255), RGB(0, 0, 128)); // 더이상 지원 안함
    CLayerExamDlg dlg;
    m_pMainWnd = &dlg;
    dlg.DoModal();
    
    return FALSE;
}

 

 

# WM_ERASEBKGND (윈도우 리사이즈 등으로 배경을 지울 때 호출됨.)

# WM_CTLCOLOR (차일드 컨트롤이 그려지기 직전 호출됨.)

 

# CLayerExamDlg.h

class CLayerExamDlg : public CDialogEx
{
private:
    CBrush m_bk_brush;  // 브러시 하나 선언


# CLayerExamDlg.cpp

BOOL CLayerExamDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    m_bk_brush.CreateSolidBrush(RGB(0, 200, 255));  // 브러시를 하늘색으로 만든다. 브러시를 만들었으면 해제도 해준다. DeleteObject()

    return TRUE;
}



void CLayerExamDlg::OnPaint()
{

    if (IsIconic())
    {
        ...
    }
    else
    {
        CPaintDC dc(this);

        // Win32 API 이전 방식. 펜속성도 바꿔줘야 테두리 색도 바뀌므로 복잡하다.
        //CRect r;
        //GetClientRect(r);
        //CBrush* p_old_brush = dc.SelectObject(&m_bk_brush);
        //dc.Rectangle(r);
        //dc.SelectObject(p_old_brush);

        CRect r;
        GetClientRect(r);
        //dc.FillSolidRect(r, RGB(0, 200, 255));  // 브러시는 따로 만들었으므로 FillRect로 간다.
        dc.FillRect(r, &m_bk_brush);  // 직접 브러시로 적용할 때.

        //CDialogEx::OnPaint();
    }
}


void CLayerExamDlg::OnDestroy()
{
    CDialogEx::OnDestroy();

    m_bk_brush.DeleteObject();
}


# 클래스 마법사에서 WM_ERASEBKGND 메시지 핸들러를 생성 (색상이 달라지므로 권장 안함)

BOOL CLayerExamDlg::OnEraseBkgnd(CDC* pDC)
{
    // 배경을 그리기 위한 메시지 핸들러
    // WM_PAINT 보다 먼저 들어옴.

    // BOOL flag = CDialogEx::OnEraseBkgnd(pDC);  // 회색으로 채움 (하늘색과 중복 적용되기 때문에 지워준다.)

    CRect r;
    GetClientRect(r);
    pDC->FillRect(r, &m_bk_brush);  // 하늘색으로 채움

    return TRUE; // 회색으로 채우지 않게 하고 TRUE로 적어주면 된다.
}


# WM_CTLCOLOR
- 대화상자나 컨트롤의 배경이나 전경을 바꿀 때 사용


HBRUSH CLayerExamDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{	
    HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);

    // 대화상자나 버튼 등의 컨트롤이 그려지기 전에 호출
    // 배경색으로 쓸 브러시를 반환한다.

    if (nCtlColor == CTLCOLOR_DLG) return m_bk_brush; // 버튼에 하늘색 배경색을 채움
    else if (nCtlColor == CTLCOLOR_STATIC)
    {
        // 스태틱 컨트롤, 텍스트 배경은 바뀌지 않으므로 투명하게 바꿔준다.
        //pDC->SetBkMode(TRANSPARENT); // 투명하게 하는건 부하가 좀 있으므로 텍스트 배경을 바꿔주는 방식으로 한다.
        pDC->SetBkColor(RGB(0, 200, 255));
        pDC->SetTextColor(RGB(255, 255, 255));
        return (HBRUSH)::GetStockObject(NULL_BRUSH); // 하늘색 브러시보다는 배경 자체를 그리지 않게 한다.
    }	
        
    return hbr;
}


# WM_CTLCOLOR 를 세분화해서 작업할 수 있다.

// WM_CTLCOLORMSGBOX, WM_CTLCOLOREDIT, WM_CTLCOLORLISTBOX, WM_CTLCOLORBTN, WM_CTLCOLORDLG, WM_CTLCOLORSCROLLBAR, WM_CTLCOLORSTATIC
// 클래스 마법사에 없으므로 직접 등록해서 사용해야 한다.
// 복잡하게 굳이 세분화해서 작업하기 보단 WM_CTLCOLOR 를 사용하는게 좋아 보인다.

# CLayerExamDlg.h

class CLayerExamDlg : public CDialogEx
{
    LRESULT OnCtrlColorDlg(WPARAM wParam, LPARAM lParam);



# CLayerExamDlg.cpp

BEGIN_MESSAGE_MAP(CLayerExamDlg, CDialogEx)
    ON_MESSAGE(WM_CTLCOLORDLG, OnCtrlColorDlg)
END_MESSAGE_MAP()
-----
LRESULT CLayerExamDlg::OnCtrlColorDlg(WPARAM wParam, LPARAM lParam)
{
    //return (LRESULT)m_bk_brush.GetSafeHandle();  // GetSafeHandle()는 gdi 객체 핸들 반환. 이것도 괜찮음
    return (LRESULT)(HBRUSH)m_bk_brush; // 연산자 오버로딩 되어 있음.
}

 

 

# WM_CTLCOLORSTATIC 직접 작성해보기 (스태틱 컨트롤 배경색 바꾸기)

 

// 3군데를 작성한다.

# 헤더 파일에 하나
LRESULT OnCtrlColorStatic(WPARAM wParam, LPARAM lParam);

# 소스 파일에 두 개

ON_MESSAGE(WM_CTLCOLORSTATIC, OnCtrlColorStatic)

LRESULT CLayerExamDlg::OnCtrlColorStatic(WPARAM wParam, LPARAM lParam)
{
    CDC* dc = CDC::FromHandle((HDC)wParam);	
    HWND hwnd = (HWND)lParam;
    dc->SetBkColor(RGB(0, 200, 255));
    dc->SetTextColor(RGB(255, 255, 255));
    return (LRESULT)(HBRUSH)::GetStockObject(NULL_BRUSH);
}

 

 


 

13. [실습 영상] MFC에서 사용자 정의 윈도우 만들기

 

클래스 마법사 -> Add Class -> MFC Class

클래스 이름 : UserWnd

기본 클래스 : CWnd

 

# 부모 다이얼로그 헤더 파일에 선언

#include “UserWnd.h”

Private:
UserWnd m_user_wnd;

 

# 부모 다이얼로그 소스 파일 OnInitDialog() 에 추가

//CRect를 사용하는 여러 방법
//CRect r;
//r.SetRect(50, 50, 200, 200);
//CRect r(50, 50, 200, 200);

m_user_wnd.Create(NULL, NULL, WS_VISIBLE | WS_CHILD | WS_BORDER, CRect(50, 50, 200, 200), this, 25000);

 

# UserWnd 클래스 소스 파일에 에 추가

void UserWnd::OnPaint()
{
    CPaintDC dc(this);

    CRect r;
    GetClientRect(r);

    dc.FillSolidRect(r, RGB(0, 0, 255));  // 배경색을 파란색으로
}


void UserWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
    CRect r;
    r.SetRect(point.x - 10, point.y - 10, point.x + 10, point.y + 10);

    CClientDC dc(this);
    dc.FillSolidRect(r, RGB(255, 0, 0));  // 마우스 클릭 지점에 빨간 사각형 추가.

    CWnd::OnLButtonDown(nFlags, point);
}

 

 


 

14. [실습 영상] 서브클래싱을 사용하여 버튼 기능 확장하기

 

기존의 윈도우나 컨트롤의 형태 또는 동작을 변경함
1. 새로운 클래스를 만들고 버튼에 서브클래싱한다.
2. 버튼을 누루면 새로운 클래스에 있는 메시지 핸들러 처리, 처리 후 부모 윈도우에 메시지 핸들러 처리.

# 서브클래싱을 하게 되면 서브클래싱된 새로운 클래스에 먼저 메시지가 전달된다.

 

# 컨트롤 배치
증가 버튼 : IDC_INC_BTN
감소 버튼 : IDC_DEC_BTN
에디트 컨트롤 : IDC_VALUE_EDIT

 

클래스 마법사 -> 클래스 추가 -> MFC 클래스 추가

클래스 이름: MyBtn
기본 클래스 : CButton

# MyBtn 클래스 (버튼 기능 확장) : 이 클래스에 있는 메시지 핸들러를 먼저 호출해서 처리 후 부모 윈도우에 있는 메시지 핸들러 처리

void MyBtn::OnLButtonDown(UINT nFlags, CPoint point)
{
    SetTimer(1, 500, NULL);
    CButton::OnLButtonDown(nFlags, point);
}


void MyBtn::OnLButtonUp(UINT nFlags, CPoint point)
{
    KillTimer(1);
    KillTimer(2); // 플래그성 메시지라 if로 확인 안하고 해도 됨. 중복 해제해도 가능. 문제 되지 않음.

    CButton::OnLButtonUp(nFlags, point);
}


void MyBtn::OnTimer(UINT_PTR nIDEvent)
{
    if (nIDEvent == 1)
    {
        KillTimer(1);
        SetTimer(2, 100, NULL);
    }
    else if (nIDEvent == 2)
    {
        GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), 20000), (LPARAM)m_hWnd);  // 부모 윈도우에 WM_COMMAND 메시지를 보냄, MSND 참조
    }

    CButton::OnTimer(nIDEvent);
}

 

 

#include "MyBtn.h"

// CMyMFCDlg dialog
class CMyMFCDlg : public CDialogEx
{
private:
    MyBtn m_inc_btn, m_dec_btn;



BOOL CMyMFCDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    SetDlgItemInt(IDC_VALUE_EDIT, 0);

    m_inc_btn.SubclassDlgItem(IDC_INC_BTN, this);  // IDC_INC_BTN 버튼에 서브클래싱을 했으므로 메시지가 m_inc_btn에 먼저 전달된다. 처리 후 아래 증가 버튼 처리
    m_dec_btn.SubclassDlgItem(IDC_DEC_BTN, this);

    return TRUE;
}


// 증가 버튼 클릭
void CMyMFCDlg::OnBnClickedIncBtn()
{
    int value = GetDlgItemInt(IDC_VALUE_EDIT);
    SetDlgItemInt(IDC_VALUE_EDIT, value + 1);
}

// 감소 버튼 클릭
void CMyMFCDlg::OnBnClickedDecBtn()
{
    int value = GetDlgItemInt(IDC_VALUE_EDIT);
    SetDlgItemInt(IDC_VALUE_EDIT, value - 1);
}

// 서브클래싱된 클래스에서 전달된 메시지 처리
BOOL CMyMFCDlg::OnCommand(WPARAM wParam, LPARAM lParam)
{
    if (HIWORD(wParam) == 20000)
    {
        int value = GetDlgItemInt(IDC_VALUE_EDIT);

        if (LOWORD(wParam) == IDC_INC_BTN) value++;
        else value--;

        SetDlgItemInt(IDC_VALUE_EDIT, value);
    }

    return CDialogEx::OnCommand(wParam, lParam);
}

 

# 다이얼로그에 있는 에디트 컨트롤은 WM_KEYDOWN 이 들지 않는다. 다음과 같이 작성해야 한다.

# 또는 서브클래싱 기능을 이용하면 WM_CHAR 또는 WM_KEYDOWN 을 받을 수 있다.

# 다음 예제는 Default Button 기능 때문에 엔터키를 눌렀을 때 프로그램이 종료되지 않게 하도록 만든다.

 

BOOL CMyMFCDlg::PreTranslateMessage(MSG* pMsg)
{
    if (pMsg->message == WM_KEYDOWN && pMsg->hwnd == GetDlgItem(IDC_EDIT1)->m_hWnd) {

        if (pMsg->wParam == VK_RETURN) {
            
            return TRUE;
        }
    }

    return CDialogEx::PreTranslateMessage(pMsg);
}

 


 

MFC 서브 클래싱

 

https://petra.tistory.com/941

 

# 정적 서브 클래싱 : 기존 컨트롤을 상속 받으면 자동으로 서브클래싱되는 방법

 

# 동적 서브 클래싱 : 동적으로 CWnd::SubclassWindow() 또는 CWnd::SubclassDlgItem() 이용하는 방법

 

 


 

15. [MFC] List Box를 사용하여 색상 선택 기능 구현하기

 

# 다이얼로그 배치

  1. 리스트 박스 1개, IDC_COLOR_LIST, Has String -> False, Ower Draw -> Fixed, Multicolmun -> True
  2. 라디오 버튼 2개 : 테두리 색상(IDC_PEN_RADIO), 채우기 색상(IDC_BRUSH_RADIO) -> Push Like 속성 True

 

# 클래스 마법사 -> 클래스 추가 -> MFC 클래스 추가

클래스 이름: CMyList
기본 클래스 : CListBox

 

# 헤더파일에 변수 추가
    CMyList m_color_list;	
    afx_msg void OnLbnSelchangeColorList();
    COLORREF m_pen_color = RGB(0, 0, 0), m_brush_color = RGB(255, 255, 255);

# 소스파일
BOOL CPenAndBrushDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    //CButton* p = (CButton*)GetDlgItem(IDC_PEN_RADIO);
    //p->SetCheck(1);

    ((CButton*)GetDlgItem(IDC_PEN_RADIO))->SetCheck(1);

    COLORREF color_table[20] = {
        RGB(0,0,0),RGB(0,0,255),RGB(0,255,0),RGB(0,255,255),RGB(255,0,0),RGB(255,0,255),
        RGB(255,255,0),RGB(255,255,255),RGB(0,0,128),RGB(0,128,0),RGB(0,128,128),RGB(128,0,0),
        RGB(128,0,128),RGB(128,128,0),RGB(128,128,128),RGB(192,192,192),RGB(192,220,192),RGB(166,202,240),
        RGB(255,251,240),RGB(160,160,164)
    };

    m_color_list.SubclassDlgItem(IDC_COLOR_LIST, this);
    m_color_list.SetColumnWidth(30);
    m_color_list.SetItemHeight(0, 30);

    for (int i = 0; i < 20; i++)
    {
        m_color_list.InsertString(i, _T("하이"));
        m_color_list.SetItemData(i, color_table[i]);
    }

    return TRUE; 
}

void CPenAndBrushDlg::OnPaint()
{
    CPaintDC dc(this);

    if (IsIconic())
    {	
        ...
    }
    else
    {
        CPen my_pen(PS_SOLID, 5, m_pen_color);
        CPen *p_old_pen = dc.SelectObject(&my_pen);  // 기존 펜 주소 반환

        CBrush my_brush(m_brush_color);
        CBrush* p_old_brush = dc.SelectObject(&my_brush);

        dc.Rectangle(20, 20, 150, 150);
        dc.SelectObject(p_old_pen);  // 엠퍼센드(&) 넣지 말것
        dc.SelectObject(p_old_brush);

        my_pen.DeleteObject();  // 확실하게 하기위해서 delete 할 것.
        my_brush.DeleteObject();  // 해제 순서는 상관 없음.
        //CDialogEx::OnPaint();
    }
}

// 리스트박스 LBN_SELCHANGE 이벤트 핸들러 추가
void CPenAndBrushDlg::OnLbnSelchangeColorList()
{
    int index = m_color_list.GetCurSel();
    if (LB_ERR != index) {
        CButton* p = (CButton*)GetDlgItem(IDC_PEN_RADIO);
        if (p->GetCheck()) m_pen_color = m_color_list.GetItemData(index);
        else m_brush_color = m_color_list.GetItemData(index);
        InvalidateRect(CRect(0,0,200,200)); // 화면 갱신 영역 지정 (깜빡임 제거 위해)
    }
}

 

 


 

16. 시스템 전역 단축키 사용하기

 

# 헤더 파일에 변수 추가

bool m_show_flag = true;

 

# 소스 파일에 추가

// shift + pause 키를 시스템 전역 단축키로 등록 (26000번은 ID 식별자)

RegisterHotKey(m_hWnd, 26000, MOD_SHIFT, VK_PAUSE); // 등록하면 핫키를 누룰 때 WM_HOTKEY 가 발생

UnregisterHotKey(m_hWnd, 26000); // 해제

 

void CMFCShortcutKeyDlg::OnHotKey(UINT nHotKeyId, UINT nKey1, UINT nKey2)
{
    // Shfit + Pause 키를 눌렀을 때 호출, ID 가 26000번이면
    if (nHotKeyId == 26000)
    {
        if (m_show_flag == 1) ShowWindow(SW_HIDE);
        else ShowWindow(SW_SHOW);
        m_show_flag = !m_show_flag;
    }

    CDialogEx::OnHotKey(nHotKeyId, nKey1, nKey2);
}

 


 

17. [MFC] 가격 계산 프로그램 만들기

 

# 리스트박스 1개 추가 (체크리스트박스 클래스를 이용하여 서브클래싱)
– IDC_ITEM_LIST
– Owner Draw : Fixed (사용자가 정의해서 그리기)
– Has Strings : True (오너 드로우를 켰을 때 안하면 AddString 이나 InsertString 이 안먹힘.)
– CCheckListBox 변수 선언 후 리스트 박스를 서브 클래싱
// m_item_list.SubclassDlgItem(IDC_ITEM_LIST, this);

# 리스트박스 1개 추가
– IDC_COUNT_LIST

# 스태틱 컨트로 1개 추가
– 합산 가격

# 에디트 컨트롤 1개 추가
– IDC_TOTAL_PRICE_EDIT

# 스핀 컨트롤 1개 추가
– IDC_COUNT_SPIN
– Orientation : Horizontal

 

# 헤더 파일

#define MAX_ITEM_COUNT 8

class CCoffeeShopDlg : public CDialogEx
{
private:
    CCheckListBox m_item_list;
    CRect m_spin_rect;

public:
    CCoffeeShopDlg(CWnd* pParent = nullptr);	// standard constructor
    void CalcTotalPrice();
    void CCoffeeShopDlg::ChangeText(CListBox* ap_list_box, int a_index, const wchar_t* ap_string);

 

# 소스 파일

BOOL CCoffeeShopDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    m_count_spin.GetWindowRect(&m_spin_rect);  // 윈도우 좌표 기준
    ScreenToClient(&m_spin_rect); // 윈도우 좌표를 클라이언트 영역 좌표로 변경해줌.

    wchar_t* p_item_name[MAX_ITEM_COUNT] = {
        _T("아메리카노    1900원"), _T("카페라떼    2500원"),
        _T("카페모카    2800원"), _T("카라멜마끼아또    3200원"),
        _T("에스프레소    1800원"), _T("바닐라라떼    3500원"),
        _T("카푸치노    3300원"), _T("비엔나    3500원"),
    };

    int price[8] = { 1900, 2500, 2800, 3200, 1800, 3500, 3300, 3500 };

    m_item_list.SubclassDlgItem(IDC_ITEM_LIST, this);

    // 체크박스가 깨지는 거 방지	
    m_item_list.SetItemHeight(0, 24);
    m_count_list.SetItemHeight(0, 24);
    
    for (int i = 0; i < MAX_ITEM_COUNT; i++)
    {
        m_item_list.InsertString(i, p_item_name[i]);
        //SetItemData는 해당 아이템마다 4바이트 크기의 저장공간을 저장할 수 있음.
        m_item_list.SetItemData(i, price[i]);
        m_count_list.InsertString(i, _T("0"));
    }

    return TRUE;
}


void CCoffeeShopDlg::CalcTotalPrice()
{
    int count = m_item_list.GetCount();
    int total_price = 0;
    CString str;

    for (int i = 0; i < count; i++)
    {
        if (m_item_list.GetCheck(i))
        {
            m_count_list.GetText(i, str);			
            total_price += m_item_list.GetItemData(i) * _ttoi(str);
        }
    }

    SetDlgItemInt(IDC_TOTAL_PRICE_EDIT, total_price);
}

// 첫번째 인자가 CListBox로 받으면 메뉴 리스트박스랑 카운트 리스트박스 모두 바꿀 수 있다.
void CCoffeeShopDlg::ChangeText(CListBox* ap_list_box, int a_index, const wchar_t* ap_string)
{	
    ap_list_box->DeleteString(a_index);
    ap_list_box->InsertString(a_index, ap_string);
    ap_list_box->SetCurSel(a_index);  // 삭제하고 다시 삭제했으므로 다시 선택을 해주어야 한다.
}

void CCoffeeShopDlg::OnLbnSelchangeItemList()
{
    int index = m_item_list.GetCurSel();
    CString str;
    m_count_list.GetText(index, str);
    int item_count = _ttoi(str);

    if (m_item_list.GetCheck(index)) {
        if (item_count == 0) ChangeText(&m_count_list, index, _T("1"));
    }
    else {
        if (item_count != 0) ChangeText(&m_count_list, index, _T("0"));
    }
    

    m_count_list.SetCurSel(index);
    
    // SWP_NOSIZE: 폭과 높이를 변경 안함, 폭과 높이 파라미터를 0으로 적어도 변경 안함 //  MoveWindow() 도 가능
    m_count_spin.SetWindowPos(NULL, m_spin_rect.left, m_spin_rect.top + index * 24, 0, 0, SWP_NOSIZE);

    CalcTotalPrice();
}



void CCoffeeShopDlg::OnDeltaposCountSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
    LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
    *pResult = 0;
    
    int index = m_item_list.GetCurSel();
    if (LB_ERR != index && m_item_list.GetCheck(index))
    {
        CString str;
        m_count_list.GetText(index, str);
        int item_count = _ttoi(str);

        if (pNMUpDown->iDelta > 0) {
            if (item_count > 1) item_count--;
        }
        else {
            if (item_count < 100)item_count++;
        }
        str.Format(_T("%d"), item_count);
        ChangeText(&m_count_list, index, str);
        CalcTotalPrice();
    }
}

 

 


 

18. Edit 컨트롤의 색상 변경하기 – Step1

 

# 클래스 마법사 WM_CTLCOLOR 메시지 추가

 

# 헤더 파일

private:
    HBRUSH mh_edit_bk_brush;
    HWND mh_old_focus;

 

# 소스 파일

BOOL CExamEditDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    mh_edit_bk_brush = ::CreateSolidBrush(RGB(0, 0, 255));  // MFC 함수도 가능, :: 붙여서 API 32 함수를 쓰겠다.

    for (int i = 0; i < 6; i++)
    {
        SetDlgItemText(IDC_EDIT1 + i, _T("안녕하세요."));
    }

    return TRUE;
}


HBRUSH CExamEditDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);

    int control_id = pWnd->GetDlgCtrlID();
    if (control_id >= IDC_EDIT1 && control_id <= IDC_EDIT6)
    {
        HWND cur_focus = ::GetFocus();  // :: 붙여서 API32 함수를 써야 HWND 값을 반환한다.		
        if (cur_focus == pWnd->m_hWnd)
        {
            if (mh_old_focus != cur_focus)
            {
                if (mh_old_focus != NULL) ::InvalidateRect(mh_old_focus, NULL, TRUE);  // WM_PAINT 를 발생하라.(윈도우 핸들, 전체 영역, 백그라운드)
                mh_old_focus = cur_focus;
            }

            pDC->SetTextColor(RGB(0, 255, 255)); // 텍스트 색
        }
        else
        {
            pDC->SetTextColor(RGB(0, 168, 168)); // 텍스트 색
        }
        
        pDC->SetBkColor(RGB(0, 0, 128));  // 텍스트 배경색
        return mh_edit_bk_brush;  // 에디트 컨트롤 배경색
    }
    return hbr;
}


void CExamEditDlg::OnDestroy()
{
    CDialogEx::OnDestroy();

    DeleteObject(mh_edit_bk_brush);
}

 


 

19. 대화상자에서 메뉴 사용하기

 

# 컨트롤 배치

시작 버튼 : IDC_START_BTN
멈춤 버튼 : IDC_STOP_BTN
에디트 컨트롤 : IDC_STATE_BTN

# 메뉴 만들기

리소스 뷰 -> 리소스 추가 -> 메뉴 : IDR_MY_MENU

다이얼로그 메뉴에서 IDR_MY_MENU 선택해줌

기능(&O) – 시작(&S), 멈춤(&P), Separator, 종료(&X)
시작->마우스 오른쪽 버튼>이벤트 처리기 추가 (COMMAND 메시지 형식)
멈춤->IDC_STOP_BTN (멈춤 버튼과 ID 동일)
종료->ID_EXIT_PROGRAM

# 헤더 파일
private:
    int m_start_flag = 0;
public:
    void UpdateMenu();

# 소스 파일
BOOL CMFCMENUDlg::OnInitDialog()
{
    OnBnClickedStopBtn();
}

void CMFCMENUDlg::OnBnClickedStartBtn()
{
    m_start_flag = 1;
    SetDlgItemText(IDC_STATE_EDIT, _T("시작했습니다."));
    GetDlgItem(IDC_START_BTN)->EnableWindow(FALSE);
    GetDlgItem(IDC_STOP_BTN)->EnableWindow(TRUE);
    UpdateMenu();
}

void CMFCMENUDlg::UpdateMenu()
{
    CMenu* p_menu = GetMenu();
    if (p_menu != NULL) {
        CMenu* p_sub_menu = p_menu->GetSubMenu(0); // 첫번째 메뉴 (기능), 인덱스로 찾기
        if (p_sub_menu != NULL)
        {			
            if (m_start_flag == 1)
            {
                p_sub_menu->EnableMenuItem(ID_START_CMD, MF_BYCOMMAND | MF_DISABLED); // 커맨드 ID로 찾기
                p_sub_menu->EnableMenuItem(IDC_STOP_BTN, MF_BYCOMMAND | MF_ENABLED);
            }
            else
            {
                p_sub_menu->EnableMenuItem(ID_START_CMD, MF_BYCOMMAND | MF_ENABLED);
                p_sub_menu->EnableMenuItem(IDC_STOP_BTN, MF_BYCOMMAND | MF_DISABLED);
            }			
            /* 짧게 코딩
            p_sub_menu->EnableMenuItem(ID_START_CMD, m_start_flag * 2);
            p_sub_menu->EnableMenuItem(IDC_STOP_BTN, (!m_start_flag) * 2);
            */
        }
    }
}


void CMFCMENUDlg::OnBnClickedStopBtn()
{
    m_start_flag = 0;
    SetDlgItemText(IDC_STATE_EDIT, _T("중지했습니다."));
    GetDlgItem(IDC_START_BTN)->EnableWindow(TRUE);
    GetDlgItem(IDC_STOP_BTN)->EnableWindow(FALSE);
    UpdateMenu();
}


void CMFCMENUDlg::OnStartCmd()
{
    OnBnClickedStartBtn();
}

void CMFCMENUDlg::OnExitMenu()
{
    EndDialog(IDOK);
}

 

 


 

20. 대화 상자에서 단축키 사용하기

 

# Accelerator 추가
리소스 뷰 -> 리소스 추가 -> Accelerator : IDR_MY_ACC

# 단축키 추가 : 오른쪽 버튼 > 다음 입력한 키
ctrl + c : ID_EDIT_CLEAR_CMD  // 이벤트 처리기 추가
ctrl + s : ID_START_CMD (메뉴 이름)
ctrl + p : ID_STOP_BTN (버튼 이름)

클래스 마법사 -> 가상 함수 -> PreTranslateMessage

# 메뉴랑 단축키 ID를 동일하게 사용하는 경우 메뉴를 비활성시키면 단축키도 함께 비활성화 되므로 플래그 변수를 만들 필요 없다.

 

# 헤더 파일
private:
    int m_start_flag = 0;
    HACCEL m_acc_key;

# 소스 파일
BOOL CMFCMENUDlg::OnInitDialog()
{
    m_acc_key = ::LoadAccelerators(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_MY_ACC));  // delete 안해도 됨, 정수형 리소스를 문자열로 바꾸기 매크로
    OnBnClickedStopBtn();
}

BOOL CMFCMENUDlg::PreTranslateMessage(MSG* pMsg)
{
    if (::TranslateAccelerator(m_hWnd, m_acc_key, pMsg)) return TRUE;  // Accelerator에 단축키가 있다면 WM_COMMAND 메시지를 보냄

    return CDialogEx::PreTranslateMessage(pMsg);
}


void CMFCMENUDlg::OnEditClearCmd()
{
    SetDlgItemText(IDC_STATE_EDIT, _T(""));
}

 

 


 

21. 바이너리 뷰어 만들기 (CListBox 사용)

 

# 버튼 1개 : ID_SELECT_BTN
# 스태틱 컨트롤 1개 : IDC_PATH_STATIC (Sunken : True, Align Text : Center, Center Image : True, Caption : 선택한 파일이 없습니다.)
# 리스트 박스 1개 : IDC_BIN_DATA_BOX (변수 추가 : m_bin_data_list)

# 헤더 파일
private:
    CFont m_font;

# 소스 파일
BOOL CMFCHexViewerDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    m_font.CreatePointFont(128, _T("굴림체"));
    m_bin_data_list.SetFont(&m_font);
}

void CMFCHexViewerDlg::OnBnClickedSelectBtn()
{
    CFileDialog ins_dlg(TRUE);  // 파일 열기 대화상자, FALSE : 다른 이름으로 저장 대화상자
    if (IDOK == ins_dlg.DoModal())        
    {
        m_bin_data_list.ResetContent();
        SetDlgItemText(IDC_PATH_STATIC, ins_dlg.GetPathName());
        
        FILE* p_file = NULL;
        if (0 == _wfopen_s(&p_file, ins_dlg.GetPathName(), _T("rb")))
        {
            /*
            CString total_str, str;
            unsigned char temp[24];
            int len = 24, line = 1;
            while (len == 24)
            {
                len = fread(temp, 1, 24, p_file);  // 1바이트씩 24개
                if (len > 0)
                {
                    total_str.Format(_T("%06d : "), line++);   // total.Empty(); 또는 total_str = _T("");
                    for (int i = 0; i < 24; i++)
                    {
                        str.Format(_T("%02X "), temp[i]);
                        total_str += str;  // 동적 메모리 할당으로 시스템 성능에 안좋다.
                    }
                    m_bin_data_list.InsertString(-1, total_str);
                }
            }
            */			

            wchar_t str[128];
            unsigned char temp[24];
            int len = 24, line = 1, str_len;
            while (len == 24)
            {
                len = fread(temp, 1, 24, p_file);  // 1바이트씩 24개
                if (len > 0)
                {
                    str_len = swprintf_s(str, 128, _T("%06d: "), line++);
                    for (int i = 0; i < 24; i++)
                    {
                        str_len += swprintf_s(str + str_len, 128 - str_len, _T("%02X "), temp[i]);						
                    }
                    m_bin_data_list.InsertString(-1, str);
                }
            }

            fclose(p_file);  // _s 안붙이는 이유, 유니코드 문자가 안썼으므로
        }
    }
}

 


 

22. 대화상자의 컨트롤중에서 Edit 컨트롤만 찾아서 문자열 설정하기

 

# 버튼 4개와 에디트 컨트롤 4개를 만든다.

# 버튼 한개 만들기 (IDC_TEST_BTN, caption : 테스트)

# FindWindow() // 탑레벨 윈도우 찾기 (부모 윈도우만 찾음)
# FindWindowEx() // 자식 윈도우 찾기

 

# 소스 파일 (버튼 클릭이벤트)
void CMFCFindWindowDlg::OnBnClickedTestBtn()
{	
    /*
    // 제일 간단하고, 제일 안정적이고, 중간에 임시 객체를 만들지 않고, Attach() Detach() 하지 않기 때문에 이 방법이 제일 좋음
    // WIN 32 API 에 익숙하지 않다면 제일 아래에 있는 방법이 그나마 낫다.
    
    // MFC 함수가 아닌 API32 함수이므로 :: 붙이기
    // 결과는 찾은 컨트롤 핸들 (없으면 NULL)
    // 첫번째 인자 : 윈도우 핸들, 두번째 : NULL은 처음부터 순서대로 찾기(핸들이면 그 다음부터 찾기), 클래스명(대소문자 구분 안함), ""이 아니라 NULL(문자열은 검색 조건 아님)
    // 버튼 클래스명: button, 에디트 컨트롤 클래스명: edit

    /*
    HWND h_find_wnd = NULL;
    while (h_find_wnd = ::FindWindowEx(m_hWnd, h_find_wnd, _T("edit"), NULL))
    {
        ::SetWindowText(h_find_wnd, _T("Hello"));
    }
    */

    /* MFC 가 제공하는 함수 사용 (Cwnd* 반환)
    * MFC의 함수 FindWindowEx 는 CWnd 임시 객체를 만들어서 임시객체의 주소를 반환한다. 
    HWND h_find_wnd = NULL;
    CWnd* p_find_wnd = FindWindowEx(m_hWnd, NULL, _T("edit"), NULL);
    while (p_find_wnd != NULL)
    {
        p_find_wnd->SetWindowTextW(_T("Hello"));
        p_find_wnd = FindWindowEx(m_hWnd, p_find_wnd->m_hWnd, _T("edit"), NULL);		
    }
    */

    /* 위 방법을 좀 더 간단하게
    CWnd* p_find_wnd;
    HWND h_find_wnd = NULL;
    while (p_find_wnd = FindWindowEx(m_hWnd, h_find_wnd, _T("edit"), NULL))
    {
        p_find_wnd->SetWindowText(_T("Hello"));
        h_find_wnd = p_find_wnd->m_hWnd;
    }
    */

    /*
    // CWnd::FromHandle() 함수 사용	
    // CWnd::FromHandle(핸들) 핸들을 이용해서 CWnd 임시객체를 만들어서 임시객체의 주소를 반환.
    CWnd* p_find_wnd;
    HWND h_find_wnd = NULL;
    while (h_find_wnd = ::FindWindowEx(m_hWnd, h_find_wnd, _T("edit"), NULL))
    {
        p_find_wnd = CWnd::FromHandle(h_find_wnd);
        p_find_wnd->SetWindowText(_T("Hello"));		
    }
    */

    // 첫번째 방법이 가장 낫지만 MFC 함수를 사용해야 한다면 그 다음으로 이 방법이 나음.	
    // CWnd 클래스는 WIN 32 API를 래핑해서 만듬.
    // MFC의 CWnd 가 제공하는 FindWindowEx 나 FromHandle 은 MFC 임시객체를 만들어서 반환하기 때문에 많이 만들수록 성능이 좋지 않다.
    // 임시객체를 만드는 방법보다는 Attach() Detach() 해서 사용해주는게 더 좋음, 멀티쓰레드 환경에서도 더 좋음.
    // Attach() 윈도우 클래스랑 윈도우 핸들이랑 연결해줌.
    // Detach() 는 분리해줌.

    CWnd find_wnd;  // 포인터로 만드는게 아니라 Cwnd 객체를 하나 만듦.
    HWND h_find_wnd = NULL;
    while (h_find_wnd = ::FindWindowEx(m_hWnd, h_find_wnd, _T("edit"), NULL))
    {
        find_wnd.Attach(h_find_wnd);
        find_wnd.SetWindowText(_T("Hello"));
        find_wnd.Detach();
    }
}

 


 

23. 원 모양의 윈도우를 만들고 마우스로 이동하기

 

# 다이얼로그 속성
Title Bar : False
Border : None

# 클래스 마법사
WM_MOUSELBUTTONDOWN
WM_MOUSELBUTTONUP
WM_MOUSEMOVE

 

# 헤더 파일
private:
    CPoint m_prev_pos;
    char m_is_clicked = 0;

# 소스 파일
OnInitDialog()
    CRgn rgn;
    rgn.CreateEllipticRgn(0, 0, 200, 200);
    SetWindowRgn(rgn, TRUE);

    SetBackgroundColor(RGB(0, 200, 255));


void CEllipseTargetDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
    if (m_is_clicked == 0) {
        m_is_clicked = 1;		
        GetCursorPos(&m_prev_pos);
        SetCapture();
    }

    CDialogEx::OnLButtonDown(nFlags, point);
}


void CEllipseTargetDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
    if (m_is_clicked == 1) {
        m_is_clicked = 0;
        ReleaseCapture();
    }

    CDialogEx::OnLButtonUp(nFlags, point);
}


void CEllipseTargetDlg::OnMouseMove(UINT nFlags, CPoint point)
{
    if (m_is_clicked == 1) {
        CRect r;
        GetWindowRect(r);

        CPoint pos;
        GetCursorPos(&pos);

        // pos (현재 마우스 좌표) - m_prev_pos (이전 마우스 좌표) = 이동 거리		
        SetWindowPos(NULL, r.left + pos.x - m_prev_pos.x, r.top + pos.y - m_prev_pos.y, 0, 0, SWP_NOSIZE);
        m_prev_pos = pos;
    }

    CDialogEx::OnMouseMove(nFlags, point);
}

 

 


 

24. 팝업 메뉴 사용하기 – Step1

 

# 리소스 뷰 -> 리소스 추가 -> 메뉴
IDR_MY_MENU

# 메뉴 추가
팝업 메뉴 : 열기(ID_MY_OPEN), 닫기(ID_MY_CLOSE)

# 클래스 마법사
메시지 : WM_RBUTTONUP
명령 : ID_MY_OPEN, ID_MY_CLOSE

void CEllipseTargetDlg::OnRButtonUp(UINT nFlags, CPoint point)
{
    CMenu menu;
    menu.LoadMenu(IDR_MY_MENU);
    CMenu* p_sub_menu = menu.GetSubMenu(0); // 0번째 메뉴

    CPoint pos;
    GetCursorPos(&pos);

    p_sub_menu->TrackPopupMenu(TPM_LEFTALIGN, pos.x, pos.y, this); // 전체화면 좌표
    menu.DestroyMenu();

    CDialogEx::OnRButtonUp(nFlags, point);
}


void CEllipseTargetDlg::OnMyOpen()
{
    AfxMessageBox(_T("열기 선택"));
}


void CEllipseTargetDlg::OnMyClose()
{
    AfxMessageBox(_T("닫기 선택"));
}

 


 

25. 탐색기에서 Drag And Drop된 파일 정보 사용하기 – Step1

 

# 다이얼로그 속성
Accept Files : True

# 리스트 박스 배치
IDC_DROP_LIST
변수 추가 : m_drop_list

# 클래스 마법사
WM_DROPFILES

 

void CEllipseTargetDlg::OnDropFiles(HDROP hDropInfo)
{
    m_drop_list.ResetContent();

    int count = DragQueryFile(hDropInfo, -1, NULL, 0); // 파일 개수 반환
    
    wchar_t temp_path[MAX_PATH]; // 경로 최대값
    for (int i = 0; i < count; i++)
    {
        DragQueryFile(hDropInfo, i, temp_path, MAX_PATH);
        m_drop_list.InsertString(i, temp_path);
    }

    CDialogEx::OnDropFiles(hDropInfo);
}

 


 

26. 파일 관리하기 – Step1 (목록구성)

 

# 리스트박스 두 개 배치
IDC_LEFT_LIST
IDC_RIGHT_LIST
변수 추가 : m_left_list, m_right_list

 

BOOL CEllipseTargetDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();
        // 리스트 박스 기능
    // 절대 경로, 상대 경로 다 됨 (..\\*.* 이전 경로)
    // DDL_ARCHIVE | DDL_HIDDEN | DDL_DIRECTORY | DDL_DRIVES (파일, 숨김, 폴더, 드라이브)
    m_left_list.Dir(DDL_ARCHIVE | DDL_HIDDEN | DDL_DIRECTORY, _T("*.*"));
    
        // 일반적으로 파일 검색할 때
    CString name;
    WIN32_FIND_DATA file_data;
    HANDLE h_item_list = FindFirstFile(_T("*.*"), &file_data);
    if (h_item_list != INVALID_HANDLE_VALUE) { // 실패가 안하면
        do {
            // memcmp(file_data.cFileName, _T("."), 4);
            // "." 디렉토리는 제외 (첫번째 문자가 '.' 이고, 두번째 문자가 0(NULL) 이면 제외)
            // if (!(file_data.cFileName[0] == '.' && file_data.cFileName[1] == 0)) {			
            if (file_data.cFileName[0] != '.' || file_data.cFileName[1]) {
                name = file_data.cFileName;
                // 폴더에 대괄호 붙이기
                if (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) name = _T("[") + name + _T("]");
                m_right_list.InsertString(-1, name);
            }
            
        } while (FindNextFile(h_item_list, &file_data));
         
        FindClose(h_item_list);
    }

    return TRUE;
}

 

 


 

27. [MFC] 마우스 휠 버튼 사용하기 – Step 1

 

# 클래스 마법사
WM_MOUSEWHELL

 

# 헤더 파일
private:
    int m_pos = 0;

# 소스 파일
void CEllipseTargetDlg::OnPaint()
{
    CPaintDC dc(this); // device context for painting
    if (IsIconic())
    {
    }
    else
    {
        dc.FillSolidRect(10, 10, 30, 130, RGB(0, 0, 168));
        dc.FillSolidRect(10, 10 + m_pos, 30, 30, RGB(0, 100, 228));
    }
}

BOOL CEllipseTargetDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
    int old_pos = m_pos;

    if (zDelta < 0) {
        if (m_pos < 100) m_pos++;
    }
    else {
        if (m_pos > 0) m_pos--;
    }

    // 깜빡임 없애기.
    if (old_pos != m_pos) InvalidateRect(CRect(10, 10, 40, 140), FALSE); // left, top, right, bottom, 배경을 지울건지 여부.

    return CDialogEx::OnMouseWheel(nFlags, zDelta, pt);
}

 


 

28. [MFC] 키보드 방향 키로 사각형 움직이기 예제 – Step 1

 

# 대화상자 키보드는 컨트롤의 포커스를 옮기는 용도이기 때문에 WM_KEYDOWN 메시지가 안들어옴
# 대화상자가 이미 쓰고 있으므로 대화상자보다 먼저 처리해야 함.

# 클래스 마법사->가상 함수->PreTranslateMessage
// TranslateMessage 보다 먼저 호출
// 대화상자보다 먼저 메시지를 처리하겠다.

# 깜빡임 제거
대화상자 속성의 Clip Children 을 True 로 바꾼다.
// Clip Children : 부모 대화상자에서 자식 컨트롤 영역은 다시 그리지 않는다.

 

# 헤더 파일
private:
    CRect m_rect;
    int m_last_key_type = 0;

# 소스 파일
멤버변수 m_rect 초기화:
1. 생성자에서
CEllipseTargetDlg::CEllipseTargetDlg(CWnd* pParent) : CDialogEx(IDD_ELLIPSETARGET_DIALOG, pParent), m_rect(50, 50, 100, 100)

2. OnInitDialog() 에서
m_rect.SetRect(50, 50, 10, 100);

void CEllipseTargetDlg::OnPaint()
{
    CPaintDC dc(this);
    if (IsIconic())
    {
    }
    else
    {
        if (m_last_key_type) {
            dc.SetBkMode(TRANSPARENT);
            dc.SetTextColor(RGB(0, 200, 0));
            dc.SelectObject(GetFont()); // 대화상자 폰트
            if (m_last_key_type == VK_LEFT) {
                dc.TextOut(m_rect.left - 15, m_rect.top + 19, _T("◀"), 1);
            } else if (m_last_key_type == VK_UP) {
                dc.TextOut(m_rect.left + 19, m_rect.top - 14, _T("▲"), 1);
            } else if (m_last_key_type == VK_RIGHT) {
                dc.TextOut(m_rect.right + 2, m_rect.top + 19, _T("▶"), 1);
            } else if (m_last_key_type == VK_DOWN) {
                dc.TextOut(m_rect.left + 19, m_rect.bottom + 1, _T("▼"), 1);
            }				
        }
        dc.FillSolidRect(m_rect, RGB(0, 255, 0));
    }
}

BOOL CEllipseTargetDlg::PreTranslateMessage(MSG* pMsg)
{
    // 모든 키가 다 들어오므로 필터링 처리 
    if (pMsg->message == WM_KEYDOWN) {
        if (pMsg->wParam >= VK_LEFT && pMsg->wParam <= VK_DOWN) {
            if (pMsg->wParam == VK_LEFT) {
                m_rect.left--;
                m_rect.right--;
            }
            else if (pMsg->wParam == VK_UP) {
                m_rect.top--;
                m_rect.bottom--;
            }
            else if (pMsg->wParam == VK_RIGHT) {
                m_rect.left++;
                m_rect.right++;
            }
            else if (pMsg->wParam == VK_DOWN) {
                m_rect.top++;
                m_rect.bottom++;
            }
            m_last_key_type = pMsg->wParam;
            Invalidate();
            return 1; // 컨트롤의 포커스도 같이 움직이므로 처리했다는 1을 리턴.
        }
    }
    else if (pMsg->message == WM_KEYDOWN) {
        if (pMsg->wParam >= VK_LEFT && pMsg->wParam <= VK_DOWN) {
            m_last_key_type = 0;
            Invalidate();
            return 1;
        }
    }

    return CDialogEx::PreTranslateMessage(pMsg);
}

 


 

29. 네트워크 프로그래밍 – CSocket으로 정숫값 전달하기

 

순서

  1. 서버에서 소켓 생성하고 Listen 상태로 만듦
  2. 클라이언트에서 Connect
  3. 접속이 성공하면 서버에서 OnAccept 함수로 인해서 CSocket 이 새로 만들어짐
  4. 서버에서 새로 만들어진 CSocket 으로 클라이언트와 Send, Receive 작업

 

# 프로젝트 2개 추가 : ValueServer, ValueClient
// 고급 기능 – Windows 소켓 체크

# 서버쪽 다이얼로그
에디트 컨트롤 1개 추가 : IDC_VALUE_EDIT

# 클라이언트쪽 다이얼로그
에디트 컨트롤 1개 추가 : IDC_VALUE_EDIT
버튼 1개 추가 : IDC_SEND_BTN, 전송

// 자기 IP 알기 : cmd -> ipconfig

 

< 서버쪽 작업 >

# Listen 하고 Accept 할 메인 소켓 만들기
클래스 마법사 -> MFC 클래스 추가
– 클래스 이름 : MyServer
– 기본 클래스 : CSocket
– 클래스 마법사 : 가상 함수 -> OnAccept 추가 //  클라이언트에서 Connect 하면 OnAccept 호출

# 접속이 성공하면 쓸 소켓 클래스 만들기
클래스 마법사 -> MFC 클래스 추가
– 기본 클래스 : CSocket
– 클래스 이름 : MyUser
– 클래스 마법사 -> 가상 함수 -> OnReceive 추가

# 클라이언트에서 접속을 해제하면 OnClose 함수 호출됨.
클래스 마법사 -> 가상 함수 -> OnClose 추가

 

// 서버쪽

# MyServer.h
#include "MyUser.h"
class MyServer : public CSocket
{
private:
    MyUser m_user;  // accept 성공하면 쓸 MFC 클래스

# MyServer.cpp (클래스 마법사 가상 함수로 OnAccept 추가)
void MyServer::OnAccept(int nErrorCode)
{
    Accept(m_user);

    CSocket::OnAccept(nErrorCode);
}


# MyUser.cpp (클래스 마법사 가상 함수로 OnReceive 와 OnClose 함수 추가)
void MyUser::OnReceive(int nErrorCode)
{
    int value;
    Receive(&value, sizeof(int));
    
    // AfxGetMainWnd() = m_pMainWnd (메인 윈도우)
    AfxGetMainWnd()->SetDlgItemInt(IDC_VALUE_EDIT, value);
    
    value = 1;
    Send(&value, sizeof(int));  // 잘 받았다는 확인 메시지 보냄

    CSocket::OnReceive(nErrorCode);
}


void MyUser::OnClose(int nErrorCode)
{
    ShutDown(2); // 읽기 쓰기 모두 해제.
    Close();

    CSocket::OnClose(nErrorCode);
}

# ValueServer (메인 다이얼로그)
BOOL CValueServerDlg::OnInitDialog()
{
    // IP는 자동으로 적어주므로 포트번호만 적어주면 된다.
    // IP 알아내기 : ipconfig
    //m_server.Create(26001, 1, _T("192.168.0.2")); // 다 적어도 됨

    m_server.Create(26001);
    m_server.Listen();  // backlog 는 안적어도 됨.
}

 

< 클라이언트쪽 작업 >

# 클래스 마법사 MFC 클래스 추가
클래스 이름 : MyClient
기본 클래스 : CSocket
// Send 작업만 한다면 만들 필요없지만
// Receive 작업도 한다면 만들어야 함.

 

# 클라이언트

// 받기를 할 때는 MFC 클래스 추가로 CScoket 을 상속받아 OnReceive 를 사용해야 한다. (MyClient)
void MyClient::OnReceive(int nErrorCode)
{
    int value;
    Receive(&value, sizeof(int));

    // AfxGetMainWnd() = m_pMainWnd (메인 윈도우)
    AfxGetMainWnd()->SetDlgItemInt(IDC_VALUE_EDIT, value);

    CSocket::OnReceive(nErrorCode);
}

# ValueClient.h (메인 다이얼로그)
#include "MyClient.h"
private:
    MyClient m_client;

#ValueClient.cpp
BOOL CValueClientDlg::OnInitDialog()
{
    m_client.Create();
    m_client.Connect(_T("192.168.0.25"), 26001);
}

void CValueClientDlg::OnBnClickedSendBtn()
{
    int value = GetDlgItemInt(IDC_VALUE_EDIT);
    m_client.Send(&value, sizeof(int));

    // 보내기만 할 때는 아래처럼 MyClient 클래스 없이 그냥 가능.
    //CSocket temp;
    //temp.Create();
    //temp.Connect(_T("192. 168.0.25"), 26001);
    //temp.Send(&value, sizeof(int)); // 4바이트 크기의 정수 값 전달
}

 

 

 

 

 

 

Related posts

Leave a Comment