I'm on your side, when times get rough.

2011-09-23

[Win32 C] Add a button to Titlebar (Windows Classic theme)

Filed under: Programming — Peter_KIM @ 08:36

Windows 운영 체제에서 아래의 그림과 같이 타이틀 바에 버튼을 생성할 수 있습니다.

image

image

Windows XP 이후부터는 운영체제에서 제공하는 테마(Theme)팩을 적용하여, 다양한 형태로 창의 표시 형태를 변경할 수 있습니다. 여기에서는 Windows 95 시절부터 현재까지 지속적으로 제공되는 고전(Windows Classic) 스타일의 윈도우 형태에서 작업을 하겠습니다.

윈도우 메인 함수에서, 아래의 코드와 같이 다이얼로그를 생성합니다.

int PASCAL WinMain(HINSTANCE hInstance,

         HINSTANCE hPrevInstance,

         LPSTR     lpCmdLine,

         int       nCmdShow)

{

         LONG dwBaseUint = 0;

         __int16 nBaseunitX = 0;

         __int16 nBaseunitY = 0;

 

         // Retrieves the system’s dialog base units,

         // which are the average width and height of characters in the system font.

         dwBaseUint = GetDialogBaseUnits();

 

         // The low-order word of the return value contains the horizontal dialog box base unit,

         // and the high-order word contains the vertical dialog box base unit.

         nBaseunitX = LOWORD(dwBaseUint);

         nBaseunitY = HIWORD(dwBaseUint);

 

         // Each horizontal base unit is equal to 4 horizontal dialog template units;

         // Each vertical base unit is equal to 8 vertical dialog template units

         g_MainDlgTemplate.cx = (__int16)MulDiv(400, 4, nBaseunitX);

         g_MainDlgTemplate.cy = (__int16)MulDiv(400, 8, nBaseunitY);

         // The style of the dialog box.

         g_MainDlgTemplate.style = DS_CENTER | DS_CONTEXTHELP | WS_POPUP | WS_SYSMENU | WS_CAPTION;

         //g_MainDlgTemplate.style = DS_CENTER | WS_OVERLAPPEDWINDOW;

         // g_MainDlgTemplate.dwExtendedStyle = WS_EX_TOOLWINDOW;

         // Creates a modal dialog box from a dialog box template in memory.

         DialogBoxIndirectA(hInstance, &g_MainDlgTemplate, GetDesktopWindow(), (DLGPROC)DialogProc);

 

         return 0;

}

다이얼로그 템플릿의 스타일에 따라서, 타이틀 바와 그 위에 그려지는 버튼의 크기와 위치가 달라질 수 있습니다.

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
         BOOL bOverButton = FALSE;
 
         switch (uMsg)
         {
         case WM_INITDIALOG:
                 OnInitialDialog(hDlg);
                 return TRUE;
 
         case WM_NCRBUTTONDOWN:
                 if (OnNcRButtonDown(hDlg, wParam, lParam))
                          return TRUE;
                 break;
 
         case WM_NCLBUTTONDOWN:
                 bOverButton = OnNcLButtonDown(hDlg, wParam, lParam);
                 DrawTitlebarButton(hDlg, bOverButton);
                 break;
 
         case WM_LBUTTONUP:
                 bOverButton = OnLButtonUp(hDlg, lParam);
                 DrawTitlebarButton(hDlg, bOverButton);
                 break;
 
         case WM_MOUSEMOVE:
                 if (g_bButtonPushed) {
                          bOverButton = OnMouseMove(hDlg, lParam);
                          DrawTitlebarButton(hDlg, g_bButtonPushed && bOverButton);
                 } 
                 break;
 
         case WM_NCPAINT:
                 DefWindowProc(hDlg, uMsg, wParam, lParam);
                 DrawTitlebarButton(hDlg, FALSE);
                 return TRUE;
 
         case WM_NCACTIVATE:
         case WM_ACTIVATE:
         case WM_ACTIVATEAPP:
                 DrawTitlebarButton(hDlg, FALSE);
         break;
 
         case WM_COMMAND:
                 switch (LOWORD(wParam))
                 {
                 case IDOK:
                 case IDCANCEL:
                          // Destroys a modal dialog box.
                          EndDialog(hDlg, LOWORD(wParam));
                          return TRUE;
                 }
                 break;
 
         case WM_DESTROY:
                 PostQuitMessage(0);
                 break;
         }
         return FALSE;
}

