SimpleIsBest.NET

유경상의 닷넷 블로그

비 정형 윈도우 만들기

by 블로그쥔장 | 작성일자: 2005-10-26 오후 1:45:00
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.
윈폼 카테고리를 만들었습니다. 제가 윈폼에 그다지 자신이 없고(다른건 자신이 있는듯... 가소롭죠? -_-), 윈폼에 관련된 다양한 테크닉이 매우 많기 때문에 윈폼쪽 글은 별로 다루지 않았었지만 용기를 내어 좀 다루어 볼까 합니다. 더불어 요즘(?) 주목을 받는 스마트 클라이언트 관련 글도 이곳 카테고리에서 다루어 볼까 합니다. 첫 번째 주제는 맛배기로 사각형이 아닌 비 정형 윈도우를 작성하는 윈폼 코드에 대한 내용입니다.

Irregularly Shaped Window

뭔 넘의 블로그 제목이 이다지도 어렵냐... Irregular 라는 단어를 어휘학적으로 접근해서(쑈하구 있넹... -_-), 부정의 접두어 ir 에다가 친숙한 단어 regular, 그리고 부사형 접미어 ly 가 붙었으니... 해석하자면 regular 하지 않은 윈도우에 대한 것 이렸다! -_-;  Regular 하지 않은 윈도우라면, 먼저 regular 한 윈도우부터 알아보면 금새 irregular 윈도우를 알 수 있을 터... 대개의 일반적인 윈도우들은 죄다 직사각형이다. 정사각형이나 직사각형처럼 각 모서리의 두 변이 직각으로 만나는 윈도우(수학 시간 냄새가 풀풀 난당... -_-)가 바로 일반적인 regular shaped window가 되겠다. 그렇다면 Irregular Shaped Window는 직사각형이 아닌 윈도우, 원형, 삼각형, 혹은 폐곡선(오옷! 이런 어려운 단어를?) 등등이 바로 비정형 윈도우가 되겠다. 대표적인 비 정형 윈도우들은 스킨(skin; 껍데기, 껍닥)을 사용하는 프로그램들이다. 화면1은 Windows Media Player(WMP)에 스킨을 적용한 것으로 WMP 윈도우가 사각형이 아닌 원형 비스무레한 기하학적 폐곡선을 이루고 있다.


화면1. 비 정형 윈도우의 대표적인 예. Windows Media Player의 스킨

이번 포스트는 윈폼(WinForm)에서 이러한 비 정형 윈도우를 만드는 방법과 관련 지식을 간단히 살펴보도록 하겠다. 뭐, Win32 API를 직접 사용하거나 MFC를 깨나 다루어본 독자라면 다 알고 있는 방법일 터, 그러한 독자라면 남는 시간에 다른 사이트에 가서 보다 이로운 지식을 습득하기 바라며 시작해 볼까 한다.

Window Region

비 정형 윈도우를 만들기 위해 핵심적으로 알아야 하는 기초지식은 윈도우 영역(window region)이다. 윈도우 영역(window region)이라 함은 윈도우즈 시스템에서 윈도우가 차지하는 2차원 영역을 말한다. 윈도우즈 시스템의 UI는 모두 윈도우로 구성되어 있고 이들 윈도우는 모두 자신의 영역을 갖는다. 특별히 지정하지 않는 한 이 윈도우 영역은 직사각형의 모양을 갖는다. 이름에서 눈치를 챌 수 있듯이 윈도우 사각형이 아닌 윈도우 영역이다. 이는 윈도우 영역이 반드시 직사각형이 아닌 다른 모양을 갖어도 된다는 말이 되겠다.

영역(region)을 만들기 위해서는 Win32 API의 CreateRectRgn, CreatePolygonRgn, CreateEllipticRgn, 등의 영역 관련 API를 사용해서 생성하면되고, C++를 이용하여 보다 손쉽게 작업하고자 한다면 GDI+Region 클래스를 사용하면 될 것이다. WTL이나 MFC 등의 C++ 라이브러리로 작업한다면 이들 API를 잘 노려봐야 할 것이다.

우리의 친절한 닷넷씨는 GDI+의 모든 클래스를 잘 래핑한 클래스 라이브러리를 가지고 있다. 영역에 대한 클래스는 System.Drawing 네임스페이스에 Region 클래스가 그 역할을 한다. 이 클래스를 통해 비 정형 영역을 다양하게 만들 수 있다.

