[MFC] MFC #2

jckim22·2024년 7월 9일

[C++] STUDY

목록 보기
2/3

메뉴

  • 기능 목록
  • 명령 목록

메뉴 리소스 편집

  • Visual Studio가 제공하는 가장 비주얼한 기능
  • 리소스 뷰가 보이도록 VS UI 설정변경
  • 리소스 목록은 .rc파일을 시각화한 것
    • rc파일은 고유 문법을 갖는 스크립트 파일
  • 리소스 데이터는 프로젝트 파일 아래 res 폴더에 위치
    • .ico, .bmp

핸들러 등록

  • 메뉴 때문에 -> WM_COMMANCD 발생하고 파라미터로 ID(메뉴마다 고유함)가 딸려간다. -> 그에 맞는 1:1 핸들러 등록
  • 여러 방법이 있으며 어떤 것을 사용하더라도 무방
  • 핸들러를 등록 할 클래스 선택 후 속성에서 추가
    • 메뉴, 메시지 핸들러, 각종 가상 함수
  • 수작업 가능 (비추천)
  • 기본적으로 메뉴는 View에 등록
  • 다중 View 환경에서 핸들러가 없는 메뉴는 자동으로 Disable
  • 리소스 뷰에서 추가하고 이벤트 처리기를 등록하거나 클래스 속성에서 이벤트 처리기를 추가한다.

핸들러 삭제

  • 수작업 삭제
    • 함수선언 삭제(.h)
    • 메시지 맵 수정(.cpp)
    • 함수정의 삭제(.cpp)

핸들러 중복

  • 메시지 라우팅에서 먼저 잡히는 것 우선순위로 실행된다.
  • 1순위. View
  • 2순위. Frame
  • 눈으로 그 상수를 참조하고 있는지 확인하고 싶으면 디버깅이나 resource.h에 들어가서 확인해보자

다른 메뉴 실행하기

  • WM_COMMAND 메시지를 코드로 전달하는 방법으로 구현
    • 메뉴의 실체는 WM_COMMAND 메시지의 매개변수 값
    • 모든 것이 메시지라는 사실을 재확인
  • PostMessage() 활용
    • PostMessage()는 메시시 큐를 거쳐서 핸들러 함수를 호출
    • SendMessage() 함수는 메시지 큐를 거치지 않고 핸들러 함수를 직접 호출
    • SendMessage() 함수는 가급적 사용하지 않는 것을 권장

단축키

  • 속성 -> 캡션

Update 메시지 핸들러

  • Update 메시지 핸들러는 일반 핸들러와 다르게 모든 핸들러가 다 동작한다.
  • 어떤 메뉴가 실행되는 실행된다.

토글 메뉴

CView

BOOL check = TRUE;

void C핸들러실습View::OnTestOption01()
{
	// TODO: 여기에 명령 처리기 코드를 추가합니다.
	this->PostMessage(WM_COMMAND, ID_TEST_STOP32774);
	check = !check;
}


void C핸들러실습View::OnUpdateTestOption01(CCmdUI* pCmdUI)
{
	// TODO: 여기에 명령 업데이트 UI 처리기 코드를 추가합니다.
	//this->PostMessage(WM_COMMAND, ID_TEST_STOP32774);
	pCmdUI->SetCheck(check);
}

위처럼 check라는 전역변수가 등록 되어있고 Option01 메뉴를 누를 때마다 check에 값이 바뀌면서 UpdateEventHandler로 인해 매번 check를 세팅한다.

근데 위처럼 check를 전역변수로 설정하는 것은 좋지 않다.

  • PreCompileHeader에 선언하자. (pch.h)
  • 다만 문제를 예방하기 위해 클래스로 감싸서 선언하고 pch.cpp에 정의하자

pch.h

#ifndef PCH_H
#define PCH_H

// 여기에 미리 컴파일하려는 헤더 추가
#include "framework.h"

class CAppOption {
	public:
		static BOOL check;
};

#endif //PCH_H

pch.cpp

// pch.cpp: 미리 컴파일된 헤더에 해당하는 소스 파일

#include "pch.h"

// 미리 컴파일된 헤더를 사용하는 경우 컴파일이 성공하려면 이 소스 파일이 필요합니다.
BOOL CAppOption::check = TRUE;

CView

void C핸들러실습View::OnTestOption01()
{
	// TODO: 여기에 명령 처리기 코드를 추가합니다.
	this->PostMessage(WM_COMMAND, ID_TEST_STOP32774);
	CAppOption::check = !CAppOption::check;
}


