본문 바로가기

Programming/MFC

[MFC] MSN스타일 스킨 에디트 박스

출처 블로그 > 모두들 아름다운 사람이 되길
원본 http://blog.naver.com/icoder/80009631120

download source (62.2Kb)

여기서 소개하는 버튼은 다음의 요소를 고려해서 만들었습니다.

    - MSN style

    - 기본 윈도우 테두리 스타일을 반영

    - 스킨 스크롤바

    - 포커스 될때의 에디트 배경색 변경



1. 에디트 박스 그리기

 

에디트 박스 테두리(보더)를 그리는 루틴은 OnPaint에서 작업합니다. 이때 중요한것은 에디트 박스를 그리는 기본 함수인 Default() 를 반드시 호출해 줘야 한다는 것입니다. 그래야 텍스트를 제대로 출력할수 있습니다. 테두리는 에디트 박스 보더 스타일에 따라서 각기 다르게 그려집니다.

 

코드 자체가 너무 설명적이라 주석달기도 귀찮습니다. ㅎㅎ

 

void CxEdit::OnPaint()
{
    Default();

    CDC* pDC = GetDC();
    DWORD dwExStyle = GetExStyle();
    COLORREF clrBlack = RGB(0, 0, 0);;
    CRect rcItem;
    int nLoop;
    int nBorderWidth = GetBorderWidth(dwExStyle);

    GetWindowRect(&rcItem);
    ScreenToClient(&rcItem);

    for (nLoop = 0; nLoop < nBorderWidth; nLoop++)
    {
        pDC->Draw3dRect(rcItem, m_cr3DFace, m_cr3DFace);
        rcItem.DeflateRect(1, 1);
    }
    rcItem.InflateRect(1, 1);

    if (dwExStyle & WS_EX_CLIENTEDGE)
    {
        pDC->Draw3dRect(rcItem, m_cr3DDkShadow, m_cr3DLight);
        rcItem.InflateRect(1, 1);
        pDC->Draw3dRect(rcItem, m_cr3DShadow, m_cr3DHilight);
        rcItem.InflateRect(1, 1);
    }

    if (dwExStyle & WS_EX_STATICEDGE && !(dwExStyle & WS_EX_DLGMODALFRAME))
    {
        pDC->Draw3dRect(rcItem, m_cr3DShadow, m_cr3DHilight);
        rcItem.InflateRect(1, 1);
    }

    if (dwExStyle & WS_EX_DLGMODALFRAME)
    {
        pDC->Draw3dRect (rcItem, m_cr3DFace, m_cr3DFace);
        rcItem.InflateRect(1, 1);
        pDC->Draw3dRect(rcItem, m_cr3DHilight, m_cr3DShadow);
        rcItem.InflateRect(1, 1);
        pDC->Draw3dRect(rcItem, m_cr3DLight, m_cr3DDkShadow);
    }

    ReleaseDC(pDC);
}

 

 

에디트 박스의 텍스트와 배경색을 변경하는 코드는 WM_CTLCOLOR 의 컨트롤 리플렉트 메시지 핸들러인 CtlColor(...)에서 변경해줍니다. 여기서 컨트롤이 포커스 될때와 그렇지 않을때의 배경색을 바꿔줘야 하므로 브러시는 두개가 필요합니다.

 

간혹, 텍스트 배경색을 설정하는 SetBkColor 대신에 SetBkMode(TRANSPARENT) 하시는 분들이 있는데 그렇게 하면, 스크롤시 텍스트가 깨질수 있습니다.

 

HBRUSH CxEdit::CtlColor(CDC* pDC, UINT nCtlColor)
{
    pDC->SetTextColor(m_crText); // 텍스트 색

    if (::GetFocus() == GetSafeHwnd())
    {
        pDC->SetBkColor(m_crBk1); // 텍스트 배경 색
        return (HBRUSH)m_bkBrush1; // 에디트 박스 배경 색
    }
    else
    {
        pDC->SetBkColor(m_crBk2); // 텍스트 배경 색
        return (HBRUSH)m_bkBrush2; // 에디트 박스 배경 색
    }

    return NULL;
}

 

 

2. 초기화 및 스크롤바 생성


