NGMsoftware

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

    학습


    C# C# .NET 매크로 프로그램 만들기. (매크로 클라이언트에서 독 페이지를 관리하는 방법)

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 오랜만에 클라이언트 프로그래밍에 대해서 글을 작성하게 되었네요. 매크로에 기능을 계속해서 추가하다보니 약간~ 지루한면이 없지않아 있어서 하루정도는 특별한걸 해보는게 좋을거라고 생각했습니다. 매크로 프로그램과 관련은 없지만, 사용자가 활용해야 하는 클라이언트 프로그램을 만들 때 참고가 될 수 있을거 같습니다. 일반적으로 풀스텍 개발자라고 한다면 클라이언트와 서버를 동시에 개발하는데요. 제 경우에는 일단 클라이언트를 만들고, 인증용 서버나 홈페이지 서버 그리고, 파일 서버는 나중에 만드는걸로 하겠습니다.

     

    사실, 서버 프로그래밍이 귀찮아서 네이버 카페에서 약 3년정도 엔지엠소프트웨어를 운영했었는데요. 아무래도, 규모가 커질수록 관리가 쉽지 않더라고요. 결국엔 내 마음대로 하려면 홈페이지가 필요하게 됩니다. 그리고, 홈페이지를 가지고 있으면 여러가지 신경써야 할 것들이 많은데요. 이런 부분들을 쉽게 처리할 수 있는 방법들도 나중에 한번 정리를 해볼께요. 아마도, 1년후가 되지 않을까 싶네요.

     

    아래 그림과 같이 보기 메뉴에 클라이언트 프로그램의 독(Dock) 윈도우들을 관리하는 방법입니다.

    YDvax0q.png

     

     

    단순히 체크 버튼을 체크하거나 해제하면 좌, 우, 하단의 독 윈도우들을 숨기고 보이는건데요. 이게 생각보다 처리하기가 복잡합니다. 하나씩 시나리오를 테스트 해보면서 관련 기능들을 찾아서 각각 처리해야 합니다. 아무래도, 크립톤 프레임워크를 가져와서 사용하다보니 모르는 부분들이 많아요. 그래서, 소스를 하나씩 까봐야 합니다. 이 작업이 엄청 고통스러운 작업입니다.

     

    좀 더 쉬운길도 있는데요. 이미 알려진 유료 프레임워크를 사용하는겁니다. 제가 처음 접한 프레임워크는 데브익스프레스(DevExpress)였습니다. 자세한 내용은 아래 링크를 참고해보시면 됩니다. 제 홈페이지에서도 몇차례 소개한적이 있습니다. 삼성쪽에서 데브익스프레스를 사용하다보니 10년 전쯤 처음 접하게 된 프레임워크였어요. 지금쯤이면 아마도 많이 발전하지 않았을까 생각되네요.

    [ 데브익스프레스 ]

     

    두번째로 사용할만한 클라이언트 프레임워크는 그레이프시티(GrapeCity)입니다. 이건 아마도 하이닉스쪽에서 주로 사용했던거 같은데요. 둘이 유사한 프레임워크라서 사용하는데 크게 어려움은 없었습니다. 제가 사용하는 크립톤과도 비슷합니다. 다만, 유료 프레임워크는 아무래도 홈페이지에 다양한 레퍼런스가 존재하고 시간은 좀 오래 걸리긴 하지만 질문을 하면 답변을 받을 수 있습니다.

    [ 그레이프시티: 메시어스 ]

     

    그레이프시티가 메시어스로 이름이 바뀌었나보네요? 정말 오랜만에 검색해서 들어가봤더니 뭔가 이상해진거 같습니다. 아무튼, 클라이언트 프레임워크는 대표적으로 저 두가지를 주로 사용하는데 개발자 한명당 라이센스 비용이 비싸기 때문에 회사에서 구매해주는게 아니면 개인이 사용하기엔 좀 부담이 있습니다. 아마도, 정책상 개인 학습용으로 쓰는건 무료로 해볼 수 있었던거 같기도 하고, 한달 체험판 같은게 있었던거 같기도 합니다. 관심 있는 분들은 한번 사용해보시는것도 좋을거 같아요.

     

    제가 회사에서 데브익스프레스와 그레이프시티로 클라이언트 프레임워크를 다뤄보다가 GUI/UX 측면에서 너무 좋아서 비슷한것을 찾아보다가 크립톤을 발견하게 되었는데요. 문제는 더이상 지원이 없기 때문에 직접 수정해가면서 사용해야 한다는 것입니다. 참고로, 유료 제품은 한달이면 만들 수 있는 것을 무료 툴은 6개월 정도 걸린거 같습니다. 정말 다양한 곳에서 문제가 터지는데 소스를 분석해 가면서 해결하려니 엄청난 시간이 걸리더라고요.

     

    지금은 대부분 제 상황에 맞게 고쳐놔서 나름 쓸만해지긴 했지만, 지금 또다시 엄청난 스트레스와 시간을 투자해서 하나씩 고쳐나가고 있습니다. 아무래도, 닷넷 프레임워크에 맞춰져 있던 것을 지금 만드는 닷넷 코어로 사용하려니 호환성이 다 깨져서 이 부분부터 처리해야 했습니다. 이 작업이 거의 3개월정도 걸렸고, 대략 테스트가 끝난 4월부터 본격적으로 개발이 가능했어요. 그런데도 지금도 여러가지 문제들로 하루를 날리는 경우가 허다해서 스트레스를 많이 받고 있네요. ㅎㅎ;

     

    아무튼, 메뉴를 일단 추가하도록 합니다.

    var viewGroup = new ComponentFactory.Krypton.Ribbon.KryptonRibbonGroup();
    viewGroup.TextLine1 = "Views";
    
    editor.Ribbon.RibbonTabs[1].Groups.Add(viewGroup);
    
    var viewChoiceGroupLines = new ComponentFactory.Krypton.Ribbon.KryptonRibbonGroupLines();
    
    var toolboxView = new ComponentFactory.Krypton.Ribbon.KryptonRibbonGroupButton();
    toolboxView.Tag = Ai.Definition.ClientControls.ToolBox;
    toolboxView.TextLine1 = "Tool Box";
    toolboxView.ButtonType = GroupButtonType.Check;
    toolboxView.PropertyChanged += View_Changed;
    toolboxView.Checked = true;
    
    var functionboxView = new ComponentFactory.Krypton.Ribbon.KryptonRibbonGroupButton();
    functionboxView.Tag = Ai.Definition.ClientControls.FunctionBox;
    functionboxView.TextLine1 = "Function Box";
    functionboxView.ButtonType = GroupButtonType.Check;
    functionboxView.PropertyChanged += View_Changed;
    functionboxView.Checked = true;

     

    위의 예제와 같이 메뉴를 하나씩 추가 해줍니다. GUI 컨트롤에서 추가하는게 더 편하긴 하지만, 새로운 비주얼 스튜디오에서 렌더링에 문제가 있는거 같습니다. 화면이 열리지 않고 다 깨지는 문제가 있어서 처음부터 다시 만들다가 몇번 갈아엎고, 코딩으로 추가하는 방법을 선택했습니다. 고쳐보려고 해봤지만... 너무 오랜 시간을 낭비하는거 같아서 일단 빠른길로 가야할거 같았거든요.

     

    클라이언트에서 메뉴의 버튼을 클릭하면 해당 정보를 저장하는 모델과 각각의 독(Dock) 컨트롤들이 동기적으로 설정이 변경되어야 합니다. 그래서, 이들을 동기화하기 위한 코드를 상당량 추가해야 합니다. 아래 코드는 클라이언트에서 리본바에 있는 라인 콘트롤들을 가져오는 방법입니다.

    private void View_Changed(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (_client is IEditor)
        {
            var editor = (IEditor)_client;
            var container = editor.Ribbon.RibbonTabs[1].Groups[0].Items.FirstOrDefault();
    
            if (container != null && container is ComponentFactory.Krypton.Ribbon.KryptonRibbonGroupLines)
            {
                var items = (ComponentFactory.Krypton.Ribbon.KryptonRibbonGroupLines)container;
                var item = items.Items.Where(w => w == sender).FirstOrDefault();
    
                if (item != null && item is ComponentFactory.Krypton.Ribbon.KryptonRibbonGroupButton)
                {

     

    각 컨트롤들은 자신이 어떤 컨트롤인지 알아야 하기 때문에 아래와 같이 swich ~ case로 하나씩 처리해줬습니다. 이 부분도 스카폴딩이나 디자인적으로 처리해야 할거 같긴합니다. 아직 추가하지 못한 독(Dock)들이 많거든요. 독을 추가할 때마다 하나씩 다시 코드를 수정해야 하기 때문에 문제가 될수도 있을거 같네요.

    switch (controlType)
    {
        case Definition.ClientControls.ToolBox:
            editor.ShowToolBox = checkButton.Checked;
            break;
        case Definition.ClientControls.FunctionBox:
            editor.ShowFunctionBox = checkButton.Checked;
            break;
        case Definition.ClientControls.ExternalAPI:
            editor.ShowExternalAPI = checkButton.Checked;
            break;
        case Definition.ClientControls.Property:
            editor.ShowProperty = checkButton.Checked;
            break;
        case Definition.ClientControls.Output:
            editor.ShowOutput = checkButton.Checked;
            break;
        case Definition.ClientControls.Console:
            editor.ShowConsole = checkButton.Checked;
            break;
    }

     

    디피니션에 콘트롤 타입을 하나 만들었습니다. 글로벌하게 가져가는 속성이라서 Ai 라이브러리 루트에 만드는게 좋을듯 합니다.

    public enum ClientControls
    {
        ToolBox = 0,
        FunctionBox = 1,
        ExternalAPI = 2,
        Property = 3,
        Output = 4,
        Console = 5
    }

     

    이제 클라이언트 설정 정보를 저장해야 합니다. 아래와 같이 모델을 하나 만들고 속성들을 정의하세요. 아마도 더 내용은 추가 될겁니다. 정말 지루한 내용들의 연속이네요^^;

    public class ClientModel
    {
        public bool ShowToolBox { get; set; } = true;
    
        public bool ShowFunctionBox { get; set; } = true;
    
        public bool ShowProperty {  get; set; } = true;
    
        public bool ShowExternalAPI { get; set; } = true;
    
        public bool ShowOutput { get; set; } = true;
    
        public bool ShowConsole { get; set; } = true;
    }

     

    현재는 필요한 내용만 넣어두었기 때문에 간단하지만, 실제로 클라이언트를 제어할 수 있는 다양한 설정들이 이 모델에 더 추가가 될거예요. 기능을 하나씩 개발할 때마다 계속해서 보게될듯 합니다. 지금은 일단 미리 개발하지 않고, 현재 상황만 완료하는걸로 해야할거 같아요.

     

    클라이언트의 Load 이벤트와 Closing 이벤트에서 모델을 처리해줘야 합니다. 우리는 이미 모델을 저장하고, 불러올 수 있는 기능을 만들어 두었기 때문에 아래와 같이 처리할 수 있습니다.

    private void MainView_Load(object sender, EventArgs e)
    {
        if (File.Exists(Path.Combine(System.Windows.Forms.Application.StartupPath, Ai.Definition.ConfigDirectory, $"{Ai.Definition.ClientFileName}{Ai.Definition.ConfigFileExtension}")))
            _clientModel = Ai.Api.ModelHelper.Load<Model.Client.ClientModel>(Path.Combine(System.Windows.Forms.Application.StartupPath, Ai.Definition.ConfigDirectory, $"{Ai.Definition.ClientFileName}{Ai.Definition.ConfigFileExtension}"));
        else
        {
            _clientModel = new Model.Client.ClientModel();
            Ai.Api.ModelHelper.Save(_clientModel, Path.Combine(System.Windows.Forms.Application.StartupPath, Ai.Definition.ConfigDirectory, $"{Ai.Definition.ClientFileName}{Ai.Definition.ConfigFileExtension}"));
        }

     

    클라이언트 루트에 미리 맴버도 추가해야 하는군요.

    public partial class MainView : KryptonForm, Ai.Interface.IEditor, Ai.Interface.IClient
    {
        private DockHelper _dockHelper;
        private ConfigHelper _configHelper;
        private Model.Client.ClientModel _clientModel;

     

    쉽게 속성에 접근할 수 있도록 클라이언트에 속성들을 만들어줍니다.

    public bool ShowFunctionBox
    {
        get
        {
            return _dockHelper.GetDockControl<Ai.Common.Client.Dock.Function>(nameof(Common.Client.Dock.Function)).Visible;
        }
        set
        {
            _dockHelper.ShowHidePage<Ai.Common.Client.Dock.Function>(nameof(Common.Client.Dock.Function), value);
        }
    }

     

    이 내용들은 인터페이스로 열어둬야 합니다. 그래야, 다른 도우미 라이브러리에서 접근이 가능하니까요.

    public interface IEditor
    {
        int NewScriptCount { get; set; }
    
        KryptonDockingManager DockingManager { get; }
    
        KryptonDockableWorkspace DockableWorkspace { get; }
    
        KryptonRibbon Ribbon { get; }
    
        Ai.Interface.IProperty Property { get; }
    
        bool ShowToolBox { get; set; }
    
        bool ShowFunctionBox { get; set; }
    
        bool ShowExternalAPI { get; set; }
    
        bool ShowProperty {  get; set; }
    
        bool ShowOutput {  get; set; }
    
        bool ShowConsole { get; set; }
    
        void AddScript(TreeNode action);
    }

     

    이제 간단하게 테스트를 해보면, 각각의 독 컨트롤들을 보이거나 안보이게 사용자가 설정할 수 있습니다.

     

     

    개발자에게 후원하기

    MGtdv7r.png

     

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

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

    감사합니다~

     

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

    댓글목록

    등록된 댓글이 없습니다.