7. 포커스 소유 UI 요소 정보 수집기 만들기

1장에서 6장까지는 UI 자동화 기술에 관한 전반적인 내용을 살펴보았습니다. 이번 장부터는 UI 자동화 기술을 이용하여 프로그래밍하는 것과 접근성과 연관있는 프로그래밍을 다룰 것입니다.

이번 장에서는 포커스를 소유한 자동화 요소의 속성과 컨트롤 패턴을 조사하여 이들의 값들을 표시하는 정보 수집기를 만들어 볼 것입니다. 이번 장에서 실습할 프로그래밍은 새로운 기술을 소개하는 부분은 없으며 앞에서 다룬 기술들을 활용하는 부분입니다.

이번 실습은 포커스가 바뀔 때마다 포커스를 점유한 자동화 요소의 속성을 컬렉션에 보관하고 수집한 자동화 요소가 어떠한 컨트롤 패턴인지 정보를 확인할 수 있게 하는 프로그램입니다.

포커스 소유 UI 요소 수집기 실행 화면
[그림 7.01] 실행화면

먼저 폼의 자식 컨트롤을 추가하세요.

번호컨트롤 타입컨트롤 명설명
1TreeViewtv_aeinfo자동화 요소 정보를 표시
2ListBoxlbox_snap수집한 자동화 요소의 정보의 스냅샷을 목록에 표시
3ListBoxlbox_pattern스냅샷 목록에서 선택한 항목의 컨트롤 패턴 표시

[표 3.5] Form1의 컨트롤

포커스 소유 UI 요소 정보 수집기 프로젝트에는 디폴트로 제공하는 Form1 클래스외에도 5개의 형식을 정의합니다. WrapAutoEvent 클래스에서는 포커스 변경 이벤트 핸들러를 등록하고 해제하는 역할을 수행하며 포커스 변경 이벤트 발생할 시점의 정보로 스냅샷 개체를 만들어 이를 이벤트 구독자들에게 통보하는 역할을 수행합니다. 그리고 FocusChangedEventArgs 클래스는 스냅샷 개체를 만들어 이벤트 구독자에게 통보할 때 전달할 이벤트 인자 형식입니다. 그리고 이러한 이벤트를 위해 FocusChangedEventHandler 대리자를 정의합니다. 스냅샷 개체는 SnapAE 이름의 클래스로 정의할 것이며 내부적으로 자동화 컨트롤 패턴을 조사하여 목록으로 갖는 WrapPattern 개체를 만들어 속성으로 제공합니다.

정의할 형식
[그림 7.02] 정의할 형식

이번 프로젝트는 Bottom up 방식으로 작성합시다.

제일 먼저 WrapPattern을 구현합시다. WrapPattern은 자동화 요소의 패턴 목록을 기억할 개체입니다. 사용하는 곳에서 foreach 문으로 제공하는 패턴 이름을 확인할 수 있게 IEnumerable 인터페이스를 구현 약속합시다.

public class WrapPattern:IEnumerable<string>
{

패턴 이름을 보관할 컬렉션을 멤버로 선언합시다.

    List<string> ptlist = new List<string>();

자동화 요소를 가져오기 할 수 있는 속성을 제공합니다.

    public AutomationElement AE
    {
        get;
        private set;
    }

생성자에서는 자동화 요소를 입력 인자로 받습니다.

    public WrapPattern(AutomationElement ae)
    {
        AE = ae;
 자동화 요소의 패턴을 분석하는 부분은 별도의 Parsing 메서드에서 정의합시다.
        Parsing();
    }
    private void Parsing()
    {
        Object obj;
 Parsing 메서드에서는 자동화 요소의 TryGetCurrentPattern 메서드를 이용하여 자동화 컨트롤 패턴들을 하나 하나 비교하여 제공하는 컨트롤 패턴일 때 패턴 이름을 목록에 추가합니다.
        if (AE.TryGetCurrentPattern(DockPattern.Pattern, out obj))
        {
            ptlist.Add("DockPattern");
        }
        if (AE.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out obj))
        {
            ptlist.Add("ExpandCollapsePattern");
        }
        ...중략...
    }