void C핸들러실습View::OnUpdateTestOption01(CCmdUI* pCmdUI)
{
	// TODO: 여기에 명령 업데이트 UI 처리기 코드를 추가합니다.
	//this->PostMessage(WM_COMMAND, ID_TEST_STOP32774);
	pCmdUI->SetCheck(CAppOption::check);
}
  • 또 시작을 누르면 Stop은 Enable이 되는 형식에 토글도 있을 것이다.

pch.h

class CAppOption {
	public:
		static BOOL check;
		static BOOL startOrStop;
		
};

pch.cpp

// pch.cpp: 미리 컴파일된 헤더에 해당하는 소스 파일

#include "pch.h"

// 미리 컴파일된 헤더를 사용하는 경우 컴파일이 성공하려면 이 소스 파일이 필요합니다.
BOOL CAppOption::check = TRUE;
BOOL CAppOption::startOrStop = FALSE;

CView.cpp

void C핸들러실습View::OnTestOption01()
{
	// TODO: 여기에 명령 처리기 코드를 추가합니다.
	this->PostMessage(WM_COMMAND, ID_TEST_STOP32774);
	CAppOption::check = !CAppOption::check;
}
void C핸들러실습View::OnUpdateTestStartTest(CCmdUI* pCmdUI)
{
	// TODO: 여기에 명령 업데이트 UI 처리기 코드를 추가합니다.
	pCmdUI->Enable(CAppOption::startOrStop);
}


void C핸들러실습View::OnUpdateTestStop32774(CCmdUI* pCmdUI)
{
	// TODO: 여기에 명령 업데이트 UI 처리기 코드를 추가합니다.
	pCmdUI->Enable(!CAppOption::startOrStop);
}

위처럼 처리할 수 있다.

팝업 메뉴

  • 리소스뷰에서 메뉴를 아예 새로 추가한다.
  • 그리고 아래처럼 등록한다.
void C핸들러실습View::OnRButtonDblClk(UINT nFlags, CPoint point)
{
	// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
	CMenu menu;
	menu.LoadMenu(IDR_MENU_POPUP);

	CMenu *pSubMenu = menu.GetSubMenu(0);

	//Point는 클라이언트 기준 마우스 좌표이기 때문에 현재 윈도우 기준 좌표로 바꾼다.
	CPoint ptPopup = point;
	this->ClientToScreen(&ptPopup);

	pSubMenu->TrackPopupMenu(
		TPM_LEFTBUTTON | TPM_LEFTALIGN,
		ptPopup.x, ptPopup.y, this);
	
	CView::OnRButtonDblClk(nFlags, point);
}
  • CPoint로 넘어온 것은 클라이언트 기준이기 때문에 윈도우 기준으로 맞춰주고 TrackPopupMenu를 사용해준다.

GDI

GDI는 Graphics Device Interface의 약자로, Microsoft Windows 운영 체제에서 그래픽 출력을 처리하는 API(Application Programming Interface)이다. GDI는 그래픽 관련 작업을 수행하는 데 필요한 함수, 구조체 및 데이터 유형을 제공하여 사용자 인터페이스와 그래픽 응용 프로그램을 지원한다.

랜더링 시점 (Rendering Time)

  • 윈도우 화면은 기본적으로 2회 랜더링
    • WM_PAINT
    • WM_ERASEBKGND
  • WM_PAINT 메시지 처리시에는 반드시 CPaintDC를 사용 (랜더링 완료 통지)

Device Context (DC)

DC는 Windows에서 그래픽 디바이스와 관련된 정보를 포함하고 있는 객체다. DC는 다양한 그래픽 출력 장치에 대한 정보를 담고 있으며, 이를 통해 화면에 그래픽을 그리거나 프린터에 출력할 수 있다.

  • 그래픽 출력 함수 호출 시 사용: DC를 이용하여 화면에 그릴 도형, 텍스트 등의 그래픽을 그린다. GDI(Graphic Device Interface) 라이브러리를 통해 제공되는 다양한 그리기 함수들은 DC를 인자로 받아 작동한다.

Pen, Line

  • 펜(CPen)객체를 DC에 적용한 후 그리기 시작
    • 별도 펜을 지정하지 않을 경우 기본 펜(1픽셀, 검정색)적용
    • 외곽선을 그리지 않으려면 NULL펜 설정
  • 선은 펜 속성에 따라 모양 결정
    • 색상 및 굵기
    • 실선
    • 점선
    • 사용자 정의 패턴