타이틀 바가 표시되는 영역은 응용 프로그램의 클라언트 영역이 아니므로 WM_NC*** 계열의 메시지를 처리해서 작업을 해야 합니다. 타이틀 바, 윈도우 경계등의 비 클라이언트 영역을 그릴 때, WM_NCPAINT 메시지가 발생하는데, 이 때에 버튼 모양을 그려주면 됩니다. 혹시, CreateWindow() 함수를 이용하여, 타이틀 바 영역에 버튼을 만들어서 표시 할 수 있는 것은 아니냐는 분들이 계실지 모르겠습니다. CreateWindow() 함수는 윈도우의 클라이언트 영역 위에서 컨트롤을 생성하는 것이므로, 이 함수를 이용하는 것은 불가능합니다. 아래의 코드를 참조하십시오.

CreateWindowExA(0, "BUTTON", "…….",

         BS_AUTOCHECKBOX | BS_PUSHLIKE | BS_VCENTER | BS_CENTER | WS_TABSTOP | WS_VISIBLE | WS_CHILD,

         0, -50, 100, 100, hDlg, (HMENU)NULL, NULL, NULL);

위 코드를 이용하여, 버튼을 생성한 경우, 타이틀 바에 의하여, 아래의 그림과 같이 생성된 버튼의 일부분이 가려지게 됩니다.

image

그러므로, 아래와 같이 버튼의 모양을 그려보겠습니다.