IEnumerable 인터페이스를 구현 약속하여 GetEnumerator 메서드를 제공해야 합니다. 실제 컨트롤 패턴 이름은 ptlist에 보관해서 ptlist의 GetEnumerator 메서드 호출 결과를 By Pass 합니다.

    IEnumerator<string> IEnumerable<string>.GetEnumerator()
    {
        return ptlist.GetEnumerator();
    }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return ptlist.GetEnumerator();
    }
}
using System;
using System.Collections.Generic;
using System.Windows.Automation;

namespace 포커스_소유_UI_요소_정보_수집기
{
    public class WrapPattern:IEnumerable<string>
    {
        List<string> ptlist = new List<string>();
        public AutomationElement AE
        {
            get;
            private set;
        }
        public WrapPattern(AutomationElement ae)
        {
            AE = ae;
            Parsing();
        }
        private void Parsing()
        {
            Object obj;
            if (AE.TryGetCurrentPattern(DockPattern.Pattern, out obj))
            {
                ptlist.Add("DockPattern");
            }
            if (AE.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out obj))
            {
                ptlist.Add("ExpandCollapsePattern");
            }
            if (AE.TryGetCurrentPattern(GridPattern.Pattern, out obj))
            {
                ptlist.Add("GridPattern");
            }
            if (AE.TryGetCurrentPattern(GridItemPattern.Pattern, out obj))
            {
                ptlist.Add("GridItemPattern");
            }
            if (AE.TryGetCurrentPattern(InvokePattern.Pattern, out obj))
            {
                ptlist.Add("InvokePattern");
            }
            if (AE.TryGetCurrentPattern(MultipleViewPattern.Pattern, out obj))
            {
                ptlist.Add("MultipleViewPattern");
            }
            if (AE.TryGetCurrentPattern(RangeValuePattern.Pattern, out obj))
            {
                ptlist.Add("RangeValuePattern");
            }
            if (AE.TryGetCurrentPattern(ScrollPattern.Pattern, out obj))
            {
                ptlist.Add("ScrollPattern");
            }
            if (AE.TryGetCurrentPattern(ScrollItemPattern.Pattern, out obj))
            {
                ptlist.Add("ScrollItemPattern");
            }
            if (AE.TryGetCurrentPattern(SelectionPattern.Pattern, out obj))
            {
                ptlist.Add("SelectionPattern");
            }
            if (AE.TryGetCurrentPattern(SelectionItemPattern.Pattern, out obj))
            {
                ptlist.Add("SelectionItemPattern");
            }
            if (AE.TryGetCurrentPattern(TablePattern.Pattern, out obj))
            {
                ptlist.Add("TablePattern");
            }
            if (AE.TryGetCurrentPattern(TableItemPattern.Pattern, out obj))
            {
                ptlist.Add("TableItemPattern");
            }
            if (AE.TryGetCurrentPattern(TextPattern.Pattern, out obj))
            {
                ptlist.Add("TextPattern");
            }
            if (AE.TryGetCurrentPattern(TogglePattern.Pattern, out obj))
            {
                ptlist.Add("TogglePattern");
            }
            if (AE.TryGetCurrentPattern(TransformPattern.Pattern, out obj))
            {
                ptlist.Add("TransformPattern");
            }
            if (AE.TryGetCurrentPattern(ValuePattern.Pattern, out obj))
            {
                ptlist.Add("ValuePattern");
            }
            if (AE.TryGetCurrentPattern(WindowPattern.Pattern, out obj))
            {
                ptlist.Add("WindowPattern");
            }
        }
        IEnumerator<string> IEnumerable<string>.GetEnumerator()
        {
            return ptlist.GetEnumerator();
        }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return ptlist.GetEnumerator();
        }
    }
}

[소스 7.1] WrapPattern.cs

이번에는 자동화 요소 개체의 현재 정보를 캡쳐하여 관리하는 SnapAE 클래스를 정의합시다.

public class SnapAE
{

캡쳐한 순번을 부여할 정적 멤버를 선언합니다.

    static int last_seq;

현재까지 캡쳐한 개수를 가져오기 할 수 있는 정적 멤버를 제공합시다.

    public static int SnapCount
    {
        get{    return last_seq;    }
    }

자신의 캡쳐 일련번호를 가져오기 할 수 있는 멤버를 제공합시다.