BOOL CxEdit::Init(COLORREF cr3DFace,        // 가운데 테두리 색
                          COLORREF cr3DLight,         // 3D 테두리의의 밝은부분 색
                          COLORREF cr3DHilight,       // 제일 바깥쪽 오른쪽/아래의 테두리 색
                          COLORREF cr3DShadow,    // 제일 바깥쪽 왼쪽/위의 테두리 색
                          COLORREF cr3DDkShadow, // 3D 테두리의 어두운색
                          COLORREF crText,             // 글자색
                          COLORREF crBk1,              // 포커스 됐을때 배경색
                          COLORREF crBk2,              // 포커스 없을때 배경색
                          UINT nScrollBmp,                // 스크롤바 배경 비트맵 아이디
                          UINT nThumb1,                   // 스크롤바 thumb(위) 비트맵 아이디
                          UINT nThumb2,                   // 스크롤바 thumb(가운데) 비트맵 아이디
                          UINT nThumb3,                   // 스크롤바 thumb(아래) 비트맵 아이디
                          UINT nThumb4,                   // 스크롤바 thumb(손잡이) 비트맵 아이디
                          CSize btnSize )                  // 스크롤바 버튼 사이즈 비트맵 아이디
{
    m_cr3DFace         = cr3DFace;
    m_cr3DLight         = cr3DLight;
    m_cr3DHilight       = cr3DHilight;
    m_cr3DShadow    = cr3DShadow;
    m_cr3DDkShadow = cr3DDkShadow;
    m_crText              = crText;
    m_crBk1              = crBk1;
    m_crBk2              = crBk2;

    ::DeleteObject(m_bkBrush1.Detach());
    ::DeleteObject(m_bkBrush2.Detach());
    m_bkBrush1.CreateSolidBrush(crBk1);
    m_bkBrush2.CreateSolidBrush(crBk2);

    BITMAP bm;
    HBITMAP bmpScroll = ::LoadBitmap(
                                    ::AfxGetInstanceHandle(),
                                    MAKEINTRESOURCE(nScrollBmp));
    ::GetObject(bmpScroll, sizeof(BITMAP), (LPSTR)&bm);
    ::DeleteObject(bmpScroll);

    // 스크롤바 up/down arrow 버튼 사이즈를 얻어냅니다.
    CRect rc;
    GetWindowRect(rc);
    GetParent()->ScreenToClient(rc);
    rc.left = rc.right - bm.bmWidth;
    rc.top = rc.top;
    rc.bottom = rc.bottom;

    // 스크롤바 컨트롤 생성
    if( m_scroll.Create(
        WS_CHILD | WS_VISIBLE | SS_NOTIFY,
        rc,
        GetParent(),
        0 ))
    {

        m_scroll.SetOwner(this);
        m_scroll.Init(nScrollBmp, nThumb1, nThumb2, nThumb3, nThumb4, btnSize);

        SCROLLINFO scrInfo;
        scrInfo.cbSize = sizeof(SCROLLINFO);
        scrInfo.fMask  = SIF_ALL;
        scrInfo.nMin    = 0;
        scrInfo.nMax   = 0;
        scrInfo.nPos   = 0;
        scrInfo.nPage  = 1;

        m_scroll.SetScrollInfo(&scrInfo);
        m_scroll.ShowWindow(SW_HIDE);

        CheckScroll();
        return TRUE;
    }

    return TRUE;
}


위 초기화 함수에서 보듯 스크롤바는 CStatic을 상속받은 별도의 윈도우 컨트롤로 만들어 놨습니다. 하지만, 처음에는 에디트 박스에 붙어있는 스크롤바를 커스터 마이징 하려고 했었죠, 이짖 저짖 삽질 다했는데... 잘 안되더군요.  긍대! 최근에 자체 스크롤바를 커스터마이징 할수 있는 가능성 있는 방법을 알아내서 다시 시도해 보고 있습니다. 조만간 꼭 성공해서 올리도록 하겠습니다. ㅎㅎ


암튼, 스크롤바는 에디트 박스의 부모윈도우(다이얼로그)의 자식으로 생성이 되었고 메시지를 에디트 박스가 받기위해서 SetOwner(this) 함수를 호출하여 스크롤바의 소유 윈도우를 에디트 박스로 설정했습니다.


