ButtonEx 컨트롤의 활용
5회차에서 빡시게 ButtonEx 서버 컨트롤을 만들어 보았습니다. 취소 가능한 이벤트를 통해 만든 서버 컨트롤을 활용할 수 있는 샘플을 보도록 하겠습니다.
 
여기서 만들어 볼 샘플은 유효성체크(Validate Check) 입니다. TextBox 컨트롤에 입력된 문자가 숫자인지 판별하는 간략한 샘플이지만, 어떻게 Before/After 이벤트를 분리하여 구현하는지 잘 보여주는 샘플이라고 생각합니다.

[그림1] 웹 폼 구성
 
아래의 소스는 int.Parse 메서드를 통해 숫자가 아닐 경우 Cancel = true 를 통해 After 이벤트를 취소하는 예제입니다.
namespace WebApplication4
{
        public partial class _Default : System.Web.UI.Page
        {
 
               protected void Page_Load(object sender, EventArgs e)
               {
               }
 
               protected void ButtonEx1_BeforeClick(object
sender,Sample.ButtonEx.BeforeClickEventArgs e)
               {
                       try
                       {
                              int.Parse( TextBox1.Text ); // 입력한값이숫자가아니라면 Exception 발생하겠죠?
                              Response.Write( TextBox1.Text + " 입력하였습니다<br/>");
                       }
                       catch
                       {
                              Response.Write("숫자만입력하세요");
                              e.Cancel = true;
                       }
               }
 
               protected void ButtonEx1_AfterClick(object sender,
Sample.ButtonEx.AfterClickEventArgs e)
               {
                       Response.Write("입력한값은숫자가맞습니다");
               }
        }
}
 
[그림2] 실행 결과
 
어떤가요? .NET Framework 은 이벤트 프로그래밍이라는 말이 과언이 아니라고 생각합니다. 이벤트는 .NET 프로그래밍에 있어서 굉장히 유용한 것 같네요. 두서 없이 아티클을 적어 보긴 했지만, 이미 필요한 분에게 많은 도움이 되었을 거라 생각합니다. 그럼 안녕^^//
Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요




 
 
실전 취소 가능한 버튼 컨트롤 만들기
 
Umc.Core.EventHandlerDictionary 클래스
이 클래스는 Umc.Core 프로젝트에 포함된 클래스 입니다. 이 클래스의 이름에서 알 수 있듯이 이벤트를 사전(Dictionary)로 관리하도록 하기 위한 클래스 입니다.
 
namespace Umc.Core
{
        ///<summary>
        /// Umc.Core<br/>
        /// Delegate 담는 EventHandlerDictionary 컬렉션
        ///</summary>
        public class EventHandlerDictionary : IDisposable
        {
               ///<summary>
               /// Umc.Core<br/>
               /// Event 담는컬렉션
               ///</summary>
               private Dictionary<object,Delegate> eventDictionary = new Dictionary<object,Delegate>();
 
               ///<summary>
               /// Umc.Core<br/>
               /// EventHandlerDictionary EventHandler 추가한다.
               ///</summary>
               ///<param name="key">키값</param>
               ///<param name="value">Delegate</param>
               public void AddHandler(object key, Delegate value)
               {
                       if ( eventDictionary.ContainsKey(key) )
                       {
                              eventDictionary[key] = Delegate.Combine(eventDictionary[key], value);
                       }
                       else
                       {
                              eventDictionary[key] = value;
                       }
               }
 
               ///<summary>
               /// Umc.Core<br/>
               /// EventHandlerDictionary EventHandler 제거한다.
               ///</summary>
               ///<param name="key">키값</param>
               ///<param name="value">Delegate</param>
               public void RemoveHandler(object key, Delegate value)
               {
                       if ( eventDictionary.ContainsKey(key) )
                       {
                              eventDictionary[key] = Delegate.Remove(eventDictionary[key], value);
                       }
               }
 
               public bool Contains(object key)
               {
                       return this.eventDictionary.ContainsKey(key);
               }
 
               ///<summary>
               /// Umc.Core<br/>
               /// EventHandlerDictionary 인덱서
               ///</summary>
               ///<param name="key"></param>
               ///<returns></returns>
               public Delegate this[object key]
               {
                       get { return eventDictionary[key]; }
                       set { eventDictionary[key] = value; }
               }
 
