TransparencyKey를 통해서 편리한 도우미 유틸리티를 하나 만들어볼까 합니다. 바로 자동 클리핑 윈도우입니다. Window Region API를 이용하여 계산하고 자르는 작업을 하지 않고서도 간단하고 깔끔하게 이런 작업을 해낼 수 있습니다.

[Flags]
[Serializable]
public enum TransformOptions : int
{
    None = 0,
    HideAllControls = None + 1,
    RemoveWindowText = HideAllControls * 2,
    HideFromTaskbar = RemoveWindowText * 2,
    All = (HideAllControls | RemoveWindowText | HideFromTaskbar)
}

public static void TransformToCustomRegion(Form targetForm, Color transparentColor, TransformOptions options)
{
    if (targetForm == null)
        throw new ArgumentNullException("targetForm");

    if (targetForm.IsDisposed)
        throw new ObjectDisposedException("targetForm");

    if (targetForm.Visible)
        targetForm.Visible = false;

    targetForm.FormBorderStyle = FormBorderStyle.None;
    targetForm.BackColor = transparentColor;
    targetForm.TransparencyKey = targetForm.BackColor;

    if (((int)options & (int)TransformOptions.HideAllControls) != 0)
        foreach (Control c in targetForm.Controls)
            c.Visible = false;

    if (((int)options & (int)TransformOptions.RemoveWindowText) != 0)
        targetForm.Text = String.Empty;

    if (((int)options & (int)TransformOptions.HideFromTaskbar) != 0)
        targetForm.ShowInTaskbar = false;

    if (!targetForm.Visible)
        targetForm.Visible = true;
}

public static void TransformToCustomRegion(Form targetForm, Color transparentColor)
{
    TransformToCustomRegion(targetForm, transparentColor, TransformOptions.None);
}

// 실제 적용

private void Form1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.FillPie(Brushes.Aqua, 140, 0, 400, 400, 30, 80);
}

private void Form1_Load(object sender, EventArgs e)
{
    TransformToCustomRegion(this, Color.Empty);
}

여기서 TransformToCustomRegion 메서드가 동작하는 원리를 살펴보면, BackColor와 TransparencyKey의 값을 일치시켜주는 것과 함께, 창의 구성요소들을 제거하는 것입니다. 이로서, 창의 다른 구성 요소가 제거된 상태에서 순수한 컨텐츠만 Paint 이벤트를 통해서 그려지게 되는데, 이 중 색이 겹치지 않는 내용만이 남아서 창으로 존재하게 되고 나머지는 잘립니다.

이와 같은 원리를 이용하여 가운데에 구멍이 뚫려있는 창도 만들 수 있고, 예전 노턴 크래쉬가드 같은 프로그램처럼 방패모양 창도 구현할 수 있습니다. Hit-Test 구현만 정확히 되어주면 기존 제목 표시줄도 대체가 가능합니다. :-)

Creative Commons License
Creative Commons License
남정현 이 작성.

당신의 의견을 작성해 주세요.

[로그인][오픈아이디란?]
오픈아이디로만 댓글을 남길 수 있습니다

Paint 이벤트에서 그리는 함수가 아니지만 Paint 이벤트와 마찬가지로 작동하기 위해서는 그리는 방법을 다르게 구현해야 합니다. 이 문제 때문에 고민하시는 분이 꽤 있을 것으로 생각됩니다. 이 문제를 해결해줄 수 있는 클래스를 하나 소개합니다. 바로 Replay 클래스입니다.

Replay 클래스를 이용하여 멤버 메서드를 호출하면 모든 기록이 Replay 클래스 안에 보관되고, 나중에 ReplayWithoutResults 메서드를 호출하여 모든 메서드가 다시 호출될 수 있도록 하는 방식입니다. 이런 방법을 통하여 Paint 이벤트에서 그린 그림이 아니라도 복원이 가능합니다.

다음은 예제입니다.

// Graphics 객체를 Paint 이벤트가 아닌 곳에서 얻을 때,

// Replay 객체에 바로 집어넣습니다.

private void Form1_Load(object sender, EventArgs e)
{
    this.g = new Replay<Graphics>(Graphics.FromHwnd(this.panel1.Handle));
}

// Paint 이벤트에서는 Replay 객체가 가지고 있는 모든 메서드들을 호출합니다.

// g 객체의 ReplayWithoutResults 대신 정적 메서드를 부를 수도 있는데

// 이것은 Cross-Thread 호출을 위한 것입니다.

private void panel1_Paint(object sender, PaintEventArgs e)
{
    Replay<Graphics>.ReplayWithoutResults(this, this.g);
}

private Random r = new Random();
private Replay<Graphics> g;

// Graphics.DrawLine을 Replay 컨테이너를 통해서 호출하도록 합니다.

private void timer1_Tick(object sender, EventArgs e)
{
    g.Invoke("DrawLine", new object[] {
        new Pen(Color.FromArgb(r.Next(255), r.Next(255), r.Next(255))),
        new Point(r.Next(this.panel1.Width), r.Next(this.panel1.Height)),
        new Point(r.Next(this.panel1.Width), r.Next(this.panel1.Height)) });
}

이 소스 코드는 Apache License 버전 2.0 조건 아래에서 배포됩니다.

Creative Commons License
Creative Commons License
남정현 이 작성.

당신의 의견을 작성해 주세요.

[로그인][오픈아이디란?]
오픈아이디로만 댓글을 남길 수 있습니다

프로그래밍을 하다보면 Windows Forms나 Windows Presentation Foundation과 같이 한 단락내에 한 객체에 대해서 여러 속성을 동시에 지정해야 하는 경우가 꼭 있기 마련입니다. VB.NET이나 Object Pascl의 경우 With 절을 이용하여 이런 일을 손쉽게 할 수 있도록 해줍니다만 C#의 경우 마땅히 좋은 방법이 없습니다. 게다가, 이렇게 여러 속성을 나열해놓는 코드를 작성하다보면 코드가 어지럽혀지기 쉬운듯 합니다.

Windows Forms나 Windows Presentation Foundation의 경우 대개 디자이너를 이용하여 작업하는 경우가 많으므로 별 다른 문제가 안되지만 가끔 컨트롤을 직접 추가해야 하거나 디자이너가 지원되지 않는 GTK# 등의 환경에서 저 개인적으로 요긴하게 쓰는 방식이 있어서 소개해봅니다.

Panel myPanel = new Panel();
{
    myPanel.BackColor = Color.Violet;
    // ...
    this.Controls.Add(myPanel);
}

위와 같이 myPanel을 최초로 생성하는 줄 다음에 별 다른 의미 없이 공 Bracket을 열고 myPanel에 관한 코드를 집어넣은 뒤 관련 처리가 끝나면 공 Bracket을 닫는 방식입니다. 이렇게 정리를 해두면 #region이나 #endregion보다 훨씬 읽기 편한것 같습니다. :-)

Creative Commons License
Creative Commons License
남정현 이 작성.

당신의 의견을 작성해 주세요.

[로그인][오픈아이디란?]
오픈아이디로만 댓글을 남길 수 있습니다