설정가능한 데이터를 미리 삽입해두고 사용자가 그 중 하나를 선택할 수 있게 도와주는 역할을 하는 ComboBox, 일반적으로는 아래 이미지와 같이 그냥 편집창을 열어서 한줄한줄 데이터를 입력하실꺼라 생각합니다.

수동 ComboBox 데이터 추가

 

하지만 프로그램을 하다보면 이 Combobox와 열거형(Enum)을 같이 사용해야 할 때가 종종 있습니다. 예를 들면, 특정 모드나 설정, 세팅 변수들을 열거형으로 지정해 놓고 ComboBox에 추가한 다음 사용자한테 어떻게 설정할지 또는 어떻게 동작할지를 고르게 하는 경우가 있을텐데요. 이 경우 특정 모드 또는 설정은 열거형(Enum)으로 만들어 두시고 사용하시는 분들이 많을겁니다.  이 경우 위 이미지 처럼 수동으로 ComboBox에 값을 타이핑하지 않고 프로그램에서 추가하는 방식을 많이 선호하실거라 생각합니다. 

 

이러한 경우, 이제까지 제가 제일 많이 사용했던 방식은 아래와 같은 방식인데요.  

enum MODE { NONE, AUTO, MANUAL }

void SetComboBoxItem()
{
    string[] data = Enum.GetNames(typeof(MODE));
            
    foreach (string value in data)
    {
        comboBox1.Items.Add(value);
    }
}

void GetComboBoxSelectData()
{
    string selectString = comboBox1.SelectedItem.ToString();
    MODE selectValue = (MODE)(Enum.Parse(typeof(MODE), selectString));
}

 

이 코드도 특별하게 복잡하지 않은 좋은 코드라 생각합니다. 하지만 제가 이 글에서 알려드리고 싶은 방법은 이 코드가 아니며 단지 비교를 위해 사용한 것으로, 제가 이 글을 쓰는 목적은 이 아래 소스코드 입니다.

 

enum MODE { NONE, AUTO, MANUAL }

void SetComboBoxItem()
{
    comboBox1.DataSource = Enum.GetValues(typeof(MODE));
}

void GetComboBoxSelectData()
{
    MODE selectValue = (MODE)comboBox1.SelectedItem;
}

그냥 봐도 이전 소스코드보다 깔끔해진게, 이 코드 내용으로만 보면 함수화가 필요없을 정도로 간단한데요. 이 방법을 쓰고 나면 DataSource를 초기화(null)를 해야 다음 삽입이 가능한 점만 알려드리고 이번 포스팅은 짧게 마무리 하겠습니다.

이 글을 쓰는 이유?

저는 보통 Button을 사용자가 눌렀을 때 동작하는 이벤트를 사용하면 보통 'Click'이벤트를 많이 사용합니다. 그 이유는 Button을 더블클릭하는 단순한 동작으로 'Click'이벤트가 생기기 때문인데요. 아마 다른분들도 많이 사용하실꺼라 생각합니다.

 

하지만 Click이벤트는 기본적으로 마우스로 클릭하거나, 버튼이 선택된 상태에서 Enter또는 Space를 누르게 되면 Click이벤트가 동작이 되는데 즉, 마우스랑 키보드로 둘 다 작동시킬 수 있다는 얘기입니다. 이 글을 보면  '마우스랑 키보드 둘 다 조작이되니까 편한것 아니냐?'라고 생각이 들기도 하는데,  바로 이 부분 때문에 문제가 됐습니다.

 

이미 진행했던 프로그램을 사용하던 고객님이 버튼이 키보드로 동작이되서 오작동하는 경우가 있다고,  키보드로는 동작하지 않게 수정해달라고 한것인데요........  이 것이 참 난감했습니다.

 

어려운 프로그램은 아니지만, 해당 프로그램은 버튼이 백개 정도가 쓰이며, 한번 수정해 준다고 끝나는 것이 아니라 앞으로 유지보수할 때 버튼이 추가된다면 그 때마다 이 내용을 기억하면서 처리를 해줘야 한다는 것인데..... 음....

 

일단 이 문제를 잘 해결하기 위한 조건을 몇 가지 생각해봤습니다.

 

  1. Click이벤트를 써야한다. - MouseClick를 사용해도 되지만, Visual Studio에서 손쉽게 Click이벤트를 생성해 사용할 수 있기 때문에 나중에 Click이벤트를 만들어 사용할 때도 이상이 없도록 Click을 사용하는 방안으로 해야함(편의성)
  2. 100개나 되는 Click이벤트를 일일이 수정하지 않아야한다.- 이는 완전 단순반복 작업이며, 실수할 소지도 많고 시간도 많이 들며, 나중에 버튼을 추가해서 사용할 시, 문제가 될 수 있음(효울성)
  3. 시간이 지나 나중에 버튼을 추가할 때, 이 내용을 기억하지 않더라도 키보드로는 동작하면 안되게 자동화. - 한번 하고 끝나는게 아니라 계속 똑같은 설정을 해줘야하는데, 이를 기억력에 의존하지 않기위함(자동화)

 

위 조건을 최대한 지키며 프로그램을 하고 싶어 자료를 찾아보며 프로그램을 하려다보니 생각보다 이런일이 없으신건지 제대로된 자료를 찾지 못해서 제가 써봅니다.

 

프로그램 적용

위 문제를 해결하기 위한 제 방법은 아래와 같습니다.

 

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) 에 대해 잘 모르시면 그 부분을 따로 알아보고 오시면 좋습니다. 기회가 되면 리플렉션에 대해서도 포스팅해보겠습니다.

 