               #region IDisposable 멤버
               ///<summary>
               /// Umc.Core<br/>
                /// EventHandlerDictionary 자원을해제한다.
               ///</summary>
               public void Dispose()
               {
                       if( eventDictionary != null )
                              eventDictionary.Clear();
 
                       eventDictionary = null;
               }
               #endregion
        }
}
 
클래스 내부에는 Dictionary 제네릭 클래스의 오브젝트를 생성하여, AddHandler(), RemoveHandler() 를 통해 이벤트를 사전에 추가/삭제 하는 로직이 들어있습니다.
 
 
ButtonEx 네임스페이스
Click 이벤트 전/후에 발생할 이벤트의 인자를 전달한 EventArgs 클래스 입니다. 이미 지난 회차를 보신 분이라면 그렇게 생소하지 않을 것입니다.
///<summary>
/// Before 이벤트가발생할전달될이벤트인자클래스입니다.
///</summary>
public class BeforeClickEventArgs : CancelEventArgs
{
        public string Reason { get; set; }
}
 
///<summary>
/// After 이벤트가발생할전달될이벤트인자클래스입니다.
///</summary>
public class AfterClickEventArgs : EventArgs { }
 
또한, 직관적인 코딩을 위해 Before/After 이벤트를 위한 델리게이트를 선언하였습니다. 특별한 변경된 EventArgs 와 같은 인자가 없다면 기존 EventHandler 를 활용해도 되지만, 언제든 추후에 확장될 가능성은 충분하다고 생각합니다. 그렇기 때문에 델리게이트를 선언해 주었답니다.
///<summary>
/// BeforeClick 이벤트를캡슐화하는델리게이트입니다.
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
public delegate void BeforeClickEventHandler(object sender, BeforeClickEventArgs e);
///<summary>
/// AfterClick 이벤트를캡슐화하는델리게이트입니다.
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
public delegate void AfterClickEventHandler(object sender, AfterClickEventArgs e);
 
여기서 이벤트는 사전(Dictionary) 을 통해 이벤트를 관리하게 됩니다. Umc.Core 프로젝트에 사용되는 EventHandlerDictionary 클래스의 일부를 직접 예제 클래스에 포함한 것입니다. 이 EventHandlerDictionary 에 접근하기 위한 Events 프로퍼티를 선언하였습니다.
private EventHandlerDictionary events;
///<summary>
/// EventHandlerDictionary 통해이벤트를관리합니다.
///</summary>
protected EventHandlerDictionary Events
{
        get
        {
               if( events == null )
                       events = new EventHandlerDictionary();
 
               return events;
        }
}
 
여기에서 선언된 이벤트는 EventHandlerDictionary 클래스에서 관리하게 될 것이기 때문에, event 선언에 대해 커스트마이징(?)이 필요합니다. 즉, 이벤트의 추가/삭제에 대한 액션을 변경할 필요가 있습니다.
private object EVENT_BEFORE_CLICK     = new object(); // BeforeClick 이벤트오브젝트
private object EVENT_AFTER_CLICK      = new object(); // AfterClick 이벤트오브젝트
 
///<summary>
/// BeforeClick 이벤트입니다. Click 이벤트가발생하기전에발생합니다.
///</summary>
[Description("Click 이벤트가발생하기전에발생합니다")]
public event BeforeClickEventHandler BeforeClick
{
        add
        {
               this.Events.AddHandler(EVENT_BEFORE_CLICK, value);
        }
        remove
        {
               this.Events.RemoveHandler(EVENT_BEFORE_CLICK, value);
        }
}
 
///<summary>
/// AfterClick 이벤트입니다. Click 이벤트가발생한발생합니다.
///</summary>
[Description("Click 이벤트가발생후에발생합니다")]
public event AfterClickEventHandler AfterClick
{
        add
        {
               this.Events.AddHandler(EVENT_AFTER_CLICK, value);
        }
        remove
        {
               this.Events.RemoveHandler(EVENT_AFTER_CLICK, value);
        }
}
 