다음 리스트1는 Region 클래스를 사용하는 예제를 보여주고 있다. GraphicsPath 클래스를 이용하여 2D 벡터 드로잉(drawing)을 수행하고(AddPie 메쏘드 호출) 그 결과를 이용하여 Region 클래스를 생성한다. OnPaint 에서 생성된 Region 클래스에 빠~알~간 색 브러쉬를 이용하여 Fill(FillRegion 메쏘드 호출)을 수행한다.

class FillRegionForm : System.Windows.Forms.Form
{
    
private Region _Region;
    
    public 
FillRegionForm()
    {
        
this.Text "Create and Fill Region";
        
// Vector drawing using GraphicsPath
        
GraphicsPath path = new GraphicsPath();
        
path.AddPie(00this.ClientSize.Width, this.ClientSize.Height, 060);
        
// Creating region using GraphicsPath.
        
_Region = new Region(path);
    
}
    
    
protected override void OnPaint(PaintEventArgs e)
    {
        
using(Brush brush = new SolidBrush(Color.Red)) {
            e.Graphics.FillRegion(brush, _Region)
;
        
}
    }
}

리스트1. Region 클래스를 이용한 간단한 Fill 예제

Setting Window Region

영역을 만들 수 있다면 이 영역을 해당 윈도우(폼 혹은 컨트롤)에 설정하기만 하면 된다. Control 클래스의 Region 속성은 윈도우의 영역을 설정하기 위해 제공되는 속성이다. Region 속성에 유효한 Region 객체를 할당함으로써 윈도우의 영역을 설정할 수 있다. Form 클래스 역시 Control 클래스에서 상속받은 클래스이므로 Region 속성을 사용할 수 있음은 당연 빤쑤이다.

컨트롤 혹은 폼의 Region 속성을 설정해 주는 시점은 어느 시점이든 가능하다. 컨트롤 혹은 폼이 항상 비 정형 윈도우를 갖는다면 생성자에서 Region 속성을 설정해 주면 될 것이고, 메뉴나 버튼 등이 선택되었을 때 윈도우 모양(영역)을 바꾸어야 한다면 해당 이벤트 핸들러에서 Region 속성을 설정해 주면 된다. 리스트2은 매우 간단한 폼 예제로써 비정형 윈도우를 만들고 있다. 생성자에서 Region 속성을 설정하고 있음을 주의깊게 보자. 리스트1의 수행결과는 화면2와 같다.

class IrregularShapedWindowForm : System.Windows.Forms.Form
{
    
public IrregularShapedWindowForm()
    {
        
// Vector drawing using GraphicsPath
        
GraphicsPath path = new GraphicsPath();
        
path.AddPie(2this.Height - this.ClientSize.Height, 
                    
this.ClientSize.Width - 4this.ClientSize.Height - 40360);
        
// Creating region using GraphicsPath.
        
this.Region = new Region(path);    // setting window region
    
}
}

리스트2. 윈도우의 영역을 바꾸는 폼 예제


화면2. 예제 코드 수행 결과

Moving Window without Caption

일반적으로 비 정형 윈도우들은 윈도우의 제목과 최대화/최소화 버튼 등이 포함된 캡션(caption) 바를 갖지 않는다. 이때 윈도우를 이동하거나 종료는 어떻게 할까? 뭐, 윈도우 종료야 종료 버튼을 만들거나 컨텍스트 메뉴(Context Menu; 오른쪽 클릭할 때 등장하는 메뉴) 등으로 처리할 수 있을 것이다. 하지만 윈도우를 이동시키는 것은 녹녹치 않다. 캡션 바가 존재한다면 캡션 바를 드래그함으로써 윈도우를 이동시키지만 캡션 바가 없는 상황에서는 윈도우 이동이 대략 난감하다는 것이다. 대개의 비 정형 윈도우들은 캡션 바가 아닌 윈도우의 임의의 부분을 드래그 하거나 특정 부분을 드래그 함으로써 윈도우 이동을 구현하고 있다. 이러한 구현은 어떻게 하는 것일까?