MSN style 스크롤바 스킨은 아래의 5가지 비트맵 타일이 필요합니다.


  : 스크롤바 배경 비트맵 (IDB_MSNSCB_BACK)

  : 윗 부분 Thumb (IDB_MSNSCB_THUMB1)
  : 가운데 부분 Thumb (IDB_MSNSCB_THUMB2)
  : 아랫 부분 Thumb (IDB_MSNSCB_THUMB3)
  : 미끄럼 방지 턱 (IDB_MSNSCB_THUMB4)



3. 스크롤바 처리


스크롤바는 CStatic 을 상속받은 윈도우인 CxScrollBar에 캡슐레이션 되어 있습니다. 세부적인 부분은 소스를 보시도록 하고요... 여기서는 이 스크롤바에서 가장 중요한 부분인 스크롤 Thumb의 위치/크기 처리를 어떻게 했는지 알아보겠습니다.


아래 소스가 Thumb의 위치와 크기를 구하는 함수입니다. 예를 들어 에디트 박스에서 스크롤 되었거나 라인의 갯수가 늘어났을 경우 m_scrInfo에 nPos 와 nMax 를 변경하게 됩니다. 결국, (nMax-nMin) 의 높이를 기준으로 nPos 가 위치해 있는 지점은 스크롤바의 높이를 기준으로 같은 비율로 어느정도의 위치인가를 알아내면 그것이 바로 Thumb의 위치입니다. 즉, 아래의 계산식중 x를 구하면 됩니다.


라인높이(nRange) : 에디트박스에서 보이는 최상단 라인(nPos) = 스크롤바높이(nHeight) : x

=> (nPos * nHeight) / nRange = x(Thumb의 시작위치)


같은 방식으로 Thumb의 세로크기를 구할수 있습니다.


라인높이(nRange) : 에디트박스의 화면높이(nPage) = 스크롤바높이(nHeight) : x

=> (nPage * nHeight) / nRange = x(Thumb의 세로크기)


void CxScrollBar::GetThumbRect(LPRECT rect)
{
    CRect rcClient;
    GetClientRect(rcClient);
    ::CopyRect(rect, rcClient);

    int nRange = m_scrInfo.nMax - m_scrInfo.nMin;
    nRange = (nRange == 0) ? 1 : nRange;

    int nHeight = rcClient.Height() - (m_btnSize.cy*2);
    int nThumbCy = (int)(((float)(m_scrInfo.nPage*nHeight)/(float)nRange)+0.5f);
    int nThumbY = (int)(((float)(m_scrInfo.nPos*nHeight)/(float)nRange)+0.5f) + m_btnSize.cy;

    if (nThumbCy < 4)
    {
        int nCenter = (nThumbY + nThumbY + nThumbCy) / 2;
        rect->top = nCenter-2;
        rect->bottom = nCenter+2;
    }
    else
    {
        rect->top = nThumbY;
        rect->bottom = nThumbY + nThumbCy;
    }

    // 비율계산시 float(반올림) -> int 의 계산 오차 때문에 thumb의 위치가
    // 100% 정확하지 않을수 있습니다(1pixel 정도의 오차발생). 이러한 문제는
    // 스크롤링이 빠르게 일어나는경우 thumb의 떨림이 발생할 수 있습니다.
    // 이 떨림은 thumb이 중간에 있을땐 그리 눈에 띠진 않지만, 맨 끝에
    // 있을경우 눈에 거슬립니다.
    // 아래 코드는 맨 끝에 thumb이 있을때, 이러한 떨림을 없애주기위해
    // 추가했습니다. (오류보정)

    if (rect->bottom > rcClient.Height() - m_btnSize.cy)
    {
        rect->top--;
        rect->bottom--;
    }
}



4. 주의 사항


이 컨트롤은 에디트 박스의 기본 스크롤바를 커스터마이징 한것이 아닙니다. 단지 스크롤바를 가리고 별도의 스크롤바 컨트롤을 생성해서 에디트 박스에서 핸들링 하고 있을 뿐입니다. 또한 Horizontal ScrollBar는 구현하지 않았습니다. 필요하시다면, 같은 방법으로 구현해 보실수 있을것입니다.


암튼, 그래서 제대로 세로 스크롤바가 보일라믄, 다이얼로그 편집 템플릿에서 에디트박스의 스크롤바에 관련된 Style 항목중 Vertical scroll이 아닌 Auto VScroll에 체크해야 합니다.





written by icoder (mecanic7@dreamwiz.com)