이 글을 쓰는 이유?
저는 보통 Button을 사용자가 눌렀을 때 동작하는 이벤트를 사용하면 보통 'Click'이벤트를 많이 사용합니다. 그 이유는 Button을 더블클릭하는 단순한 동작으로 'Click'이벤트가 생기기 때문인데요. 아마 다른분들도 많이 사용하실꺼라 생각합니다.
하지만 Click이벤트는 기본적으로 마우스로 클릭하거나, 버튼이 선택된 상태에서 Enter또는 Space를 누르게 되면 Click이벤트가 동작이 되는데 즉, 마우스랑 키보드로 둘 다 작동시킬 수 있다는 얘기입니다. 이 글을 보면 '마우스랑 키보드 둘 다 조작이되니까 편한것 아니냐?'라고 생각이 들기도 하는데, 바로 이 부분 때문에 문제가 됐습니다.
이미 진행했던 프로그램을 사용하던 고객님이 버튼이 키보드로 동작이되서 오작동하는 경우가 있다고, 키보드로는 동작하지 않게 수정해달라고 한것인데요........ 이 것이 참 난감했습니다.
어려운 프로그램은 아니지만, 해당 프로그램은 버튼이 백개 정도가 쓰이며, 한번 수정해 준다고 끝나는 것이 아니라 앞으로 유지보수할 때 버튼이 추가된다면 그 때마다 이 내용을 기억하면서 처리를 해줘야 한다는 것인데..... 음....
일단 이 문제를 잘 해결하기 위한 조건을 몇 가지 생각해봤습니다.
- Click이벤트를 써야한다. - MouseClick를 사용해도 되지만, Visual Studio에서 손쉽게 Click이벤트를 생성해 사용할 수 있기 때문에 나중에 Click이벤트를 만들어 사용할 때도 이상이 없도록 Click을 사용하는 방안으로 해야함(편의성)
- 100개나 되는 Click이벤트를 일일이 수정하지 않아야한다.- 이는 완전 단순반복 작업이며, 실수할 소지도 많고 시간도 많이 들며, 나중에 버튼을 추가해서 사용할 시, 문제가 될 수 있음(효울성)
- 시간이 지나 나중에 버튼을 추가할 때, 이 내용을 기억하지 않더라도 키보드로는 동작하면 안되게 자동화. - 한번 하고 끝나는게 아니라 계속 똑같은 설정을 해줘야하는데, 이를 기억력에 의존하지 않기위함(자동화)
위 조건을 최대한 지키며 프로그램을 하고 싶어 자료를 찾아보며 프로그램을 하려다보니 생각보다 이런일이 없으신건지 제대로된 자료를 찾지 못해서 제가 써봅니다.
프로그램 적용
위 문제를 해결하기 위한 제 방법은 아래와 같습니다.
public void AllRemoveEvent(Control parent)
{
foreach (Control c in parent.Controls)
{
if (c as Button != null)
{
string methodName = string.Format("{0}_Click", c.Name);
RemoveEventHandlers(parent, c, "Click", methodName);
}
else if (c.HasChildren)
{
AllRemoveEvent(c); // 재귀호출
}
}
}
public bool RemoveEventHandlers<T>(T container, object target, string eventName, string eventmethodName)
{
if (target == null)
return false;
Type targetType = target.GetType();
EventInfo eventInfo = targetType.GetEvent(eventName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); // 이벤트 찾기
if (eventInfo == null)
return false;
Type containerType = typeof(T);
MethodInfo methodInfo = containerType.GetMethod(eventmethodName, BindingFlags.NonPublic | BindingFlags.Instance); // 이벤트 메서드 찾기
if (methodInfo == null)
return false;
Delegate eventDelegate = Delegate.CreateDelegate(typeof(EventHandler), container, methodInfo); // 이벤트 메서드 -> 델리게이트
if (eventDelegate == null)
return false;
eventInfo.RemoveEventHandler(target, eventDelegate); // 기존 이벤트 삭제
eventInfo.AddEventHandler(target, new EventHandler((sender, args) => // 새 이벤트 등록
{
if (args is MouseEventArgs) // 마우스 클릭확인
{
methodInfo.Invoke(container, new object[] { sender, args });
}
}));
return true;
}
위 소스코드는 리플렉션(Reflection)을 사용하여 기존 등록된 Event Method를 찾아, 해당 Button에 Click이벤트에서 제거 하고 새로운 이벤트를 생성하여 마우스로 작동시킨 경우에만 기존 Event Method를 작동 시킨 소스입니다.
이 함수를 사용하는 방법은 어떤 버튼이 마우스로만 동작을 원하는 Form생성자에 아래와 같이 사용해주시면 됩니다.
AllRemoveEvent(this);
설명
위 소스코드는 AllRemoveEvent메서드와 RemoveEventHandlers메서드를 구현했으며 각각의 역할은 아래와 같습니다.
AllRemoveEvent : parent에 있는 하위 Control을 포함한 Control중 Button을 찾아 RemoveEventHandlers()을 호출한다.
RemoveEventHandlers : container에 정의된 Event Method를 찾아 target에 등록된 이벤트를 제거하고 마우스로 작동했을 때 기존 Event Method를 실행한다.
AllRemoveEvent 는 재귀호출을 통해 하위컨트롤까지 Button을 찾아 RemoveEventHandlers에 Button객체와 제거할 이벤트명, Event Method이름, Event메소드가 정의된 container를 전달하며, RemoveEventHandlers에서는 container에 정의된 EventMethod를 찾아 Click이벤트에서 제거하고 새로운 이벤트를 만들어 마우스로 눌렀을때만 기존 메서드가 동작하게 만듭니다.
위 소스코드는 리플렉션에 대한 지식이 있으시면 쉽게 이해가 가능하실텐데, 리플렉션 (Reflection) 에 대해 잘 모르시면 그 부분을 따로 알아보고 오시면 좋습니다. 기회가 되면 리플렉션에 대해서도 포스팅해보겠습니다.
위 소스코드의 단점
- Click이벤트로 등록된 Event Method의 이름이 '[button이름]_Click' 이여야 정상작동을 합니다. 버튼명이 변경 되었거나 버튼명과 이벤트명이 다르면 사용불가.
- Event Method가 container에 정의되어있지 않으면 사용이 불가능.
- 체감이 크게 갈지는 잘 모르겠으나, 일반적으로 리플렉션은 프로그램의 성능을 저하시키기는 걸로 알려져 있는데, 이 소스코드도 마찬가지 입니다. (리플렉션의 성능을 향상시키는 방법도 존재하기는 하는데 이것도 나중에 한번 포스팅하겠습니다.)
그래서 이 글의 요약 ......
위 소스는, 위에 설명된 단점때문에 상황에 따라 이 글을 보는 모든 사람이 사용하긴힘들겠지만 '이런 방법으로 하면된다!' 또는 '이런 방법도 있다!' 라는걸 공유하기 위해 글을 썻습니다. 즉 소스가 개판으로 보일시 양해바라고
다른 방법이 있다면 댓글 부탁드립니다.