윈도우즈가 제공하는 수천개의 WIN32 메시지(WM_XXX 류의 윈도우 메시지를 말한다) 중에 WM_NCHITTEST 라는 메시지가 정확히 이러한 문제를 해결하는 메시지이다. 이 메시지는 마우스가 이동하거나 클릭되었을 때, 마우스가 윈도우의 어떤 영역(캡션인지, 우측상단 종료 버튼인지 등등)에 존재하는지를 반환하는 메시지이다. 이 메시지는 대개 디폴트 윈도우 프로시저에 의해 처리되지만 이것을 직접 처리할 수도 있다. 이 메시지를 처리하면 마우스 좌표에 해당하는 윈도우 영역을 반환해야 하는데, 우리의 목적은 캡션이 없는 윈도우를 이동하는 것이므로 마우스가 항상 캡션 위에 있는 것처럼 해주면 된다.

리스트3은 WM_NCHITTEST를 사용하는 간단한 예제이다. WndProc 메쏘드를 오버라이드하여 WM_NCHITTEST를 처리할 수 있도록 하고, 이 메시지가 도착하면 항상 HTCAPTION 값을 반환한다. HTCAPTION 값은 마우스의 현재 위치가 캡션 바임을 알리는 것이다. 이렇게 함으로써 클라이언트 영역의 임의 부분을 드래그하더라도 윈도우는 마치 캡션 바를 드래그하는 효과, 즉 윈도우 이동을 수행하는 것이다.

protected override void WndProc(ref Message m)
{
    
if (m.Msg == 0x84) {            // WM_NCHITTEST == 0x84
        
m.Result (IntPtr)2;        // HTCAPTOIN == 2
        
return;
    
}
    
base.WndProc(ref m);
}

리스트3. WM_NCHITTEST 메시지 처리를 이용한 윈도우 이동

Application

이제 비 정형 윈도우를 만드는 개략적인 방법은 알았을 것이다. 그렇다면 마음껏 상상의 나래를 펼쳐서 여러분의 프로그램에 응용하면 될 것이다. 사실 필자의 예제처럼 썰렁한 동그라미 윈도우는 그다지 유용하지 않아 보인다. 화면1과 같이 멋진 비 정형 윈도우를 만들어 보고 싶다면 많은 작업이 필요하다. 먼저 비 정형 윈도우로 사용할 이미지를 정해야 한다. 그 이미지로부터 외곽선을 추출하여 GraphicsPath 클래스를 통해 외곽선을 그린다. 그리고 생성한 GraphicsPath 객체로 부터 Region을 만들어야 한다. 그 다음엔 해당 Region에 이미지를 그려주면 될 것이다. 보다 구체적으로 해야 할 추가 작업들은 종료 버튼(X 버튼을 말한다)이나 최소화 버튼 등이 표준 윈도우와 같지 않게 나타나도록 하는 것일 텐데, 이것 역시 이미지 버튼을 이용하여 적절한 위치에 배치시키면 된다.

다른 것은 다 해볼만 한데, 이미지로부터 외곽선을 추출하는 것이 영 꺼림직 하다. 걱정할 건 없다. 우리는 거의 무한대의 정보를 제공하는 인터넷 세상에서 살고 있지 않은가? 어렵지 않게 외곽선 추출 알고리즘을 알아낼 수 있을 것이다. 운이 좋다면 이것을 구현한 닷넷 코드를 얻을 수도 있을 것이다. 두드려라~ 그러면 열릴지어니....

p.s. 필자가 이 글을 쓸 때 작성한 예제 코드는 VS.NET을 사용하지 않았다. VS.NET에서 작업하더라도 코드를 작성하는데 큰 무리가 없을 것이다. 하지만 VS.NET의 폼/컨트롤 디자이너는 윈도우의 영역을 제대로 표시해주지 않으므로 디자인상에서 보이던 버튼, 라벨등의 컨트롤이 영역이 적용됨에 따라 보이지 않을 수 있음에 유의하기 바란다. 뷁~



Comments (read-only)
#re: 비 정형 윈도우 만들기 / 나그네 / 2008-07-11 오후 1:59:00
음.. 웹사이트가 특이하네용 ^^;

정감있다고나 할까용...

글찾다 왔는데 잘 배우고 갑니다.

좋은 하루 되세요 ~