Visual Studio 2017 환경에서 C# Windows Forms 앱으로 Tray Message를 구현해보자.
(이 글은 가메출판사의 C# 7.0프로그래밍 실전프로젝트를 통해 공부한 내용을 작성했다.)
1. 새로운 프로젝트 만들기
파일->새로만들기->프로젝트 (Ctrl + Shift + N)를 누르고 Visual C# 카테고리의 Windows Forms 앱 프로젝트를 하나 만든다.
2. form1 만들기
위와 같이 form1을 버튼과 텍스트박스를 이용해 만들어준다.
*버튼: btnMsg
*텍스트박스: txtMsg
form1에 다음 코드를 추가해주자.
private void btnMsg_Click(object sender, EventArgs e)
{
Form2 frm2 = new Form2();
frm2.MsgText = this.txtMsg.Text;
frm2.ShowDialog();
}
버튼을 클릭 시 form2를 텍스트박스에 입력되어 있는 값을 전달한 후 출력한다.
3. form2 만들기
위와 같이 form2를 Panel과 LinkLabel을 이용해서 만들어준다.
*Panel: plBack
*LinkLabel: lblResult
form2의 작성코드는 다음과 같다.
using System.Timers; //Timer 클래스 사용
.
.
.
public partial class Form2 : Form
{
private static System.Timers.Timer TimerEvent; //Timer 개체 생성
private delegate void OnDelegateHeight(int Flag); //대리자 선언
private OnDelegateHeight OnHeight = null; //대리가 개체 생성
public string MsgText //Form1에서 입력받은 문자열을 전달
{
set
{
this.lblResult.Text = value;
}
}
public Form2() //InitializeComponent 호출 전 메시지 창의 위치와 크기를 설정
{
int x = Screen.PrimaryScreen.WorkingArea.Width - this.Width - 20;
int y = Screen.PrimaryScreen.WorkingArea.Height - this.Height;
DesktopLocation = new Point(x, y);
this.Size = new Size(170, 0);
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e) //폼을 더블클릭하여 생성
{
OnHeight = new OnDelegateHeight(MsgView); //대리자 초기화
this.Size = new System.Drawing.Size(170, 0);
this.Location = new System.Drawing.Point(
Screen.PrimaryScreen.WorkingArea.Width - this.Width - 20,
Screen.PrimaryScreen.WorkingArea.Height - this.Height);
TimerEvent = new System.Timers.Timer(2); //2밀리 초마다 TimerEvent초기화
TimerEvent.Elapsed += new ElapsedEventHandler(OnPopUp);
TimerEvent.Start();
}
private void MsgView(int Flag) //폼을 위아래로 이동 혹은 종료하는 메서드
{
if (Flag == 0) //폼을 올린다
{
Height++;
Top--;
}
else if (Flag == 1) //폼을 내린다
{
Height--;
Top++;
}
else if (Flag == 2)
{
this.Close();
}
}
private void OnPopUp(object sender, ElapsedEventArgs e) //폼을 올리는 작업을 수행하는 메서드
{
if (Height < 120) //Height가 120보다 작을 시 폼을 올린다
{
Invoke(OnHeight, 0);
}
else //Height가 120이상일 시, 폼을 내리는 작업으로 변경
{
TimerEvent.Stop();
TimerEvent.Elapsed -= new ElapsedEventHandler(OnPopUp);
TimerEvent.Elapsed += new ElapsedEventHandler(OnPopOut);
TimerEvent.Interval = 3000; //3초 정지
TimerEvent.Start();
}
Application.DoEvents();
}
private void OnPopOut(object sender, ElapsedEventArgs e) //폼을 내리는 작업을 수행하는 메서드
{
while (Height > 2) //Height가 2보다 크면 폼을 내린다
{
Invoke(OnHeight, 1);
}
TimerEvent.Stop(); //Height가 2이하일 시 폼을 종료한다
Application.DoEvents();
Invoke(OnHeight, 2);
}
private void lblResult_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) //LinkLabel을 더블클릭하여 생성
{
this.Close(); //폼 종료
TimerEvent.Stop();
}
}
4. Delegate, Invoke
대리자(Delegate)는 C/C++에서의 함수 포인터와 비슷한 역할이라고 볼 수 있다. 대리자는 이름 그대로, 메소드를 대신해서 호출하는 역할을 수행한다.
private delegate void OnDelegateHeight(int Flag); //대리자 선언
private OnDelegateHeight OnHeight = null; //대리가 개체 생성
.
.
OnHeight = new OnDelegateHeight(MsgView); //대리자 초기화
.
.
private void MsgView(int Flag) //폼을 위아래로 이동 혹은 종료하는 메서드
{
if (Flag == 0) //폼을 올린다
{
Height++;
Top--;
}
else if (Flag == 1) //폼을 내린다
{
Height--;
Top++;
}
else if (Flag == 2)
{
this.Close();
}
}
위에 있던 대리자가 사용되었던 부분이다.
OnHeight라는 대리자 개체를 MsgView라는 함수를 대리할 수 있도록 초기화해놓았다.
이제 OnHeight(0)과 MsgView(0)은 같은 결과를 나타낼 것이다. 하지만, 왜 굳이 이런 식으로 대리자를 사용했을까?
우선, Invoke에 대해 알아보자.
private void OnPopOut(object sender, ElapsedEventArgs e) //폼을 내리는 작업을 수행하는 메서드
{
while (Height > 2) //Height가 2보다 크면 폼을 내린다
{
Invoke(OnHeight, 1);
}
TimerEvent.Stop(); //Height가 2이하일 시 폼을 종료한다
Application.DoEvents();
Invoke(OnHeight, 2);
}
크로스 스레드 문제 (특정 스레드에서 생성된 Windows Forms 컨트롤을 다른 스레드에서 접근할 때 생기는 문제)를 해결하는 방법 중 하나가 Invoke문을 사용하는 것이다.
Invoke를 사용하기 위해서는 메서드를 매개변수로 주어야하고, 대리자는 이를 가능하게 해준다.