    public int SnapSeq{    get;    private set;    }

원본 자동화 요소 개체를 가져오기 할 수 있는 멤버를 제공합니다.

    public AutomationElement AE{    get;    private set;    }

자동화 속성마다 가져오기 할 수 있는 멤버를 제공합니다.

    public string Name{    get;    private set;    }

    public string AcceleratorKey{    get;    private set;    }

    ...중략...

캡쳐한 시각을 가져오기 할 수 있는 멤버를 제공합시다.

    public DateTime DT{    get;    private set;    }

컨트롤 패턴 이름을 목록화한 개체 WP를 가져오기 할 수 있는 멤버를 제공합니다.

 public WrapPattern WP{    get;    private set;    }

생성자는 자동화 요소 개체를 입력 인자로 받습니다.

    public SnapAE(AutomationElement ae)
    {
 정적 멤버인 가장 최근에 부여한 일련번호를 1 증가합니다.
        last_seq++;
 그리고 개체의 일련번호를 부여합니다.
        SnapSeq = last_seq;
 자동화 요소 개체 속성과 패턴 목록 개체 속성을 설정합니다.
        AE = ae;
        WP = new WrapPattern(ae);
 현재 시각을 캡쳐 시각으로 설정합니다.
        DT = DateTime.Now;
 자동화 요소 개체의 현재 정보 구조체를 가져오기 합니다.
        AutomationElement.AutomationElementInformation aei = ae.Current;
 자동화 요소 개체의 속성으로 멤버 속성을 설정합니다.
        Name = aei.Name;
        AcceleratorKey = aei.AcceleratorKey;
        AccessKey = aei.AcceleratorKey;
        ControlType = aei.ControlType.ProgrammaticName;
        HelpText = aei.HelpText;
        ClassName = aei.ClassName;
        ...중략...
    }

속성 정보를 트리 노드를 만들는 메서드도 제공합시다.

    public TreeNode MakeTreeNode()
    {
 일련번호를 입력 인자로 트리 노드를 생성합니다.
        TreeNode tn = new TreeNode("No:"+SnapSeq);
 멤버 속성 정보를 입력 인자로 하위 노드를 추가합니다.
        tn.Nodes.Add("이름:" + Name);
        tn.Nodes.Add("수집 시각:" + DT.ToShortTimeString());
        tn.Nodes.Add("가속화 키:" + AcceleratorKey);
        tn.Nodes.Add("액세스 키:" + AccessKey);
        tn.Nodes.Add("자동화 ID:" + AutomationId);
        ....중략...
        return tn;
    }
    public override string ToString()
    {
 ToString 메서드를 재정의하여 일련번호, 이름 속성, 캡쳐 시각을 문자열로 형성하여 반환합시다.
        return string.Format("{0,4}:{1}:{2}",SnapSeq, Name, DT.ToShortTimeString());
    }
}

using System;
using System.Windows.Automation;
using System.Windows.Forms;
using System.Windows;

namespace 포커스_소유_UI_요소_정보_수집기
{
    public class SnapAE
    {
        static int last_seq;
        public static int SnapCount
        {
            get
            {
                return last_seq;
            }
        }
        public int SnapSeq{    get;    private set;    }
        public AutomationElement AE{    get;    private set;    }
        public string Name{    get;    private set;    }
        public string AcceleratorKey{    get;    private set;    }
        public string AccessKey{    get;    private set;    }
        public string ControlType{    get;    private set;    }
        public string HelpText{    get;    private set;    }
        public string ClassName{    get;    private set;    }
        public string AutomationId{    get;    private set;    }
        public Rect BoundingRectangle{    get;    private set;    }
        public bool IsContent{    get;    private set;    }
        public bool IsControl{    get;    private set;    }
        public bool IsEnabled{    get;    private set;    }
        public bool IsOnScreen{    get;    private set;    }
        public bool IsPassword{    get;    private set;    }
        public int Handle{    get;    private set;    }
        public string LocalizedControlType{    get;    private set;    }
        public int ProcessId{    get;    private set;    }
        public DateTime DT{    get;    private set;    }
        public WrapPattern WP{    get;    private set;    }