도형과 브러시

  • 기본 도형은 네모와 원 두 가지
    • 결과적으로 2차원 화면상의 모든 도형은 네모
    • 원은 네모 속에 존재
    • CRect, CPoint, CSize
  • 도형 외곽은 DC가 선택한 Pen으로 그려지며 그 내부는 브러시로 채워짐
    • CPen, CBrush
	//지금 윈도우에 대한 dc이다.
	CPaintDC dc(this); // device context for painting
	
	// 그리기 메시지에 대해서는 CView::OnPaint()을(를) 호출하지 마십시오.
	
	//기본 검정펜 설정
	//하지만 NULL 펜을 사용해서 외곽선을 지우고 싶어서 NULL펜 정의

	CPen nullPen(PS_NULL, 0, RGB(0, 0, 0));
	//윈도우 배경
	CRect window;
	CBrush newBrush(RGB(100, 100, 300));

	this->GetClientRect(&window);
	//dc.SelectObject(&newBrush);
	//dc.SelectObject(&nullPen);
	//dc.Rectangle(&window);

	//FillSolidRect로 브러쉬없이 네모를 채울 수 잇다.
	//nullPen도 기본적으로 탑재되어 있다.
	dc.FillSolidRect(&window, RGB(145, 245, 300));


	//네모 만들기
	//브러시
	CBrush brush(RGB(245, 245, 245));

	dc.SelectObject(&brush);
	dc.SelectObject(&nullPen);
	CRect rect(0, 0, 300, 300);
	dc.Rectangle(&rect);

	//새로운 펜으로 변경
	CPen pen(PS_SOLID, 5, RGB(192, 0, 0));
	CPen* pOldPen = dc.SelectObject(&pen);

	//선 그림
	dc.MoveTo(100, 100);
	dc.LineTo(200, 100);
	dc.SelectObject(pOldPen);

글꼴과 문자열 출력

  • 문자열은 그려지는 것으로 이해 할 수 있음
  • MBCS와 Unicode 환경을 반드시 고려
  • 응용 프로그램 수준에서 글꼴을 일치시키려면 레더링 전에 미리 전역 객체로 생성해두는 것이 바람직 (글꼴은 복잡한 정의가 필요함)
  • 핵심 함수
    • TextOut()
    • TabbedTextOut()
    • DrawText()

TextOut() 예시

	dc.SetBkMode(TRANSPARENT); //배경이 그대로
	//dc.SetBkMode(OPAQUE); //문자열 배경만 따로
	dc.SetBkColor(RGB(255, 255, 255));
	dc.TextOut(50,300,_T("TEST STRING"));	

비트맵 다루기

비트맵은 크게 2가지가 있다.

  • DiB -> File (.bmp) -> 리소스로 등록

  • DDB -> 모니터 -> Video (장치 비트맵)

    • 장치에 의존한다.
    • 윈도우가 그렇다. (DC.Pen, DC.Brush 등 장치가 윈도우에 의존)

비트맵 리소스 다루기

  • 응용 프로그램 리소스로 비트맵을 포함 시킨 후 활용

    • 24비트 비트맵(DIB)을 활용하는 가장 일반적인 방법
    • 실행파일에 비트캡이 포함되어 빌드
  • 리소스에서 비트맵을 로드 한 후 출력하는 방식

    • 리소스 비트맵을 메모리 DC로 로드
    • 메모리 DC를 화면 DC로 출력
    • 메모리 DC와 화면 DC를 합성해 출력하는 것도 가능(합성모드 존재)

      메모리 DC는 이미지(DIB)을 들고있는 디바이스 컨텍스트이다.
      이제 화면(DDB)에 화면 DC에 합성해 화면에 출력한다.

  • 비트맵을 리소스 추가하고 OnPaint 핸들러를 edit하자

	//DDB DC
	CPaintDC dcBitMap(this); // device context for painting

	//DIB DC
	CDC memdc;
	memdc.CreateCompatibleDC(&dcBitMap);

	CBitmap bmp;
	bmp.LoadBitmap(IDB_NULLNULL);

	//메모리 DC와 Bitmap을 연결
	memdc.SelectObject(&bmp);

	dcBitMap.MoveTo(100, 100);
	dcBitMap.LineTo(200, 200); //화면 DC말고 메모리 DC에 바로 쓰면 속도가 더 빨라질 것이다.

	dcBitMap.BitBlt(0, 0, 300, 300,
		&memdc, 0, 0, SRCCOPY);

이미지 출력 API

  • BitBlt()
    • 대상 DC로 원본 DC에 세트된 비트맵을 비트 블록 단위로 전송하는 함수
  • TransparentBlit()
    • 특정 색상을 투명처리해 출력
  • AlphaBlend()
    • 대상 이미지와 원본을 반투명하게 합성
  • StratchBlit()
    • 이미지 사이즈에 자유도가 있지만 사용을 지양하자 (원본 크기에 맞추자)

