NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 팁 앤 테크
  • 매뉴얼

    팁과 테크니컬 노하우를 확인하세요.

    팁 앤 테크

    팁과 테크니컬 노하우를 확인하세요.

    본 사이트의 컨텐츠는 저작권법의 보호를 받으므로 무단 복사, 게재, 배포 등을 금합니다.

    커스텀 엔지엠 RPA 매크로 - 비활성 키보드 매크로 만드는 방법. (How to make an inactive keyboard ma…

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 오늘은 디자이너/프레임워크 또는 커스텀 모듈을 사용해서 비활성 키보드 입력에 대해 알아보겠습니다. 우선, 활성/비활성/하드웨어 동작 방식에 대한 이해가 필요합니다. 제가 제공해드리는 코드를 복사/붙여넣기로 간단한 동작은 할겁니다. 하지만, 이 코드를 응용해서 뭔가 더 고차원적인 액션을 만들려면 기본적인 프로세스 로직을 이해 하는게 중요합니다.

    ※ 필독 - [ 비활성 다클라 매크로를 만들 때 주의사항 ]

    [ 활성/비활성/하드웨어 방식으로 동작 설명 ]

     

    비활성 키보드를 입력하는 방법은 [ 마우스를 비활성으로 처리 ]하는 것보다는 쉽습니다. 간단한 예제를 만들기 위해 Visual Studio Community를 실행하고 아래 그림처럼 CustomInactiveKeyboard 이름으로 새 프로젝트를 추가해주세요. 물론, 프로젝트 형식은 "클래스 라이브러리"로 선택해야 합니다^^;

    WT13XED.png

     

     

    엔지엠 엔터프라이즈 라이센스를 사용중이시면 아래와 같이 커스텀 모듈 개발용 라이브러리를 3개 추가해줘야 합니다.

    N5rLYiE.png

     

     

    도구 상자에 추가할 키보드 입력 모델이기 때문에 "BaseCustomToolModel" 추상 클래스를 상속 받습니다. 그리고, 핸들을 구현하기 위해 "IHandleModel" 인터페이스도 상속 받습니다. IHandleModel 인터페이스를 구현하면 엔지엠의 매크로 엔진에서 핸들로 처리되는 많은 것들을 자동화할 수 있습니다. 키보드 입력이 아니더라도 핸들로 뭔가 처리하고 싶은 액션이 있으면 IHandleModel 인터페이스를 상속 받고 구현해주면 됩니다.

    using NGM.Models.Item;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Linq;
    using System.Xml.Serialization;
    
    namespace CustomInactiveClick
    {
        [Serializable]
        public class InactiveClickModel : NGM.Models.Interface.BaseCustomToolModel, NGM.Models.Interface.IHandleModel
        {
            private string _findControl;
    
            public override string DisplayCategory => "NGMsoftware";
    
            public override string DisplayName => "비활성 키보드 입력";

     

     

    IHandleModel 인터페이스 구현부입니다. MainRectangleControlRectangle은 핸들을 설정할 때 초기화됩니다. 키보드 입력에서 사용되는 속성은 아닙니다. 다른 액션에서 사용할 수 있도록 추가적인 정보를 제공하는 용도입니다. 인터페이스에 정의되어 있는 속성으로 구현은 해야하지만 외부에 정보를 노출할 필요는 없습니다. 그렇기 때문에 접근 제한자를 private(프라이빗)으로 설정했습니다.

            private Rectangle MainRectangle { get; set; }
    
            private Rectangle ControlRectangle { get; set; }
    
            [XmlIgnore]
            [Category("Data")]
            [DisplayName("매인 핸들")]
            [Description("컨트롤의 매인 핸들 값입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public IntPtr MainHandle { get; set; }
    
            [XmlIgnore]
            [Category("Data")]
            [DisplayName("컨트롤 핸들")]
            [Description("선택된 컨트롤의 핸들 값입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public IntPtr ControlHandle { get; set; }
    
            [Category("Action")]
            [DisplayName("컨트롤 제목")]
            [Description("선택한 컨트롤의 상단에 표시되는 제목입니다.")]
            [Browsable(true)]
            [DefaultValue(null)]
            public string Title { get; set; }
    
            [Category("Action")]
            [DisplayName("컨트롤 구조")]
            [Description("선택된 컨트롤의 구조입니다. 컨트롤을 찾기 위해 직접 편집이 가능합니다.")]
            [Browsable(true)]
            public List<NGM.Models.Item.HandleItem> Children { get; set; } = new List<NGM.Models.Item.HandleItem>();

     

     

    이 속성은 제어할 윈도우 또는 컨트롤의 핸들 값을 가져오는 프로세스입니다. "NGM.Models.TypeEditor.FindControlEditor" 특성을 사용하세요. 그러면 엔지엠 에디터에서 [ 핸들 추가 ] 액션과 동일한 기능을 추가할 수 있습니다. 아래 코드는 상당히 복잡해 보이는데요. 전체적인 내용은 간단합니다. 엔지엠 프레임워크에서 제공해주는 함수를 사용해서 제어할 윈도우의 매인 핸들과 컨트롤 핸들을 가져옵니다. 핸들 값을 알면 창의 위치도 알 수 있게 되는데요. Window 클래스의 GetWindowRect 함수를 사용합니다.

            [Category("Action")]
            [DisplayName("컨트롤 찾기")]
            [Description("응용 프로그램 또는 응용 프로그램의 컨트롤을 선택합니다.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [Editor(typeof(NGM.Models.TypeEditor.FindControlEditor), typeof(System.Drawing.Design.UITypeEditor))]
            public virtual string FindControl
            {
                get { return _findControl; }
                set
                {
                    _findControl = value;
                    if (string.IsNullOrEmpty(_findControl))
                    {
                        this.Title = null;
                        this.MainHandle = IntPtr.Zero;
                        this.ControlHandle = IntPtr.Zero;
                        Children.Clear();
    
                        if (MainView != null && MainView is NGM.Interface.IEditor)
                        {
                            (MainView as NGM.Interface.IEditor).RefreshProperty();
                        }
    
                        return;
                    }
    
                    List<string> nodes;
                    nodes = _findControl.Split(new string[] { NGM.Definition.DOMSeparator }, StringSplitOptions.RemoveEmptyEntries).ToList();
                    Title = nodes.FirstOrDefault();
                    nodes.RemoveAt(0);
                    Children.Clear();
    
                    foreach (string node in nodes)
                    {
                        Children.Add(new HandleItem()
                        {
                            Node = (NGM.API.Options.FindWindow)Enum.Parse(typeof(NGM.API.Options.FindWindow), node)
                        });
                    }
    
                    var handles = NGM.Common.GetHandles(_findControl);
                    MainHandle = handles.Item1;
                    ControlHandle = handles.Item2;
                    NGM.API.Window.GetWindowRect(MainHandle, out Rectangle mainRectangle);
                    NGM.API.Window.GetWindowRect(ControlHandle, out Rectangle controlRectangle);
                    MainRectangle = mainRectangle;
                    ControlRectangle = controlRectangle;
    
                    if (MainView != null && MainView is NGM.Interface.IEditor)
                    {
                        (MainView as NGM.Interface.IEditor).RefreshProperty();
                    }
                }
            }

     

     

    마지막으로 추상 클래스(Abstract Class)인 BaseCustomToolModel이 상속 받고 있는 BaseModelICloneable 인터페이스를 재정의 합니다. 이 인터페이스는 기본 타입(Primitive Types)이 아닌 참조 타입(Reference Types)의 경우 Deep Copy(깊은 복사)를 처리해줍니다. 일반적으로 C++, Java, C# 과 같은 컴파일 언어들은 얕은 복사가 기본입니다. 얕은 복사의 경우 개체의 주소를 참조하므로 복사된 다른 액션의 속성 변경에 영향을 받게 됩니다. 얕은 복사(Shallow Copy)깊은 복사(Deep Copy)의 자세한 내용은 [ 여기 ]에서 확인할 수 있습니다.

            /// <summary>
            /// 이 액션을 깊은 복사하여 반환합니다.
            /// </summary>
            /// <returns>object는 복사한 액션입니다.</returns>
            public override object Clone()
            {
                var other = (InactiveKeyboardModel)base.Clone();
                other.Children = new List<HandleItem>();
    
                foreach (var item in this.Children)
                {
                    other.Children.Add(new HandleItem() { Node = item.Node });
                }
    
                return other;
            }

     

     

    아래는 지금까지 작업한 전체 코드입니다.

    using NGM.Models.Item;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Linq;
    using System.Xml.Serialization;
    
    namespace CustomInactiveClick
    {
        [Serializable]
        public class InactiveKeyboardModel : NGM.Models.Interface.BaseCustomToolModel, NGM.Models.Interface.IHandleModel
        {
            private string _findControl;
    
            public override string DisplayCategory => "NGMsoftware";
    
            public override string DisplayName => "비활성 키보드 입력";
    
            private Rectangle MainRectangle { get; set; }
    
            private Rectangle ControlRectangle { get; set; }
    
            [XmlIgnore]
            [Category("Data")]
            [DisplayName("매인 핸들")]
            [Description("컨트롤의 매인 핸들 값입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public IntPtr MainHandle { get; set; }
    
            [XmlIgnore]
            [Category("Data")]
            [DisplayName("컨트롤 핸들")]
            [Description("선택된 컨트롤의 핸들 값입니다.")]
            [Browsable(true)]
            [ReadOnly(true)]
            public IntPtr ControlHandle { get; set; }
    
            [Category("Action")]
            [DisplayName("컨트롤 제목")]
            [Description("선택한 컨트롤의 상단에 표시되는 제목입니다.")]
            [Browsable(true)]
            [DefaultValue(null)]
            public string Title { get; set; }
    
            [Category("Action")]
            [DisplayName("컨트롤 구조")]
            [Description("선택된 컨트롤의 구조입니다. 컨트롤을 찾기 위해 직접 편집이 가능합니다.")]
            [Browsable(true)]
            public List<NGM.Models.Item.HandleItem> Children { get; set; } = new List<NGM.Models.Item.HandleItem>();
    
            [Category("Action")]
            [DisplayName("키보드 입력")]
            [Description("입력할 키보드의 코드를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(typeof(NGM.API.Helper.InputBehavier), "Up")]
            public NGM.API.Helper.InputBehavier InputBehavier { get; set; } = NGM.API.Helper.InputBehavier.Up;
    
            [Category("Action")]
            [DisplayName("키보드 입력")]
            [Description("입력할 키보드의 코드를 선택하세요.")]
            [Browsable(true)]
            [DefaultValue(typeof(NGM.Utility.NativeInputManager.Native.KeyboardVirtualKeyCode), "NONE")]
            public NGM.Utility.NativeInputManager.Native.KeyboardVirtualKeyCode Key { get; set; } = NGM.Utility.NativeInputManager.Native.KeyboardVirtualKeyCode.NONE;
    
            public override void Execute()
            {
                var inactiveHelper = new NGM.API.Helper();
                inactiveHelper.InactiveKeyboard(ControlHandle, Key, InputBehavier);
            }
    
            [Category("Action")]
            [DisplayName("컨트롤 찾기")]
            [Description("응용 프로그램 또는 응용 프로그램의 컨트롤을 선택합니다.")]
            [Browsable(true)]
            [DefaultValue(null)]
            [Editor(typeof(NGM.Models.TypeEditor.FindControlEditor), typeof(System.Drawing.Design.UITypeEditor))]
            public virtual string FindControl
            {
                get { return _findControl; }
                set
                {
                    _findControl = value;
                    if (string.IsNullOrEmpty(_findControl))
                    {
                        this.Title = null;
                        this.MainHandle = IntPtr.Zero;
                        this.ControlHandle = IntPtr.Zero;
                        Children.Clear();
    
                        if (MainView != null && MainView is NGM.Interface.IEditor)
                        {
                            (MainView as NGM.Interface.IEditor).RefreshProperty();
                        }
    
                        return;
                    }
    
                    List<string> nodes;
                    nodes = _findControl.Split(new string[] { NGM.Definition.DOMSeparator }, StringSplitOptions.RemoveEmptyEntries).ToList();
                    Title = nodes.FirstOrDefault();
                    nodes.RemoveAt(0);
                    Children.Clear();
    
                    foreach (string node in nodes)
                    {
                        Children.Add(new HandleItem()
                        {
                            Node = (NGM.API.Options.FindWindow)Enum.Parse(typeof(NGM.API.Options.FindWindow), node)
                        });
                    }
    
                    var handles = NGM.Common.GetHandles(_findControl);
                    MainHandle = handles.Item1;
                    ControlHandle = handles.Item2;
                    NGM.API.Window.GetWindowRect(MainHandle, out Rectangle mainRectangle);
                    NGM.API.Window.GetWindowRect(ControlHandle, out Rectangle controlRectangle);
                    MainRectangle = mainRectangle;
                    ControlRectangle = controlRectangle;
    
                    if (MainView != null && MainView is NGM.Interface.IEditor)
                    {
                        (MainView as NGM.Interface.IEditor).RefreshProperty();
                    }
                }
            }
    
            /// <summary>
            /// 이 액션을 깊은 복사하여 반환합니다.
            /// </summary>
            /// <returns>object는 복사한 액션입니다.</returns>
            public override object Clone()
            {
                var other = (InactiveKeyboardModel)base.Clone();
                other.Children = new List<HandleItem>();
    
                foreach (var item in this.Children)
                {
                    other.Children.Add(new HandleItem() { Node = item.Node });
                }
    
                return other;
            }
        }
    }

     

    프로젝트를 컴파일하세요. 아래 그림과 같이 프로젝트에서 우클릭 후 "다시 빌드"를 클릭하시면 됩니다.

    srgZm7a.png

     

     

    컴파일이 정상적으로 완료 되었으면, CustomInactiveKeyboard 프로젝트에서 우클릭 후 "파일 탐색기에서 폴더 열기"를 선택하세요.

    l6uhRx7.png

     

     

    bin/Debug 폴더로 이동하세요. 만약, Release 모드로 컴파일했다면 bin/Release 폴더로 이동해야 합니다. 가급적이면 배포시에는 Release로 컴파일하세요. 아래 그림과 같이 CustomInactiveKeyboard.dll이 있을겁니다. 이 라이브러리를 복사한 후 바탕화면에 붙여넣기하세요.

    rIbiL87.png

     

     

    엔지엠 에디터를 실행한 후 "관리자" 메뉴의 "도구 모듈 설치" 버튼을 클릭하세요. (아래 그림은 함수 모듈 설치지만, 도구 모듈 설치로 해야 합니다.)

    ySJp52M.png

     

     

    모듈이 정상적이라면 아래와 같이 설치 작업이 성공했다는 메시지를 표시합니다.

    baqVyFP.png

     

     

    사용자 도구 상자에 보면 추가한 모듈의 액션이 추가된 것을 확인할 수 있습니다. 새로운 스크립트를 생성하고, 액션을 추가하세요.

    6zwgSXQ.png

     

     

    메모장의 핸들을 추가하고, 입력할 키보드의 키 코드를 설정해줍니다. 그리고, 실행해보면 비활성으로 키보드가 입력되는걸 확인할 수 있습니다.

    oXR6DLT.gif

     

     

    개발자에게 후원하기

    MGtdv7r.png

     

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

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

    감사합니다~

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

    댓글목록

    등록된 댓글이 없습니다.