        public SnapAE(AutomationElement ae)
        {
            last_seq++;
            SnapSeq = last_seq;
            AE = ae;
            WP = new WrapPattern(ae);

            DT = DateTime.Now;
            AutomationElement.AutomationElementInformation aei = ae.Current;

            Name = aei.Name;            
            AcceleratorKey = aei.AcceleratorKey;
            AccessKey = aei.AcceleratorKey;
            ControlType = aei.ControlType.ProgrammaticName;
            HelpText = aei.HelpText;
            ClassName = aei.ClassName;
            AutomationId = aei.AutomationId;
            BoundingRectangle = aei.BoundingRectangle;
            IsContent = aei.IsContentElement;
            IsControl = aei.IsControlElement;
            IsEnabled = aei.IsEnabled;
            IsOnScreen = !aei.IsOffscreen;
            IsPassword = aei.IsPassword;
            LocalizedControlType = aei.LocalizedControlType;
            Handle = aei.NativeWindowHandle;
            ProcessId = aei.ProcessId;
        }
        public TreeNode MakeTreeNode()
        {
            TreeNode tn = new TreeNode("No:"+SnapSeq);

            tn.Nodes.Add("이름:" + Name);
            tn.Nodes.Add("수집 시각:" + DT.ToShortTimeString());
            tn.Nodes.Add("가속화 키:" + AcceleratorKey);
            tn.Nodes.Add("액세스 키:" + AccessKey);
            tn.Nodes.Add("자동화 ID:" + AutomationId);
            tn.Nodes.Add("사각 영역:" + BoundingRectangle);
            tn.Nodes.Add("클래스 이름:" + ClassName);
            tn.Nodes.Add("컨트롤 유형:" + ControlType);                        
            tn.Nodes.Add("도움말:" + HelpText);
            tn.Nodes.Add("콘텐츠:" + IsContent);
            tn.Nodes.Add("컨트롤:" + IsControl);
            tn.Nodes.Add("활성화:" + IsEnabled);            
            tn.Nodes.Add("화면 표시:" + IsOnScreen);
            tn.Nodes.Add("비밀번호:" + IsPassword);                      
            tn.Nodes.Add("컨트롤 지역 이름:" + LocalizedControlType);
            tn.Nodes.Add("윈도우 핸들:" + Handle);
            tn.Nodes.Add("프로세스 ID:" + ProcessId);
            tn.ExpandAll();
            return tn;
        }
        public override string ToString()
        {
            return string.Format("{0,4}:{1}:{2}",SnapSeq, Name, DT.ToShortTimeString());
        }
    }
}

[소스 7.2] SnapAE.cs

포커스 변경 이벤트가 발생하였을 때 수집한 스냅샷 개체를 전달하는 이벤트 인자 형식을 정의합시다.

public class FocusChangedEventArgs
{
 이벤트를 처리하는 핸들러에서 스냅샷 가져오기 할 수 있게 멤버 속성을 제공합시다.
    public SnapAE SAE{    get;    private set;    }
 생성자는 스냅샷 개체를 입력 인자로 받아 멤버 속성을 설정합니다.
    public FocusChangedEventArgs(SnapAE sae){    SAE = sae;    }
}

이벤트에 사용할 대리자도 정의합니다.

public delegate void FocusChangedEventHandler(object sender, FocusChangedEventArgs e);
namespace 포커스_소유_UI_요소_정보_수집기
{
    public class FocusChangedEventArgs
    {
        public SnapAE SAE{    get;    private set;    }
        public FocusChangedEventArgs(SnapAE sae)
        {
            SAE = sae;
        }
    }
    public delegate void FocusChangedEventHandler(object sender, FocusChangedEventArgs e);
}

[소스 7.3] FocusChangedEventArgs.cs

이번에는 자동화 포커스 변경 이벤트 핸들러를 등록하게 해제하며 포커스 변경할 때마다 스냅샷 개체를 생성하여 이벤트를 게시하는 WrapAutoEvent 클래스를 정의합시다.

public static class WrapAutoEvent
{

자동화 포커스 변경 이벤트 핸들러를 기억할 멤버를 선언합니다.

    static AutomationFocusChangedEventHandler afc_handler = null;

스냅샷 개체를 생성할 때 게시할 이벤트 멤버를 선언하세요.

