NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# C# .NET Core 매크로 프로그램 만들기. (메뉴 만들기)

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 요즘 이런저런 일들로 바빠서 매크로 프로그램 제작 글을 작성하지 못했는데요. 오랜만에 이어서 작업을 할 수 있게 되었습니다. 이전 시간에 다국어 기능까지 개발했었죠? 오늘은 리본 메뉴에 서브 메뉴들을 추가 해보도록 하겠습니다. 엔지엠 매크로 3부터 6까지는 동일한 방식으로 메뉴를 구성했었습니다. 하지만, 이번에는 다른 방식으로 구현할 예정입니다.

     

    이전 버전들의 문제점은 중복 코드가 너무 많다는것이었습니다. 에디터와 플레이어의 메뉴가 비슷한것도 있지만, 제품 등급별로도 대부분의 코드가 동일합니다. 그래서, 이번에는 좀 더 공통적으로 메뉴를 구성할 수 있도록 별도의 라이브러리로 만들었습니다. 프로그램을 설계할 때 공통적으로 사용되는 함수들을 Common 모듈로 만드는데요. 메뉴를 구성하는 부분들이 여기에 부합됩니다. 사실 메뉴 부분은 Visual Studio의 디자인에서 직접 만드는게 보통입니다. 엔지엠 매크로의 경우는 약간 특이하게 같은 프로그램이라도 등급이 나눠져 있기 때문에 이런 방식으로 메뉴를 만드는게 효율적입니다.

     

    디자인에서 리본 컨트롤을 선택하고, 속성창에서 RibbonAppButton안에 AppButtonMenuItems를 클릭해서 메뉴를 추가할 수 있습니다.

    daXWwQK.png

     

     

    아래와 같이 에디터의 File 메뉴를 클릭했을 때 펼쳐지는 서브 메뉴들을 하나씩 추가 해줍니다.

    d9vKrH3.png

     

     

    이런 작업을 엔지엠 매크로 커뮤니티, 얼티밋, 엔터프라이즈에 동일하게 반복해야 합니다. 그리고, 뭔가 공통적으로 메뉴를 추가한다면 3번 작업해야 하는 불편함이 있는데요. 그래서, 새롭게 만드는 AI 에디터는 중앙에서 메뉴를 통합관리할 수 있는 공통 모듈을 추가했습니다. 프로그램 전체 라이프 사이클에서 반복적으로 사용되는 기능은 Ai 라이브러이에 추가하고, 메뉴와 같이 처음 한번만 사용되는 경우에는 Client 라이브러리에서 Helper클래스로 만듭니다.

    RIpmJOd.png

     

     

    클라이언트의 MainView에서 처음 한번 셋팅되는 것들을 처리해야 합니다.

    public MainView()

     

    다국어 처리를 위해 읽기 전용 속성을 만들었습니다.

            [ReadOnly(true)]
            public System.Resources.ResourceManager ResxMessage { get; }
    
            [ReadOnly(true)]
            public System.Resources.ResourceManager ResxCaption { get; }
    
            [ReadOnly(true)]
            public System.Resources.ResourceManager ResxImage { get; }

     

    그리고, 중요한 부분이 하나 있습니다. 이전에는 빠른 개발 및 편의성을 위해 거의 모든 클래스를 static으로 만들어서 사용했습니다. 그렇다보니 불필요하게 메모리에 올라가 있는 클래스가 많았는데요. 가능하면 static을 사용하지 않는 방향으로 가도록 해야 할거 같습니다. 엔지엠 매크로는 엔터프라이즈급 프로그램이라서 수많은 기능들이 유기적으로 참조하고 있는데요. 이런 부분들을 어떻게 풀어야할지 많은 고민이 필요할듯 합니다.

            public MainView()
            {
                this.ResxMessage = Ai.ResourceManager.MessageResource.Instance;
                this.ResxCaption = Ai.ResourceManager.CaptionResource.Instance;
                this.ResxImage = Ai.ResourceManager.ImageResource.Instance;
    
                InitializeComponent();
    
                var menuHelper = new MenuHelper(this);
                menuHelper.CreateFileMenu();
            }

     

    MenuHelper 생성자에 에디터를 넘겨주었습니다. 여기서 고려해야 할 사항으로 에디터와 플레이어 그리고, 스피드 플레이어와 같이 다양한 제품들을 모두 처리할 수 있어야 합니다. 그래서, 에디터, 플레이어, 스피드 플레이어 또는 리모트와 같은 프로그램을 추상화해야 하는데요. C# 언어에서는 추상화할 때 abstract와 interface를 사용합니다. 메뉴가 공통적이라고 해도 에디터 메뉴와 플레이어 메뉴가 다를 수 있기 때문에 abstract보다는 interface를 사용하는게 좋습니다.

     

    namespace를 구성할 때 상호 참조가 일어나지 않도록 네이밍을 잘해야 합니다. 그리고, 참조 관계를 잘 생각해서 레벨을 설정해야 합니다. 이 부분이 개발자와 아키텍트에게도 어려운 부분입니다. 특히나 처음 접해보는 시스템을 구축한다면 많은 시행착오를 겪을 수 밖에 없습니다. 엔지엠 매크로를 이미 수차례 반복해서 갈아엎어봤기 때문에 이미 문제점들을 파악해놓고, 머리속으로 방향성을 잡아두었습니다.

     

    Ai 라이브러리에서 인터페이스 폴더를 만들고, 클라이언트와 에디터 그리고, 플레이어를 만들었습니다. 클라이언트는 최상위 개념의 추상적인 내용을 담고, 에디터와 플레이어는 하위 레벨의 인터페이스입니다. 에디터는 Ribbon 콘트롤을 사용하고, 플레이어는 MenuItemStrip 콘트롤을 사용합니다. 이 둘 모두 다국어로 메뉴에 표시되는 언어를 만들어야 하기 때문에 상위의 클라이언트가 다국어 모듈을 가지는 방식입니다.

    ov6l5RE.png

     

     

    메뉴 도우미 클래스에 프라이빗 속성을 추가합니다.

            private Ai.Interface.IClient? _client = null;
            private System.Resources.ResourceManager _resxMessage;
            private System.Resources.ResourceManager _resxCaption;
            private System.Resources.ResourceManager _resxImage;

     

    생성자에서 속성을 채워줍니다. HenuHelper는 엔지엠 매크로에서만 사용되기 때문에 접근 제한자를 internal로 설정했습니다. 디자이너에서 메뉴를 구성할 때는 직접 구현해야 할겁니다. 기본적인 내용을 추상화해서 접근할 수 있도록 할수도 있겠지만, 디자이너에서 리본 콘트롤까지 사용하지는 않을듯하네요. 대부분은 MenuItemStrip 컨트롤을 사용할거 같은데요. 이 부분은 나중에 플레이어 메뉴 추상 클래스에서 고민하기로 하고 일단 넘어갑니다.

            internal MenuHelper(Ai.Interface.IClient client)
            {
                _client = client;
                _resxMessage = client.ResxMessage;
                _resxCaption = client.ResxCaption;
                _resxImage = client.ResxImage;
            }

     

    헬퍼 클래스를 생성하자마자 클라이언트 타입에 맞게 메뉴를 구성해도 됩니다. 하지만, 생성자에서 직접 구현한다면 추상 클래스로 만들고, 에디터와 플레이어가 상속 받는게 좋습니다. 하지만, 메뉴의 종류가 여러가지라서 동작을 분리하는게 좋겠다고 판단했습니다. 아래와 같이 메소드를 하나 추가하세요. 인터페이스로 클라이언트를 구분하는게 맞을지 모르겠지만, 외부로 노출하는게 아니라서 타입으로 로직을 분리 했습니다.

            internal void CreateFileMenu()
            {
                try
                {
                    if (_client is Ai.Interface.IEditor)
                        EditorFileMenu((Ai.Interface.IEditor)_client);
                    else if (_client is Ai.Interface.IPlayer)
                        PlayerFileMenu((Ai.Interface.IPlayer)_client);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                    throw;
                }
            }

     

    완성된 클라이언트를 실행하면 아래 그림과 같이 File 메뉴 아래에 서브 메뉴가 2개 추가되고 그 안에 또 2개의 서브 메뉴가 추가된 것을 확인할 수 있습니다.

    ※ 메뉴 추가는 KryptonContextMenuItemBase 추상 클래스를 상속 받고 있는 KryptonContextMenuItem을 직접 추가해야 합니다.

    WeYby7N.png

     

     

    이렇게해서 AppButton 메뉴를 추가해봤습니다. 이와 비슷하게 리본 그룹 메뉴들도 하나씩 추가하면서 기능을 만들어 나가야합니다. 이제 기본적인 골격은 갖춰졌으므로 본격적으로 매크로 기능을 만들어 볼께요. 기본적인 기능이라는건 마우스 클릭과 키보드 입력을 말합니다. 나머지는 마우스와 키보드를 제어하고, 입력 받을 프로그램을 어떻게 관리할지에 대한 내용입니다. 이제 하나씩 만들어보면서 골격에 살을 붙여나가면 되겠네요^^

     

    개발자에게 후원하기

    MGtdv7r.png

     

    추천, 구독, 홍보 꼭~ 부탁드립니다.

    여러분의 후원이 빠른 귀농을 가능하게 해줍니다~ 답답한 도시를 벗어나 귀농하고 싶은 개발자~

    감사합니다~

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.