여기는 각각의 Before/After 이벤트를 발생하는 메서드와 CancelEventArgs 의 Cancel 프로퍼티를 검사하는 메서드가 존재합니다. OnEventsFire() 메서드를 눈여겨 보시면 될 것 같습니다.
///<summary>
/// BeforeClick 이벤트를발생합니다.
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
protected virtual void OnBeforeClick(object sender, BeforeClickEventArgs e)
{
        if( !this.Events.Contains( EVENT_BEFORE_CLICK ) ) return;
 
        BeforeClickEventHandler handler       =
this.Events[ EVENT_BEFORE_CLICK ] as BeforeClickEventHandler;
        if( handler != null )
               handler(sender, e);
}
 
///<summary>
/// AfterClick 이벤트를발생합니다.
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
protected virtual void OnAfterClick(object sender, AfterClickEventArgs e)
{
        if( !this.Events.Contains(EVENT_AFTER_CLICK) ) return;
 
        AfterClickEventHandler handler =
this.Events[ EVENT_AFTER_CLICK ] as AfterClickEventHandler;
        if( handler != null )
               handler(sender, e);
}
 
///<summary>
/// Click 이벤트가발생할경우 BeforeClick/AfterClick 이벤트를발생합니다.
///</summary>
///<param name="sender"></param>
protected virtual void OnEventsFire( object sender )
{
        BeforeClickEventArgs beforeArgs       = new BeforeClickEventArgs();
        OnBeforeClick( sender, beforeArgs );
 
        if( beforeArgs.Cancel ) return;
 
        AfterClickEventArgs afterArgs = new AfterClickEventArgs();
        OnAfterClick( sender, afterArgs );
}
 
[Obsolete("ButtonEx 컨트롤에서는 Click 이벤트를사용하지않도록합니다. 대신 AfterClick 이벤트를사용하세요")]
protected override void OnInit(EventArgs e)
{
        base.OnInit(e);
        this.Click += new EventHandler(ServerControl1_Click);
}
 
private void ServerControl1_Click(object sender, EventArgs e)
{
        OnEventsFire(sender);
}
 
이제 간략한 소스 코드 설명은 끝났네요. 힘들게 만든 ButtonEx 서버 컨트롤을 어떻게 활용하면 될지 다음 회차를 참고하세요.
Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요




 
 
CancelEventArgs 클래스
CancelEventArgs 는 .NET Framework 이 제공하는 클래스 입니다. 이 클래스는 특정한 기능의 구현이 된 것이 아니라, 몇 가지 프로퍼티가 제공이 되는 것 뿐입니다.
 
CancelEventArgs 클래스의 메타 데이터를 보면 두 개의 생성자와 Cancel 프로퍼티를 제공하는 것을 알 수 있습니다.
 
[그림1] CancelEventArgs 클래스 메타데이타
 
 
예제
그럼 한번 취소 가능한 이벤트를 작성해 보겠습니다.
 
우선 이벤트가 사용할 대리자를 선언해 보았습니다. AfterEventHandler 의 경우 특별히 지정하지 않아도 되지만, 직관적으로 코딩이 나름대로 편하기 때문에 선언해 보았답니다.
public delegate CancelEventHandler BeforeEventHandler(object sender, CancelEventArgs e);
public delegate EventHandler AfterEventHandler(object sender, EventHandler e);
 
다음은 Sample 클래스의 모든 소스를 한번에 보도록 하겠습니다.
 
public class Sample
{
        private object EVENT_BEFORE_EVENT     = null; // Before 이벤트객체
        private object EVENT_AFTER_EVENT      = null; // After 이벤트객체
 
        // 이벤트의수가많아경우, 대부분의이벤트가사용되지않을것이라고예상될경우유용한방법이다.
        // 다음회차에자세히알아보자.
        public event CancelEventHandler BeforeEvent
        {
               add
               {
                       EVENT_BEFORE_EVENT     =
(CancelEventHandler)EVENT_BEFORE_EVENT + value;
               }
               remove
               {
                       EVENT_BEFORE_EVENT     =
(CancelEventHandler)EVENT_BEFORE_EVENT - value;
               }
        }
 
        public event EventHandler AfterEvent
        {
               add
               {
                       EVENT_AFTER_EVENT      = (EventHandler)EVENT_AFTER_EVENT + value;
               }
               remove
               {
                       EVENT_AFTER_EVENT      = (EventHandler)EVENT_AFTER_EVENT - value;
               }
        }
 