    public static event FocusChangedEventHandler FocusChanged = null;

자동화 포커스 변경 이벤트 핸들러를 등록하는 메서드를 제공합시다.

    public static void AddFocusChangedEventHandler()
    {
        if (afc_handler == null)
        {
 이미 등록한지 확인하여 없을 때만 자동화 포커스 변경 이벤트 핸들러를 생성하여 등록합니다.
            afc_handler = new AutomationFocusChangedEventHandler(OnFocusChange);
            Automation.AddAutomationFocusChangedEventHandler(afc_handler);
        }
    }

자동화 포커스 변경 이벤트 핸들러를 해제하는 메서드도 제공합니다.

    public static void RemoveFocusChangedEventHandler()
    {
        if (afc_handler != null)
        {
 자동화 포커스 변경 이벤트 핸들러가 있는지 확인하여 존재할 때만 해제하고 null로 리셋합니다.
            Automation.RemoveAutomationFocusChangedEventHandler(afc_handler);
            afc_handler = null;
        }
    }
   static private void OnFocusChange(Object sender, AutomationFocusChangedEventArgs e)
   {
        try
        {
 입력 인자로 받은 sender를 자동화 요소 형식으로 참조합니다.
            AutomationElement ae = sender as AutomationElement;

 자동화 요소 개체가 없으면 이벤트 핸들러를 종료합니다.
            if (ae == null){    return;    }
 자동화 요소의 프로세스 ID가 현재 프로세스 ID와 같으면 이벤트 핸들러를 종료합시다.
            if (ae.Current.ProcessId == Process.GetCurrentProcess().Id){    return;    }
            if (FocusChanged != null)
            {
 구독자가 있을 때 자동화 요소 개체의 스냅샷 개체를 생성하여 이벤트를 게시합니다.
                FocusChanged(null, new FocusChangedEventArgs(new SnapAE(ae)));
            }
        }
        catch{}
    }
}
using System;
using System.Windows.Automation;
using System.Diagnostics;
namespace 포커스_소유_UI_요소_정보_수집기
{
    public static class WrapAutoEvent
    {
        static AutomationFocusChangedEventHandler afc_handler = null;
        public static event FocusChangedEventHandler FocusChanged = null;
        public static void AddFocusChangedEventHandler()
        {
            if (afc_handler == null)
            {
                afc_handler = new AutomationFocusChangedEventHandler(OnFocusChange);
                Automation.AddAutomationFocusChangedEventHandler(afc_handler);
            }
        }
        public static void RemoveFocusChangedEventHandler()
        {
            if (afc_handler != null)
            {
                Automation.RemoveAutomationFocusChangedEventHandler(afc_handler);
                afc_handler = null;
            }
        }
        static private void OnFocusChange(Object sender, AutomationFocusChangedEventArgs e)
        {
            try
            {
                AutomationElement ae = sender as AutomationElement;

                if (ae == null)
                {
                    return;
                }
                if (ae.Current.ProcessId == Process.GetCurrentProcess().Id)
                {
                    return;
                }
                if (FocusChanged != null)
                {
                    FocusChanged(null, new FocusChangedEventArgs(new SnapAE(ae)));
                }
            }
            catch
            {
            }
        }
    }
}

[소스 7.5] WrapAutoEvent.cs

마지막으로 Form1 을 구현합시다. 먼저 Load 이벤트 핸들러를 등록하세요.

private void Form1_Load(object sender, EventArgs e)
{
 포커스 변경 이벤트 핸들러를 등록합니다.
    WrapAutoEvent.FocusChanged +=
        new FocusChangedEventHandler(WrapAutoEvent_FocusChanged);
 자동화 포커스 이벤트 핸들러를 등록하는 AddFocusChangedEventHandler메서드를 호출합니다.
    WrapAutoEvent.AddFocusChangedEventHandler();
}
void WrapAutoEvent_FocusChanged(object sender, FocusChangedEventArgs e)
{
 포커스 변경 이벤트 핸들러에서는 스냅샷 개체의 정보로 컨트롤에 표시해야 하는데 크로스 문제가 발생할 수 있어서 별도의 메서드로 정의합니다.
    ChangeAE(e.SAE);
}