VOID DrawTitlebarButton4WinClassic(HWND hDlg, BOOL bPushed)
{
#if (WINVER >= 0x0500)
         TITLEBARINFO ti = {0}; // 타이틀바의 정보
         RECT rcWnd = {0}; // 윈도우 크기
         HDC hDC = NULL; 
 
         POINT ptLeftTop = {0};
         SIZE  szBtn = {0};
         RECT  rcBtn = {0};
         PRECT pRect = NULL;
 
         DWORD dwStyleEx = 0;
         __int32 nButtonCount = 0;
 
         // 타이틀바의 정보를 획득한다.
         ti.cbSize = sizeof(TITLEBARINFO);
         if (!GetTitleBarInfo(hDlg, &ti))
                 return;
         // 타이틀바가 표시되지 않으면 함수를 종료한다.
         if (STATE_SYSTEM_FOCUSABLE != ti.rgstate[0])
                 return;
 
         // 윈도우 크기를 구한다.
         GetWindowRect(hDlg, &rcWnd);
         // 윈도우 스타일을 확인한다.
#if (WINVER >= 0x0501)
         dwStyleEx = GetWindowLongPtr(hDlg, GWL_EXSTYLE);
#else
         dwStyleEx = GetWindowLong(hDlg, GWL_EXSTYLE);
#endif
 
         if ((dwStyleEx & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW) {
                 nButtonCount = 1;
                 // SM_CXSMSIZE, SM_CYSMSIZE :The width, height of small caption buttons, in pixels.
                 szBtn.cx = GetSystemMetrics(SM_CXSMSIZE);
                 szBtn.cy = GetSystemMetrics(SM_CYSMSIZE);
         } else {
                 //2 Minimize button. 
                 //3 Maximize button. 
                 //4 Help button.
                 //5 Close button. 
                 if (ti.rgstate[2] == 0) { 
                          // 최소화 버튼이 활성화되어 있으면, 버튼의 갯수는 3개이다.
                          nButtonCount = 3;
                 }
                 else if (ti.rgstate[4] == 0) {
                          // HELP 버튼이 활성화된 경우, Close 버튼도 함께 고려해야한다.
                          nButtonCount = (ti.rgstate[5] == 0) ? 2: 0;
                 } 
                 else if (ti.rgstate[5] == 0) {
                          nButtonCount = 1;
                 } 
 
                  // SM_CXSIZE, SM_CYSIZE :The width, height of a button in a window caption or title bar, in pixels..
#if (WINVER >= 0x0501)
                 if (IsThemeActive()) {
                          szBtn.cx = GetThemeSysSize(NULL, SM_CXSIZE);
                          szBtn.cy = GetThemeSysSize(NULL, SM_CYSIZE);
                 }
                 else 
#endif 
                 {
                          szBtn.cx = GetSystemMetrics(SM_CXSIZE);
                          szBtn.cy = GetSystemMetrics(SM_CYSIZE);
                 }
         }
         // 버튼의 위치를 구한다.
         ptLeftTop.x = ti.rcTitleBar.right - (szBtn.cx * (nButtonCount + 1));
         ptLeftTop.y = ti.rcTitleBar.top;
 
         pRect = &g_rcButton;
         // 윈도우의 Frame 위치로부터, Non Client 영역에 버튼이 그려질 위치를 구한다.
         pRect->left = ptLeftTop.x - rcWnd.left;
         pRect->right = pRect->left + szBtn.cx;
         pRect->top = ptLeftTop.y - rcWnd.top - 1;
         pRect->bottom = pRect->top + szBtn.cy + 1;
 
         // 윈도우 DC 객체를 획득한다.
         hDC = GetWindowDC(hDlg);
         // 실제 버튼의 영역과 버튼이 그려질 영역을 구분하여 그린다.
         CopyRect(&rcBtn, pRect);
         if (nButtonCount > 1) {
                 rcBtn.left += 2;
         } else { 
                 rcBtn.right -= 2;
         }
         rcBtn.top += 3;
         rcBtn.bottom -= 2;
         DrawFrameControl(hDC, &rcBtn, DFC_BUTTON, bPushed ? DFCS_BUTTONPUSH | DFCS_PUSHED : DFCS_BUTTONPUSH);
         // DC 객체를 해제한다.
         ReleaseDC(hDlg, hDC);
 
#endif
}

여기에서 주의 할 점은 DrawFrameControl 함수를 사용하게 되면, 운영 체제에 테마가 설정되어 있더라도, 고전 형식으로 타이틀 바가 그려지게 됩니다. 때에 따라서, 윈도우가 아래의 그림과 같이 비 정상적으로 표시 될 수 있습니다.

image

그러므로, 이 프로그램에서는 아래의 코드와 같이 SetWindowTheme() 함수를 이용하여, 응용 프로그램의 테마를 해제하겠습니다.

VOID OnInitialDialog(HWND hDlg)
{
#if (WINVER >= 0x0501)
         if (IsThemeActive())
                 SetWindowTheme(hDlg, L””, L””);
#endif
}

타이틀 바에 그려 넣은 버튼 모양이 버튼의 기능을 수행하도록 만들기 위하여, 마우스 왼쪽 버튼의 클릭, 마우스 포인터 이동 등의 동작에 대한 이벤트를 처리합니다. , 응용 프로그램이 다시 그려지는 경우, 그려 넣은 버튼 모양이 타이틀 바에 표시되도록 해야 합니다. 위의 코드에서 알 수 있겠지만, 마우스 클릭 이벤트는 본래, 버튼의 눌림(DOWN)과 해제(UP) 이벤트의 조합입니다. SPY++ 같은 응용 프로그램으로 확인 할 수 있듯이, 타이틀 바의 시스템 버튼을 클릭하면, WM_NCLBUTTONDOWN, WM_LBUTTONUP 이벤트가 발생합니다. WM_NCLBUTTONUP 이벤트를 처리하는 것이 아님에 주의해야 합니다. 이 때에, 마우스 포인터의 위치 값이 LPARAM 인자와 함께 전달되는데, WM_NC*** 계열의 이벤트와 WM_*** 계열의 이벤트에서 전달되는 위치 값은 서로 상이합니다. 이런 이유는 WM_*** 계열의 이벤트는 클라이언트 영역의 좌상(left-top) 단의 값을 기준으로 증가 또는 감소한 값을 전달하는 반면, WM_NC*** 계열의 이벤트는 운영 체제가 표시하는 화면 영역의 좌상(left-top) 단을 기준으로 결정한 값을 반환하기 때문입니다. 그러므로, WM_*** 계열의 이벤트에서 전달되는 위치 값을 ClientToScreen() 함수를 이용하여, 좌표를 시스템의 데스크 탑 좌표로 변경해야 합니다.

이벤트를 처리하는 함수는 아래와 같습니다.

BOOL OnMouseMove(HWND hDlg, LPARAM lParam)
{
         POINT ptMove = {0};
 
         ptMove.x = GET_X_LPARAM(lParam); 
         ptMove.y = GET_Y_LPARAM(lParam);
 
         ClientToScreen(hDlg, &ptMove);
 
         return IsOverButton(hDlg, &ptMove);
}
 
BOOL OnNcMouseMove(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
         POINT ptMove = {0};
         BOOL bOver = FALSE;
 
         ptMove.x = GET_X_LPARAM(lParam); 
         ptMove.y = GET_Y_LPARAM(lParam);
 
         if (wParam == HTCAPTION)
                 bOver = IsOverButton(hDlg, &ptMove);
 
         return bOver;
}
 
BOOL OnNcRButtonDown(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
         POINT ptDown = {0};
 
         ptDown.x = GET_X_LPARAM(lParam); 
         ptDown.y = GET_Y_LPARAM(lParam); 
 
         if (wParam == HTCAPTION)
                 return IsOverButton(hDlg, &ptDown);
 
         return FALSE;
}
 
BOOL OnNcLButtonDown(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
         POINT ptDown = {0};
 
         ptDown.x = GET_X_LPARAM(lParam); 
         ptDown.y = GET_Y_LPARAM(lParam); 
 
         if (wParam == HTCAPTION)
                 g_bButtonPushed = IsOverButton(hDlg, &ptDown);
         else
                 g_bButtonPushed = FALSE;
 
         if (g_bButtonPushed)
                 SetCapture(hDlg);
 
         return g_bButtonPushed;
}
 
BOOL OnLButtonUp(HWND hDlg, LPARAM lParam)
{
         POINT ptDown = {0};
 
         ReleaseCapture();
 
         if (g_bButtonPushed)
         {
                 ptDown.x = GET_X_LPARAM(lParam); 
                 ptDown.y = GET_Y_LPARAM(lParam); 
                 ClientToScreen(hDlg, &ptDown);
 
                 if (IsOverButton(hDlg, &ptDown))
                          MessageBoxA(hDlg, "Button Clicked", "", MB_OK | MB_ICONINFORMATION);
         }
         g_bButtonPushed = FALSE;
 
         return FALSE;
}
 
BOOL IsOverButton(HWND hDlg, LPPOINT lp)
{
         RECT rcWnd = {0};
         RECT rcBtn = {0};
 
         GetWindowRect(hDlg, &rcWnd);
 
         rcBtn.left = rcWnd.left + g_rcButton.left;
         rcBtn.top = rcWnd.top + g_rcButton.top;
         rcBtn.right = rcBtn.left + RECTWIDTH(&g_rcButton);
         rcBtn.bottom = rcBtn.top + RECTHEIGHT(&g_rcButton);
         OffsetRect(&rcBtn, 0, 1);
 
         return PtInRect(&rcBtn, *lp);
}
 

코드를 이용하여, 표시되는 타이틀 바의 모습은 아래의 그림과 같습니다.

image

전체 소스 코드는 아래의 주소에서 얻을 수 있습니다.

https://skydrive.live.com/embedicon.aspx/.Public/TitlebarButton.7z?cid=1bbcdfedee1c617e&sc=documents

2011-09-07

[Win32 C] Automatically Resize Rich Edit Controls

Filed under: Programming — Peter_KIM @ 15:02

RichEdit 컨트롤을 이용하여, RTF 텍스트의 높이를 구하는 방법 , 가장 쉬운 방법은 EN_REQUESTRESIZE 코드의 Notify 메시지를 이용하는 것입니다. 메시지를 RichEdit 컨트롤에 전달하면, 컨트롤을 소유하고 있는 윈도우에 WM_NOTIFY 메시지와 함께, EN_REQUESTRESIZE 코드, 변경된 크기 정보가 전달됩니다.

메시지를 사용하기 위해서는, 먼저 RichEdit 컨트롤에 EM_SETEVENTMASK메시지와 함께 ENM_REQUESTRESIZE값을 전달하여, ENM_REQUESTRESIZE코드의 Notify 메시지를 받을 있도록 설정해야 합니다.

SendMessage(g_hWndRichEdit, EM_SETEVENTMASK, 0, ENM_REQUESTRESIZE);

이제 RichEdit 컨트롤의 텍스트가 추가/제거되는 경우, 실시간으로 컨트롤의 크기를 설정할 있습니다.

윈도우의 기본 메시지 처리 프로시저에서 다음과 같이 코드를 구현합니다.

……

      case WM_NOTIFY:

            switch (((LPNMHDR)lParam)->code)

            {

            case EN_REQUESTRESIZE:

                  if (((LPNMHDR)lParam)->idFrom == IDC_RICHEDIT)

                  {

                        OnRequestResize((REQRESIZE*)lParam);

                        return TRUE;

                  }

                  break;

            }

            break;

……

 

LRESULT OnRequestResize(REQRESIZE* pReqResize)

{

       __int32 nWidth = 0;

       __int32 nHeight = 0;

 

       nWidth = pReqResize->rc.right – pReqResize->rc.left;

       nHeight = pReqResize->rc.bottom – pReqResize->rc.top;

 

       SetWindowPos(g_hWndRichEdit, NULL, 0, 0, nWidth, nHeight, SWP_NOMOVE | SWP_NOZORDER);

       return 0;

}

이렇게 하면, RTF 텍스트의 크기에 따라서 자동으로 크기가 변경되는 RichEdit 컨트롤을 구현 있습니다. RTF 텍스트의 높이는 Notify 메시지에서 전달된 REQRESIZE 구조체의 RECT 값이 됩니다.

때에 주의 점은 RichEdit 컨트롤을 생성 , 스크롤 형식을 지정하지 않아야 한다는 점입니다. 만일 WS_VSCROLL 또는 WS_HSCROLL 형식과 함께, 컨트롤이 생성되고, Wordwrap 설정이 경우, REQRESIZE 구조체에 적절하지 못한 크기 정보가 전달될 수도 있습니다.

 

전체 코드는 아래의 주소에서 내려 받을 있습니다.

https://skydrive.live.com/embedicon.aspx/.Public/AutoResizeRichEdit.7z?cid=1bbcdfedee1c617e&sc=documents

[Win32 C] Height of RTF content in RichEdit Control

Filed under: Programming — Peter_KIM @ 04:11

Win32 기반의 RichEdit 컨트롤에 표시된 RTF 텍스트의 높이를 구하는 코드를 작성해 보겠습니다. 간단히 처리하는 코드도 있지만, RichTextBox 컨트롤에서 처리했던 방식을 그대로 이용해 보겠습니다.

워낙 .NET Framework컨트롤이 Win32 컨트롤의 기본 동작을 그대로 사용하거나, 기본 기능에 비하여 진보, 개선한 코드들이 존재하다 보니, 순수하게 Win32 코드로 작성하는 데에는 적지 않은 설계와 코드가 필요합니다. 그러나, 이렇게 하다 보면, 윈도우 시스템에 대하여 더욱 깊이 있는 지식을 쌓을 있으니, 어찌 좋지 아니하다 말할 있겠습니까?

RichEdit 컨트롤은 .NET 기반의 RichTextBox 컨트롤과 많은 부분이 다르므로, 여러 가지 기술을 적용해서 구현해야 합니다.

LoadLibraryA("Msftedit.dll");

……

HWND g_hWndRichEdit = CreateWindowExA(0, "RICHEDIT50W", "",

ES_MULTILINE | WS_VSCROLL | WS_HSCROLL | WS_VISIBLE | WS_CHILD | WS_TABSTOP,

……

RichEdit Version 4.1 컨트롤을 사용하기 위하여, Msftedit.dll 라이브러리를 동적으로 로드하고, CreateWindow 함수에서 클래스 이름을  RICHEDIT50W”으로 설정합니다.

RichTextBox 컨트롤의 Boarder 속성 값을 None”으로 설정한 것과 마찬가지로, RichEdit 컨트롤을 생성할 때에Boarder선이 표시되지 않도록 주의해야 합니다. Boarder선이 표시 되도록 설정되면, RTF 텍스트의 높이에 선의 Pixel 값이 포함되므로, 계산된 값에 만큼의 Pixel 값을 더해주어야 합니다.

RichTextBox 컨트롤에서는 ScrollBars 속성 값을 None”으로 설정해도 컨트롤의 크기를 넘어서는 텍스트의 너비와 높이에 대한 스크롤이 가능합니다. 하지만, RichEdit 컨트롤에서 WS_VSCROLL, WS_HSCROLL 스타일을 지정하지 않으면, 스크롤 바가 표시되지 않는 것은 물론, WM_VSCROLL, WM_HSCROLL 메시지 이벤트가 발생되지 않으므로 마우스나 키보드를 이용하여, 스크롤을 아예 없게 됩니다. 그러므로, 일단 컨트롤을 생성할 때에 WS_VSCROLL, WS_HSCROLL 스타일을 설정해야 합니다.

BOOL g_bShowScrollBar = FALSE;

HHOOK g_hHookProcedure

= SetWindowsHookEx(WH_CALLWNDPROC, CallRichEditProc, NULL, GetCurrentThreadId());

……

LRESULT CALLBACK CallRichEditProc(int nCode, WPARAM wParam, LPARAM lParam)

{

       // The message parameters passed to a WH_CALLWNDPROC hook procedure

       PCWPSTRUCT pcwstruct = (PCWPSTRUCT)lParam;

if (nCode >= 0)

       {

              if (pcwstruct->hwnd == g_hWndRichEdit)

                     if (!g_bShowScrollBar)

                           ShowScrollBar(pcwstruct->hwnd, SB_BOTH, FALSE);

       }

       // Passes the hook information to the next hook procedure in the current hook chain.

       return CallNextHookEx(g_hHookProcedure, nCode, wParam, lParam);

}

RichEdit 컨트롤의 스크롤 표시를 숨김으로 설정하기 위하여, ShowScrollBar 함수를 호출 있습니다. 그러나 일반적으로 함수를 이용해서, 순간적으로 스크롤 바를 숨길 있으나, 컨트롤에서 스크롤이 발생되는 순간 다시 표시되며, RTF 텍스트의 가로, 세로 크기가 모두 컨트롤의 크기보다 경우, 세로 스크롤 바는 숨겨지지도 않습니다.

RichTextBox 컨트롤과 같이 스크롤 바가 표시되지 않는 상태에서 스크롤이 가능하도록 하기 위하여, 메시지 Hooking 방법을 이용합니다.

VOID OnUpdateRichEdit(HWND hDlg)

{

……

       __int32 nMinPos = 0;

       __int32 nMaxPos = 0;

       __int32 nHeight = 0;

       SCROLLBARINFO sbi = {0}; // Scroll bar information.

……

       // 1st, minimize the Richedit control’s height.

       nHeight = 10;

       SetWindowPos(g_hWndRichEdit, NULL, 0, 0, cx, nHeight, SWP_NOMOVE | SWP_NOZORDER);

       // 2nd, show the Richedit’s vertical scroll bar.

       g_bShowScrollBar = TRUE;

       ShowScrollBar(g_hWndRichEdit, SB_VERT, TRUE);

       // 3rd, retrieves information about the vertical scroll bar.

       sbi.cbSize = sizeof(SCROLLBARINFO);

       GetScrollBarInfo(g_hWndRichEdit, OBJID_VSCROLL, &sbi);

               

       // 4th, check the state of a scroll bar itself

       if (sbi.rgstate[0] == 0)

       {

              // Retrieves the current minimum and maximum positions for the vertical scroll bar.

              GetScrollRange(g_hWndRichEdit, SB_VERT, &nMinPos, &nMaxPos);

              // Calc the height of RTF text.

              nHeight = nMaxPos – nMinPos;

……

       }

}

주석에도 표시된 것과 같이, RichTextBox 컨트롤을 이용하여, RTF 텍스트의 높이를 계산했던 것과 동일한 방식을 이용합니다.

전체 코드는 아래의 주소에서 내려 받을 있습니다.

https://skydrive.live.com/embedicon.aspx/.Public/RTFHeight.7z?cid=1bbcdfedee1c617e&sc=documents

2011-09-06

[C# WinForm] Height of RTF content in RichTextBox. #1

Filed under: Programming — Peter_KIM @ 09:56

RTF 텍스트는 다양한 글꼴의 형식으로 표시되므로, 전체 텍스트를 표시하는 각각의 문자에 대한 높이와 간격 등의 값을 구하여, 텍스트의 높이를 구하는 것이, 정석일 듯합니다.

여러 가지 방법이 있겠지만, RTF 텍스트의 전체 높이를 계산하는 방법 중의 한가지는 RichTextBox 컨트롤의 세로 스크롤 바를 이용하는 것입니다.

TextBox, ListBox, ListView 컨트롤과 같이 다양한 컨트롤에 스크롤 바가 포함되어 있습니다. 이런 컨트롤의 스크롤 바를 제어할 있는 Win32 API 함수가 존재합니다. 언젠가 이야기 했지만, 대부분의 .NET 기본 컨트롤 역시 Win32 컨트롤에 대한 확장일 뿐이므로, 다음과 같은 Win32 API 함수를 호출하여 적용할 있습니다.

GetScrollBarInfo(), GetScrollInfo(), GetScrollPos(), GetScrollRange(), SetScrollInfo(), SetScrollPos(), SetScrollRange()

외에도 Win32 스크롤과 관련된 메시지를 전달하는 것도 가능합니다.

RTF 텍스트 높이를 계산하는 데에는 GetScrollBarInfo(), GetScrollRange() 함수를 사용합니다.

우선 WinForm 프로젝트를 생성하고 아래의 그림과 같이 버튼과 RichTextBox 컨트롤을 배치합니다.

clip_image001[3]

RichTextBox 컨트롤의 BoarderStyle, ScrollBars 속성을 None 값으로 설정합니다.

private UInt32 SB_VERT = 1;

private UInt32 OBJID_VSCROLL = 0xFFFFFFFB;

 

[StructLayout(LayoutKind.Sequential)]

public struct RECT

{

    public Int32 left;

    public Int32 top;

    public Int32 right;

    public Int32 bottom;

}

 

[StructLayout(LayoutKind.Sequential)]

public struct SCROLLBARINFO

{

    public Int32 cbSize;

    public RECT rcScrollBar;

    public Int32 dxyLineButton;

    public Int32 xyThumbTop;

    public Int32 xyThumbBottom;

    public Int32 reserved;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]

    public Int32[] rgstate;

}

 

[DllImport("user32.dll")]

private static extern

    Int32 GetScrollRange(IntPtr hWnd, UInt32 nBar, out Int32 lpMinPos, out Int32 lpMaxPos);

 

[DllImport("user32.dll")]

private static extern

    Int32 GetScrollBarInfo(IntPtr hWnd, UInt32 idObject, ref SCROLLBARINFO psbi);

WinForm 클래스의SizeChanged 이벤트 함수를 아래와 같이 구현합니다.

private void Form1_SizeChanged(object sender, EventArgs e)

{

    this.richTextBox1.Width = this.ClientSize.Width;

}

버튼의 Click 이벤트를 아래와 같이 구현합니다.

private void button1_Click(object sender, EventArgs e)

{

    int nHeight = 0;

    int nMin = 0, nMax = 0;

 

    SCROLLBARINFO psbi = new SCROLLBARINFO();

    psbi.cbSize = Marshal.SizeOf(psbi);

 

    this.richTextBox1.Height = 10;

    this.richTextBox1.ScrollBars = RichTextBoxScrollBars.Vertical;

 

    int nResult = GetScrollBarInfo(this.richTextBox1.Handle, OBJID_VSCROLL, ref psbi);

    if (psbi.rgstate[0] == 0)

    {

        GetScrollRange(this.richTextBox1.Handle, SB_VERT, out nMin, out nMax);

 

        nHeight = (nMax – nMin);

        this.richTextBox1.Height = nHeight;

    }

    this.richTextBox1.ScrollBars = RichTextBoxScrollBars.None;

}

우선, RichTextBox 컨트롤에서 세로 스크롤 바가 표시될 있도록 크기를 대략 10Pixel 정도로 조정하고, Scrollbars 속성을 설정합니다. 이후, API 함수를 호출하여, 스크롤 영역의 최소, 최대 값을 구하여, 차이를 RichTextBox 컨트롤의 높이로 설정합니다.

전체 소스 코드는 아래의 주소에서 내려 받을 있습니다.

https://skydrive.live.com/embedicon.aspx/.Public/RichTextBoxHeight.7z?cid=1bbcdfedee1c617e&sc=documents

Create a free website or blog at WordPress.com.