        // Before 이벤트를발생한다. private 메서드에주의
        private void OnBefore(object sender, CancelEventArgs e)
        {
               CancelEventHandler handler = (CancelEventHandler)EVENT_BEFORE_EVENT;
 
               if( handler != null )
                       handler(sender, e);
        }
 
        // After 이벤트를발생한다. private 메서드에주의
        private void OnAfter(object sender, EventArgs e)
        {
               EventHandler handler          = (EventHandler)EVENT_AFTER_EVENT;
 
               if( handler != null )
                       handler( sender, e);
        }
 
        // Before, After 이벤트를발생하여 Before 이벤트의 Cancel 여부를판단한다.
        public void OnFire(object sender)
        {
               CancelEventArgs args          = new CancelEventArgs();
               this.OnBefore(sender, args);
 
               if( args.Cancel ) return;
 
               this.OnAfter(sender, EventArgs.Empty);
        }
}
 
여기서 event 를 선언하는 코드가 약간 생소하네요.
 
public event CancelEventHandler BeforeEvent { add; remove; }
 
이 구문은 대부분의 이벤트가 사용하지 않을 것으로 예상될 경우 이벤트의 추가/삭제를 커스트마이징 할 수 있기 때문에 굉장히 효율적인 방법입니다. 여기에서 이벤트는
 
EVENT_BEFORE_EVENT
 
오브젝트(object) 를 통해 추가/삭제 되는 것을 알 수 있습니다. 즉, EVENT_BEFORE_EVENT 오브젝트는 이벤트의 키 값이라고 보셔도 무방할 것 같네요.
 
OnBefore(), OnAfter() 와 같이 실제로 이벤트를 발생하는 메서드는 이미 1,2 회차에서 자주 보셨을 거라 생각합니다. 실제로 가장 중요한 구문은 OnFire() 메서드 입니다. OnFire() 메서드는 참조 타입(클래스)인 CancelEventArgs 클래스의 인스턴스를 생성하여 OnBefore() 메서드에게 넘겨주고 있습니다. Before 이벤트의 실제 구현부가 실행되고 CancelEventArgs 의 Cancel 프로퍼티가 True 가 될 경우 조건문에 의해 더 이상 After 이벤트가 실행되지 않도록 return 이 된답니다.
 
소스 코드에 주석을 잘 보시면서 차근차근 생각하시면 이해가 되실 겁니다.
 
Default.aspx.cs
[그림2] default.aspx 실행결과
 
만약, Before 이벤트 중 이벤트의 진행을 취소하고 싶다면 Cancel 프로퍼티를 True 설정하는 것만으로 이벤트의 진행의 취소가 가능합니다.
 
[그림3] Cancel 프로퍼티를 True 로 설정한 실행결과

Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요


 
 
1,2 회 이벤트에 대해 잘 알아 보셨는지요. 이번 3회차 에서는 1,2회차에 비해 난이도가 월등히 높아지게 됩니다. 반드시 이벤트를 정복하고자 한다면 1,2 회차를 직접 코드로 작성해 보며 이벤트에 대한 감각을 익히세요.
 
취소 가능한 이벤트
아마도 윈폼을 조금이라도 해 보신 분이라면, FormClosing/FormClosed 이벤트를 보신적이 있을 것입니다. 이 이벤트 중 FormClosing 은 폼이 닫히기 전에 발생하는 이벤트로 FormClosed 이벤트가 발생하는 것을 방지할 수 도 있답니다. 바로 이것이 취소 가능한 이벤트 입니다.(제가 붙혀서 부르는 것임 +_+)
 
아래의 간략한 윈폼 소스를 보면 알 수 있을 것입니다.
 
[그림1] FormClosing/FormClosed 예제
 
FormClosing 과 FormClosed 는 폼이 닫힐 때 차례로 발생하는 것을 알 수 있습니다.
 
[그림2] FormClosing/FormClosed 이벤트
 
하지만, 만약 CancelEventArgs 에서 제공하는 Cancel 프로퍼티를 True 로 설정할 경우 FormClosing 이후 FormClosed 이벤트를 실행되지 않습니다.
 
[그림3] CancelEventArgs 의 Cancel 프로퍼티를 True 로 설정한 경우
 
위와 같이 Cancel 프로퍼티를 True 로 설정한 경우 더 이상 FormClosed 이벤트는 발생하지 않게 되며, 폼 또한 종료하게 되지 않습니다.
 