ChangeAE 메서드와 원형이 같은 대리자를 정의하세요.

delegate void ChangeDele(SnapAE sae);
private void ChangeAE(SnapAE sae)
{
    if (this.InvokeRequired)
    {
 현재 수행 스레드가 컨트롤을 소유한 스레드와 다르면 .NET 프레임워크에게 수행을 부탁합니다.
        this.Invoke(new ChangeDele(ChangeAE), new object[] { sae });
    }
    else
    {
 현재 수행 스레드가 컨트롤을 소유한 스레드와 같으면 스냅샷 정보를 트리뷰에 추가하고 수집 목록을 보여줄 리스트 박스에 추가합니다.
 트리 뷰에 추가하는 부분은 다른 부분에서도 다시 사용하므로 별도의 메서드로 정의합시다.
        SetAEInfo(sae);
        lbox_snap.Items.Add(sae);
        lbox_snap.SelectedIndex = lbox_snap.Items.Count - 1;
    }
}
private void SetAEInfo(SnapAE sae)
{
 현재 트리뷰의 노드 목록을 지우고 스냅샷 개체의 MakeTreeNode 메서드를 호출할 결과 노드를 추가합니다.
    tv_aeinfo.Nodes.Clear();
    tv_aeinfo.Nodes.Add(sae.MakeTreeNode());
}

FormClosing 이벤트 핸들러를 등록하세요.

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
 자동화 포커스 변경 이벤트 핸들러를 해제하는 RemoveFocusChangedEventHandler 메서드를 호출합니다.
    WrapAutoEvent.RemoveFocusChangedEventHandler();
}

수집 목록 리스트 박스의 선택 변경 이벤트 핸들러를 등록하세요.

private void lbox_snap_SelectedIndexChanged(object sender, EventArgs e)
{
 먼저 패턴 목록 리스트 박스의 항목을 지웁니다.
    lbox_pattern.Items.Clear();
 선택 항목이 없으면 이벤트 핸들러를 종료합니다.
    if (lbox_snap.SelectedIndex == -1){    return;    }
 선택 항목을 스냅샷 개체로 참조합니다.
    SnapAE sae = lbox_snap.SelectedItem as SnapAE;
 트리 뷰 항목을 스냅샷 개체 정보로 설정합니다.
    SetAEInfo(sae);
    WrapPattern wp = sae.WP;
 래핑한 패턴의 지원 컨트롤 패턴 이름을 패턴 목록 상자 아이템 항목에 추가합니다.
    foreach (string str in wp)
    {
        lbox_pattern.Items.Add(str);
    }
}
using System;
using System.Windows.Forms;
using System.Windows.Automation;

namespace 포커스_소유_UI_요소_정보_수집기
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            WrapAutoEvent.FocusChanged +=
                 new FocusChangedEventHandler(WrapAutoEvent_FocusChanged);
            WrapAutoEvent.AddFocusChangedEventHandler();
        }
        void WrapAutoEvent_FocusChanged(object sender, FocusChangedEventArgs e)
        {
            ChangeAE(e.SAE);
        }

        delegate void ChangeDele(SnapAE sae);
        private void ChangeAE(SnapAE sae)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new ChangeDele(ChangeAE), new object[] { sae });
            }
            else
            {
                SetAEInfo(sae);
                lbox_snap.Items.Add(sae);
                lbox_snap.SelectedIndex = lbox_snap.Items.Count - 1;
            }
        }

        private void SetAEInfo(SnapAE sae)
        {
            tv_aeinfo.Nodes.Clear();
            tv_aeinfo.Nodes.Add(sae.MakeTreeNode());
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            WrapAutoEvent.RemoveFocusChangedEventHandler();
        }

        private void lbox_snap_SelectedIndexChanged(object sender, EventArgs e)
        {
            lbox_pattern.Items.Clear();
            if (lbox_snap.SelectedIndex == -1)
            {
                return;
            }
            SnapAE sae = lbox_snap.SelectedItem as SnapAE;
            SetAEInfo(sae);
            WrapPattern wp = sae.WP;

            foreach (string str in wp)
            {
                lbox_pattern.Items.Add(str);
            }
        }
    }
}

[소스 7.6] Form1.cs