프로그래밍/MFC/WIN32

MFC 다이얼로그(Dialog) 스크롤(Scroll) 적용

nanze 2021. 12. 28. 14:41
반응형

MFC에서 다이얼로그에 스크롤을 적용하기 위한 코드를 보도록 하자.

우선 리소스에디터에서 해당 다이얼로그의 속성 중 Border 를 Resizing 으로 수정해 놓아야 한다. 

다이얼로그의 border 속성

그 이후 필요한 것은 크기 변동 이벤트 시 적절한 대응과 스크롤 발생 이벤트 시 적절한 행동을 수행하는 것이다. 

우선 WM_SIZE 이벤트에 대한 처리를 진행하자.

void TestDlg::OnSize(UINT nType, int cx, int cy)
{
	CDialogEx::OnSize(nType, cx, cy);
	UpdateScrollInfo(cx, cy);
}

void TestDlg::UpdateScrollInfo(int cx, int cy)
{
	int nScrollMaxV = 0;
	int nScrollMaxH = 0;
	int nTotalHeight = 0;
	int nTotalWidth = 0;
	int nDelta = 0;
	SCROLLINFO siV, siH;

	m_nPageSizeV = 0;
	m_nPageSizeH = 0;
	nTotalHeight = m_rectWindowSize.Height();
	nTotalWidth = m_rectWindowSize.Width();

	if (cy < nTotalHeight) {
		nScrollMaxV = nTotalHeight - 1;
		m_nPageSizeV = cy;
		m_nScrollPosV = min(m_nScrollPosV, nTotalHeight - m_nPageSizeV - 1);
	}
	TRACE(_T("cx size : %d \n"), cx);
	if (cx < nTotalWidth) {
		nScrollMaxH = nTotalWidth - 1;
		m_nPageSizeH = cx;
		m_nScrollPosH = min(m_nScrollPosH, nTotalWidth - m_nPageSizeH - 1);
	}

	siV.cbSize = sizeof(SCROLLINFO);
	siV.fMask = SIF_ALL;
	
	siH.cbSize = sizeof(SCROLLINFO);
	siH.fMask = SIF_ALL;

	GetScrollInfo(SB_VERT, &siV);
	GetScrollInfo(SB_HORZ, &siH);

	if (siV.nPage != 0) {
		if (cy > siV.nPage) {
			if (siV.nPos + cy > siV.nMax) {
				nDelta = siV.nPos + cy - siV.nMax;
				ScrollWindow(0, nDelta);
			}
		}
	}

	siV.nMin = 0;
	siV.nMax = nScrollMaxV;
	siV.nPage = m_nPageSizeV;
	siV.nPos = m_nScrollPosV;
	SetScrollInfo(SB_VERT, &siV, TRUE);

	if (siH.nPage != 0) {
		if (cx > siH.nPage) {
			if (siH.nPos + cx > siH.nMax) {
				nDelta = siH.nPos + cx - siH.nMax;
				ScrollWindow(nDelta, 0);
			}
		}
	}

	siH.nMin = 0;
	siH.nMax = nScrollMaxH;
	siH.nPage = m_nPageSizeH;
	siH.nPos = m_nScrollPosH;
	SetScrollInfo(SB_HORZ, &siH, TRUE);

}

상위 코드는 일부를 발췌한 것으로 WM_SIZE 발생 시 해당하는 부분이다. 우선 다이얼로그가 초기화되는 시점 OnInitDialog 에서 클라이언트 영역의 크기를 얻어와 m_rectWindowSize 에 저장해둔다. 다이얼로그 본래의 크기인 것이다. 그리고 다이얼로그의 크기 변경이 발생하였을 경우 해당 크기가 본래의 크기보다 작으면 스크롤이 발생되기 해놓은 것이다. 그리고 스크롤이 있을 경우 크기 변화시 이전 크기 상태의 스크롤 페이지 크기보다 클 때 현재 위치에서 변환된 페이지 크기를 더한 경우 그 값이 최대 값보다 큰 경우 그 차만큼 윈도우를 스크롤 해준다. 이와 같은 과정이 없을 경우 윈도우 사이즈를 줄여서 스크롤 한 후 윈도우 사이즈를 다시 늘릴 경우 오동작하게 된다. 

다음은 스크롤 발생 시에 대한 대응 부분이다. 

void TestDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	int nNextPos = 0;
	int nDelta = 0;
	SCROLLINFO si = { 0, };
	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_ALL;
	GetScrollInfo(SB_HORZ, &si);

	switch (nSBCode)
	{
	case SB_LINERIGHT:
		nNextPos = si.nPos + 20;
		nDelta = -20;
		if (nNextPos + si.nPage >= si.nMax) {
			if (si.nPos + si.nPage >= si.nMax) {
				nDelta = 0;
				nNextPos = si.nPos;
			}
			else {
				nDelta = (20 - ((nNextPos + si.nPage) - si.nMax));
				nNextPos = si.nMax - si.nPage;
				nDelta = -nDelta;
			}
		}
		break;
	case SB_LINELEFT:
		nNextPos = si.nPos - 20;
		nDelta = 20;
		if (nNextPos < 0) {
			nNextPos = 0;
			nDelta = si.nPos;
		}
		break;
	case SB_PAGERIGHT:
		nNextPos = si.nPos + si.nPage;
		nDelta = si.nPage;
		if (nNextPos + si.nPage >= si.nMax) {
			nNextPos = si.nMax - si.nPage;
			nDelta = nNextPos - si.nPos;
		}
		nDelta = -nDelta;
		break;
	case SB_THUMBTRACK:
		nNextPos = nPos;
		nDelta = -(nNextPos - si.nPos);
		break;
	case SB_PAGELEFT:
		nNextPos = si.nPos - si.nPage;
		nDelta = si.nPage;
		if (nNextPos < 0) {
			nNextPos = 0;
			nDelta = si.nPos;
		}
		break;
	default:
		return;
	}

	m_nScrollPosH = nNextPos;
	SetScrollPos(SB_HORZ, nNextPos, TRUE);
	ScrollWindow(nDelta, 0);

	CDialogEx::OnHScroll(nSBCode, nPos, pScrollBar);
}


void TestDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	// TODO: Add your message handler code here and/or call default
	int nNextPos = 0;
	int nDelta = 0;
	SCROLLINFO si = { 0, };

	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_ALL;
	GetScrollInfo(SB_VERT, &si);
	switch (nSBCode)
	{
	case SB_THUMBTRACK:
		nNextPos = nPos;
		nDelta = -(nNextPos - si.nPos);
		break;
	case SB_LINEUP:
		nNextPos = si.nPos - 20;
		nDelta = 20;
		if (nNextPos < 0) {
			nNextPos = 0;
			nDelta = si.nPos;
		}
		break;
	case SB_PAGEUP:
		nNextPos = si.nPos - si.nPage;
		nDelta = si.nPage;
		if (nNextPos < 0 ) {
			nNextPos = 0;
			nDelta =  si.nPos;
		}
		break;
	case SB_LINEDOWN:
		nNextPos = si.nPos + 20;
		nDelta = -20;
		if (nNextPos + si.nPage >= si.nMax) {
			if (si.nPos + si.nPage >= si.nMax) {
				nDelta = 0;
				nNextPos = si.nPos;
			}
			else {
				nDelta = (20 - ((nNextPos + si.nPage) - si.nMax));
				nNextPos = si.nMax - si.nPage;
				nDelta = -nDelta;
			}
		}
		break;
	case SB_PAGEDOWN:
		nNextPos = si.nPos + si.nPage;
		nDelta = si.nPage;
		if (nNextPos + si.nPage >= si.nMax) {
			nNextPos = si.nMax - si.nPage;
			nDelta = nNextPos - si.nPos;
		}
		nDelta = -nDelta;
		break;
	default:
		return;
	}

	m_nScrollPosV = nNextPos;
	SetScrollPos(SB_VERT, nNextPos, TRUE);
	ScrollWindow(0, nDelta);

	CDialogEx::OnVScroll(nSBCode, nPos, pScrollBar);
}

위 코드는 각각 수평 스크롤과 수직 스크롤 이벤트가 발생할 경우 스크롤 정보 설정과 대상 윈도우를 얼만큼 움직일까 하는 계산 부분이다.

 이렇게 세 부분만 대응시키면 다이얼로그 기반의 UI에 크기 변화에 따른 스크롤을 적용시킬 수 있다. 

위 함수들을 사용하기 위해서는 당연히 윈도우 메시지 맵핑을 해야하는 것은 필수겠죠 ㅋ;

반응형