CImage와 CImageList

  • CImage 클래스를 이용해 리소스가 아닌 외부 파일 이미지를 쉽게 다룰 수 있음
  • CImageList 클래스를 이용해 아이콘 이미지 다수를 가져와 배열처럼 묶어 사용가능
	CPaintDC dc(this); // device context for painting
	// TODO: 여기에 메시지 처리기 코드를 추가합니다.
	// 그리기 메시지에 대해서는 CView::OnPaint()을(를) 호출하지 마십시오.

	CImage img;
	//vcxproj가 있는 디렉토리가 루트
	img.Load(_T("res//example.png")); //png 리소스 형식을 추가한뒤 LoadFromResource를 통해 리소스로 직접 가져올 수도 있다.

	img.BitBlt(dc.m_hDC, 0, 0);

	CImageList list;
	list.Create(128, 128, ILC_COLOR32, 3, 0);
	list.Add(AfxGetApp()->LoadIcon(IDI_ICON1));
	list.Add(AfxGetApp()->LoadIcon(IDI_ICON2));
	list.Add(AfxGetApp()->LoadIcon(IDI_ICON3));
	list.Add(AfxGetApp()->LoadIcon(IDI_ICON4));

	list.Draw(&dc, 0, CPoint(0, 0), ILD_TRANSPARENT);
	list.Draw(&dc, 1, CPoint(150, 0), ILD_TRANSPARENT);
	list.Draw(&dc, 2, CPoint(300, 0), ILD_TRANSPARENT);
	list.Draw(&dc, 3, CPoint(450, 0), ILD_TRANSPARENT); 

	//ico 파일로 하면 리소스에 추가할 수 있어서 좋다
  • 리소스 뷰에서 icon 리소스들을 추가 한다.

윈도우 영역

  • CRgn 클래스 이용
  • DC의 기본 설정은 윈도우 크기와 영역 크기가 일치
  • 영역을 벗어난 그리기는 모두 무시
    • 이 특징을 이용해 고급 UI개발 가능
    • Syntax coloring editor 개발 시 유용
  • 최상위 프레임 윈도우의 영역을 변경할 경우 윈도우 모양이 달라질 수 있음

GDI+

  • GPU를 이용할 수 있는 보다 개선된 랜더링 기법이다.

곡선과 안티 에일리어싱

  • 픽셀 사이에 작은 픽셀들을 배치함으로서 매끄러운 UI를 제공
  • GDI+의 변화중 큰 변화라고 할 수 있음

키보드 입력

  • 키보드와 마우스는 각각 유일한 입력장치
    • 한 프로그램이 독점하지 말아야 함
  • 모든 키보드 입력은 입력 포커스를 가진 응용 프로그램에 전달되는 것이 원칙
  • 입력한 키 값은 Virtual key code로 변환해 전달 됨
  • 키를 계속 누르고 있을 경우 지속적으로 메시지는 계속 발생
    • 눌린 회수가 매개변수로 함께 전달되며 1을 초과할 수 있음
  • KeyDown 메시지를 처리
  • Char 메시지를 처리 (키보드 문자열)
  • Paint 핸들러에서 처리하면 영속성 부여를 할 수 있음

char

ClientDc dc(this);

CString tmp;
tmp.Format(_T("%c"), nChar);
dc.TextOut(10,10,tmp);

CView::OnChar(nChar, nRepCnt, nFlags);

paint

CPaintDC dc(this);
dc.SetBkMode(OPAQUE);
dc.SetBkColrr(RGB(255,255,255));

if(m_char != 0)
{
	CString tmp;
    tmp.Format(_T("%c"), m_char);    
    
    dc.TextOut(10, 10, tmp);
}

컨트롤 윈도우 직접 생성하기

CSampleVies.h

protected:
	CEdit m_wndName;
  • CEdit 선언

CSampleViews.cpp

int CsampleView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;

	// TODO:  여기에 특수화된 작성 코드를 추가합니다.

	m_wndName.Create(
		ES_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER,
		CRect(10, 10, 100, 40), this, 1);

	return 0;
}

  • Window가 생성될 때 (OnCreate) 컨트롤 윈도우도 생성해준다.
void CsampleView::OnTestGd()
{
	// TODO: 여기에 명령 처리기 코드를 추가합니다.
	CString tmp;
	m_wndName.GetWindowText(tmp);
	AfxMessageBox(tmp);

	m_wndName.SetWindowTextW(_T("hihi"));
}
  • 그 값들은 위처럼 사용해볼 수 있다.