[그림4] FormClosed 가 발생하지 않는 예제
 
이렇게 특정 이벤트가 발생하기 위해 Before/After(전후) 의 처리가 필요한 이벤트의 경우 After 이벤트를 발생하지 않도록 함으로써 Before 이벤트의 활용도가 많아진다.
 
이러한 Before 이벤트는 여러 가지 용도로 사용할 수 있습니다.
l 하나의 이벤트 구현에 들어갈 내용을 Before/After 단계로 구분하여 작성할 수 있습니다.
l Before 이벤트에서 유효성 검사를 실시하여, 이벤트의 진행/취소 유무를 결정할 수 있습니다.
 
대략 위 두 가지 정도가 가장 많이 활용될 수 있을 것 같습니다.
 
실제로 여러 상용 컨트롤에서는 위와 같은 Before/After 와 같은 굉장히 방대한 이벤트를 제공해 줍니다. 하나의 이벤트로 처리될 것이 두 개의 Before/After 이벤트로 제어할 수 있게 되면 보다 직관적이고 구현을 분리하여 작성하는데 도움이 됩니다.
 
다음 회차에 이러한 취소 가능한 이벤트를 만들어 보도록 하겠습니다.
Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요



 
 
우리는 전편에서 이벤트의 간략한 소개와 예제를 통해 이벤트의 사용 방법을 익혀보았습니다. 이번편 부터는 이벤트를 활용할 수 있는 예제들을 중심으로 소개하려고 합니다. 아마 그동안 이벤트가 뭉게구름처럼 정확하게 머리 속에 그려지지 않았다면, 이번 예제들을 통해 확실히 이벤트의 개념에 대해 알 수 있을 것 같습니다.
 
 
유저컨트롤에서 페이지로 값 전달
1편에서 선언부와 구현부의 분리로 점차적으로 프로그램을 보다 융통성 있게 만들 수 있다고 하였습니다.  페이지로 값을 이벤트를 통해 전달하는 방법을 살펴보겠습니다.
 
우선 우리가 사용할 이벤트 인자를 넘길 ReceiveEventArgs 클래스를 만들 것입니다. 이 ReceiveEventArgs 는 이벤트가 발생할 때 이벤트가 전달하는 Argument(인자)를 전달받을 수 있기 위함입니다.
 
받을 인자는 간단한 string 값으로 하겠습니다.
 
ReceiveEventArgs.cs
public class ReceiveEventArgs : EventArgs
{
        public string Item { get; set; }
}
 
유저컨트롤을 만들기 전에 UserControlBase 를 만들 것입니다. 이 UserControlBase 에는 기본적인 델리게이트와 이벤트를 선언하고 이벤트를 발생시키는 메서드가 포함이 됩니다.
 
UserControlBase.cs


public delegate void ReceiveEventHandler(object sender, ReceiveEventArgs e);
 
public class UserControlBase : System.Web.UI.UserControl
{
        public event ReceiveEventHandler ReceiveEvent;
 
        protected void OnRecevieEvent(object sender, ReceiveEventArgs e)
        {
               if( ReceiveEvent != null )
                       ReceiveEvent( sender, e);
        }
}
 
복잡하게 생각할 것 없이, 1편에서 보던 간략한 예제를 클래스 별로 분리하였다고 보시면 됩니다.
 
그럼 이제 유저컨트롤을 만들어 보겠습니다. 유저컨트롤에는 RadioButtonList 를 두고 아이템을 선택 후 전달 버튼을 누르면 이벤트를 발생하는 부분이 포함될 것입니다.
 
WebUserControl1.ascx.cs
public partial class WebUserControl1 : UserControlBase
{
        protected void Page_Load(object sender, EventArgs e) { }
 
        protected void Button1_Click(object sender, EventArgs e)
        {
               OnRecevieEvent(sender,
new ReceiveEventArgs{ Item=RadioButtonList1.SelectedItem.Text });
        }
}
 
보시는 것과 같이 버튼을 클릭하였을 때, OnReceiveEvent 를 발생하는 메서드를 호출하여 RadioButtonList 에서 선택된 아이템의 텍스트를 인자 값으로 전달합니다.
 