위 소스코드의 단점

  1. Click이벤트로 등록된 Event Method의 이름이 '[button이름]_Click' 이여야 정상작동을 합니다. 버튼명이 변경 되었거나 버튼명과 이벤트명이 다르면 사용불가.
  2. Event Method가 container에 정의되어있지 않으면 사용이 불가능.
  3. 체감이 크게 갈지는 잘  모르겠으나, 일반적으로 리플렉션은 프로그램의 성능을 저하시키기는 걸로 알려져 있는데, 이 소스코드도 마찬가지 입니다. (리플렉션의 성능을 향상시키는 방법도 존재하기는 하는데 이것도 나중에 한번 포스팅하겠습니다.)

 

 

 

 

그래서 이 글의 요약 ......

위 소스는, 위에 설명된 단점때문에 상황에 따라 이 글을 보는 모든 사람이 사용하긴힘들겠지만 '이런 방법으로 하면된다!' 또는 '이런 방법도 있다!' 라는걸 공유하기 위해 글을 썻습니다.  즉 소스가 개판으로 보일시 양해바라고

 

다른 방법이 있다면 댓글 부탁드립니다.

'C#' 카테고리의 다른 글

C# Dispose()  (2) 2023.11.20

C# 프로그램을 지금까지하면서 주구장창 사용해왔던 Dispose()...... 하지만 정확히 어떻게 동작하는지는 모르고 객체에 Dispose() 함수가 들어가 있으면 메모리관리를 위해 거의 무조건 Dispose()를 해왔었는데요.

 

Dispose()가 정확히는 몰라도 메모리를 해제해준다는건 알고 있었으니까 메모리의 누수나 가비지컬렉터가 조금이라도 덜작동하게 만들기 위해 그냥 일단 사용을 해왔고 실제로 이렇게 사용하고 나서는 특별히 메모리 관련된 문제가 나오지 않았는데요.

 

그러나 최근 만들었던 프로그램에 메모리문제인지 뭔지 원인을 알수 없는, 프로그램이 강제종료되는 상황이 생겨 골치아픈 나날을 보내고 있는데...... 메모리 문제인지 무슨 문제인지는 잘 모르겠으나 시간이 좀 있을 때, Dispose에 대해 알아보는 것이 좋을 것 같아 이번 포스팅을 남깁니다.

 

Dispose(). 마이크로소프트 문서에서는 아래와 같이 정의 하고 있습니다.

 

Microsoft 문서 이미지 출처 :&nbsp;https://learn.microsoft.com/ko-kr/dotnet/api/system.idisposable?view=net-6.0

 

내용은 간단하게 써져 있는데 '관리되지 않은 리소스 해제'를 위한 메커니즘을 제공합니다. 라고 나와 있습니다.

 

'관리되지 않은 리소스 해제' 즉 관리되는 리소스는 대상이 아니고 관리되지 않은 리소스를 대상으로 리소스를 해제한다는 얘기인데...... '관리되는 리소스'와 '관리되지 않은 리소스'는 무슨 차이가 있고 뭐가 다른 것일까요?

 

'관리되는 리소스'란? CLR(Common Language Runtime)의 가비지 컬렉터에 의해 관리되는 리소스로 이 리소스는 가비지 컬렉터가 알아서 리소스를 해제시켜주지만, '관리되지 않은 리소스'는 가비지 컬렉터가 리소스를 해제시켜주지 않으며 해당 리소스의 사용을 완료할 때 명시적으로 해제 해야 한다고 합니다.

 

즉, '관리되는 리소스'는 가비지 컬렉터가 알아서 리소스를 해제시켜 주고 '관리되지 않은 리소스'는 개발자가 Dispose()를 수행하여 알아서 리소스를 해제하지 않으면 메모리 누수가 발생하는 것인데, 이는 프로그램의 성능이 점차 떨어지고, 최악의 경우에는 프로그램이 비정상 종료되는 문제가 발생할 수 있습니다.

 

또한 모든 경우는 아니지만 일반적으로는 Dispose()는 '관리되지 않은 리소스'만 즉시 해제할 뿐, '관리되는 리소스'는 즉시 해제하지 않으며, 후에 가비지 컬렉터가 돌아가야 리소스가 해제되는 시스템으로 어떤 객체를 Dispose() 하였다고 하더라도 그 객체의 '관리되는 리소스'는 가비지 컬렉터가 돌아갈때 까지 남아있다는 겁니다.

 

간단한 프로그램이라면 모를까, '관리되지 않은 리소스'를 Dispose()를 해야하는게 사실상 선택이 아닌 필수사항인 것인데 그렇다면 '관리되지 않은 리소스'가 무엇이 있는지 한번 파악을 해보면... 

 

파일 핸들, 네트워크 핸들, 윈도우, 데이터 베이스의 연결, 네트워크 소켓, 그래픽 핸들, System.Runtimer.InteropServices.Marshal.AllocHGlobal메서드 등이 있으며, 이는 반드시 Dispose()또는 다른 방식의 명시적인 리소스 해제를 하셔야 합니다.

 

 

 

 

그래서 이 글의 요약 ......

Dispose() 함수가 있는 객체는 사용종료 후 Dispose() 합시다.

'C#' 카테고리의 다른 글

C# Button Click 이벤트 마우스로만 동작시키게 바꾸기  (0) 2024.04.01

+ Recent posts