대화상자

대화상자(Form) 리소스 편집

  • 리소스 편집기로 파워포인트처럼 GUI화면을 구성
  • 적용되는 글꼴 크기에 따라 대화상자, 컨트롤 윈도우 크기 및 배치가 달라질 수 있음
    • 다국어 지원 시 문제가 될 수 있음
    • 사용자가 임의로 글꼴 변경 시 UI 구성이 손상될 수 있음
    • 크기 및 배치 자동조정 기능 없음
  • 모든 컨트롤 윈도우는 독립 팝업이 불가능한 차일드 윈도우
    • 윈도우 핸들 외에 ID로도 식별
  • 탭 오더를 고려해 적절한 순서를 부여
  • 탭 오더 입력 단축키는 Ctrl + D
  • 탭 오더는 설정 화면에서 마우스로 직접 클릭해 순서 설정
  • 데이터 입력이 많은 화면 개발 시 적절한 탭 오더 반영은 필수
    • 단, 입력 데이터가 범위를 벗어나거나 누락되는 오류 발생에 대한 편의 기능 제공은 별도로 구현

실습

  • 다이얼로그 리소스를 추가하고 그에 따른 클래스도 추가한다.
  • view에서 메뉴 이벤트처리기를 추가하고 추가한 다이얼로그 클래스를 include한다.
  • 리소스 뷰에 가서 도구 상장에서 edit control을 추가해본다.
  • 그리고 edit control에 대한 변수를 추가한다.
  • DDX로 인해 변수에 저장된다.
  • 확인 버튼을 눌렀을 때 OnBnClickedOk() 핸들러로 업데이트가 된다.
  • 이후 해당 변수에 대한 정보를 갖고 있는 다이얼로그의 멤버변수를 다룰 수 있다.

DDX, DDV

스태틱, 에디트 컨트롤

  • 스태틱(Static) 컨트롤 윈도우
    • 문자열을 표시하는 것에 특수화된 컨트롤
    • 메시지를 받지 않는 것이 기본속성
  • 에디트(Edit) 컨트롤 윈도우
    • 문자열 입력을 입력 받는 기능에 특수화
    • 복사하기와 붙여넣기 기능 제공
    • 속성 설정에 따라 여러 행을 입력 받을 수 있음
    • 스태틱 컨트롤처럼 보이도록 할 수 있음

DDX, DDV 매커니즘

  • 컨트롤 윈도우에 대한 멤버 등록 시 윈도우 객체 혹은 데이터 객체로 등록
    • 윈도우 객체 사용 시 데이터 동기화 코드 추가
  • 컨트롤 윈도우와 데이터 객체 연동 자동화
    • 컨트롤과 멤버 데이터 간 매핑 설정에 따라 동기화
    • UpdateData() 함수를 호출해 동기화
    • 자료에 대한 유효성 검사 기능 제공
    • True 시 변수에 데이터 입력(CEdit -> Value Object)
    • False 시 데이터를 가져옴(Value Object -> CEdit)

  • 먼저 위처럼 static 컨트롤 윈도우로 아이디, 비밀번호 창을 안내한다.
  • edit 컨트롤 윈도우를 각각 만들고 변수를 설정한다.
public:
	CEdit str_id;
	CString str_pwd;
	afx_msg void OnBnClickedOk();
	CString str_id2;
};
  • 그럼 위처럼 추가가 된다.
  • 이제 OnBnClickedOk()를 보자
void CDlog::OnBnClickedOk()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	if (UpdateData(true)) {
		CDialog::OnOK();
	}	
}
  • 위처럼 ok를 누르면 UpdateData(true)로 멤버 변수에 저장된다.

그럼 Dialog를 보자

void Csample3View::OnTestTest()
{
	// TODO: 여기에 명령 처리기 코드를 추가합니다.
	CDlog dlg;
	if (dlg.DoModal() == IDOK)
	{
		CString message;
		message.Format(_T("로그인 성공\n아이디: %s\n비밀번호: %s"), dlg.str_id2, dlg.str_pwd);
		AfxMessageBox(message);
	}
	else {
		dlg.DoModal();
	}		
}
  • test 메뉴를 클릭하면 dlg를 연다.
  • dlig.DoModal()를 확인을 눌렀다면 ( ==OK)
  • OnBnClickedOk의 UpdateData(true)로 인해 멤버 변수에 저장이 될 것이다.
  • 그리고 그걸 참조로 꺼내서 확인한다.
profile
개발/보안

0개의 댓글