이제 default.aspx.cs 를 만들어 보겠습니다. 여기의 코드도 무척 심플하네요.
 
Default.aspx.cs
public partial class _Default : System.Web.UI.Page
{
        protected void Page_Load(object sender, EventArgs e)
        {
               this.ucWebUserControl1.ReceiveEvent +=
new ReceiveEventHandler(ucWebUserControl1_ReceiveEvent);
        }
 
        void ucWebUserControl1_ReceiveEvent(object sender, ReceiveEventArgs e)
        {
               string msg     = string.Format("선택된아이템은 {0} 입니다",e.Item);
               lbl.Text       = msg;
        }
}
 
 
결과 화면을 보겠습니다.
[그림2] 실행화면
 
글 만으로 잘 이해가 안되시면 첨부 파일을 다운로드 하여 한번 살펴보시면 좋을 것 같습니다.
Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요



실전 event 목차
 
 
이벤트란 무엇인가?
이벤트는 간단히 말하자면 특정 사건이 발생했음을 알리는데 사용됩니다. Page_Load 되었을때 Page_Load 이벤트가 발생할 것이고, 버튼을 클릭했을 경우 _Click 이벤트가 발생할 것입니다. 예를 들어, 내 여자친구의 생일날이 오면 생일 이벤트가 발생할 것이고, 12월 25일 크리스마스가 오면 크리스마스 이벤트가 발생할 것입니다.
 
점차적으로 프로그램의 복잡성이 증가함에 따라 이벤트를 굉장히 유용하게 사용될 수 있습니다. 대리자를 통해 메서드의 형식을 캡슐화할 수 있기 때문에, 선언부와 구현부를 따로 분리할 수 있기 때문입니다. 단지, 우리는 이벤트의 발생을 알리기만 하면 될 뿐이니까요.
 
 
이벤트 만들기
이벤트는 대리자(Delegate) 와 떨어질 수 없는 관계입니다. 바로 이벤트는 이 대리자를 통해 메서드의 구현을 실행시키게 되는 것이기 때문입니다. 하지만 이곳에서는 대리자에 대한 설명은 하지 않을 것입니다^^;
 
이벤트는 선언을 하는 것만으로 하나의 이벤트가 완성됩니다.
 
event EventHandler Event;
 
굉장히 간단하지 않습니까? 이벤트의 선언은 위와 같이
 
event 키워드 + 대리자 + 이벤트 이름
 
이렇게 3가지만 알고 있으면 이벤트가 완성됩니다.
 
참고 : EventHandler 대리자는 다음과 같이 정의 되었습니다.
public delegate void EventHandler(object sender, EventArgs e);
 
 
실전 기초 예제
그럼 어디서나 볼 수 있는 간단한 예제를 한번 만들어 보겠습니다.
 
우선 폼에 버튼을 하나 올려 놓았습니다.

[그림1] 웹폼에 버튼 컨트롤을 올림.
 
아래 소스는 Delegate 와 event 를 선언하고 이벤트를 발생하는 모든 과정을 구현한 것입니다. 별거 아니죠??
 
public partial class _Default : System.Web.UI.Page
{
        delegate void MyEventHandler();
        event MyEventHandler MyEvent;
 
        void OnMyEvent()
        {
               if( MyEvent != null )
                       MyEvent();
        }
 
        protected void Page_Load(object sender, EventArgs e)
        {
               this.MyEvent += new MyEventHandler(_Default_MyEvent);
        }
 
        protected void Button1_Click(object sender, EventArgs e)
        {
               OnMyEvent();
        }
 
        void _Default_MyEvent()
        {
               Response.Write("MyEvent 발생하였습니다");
        }
}
 
실행결과는 더욱 더 별거 아닙니다.
 
[그림2] 실행결과
 
만약 위 소스가 이해가 안되신다면, 절대로 다음 회차를 보지 마시고, 다음의 사이트를 통해 대리자와 이벤트에 대한 기초 문법을 더 익히시기 바랍니다.
 
소설 같은 자바
 
훈스 닷넷
Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요


동적 이벤트 처리.. 어디에 써먹으면 좋은까..
 
우선 이에 앞서 리플랙션 이야기를 잠시만 언급하겠습니다.
System.Reflection 네임스페이스와 System.Type을 사용하면 우리가 원하는 어셈블리의 클래스, 인터페이스, 프로퍼티 와 맴버에 대한 정보를 얻을 수 있습니다.
간단히 말하면, 런타임으로 동적으로 다양한 작업을 하고자 할 때 사용됩니다.
 
대부분 다음과 같은 경우에 자주 사용됩니다.
 
1) 실제 코드가 아닌 정보들, 그 파일에 따라다니는 정보들 등 어플케이션의 메타 정보를 얻어서 유지보수에 도움을 받기도 합니다.
 
2) 어셈블리의 내용을 알고자 할 때 사용할 수 있습니다.
Assembly asm = Assembly.Load("Mscorlib.dll");
Type[] types = asm.GetTypes();
foreach(Type t in types)
{
Console.WriteLine("Type : {0}", t)
}
 
3) Design Pattern 의 Abstract Factory Pattern 의 단점을 보완할 수 있습니다. Factory Class가 추가될 때 마다 본문의 내용이 수정이 불가피 하지만, 리플랙션을 이용하면 Class명만으로도 이것을 쉽게 해결 할 수 있습니다.
 
4) Com 개체를 Late-Binding 으로 불러 사용할 수 있습니다.
private void OpenExcel()
{
Type excel = Type.GetTypeFromProgID("Excel.Microsoft");
 
object objExcel = Activator.CreateInstance(excel)
object[] param = object[1];
param[0]=true;
excel.InvokeMember("Visible", BindingFlags.SerProperty, null, objExcel, param);
}
 
대부분 리플랙션은 접근제한 맴버 또는 프로퍼티, 메서드 호출 등에 사용되거나, 참조되지 않은 어셈블리를 제어할 때 자주 사용되곤 합니다.
 
하지만, 이번에는 자주 사용되지 않는 Event 제어를 해 보도록 하겠습니다.
 
소개될 예제는 WinForm 을 기준으로 작성되었지만, WebForm 에서도 약간의 소스를 변경하면 정상적으로 작동합니다.
 
우리가 즐겨 사용하는 컨트롤중 Button 컨트롤이 있습니다. 모든 닷넷 컨트롤은 Control 을 상속하므로 기본적인 많은 Event 가 존재하고, 컨트롤의 기능에 따라 CustomEvent 또한 존재할 것입니다.
 
우선 컨트롤의 이벤트 목록을 얻어보도록 하겠습니다.
string msg = string.Empty;
EventDescriptorCollection events = TypeDescriptor.GetEvents(button1);
foreach (EventDescriptor ed in events)
{
             msg += ed.Name + "\r\n";
}
MessageBox.Show( msg );
 
실행결과)
 
EventDescriptor 클래스는 이벤트 이름, 특성, delegate 등으로 구성되어 있습니다.
이 클래스의 주요 메서드 중 AddEventHandler 와 RemoveEventHandler 가 존재합니다.
이것을 통해 이벤트를 제어할 수 있습니다.
 
그럼, 이제 우리가 이벤트를 제어해야할 가상의 상황을 만들도록 하겠습니다.
public Form1()
{
             InitializeComponent();
             button1.Click += new EventHandler(button1_Click);
}
 
private void button1_Click(object sender, EventArgs e)
{
             System.Threading.Thread.Sleep(1000);
             MessageBox.Show("처리되었습니다");
}
 
위와같이 샘플로 만든 폼은 button1 을 클릭하면 가상의 1초의 작업 후 MessageBox 를 띄우는 경우입니다.
 
하지만 갑자기 변해버린 요구사항엔 ‘주요 몇몇 공통버튼에 대해 “Loading…” 텍스트나 “작업중입니다” 라는 메시지를 보여달라’는 변경사항이 있을 수 있습니다.
단지 몇 개의 폼이라면 수작업으로 간단히 해결할 수 있지만, 몇백개에 해당하는 폼이라면 사정은 달라지게 됩니다. Otl
그렇다면 당신은 어떻게 하겠습니까?
 
우선 현재 Form 의 Type 과 Loading… 텍스트를 뿌려줘야 하는 Button Type 을 알아와야 합니다
Type thisType                   = this.GetType();
Type btn1                          = button1.GetType();
주의할 점은, button1 이 실행중인 어셈블리가 아니라면 다른 어셈블리에서 PropertyInfo 를 통해 Type을 찾아와야 합니다
 
그리고 Loading.. 이벤트와 Button_Click 이벤트의 메서드가 다음과 같이 구현되어 있습니다.
private void OnLoadingHandler(object sender, EventArgs e)
{
        lblLoading.Visible = true;
        this.Refresh();
}
 
private void OffLoadingHandler(object sender, EventArgs e)
{
        lblLoading.Visible = false;
}
 
private void button1_Click(object sender, EventArgs e)
{
        System.Threading.Thread.Sleep(1000);
        MessageBox.Show("처리되었습니다");
}

위  이벤트의 메서드를 MethodInfo 를 통해 메서드의 정보를 찾습니다.
MethodInfo thisMi = thisType.GetMethod("button1_Click", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
MethodInfo onLoadMi         = thisType.GetMethod("OnLoadingHandler", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
MethodInfo offLoadmi= thisType.GetMethod("OffLoadingHandler", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
여기에서도 Button1_Click 이 실행중인 어셈블리가 아니라면 thisType 은 button1_Click 이 구현되어 있는 Class의 Type 이 되어야 합니다.
 
이제 Button 의 Click 이벤트를 가져와야 합니다.
EventInfo ei          = btn1.GetEvent("Click" , BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
으읔…. 정말 간단하네요 -_-;
 
위의 처음 샘플의 EventDescriptor.AddEventHandler 의 메서드는 EventInfo 에도 존재합니다.
 
아시다시피, 모든 이벤트는 delegate 의 대리자를 통해 추가 또는 삭제할 수 있습니다.
그럼 우리는 이벤트의 delegate 를 만들어야 합니다.
 
Delegate d                         = Delegate.CreateDelegate( ei.EventHandlerType, this, thisMi );
Delegate onLoadD = Delegate.CreateDelegate( ei.EventHandlerType, this, onLoadMi );
Delegate offLoadD = Delegate.CreateDelegate( ei.EventHandlerType, this, offLoadmi);
 
각각 d 는 기존의 Click Event 이고, onLoadD, offLoadD 는 Loading.. 메시지를 보여주고, 없애는 delegate 가 됩니다.
 
이벤트는 += 와 -= 연산을 통해 추가, 또는 삭제할 수 있습니다. 이것을 리플랙션에서 RemoveEventHandler 메서드를 통해 구현할 수 있습니다.
ei.RemoveEventHandler( button1, d );
여기까지 샘플을 실행하게 되면 이미 Event 가 연결되어 있음에도 불구하고, 더 이상 Click 이벤트가 발생하지 않게 됩니다.
 
그럼 다음과 같이 AddEventHandler 메서드를 사용하여 delegate 를 추가합니다.
ei.AddEventHandler( button1, onLoadD );
ei.AddEventHandler( button1, d );
ei.AddEventHandler( button1, offLoadD );
우리는 Button_Click Event 를 제거한 상태입니다. 즉, 아무 반응이 없어 지지요.
Event 는 += 연산을 통해 여러 개의 delegate 를 추가할 수 있습니다.
바로 그것입니다.
Click 이벤트를 제거한 후 On Loding Event -> Click Event -> Off Loading Event 가 발생하도록 Event 를 조작하게 되었습니다.
 
그렇게 많은 라인을 추가하지 않았음에도, 리플랙션을 통해 이벤트를 조작함으로써 해결하였네요^^
 
대부분의 사람들은 “리플랙션” 이라고 하면 느리다고 합니다. 몇몇 사람들은 리플랙션 코드를 보는 순간, “느려집니다” 라고 단언합니다.
리플랙션을 사용하면 최대 1000배의 성능저하가 있다고 들은바 있습니다. 하지만 그것은 Invoke 를 하였을 때의 이야기 입니다.

하지만, Late-Binding 이므로 분명 눈에보이지는 않지만 성능저하는 있을 수 있습니다.
이것을 어떻게 적절히 사용하느냐가 문제인 것 같습니다. 분명 리플랙션은 OOP 의 상속성과 객체지향성을 거스르고, 퍼포먼스를 떨어 뜰일 수 있을테니까요.
 
오늘도, 내일도.. 바쁘시더라도 모두 좋은 하루 되시길 바랍니다. ^^
Posted by 땡초 POWERUMC

댓글을 달아 주세요