7) AccEvalProject

접근성 평가 프로젝트의 자동화 요소를 조사하고 관리하는 AccEvalProject 클래스를 정의합시다. 이 부분이 전체 프로그램의 핵심적인 역할을 하는 부분입니다.

자동화 요소를 발견할 때 발견한 개수를 이벤트로 전달하기 위한 간단한 대리자를 정의합시다.

public delegate void AddFindElementDele(int cnt);
public class AccEvalProject
{

AccEvalProject에서는 자동화 요소의 계층 구조를 파악할 수 있어야 합니다. 이를 위해 사전 컬렉션을 멤버로 선언합시다.

    Dictionary<string, TreeNode> node_dic = null;

평가가 끝났을 때 이를 구동하는 이벤트 핸들러를 등록하기 위한 이벤트를 선언합니다.

    public event EventHandler EndEvalProject = null;

자동화 속성이 이동할 때 프로그램 방식의 하이라이트도 이동해야 합니다. 이를 위한 이벤트를 선언합니다.

    public event AutomationPropertyChangedEventHandler AEMoved = null;

자동화 속성 변경 이벤트 핸들러와 닫힐 때 처리를 위한 이벤트 핸들러 멤버를 선언합니다.

    AutomationPropertyChangedEventHandler apceh = null;
    AutomationEventHandler close_eventHandler = null;

최상위 자동화 요소를 기억할 멤버를 선언합시다.

    AutomationElement mae = null;

평가 프로젝트 제목을 속성으로 제공합시다.

    public string Title{    get;    private set;    }

자동화 요소 개수를 속성으로 제공합시다.

    public int UICount
    {
        get{    return Table.Rows.Count;    }
    }

평가 테이블을 속성으로 제공합시다. 평가 테이블은 평가 내용을 XML로 저장하기 위한 개체로 XML로 기록하는 메서드를 제공하는 ADO.NET 기술의 클래스 DataTable 형식을 사용합시다.

    public DataTable Table{    get;    private set;    }

평가 대상 프로세스도 속성으로 제공합니다.

    public EHProcess EHProcess{    get;    private set;    }

평가 대상 프로세스의 메인 창의 윈도우 핸들도 속성으로 제공합시다.

    public IntPtr MainWndHandle
    {
        get{    return EHProcess.MainHandle;    }
    }

계층화 구조의 루트 노드를 속성으로 제공합시다.

    public TreeNode Root{    get;    private set;    }

대상 프로세스의 메인 창의 이미지도 속성으로 제공합시다.

    public Image Image{    get;    private set;    }

사전 개체에서 키로 트리 노드를 가져오기 할 수 있게 속성을 제공합시다.

    public TreeNode this[string key]
    {
        get{    return node_dic[key];    }
    }

생성자 메서드에서는 프로젝트 이름, 데이터 테이블, 평가 대상 프로세스 개체를 입력 인자로 받습니다.

    internal AccEvalProject(string title, DataTable dt, EHProcess ehprocess)
    {

생성자에서는 입력 인자로 속성을 설정합니다.

        Title = title;
        Table = dt;
        EHProcess = ehprocess;

그리고 계층화 정보에 사용할 트리 노드 개체를 기억할 사전 개체를 생성합니다.

        node_dic = new Dictionary<string, TreeNode>();
    }

프로세스 모드를 초기화하는 메서드를 제공합시다. 이번 프로젝트는 프로세스 모드만 제공하지만 보다 효과적인 평가 도구로 만들려면 포커스 모드와 창 모드 등을 제공하는 것이 좋습니다.

    public void InitProcessMode(AddFindElementDele find_dele)
    {

프로세스의 메인 창의 자동화 요소를 찾습니다.

        mae = AutomationElement.FromHandle(MainWndHandle);

속성 변경 이벤트 핸들러를 생성합니다.

        apceh = new AutomationPropertyChangedEventHandler(OnPropertyChange);

메인 창의 사각 영역 속성이 변하는 것을 알기 위해 이벤트 핸들러를 등록합니다. 이를 통해 창의 위치가 바뀌어도 프로그램 방식의 하이라이트가 유효한 위치에 표시할 수 있습니다.

        Automation.AddAutomationPropertyChangedEventHandler(
            mae, TreeScope.Element, apceh, AutomationElement.BoundingRectangleProperty);

창의 닫히는 시점을 알기 위해 이벤트 핸들러를 등록합시다. 여기에서는 평가 대상 프로젝트의 메인 창이 닫히면 프로젝트를 끝낼 것인지 메시지 창을 띄워 확인하는 구조로 만들 것입니다.

        close_eventHandler = new AutomationEventHandler(OnWindowClose);
        Automation.AddAutomationEventHandler(
            WindowPattern.WindowClosedEvent, mae, TreeScope.Element, close_eventHandler);

그리고 메인 창의 하위 자동화 요소들을 검색합니다. 이 부분은 별도의 메서드로 구현합시다.

        FindAETree(mae, find_dele);
    }
    private void OnPropertyChange(object src, AutomationPropertyChangedEventArgs e)
    {

첫번째 입력 인자로 받은 개체를 자동화 요소 형식으로 참조합니다.

        AutomationElement sourceElement = src as AutomationElement;
        if (e.Property == AutomationElement.BoundingRectangleProperty)
        {

이벤트 발생 이유가 사각 영역의 속성 변경이면 구독자에게 By Pass합니다.

            if (AEMoved != null){    AEMoved(this, e);    }
        }
    }
    private void OnWindowClose(object src, AutomationEventArgs e)
    {
        try{AutomationElement ae = src as AutomationElement;}catch{    return;    }

윈도우 창 닫힘 이벤트 핸들러에서는 평가 프로젝트를 닫습니다.

        End();
    }

계층 정보와 함께 자동화 요소를 검색하는 메서드를 구현합시다.

    private void FindAETree(AutomationElement ae, AddFindElementDele find_dele)
    {
        try
        {

입력 인자로 받은 자동화 요소에 포커스를 부여하고 약간의 시간을 지연합니다. 시간을 지연하는 이유는 SetFocus 메서드를 호출하면 바로 포커스를 소유하는 것이 아니기 때문입니다.

            ae.SetFocus();
            Thread.Sleep(100);
        }
        catch
        {
            try
            {

하위 트리에서 키보드로 초점을 받을 수 있는 자동화 요소를 탐색합니다.

                AutomationElement sae=ae.FindFirst(TreeScope.Subtree, new PropertyCondition
                                  ( AutomationElement.IsKeyboardFocusableProperty, true));

그리고 탐색한 개체에 포커스를 부여하고 지연합니다. 위에서 한 작업을 예외가 발생할 때 다시 하는 이유는 인터넷 익스플로어나 일부 응용 프로그램은 메인 창이 초점을 갖지 못할 때가 있습니다. 이러한 응용들을 위해 예외처리를 한 것입니다. 물론 이 책은 자동화 기술을 소개하는 것이 주 목적이며 상용 프로그램을 만드는 것이 아니어서 예외 처리를 최소화한 것입니다.

                sae.SetFocus();
                Thread.Sleep(100);
            }
            catch
            {
                MessageBox.Show("선택한 윈도우에 초점을 가질 수 있는 요소가 없습니다.");
            }
        }
        try
        {

ImageCapture 정적 클래스의 CaptureFromRect 메서드를 이용하여 메인 창 이미지를 비트맵 개체로 만들어 속성에 설정합니다.

            Image = ImageCapture.CaptureFormRect(ae.Current.BoundingRectangle);
        }
        catch { }

만약 탐색 대리자를 입력 인자로 받았다면 첫번째 항목을 발견하였음을 전달합니다.

        if (find_dele != null){    find_dele(1);    }

그리고 EHAutoElem 개체를 생성합니다.

        EHAutoElem eae = new EHAutoElem(ae);

계층화 정보를 만들어 최상위 노드를 설정합니다. 계층화 정보를 만드는 부분은 별도의 메서드로 구현합시다.

        Root = MakeUIHierarchy(eae, find_dele);
    }
    TreeNode MakeUIHierarchy(EHAutoElem eae, AddFindElementDele find_dele)
    {

입력 인자로 받은 요소의 ToString 메서드의 반환 문자열로 트리 노드를 생성합니다.

        TreeNode sr = new TreeNode(eae.ToString());

구분하기 위한 ID를 구합니다.

        string ehid = eae.GetEHID();
        if (node_dic.ContainsKey(ehid) == false)
        {

만약 사전 개체에 없다면 사전 개체에 등록합니다.

            node_dic[ehid] = sr;

트리 노드의 Tag 속성에 EHAutoElem 개체를 설정하고 반대로 EHAutoElem 개체의 Tag 속성에 트리 노드를 설정합니다. 이를 통해 트리 뷰의 트리 노드를 선택하면 태그에 기억한 정보로 대응하는 자동화 요소를 확인하거나 그 역을 수행할 수 있습니다.

            sr.Tag = eae;
            eae.Tag = sr;
            FindedAutoElement(null, new FindAutoElemEventArgs(eae));
        }

이미 사전 개체에 있다면 더 이상 진행하지 않고 메서드를 종료합니다.

        else{    return null;    }

이제 자식 요소를 탐색합니다.

        Condition con = TreeWalker.RawViewWalker.Condition;
        AutomationElement ae = eae.AE;
        AutomationElementCollection aec = ae.FindAll(TreeScope.Children, con);

탐색 대리자가 존재하면 탐색한 요소 개수를 인자로 전달합니다.

        if (find_dele != null){    find_dele(aec.Count);    }

탐색한 자동화 요소 개체 항목마다 재귀적으로 MakeUIHierarchy 메서드를 호출합니다.

        foreach (AutomationElement sae in aec)
        {
            TreeNode tr = null;
            if ((tr = MakeUIHierarchy(new EHAutoElem(sae), find_dele)) != null)
            {
                sr.Nodes.Add(tr);
            }
        }
        return sr;
    }
    void FindedAutoElement(object send, FindAutoElemEventArgs e)
    {

자동화 요소를 발견하였때 인자로 전달받은 EHAutoElem 개체를 참조합니다.

        EHAutoElem eae = e.EAE;

만약 테이블이 없으면 메서드를 종료합니다.

        if (Table == null){    return;    }

테이블의 NewRow 메서드를 호출하여 행 개체를 생성합니다.

        DataRow dr = Table.NewRow();

자동화 속성 정보를 구하여 행 개체의 쉘에 설정합니다.

        for (ENUM_UIProperty uip = 0; uip < ENUM_UIProperty.MAX_UIPROPERTY; uip++)
        {
            dr[uip.ToString()] = eae.GetAEProperty(uip);
        }

지원하는 컨트롤 패턴 정보도 마찬가지로 행 개체의 쉘에 설정합니다.

        for (ENUM_CONTROL ctrl = 0; ctrl < ENUM_CONTROL.MAX_CONTROL; ctrl++)
        {
            dr[ctrl.ToString()] = eae.GetPattern(ctrl);
        }

어느 자동화 요소 개체의 정보인지 설정합니다.

        dr["EHAutoElem"] = eae;

이렇게 설정한 행 개체를 테이블의 행 항목에 추가합니다.

        Table.Rows.Add(dr);
    }
    public void End()
    {

평가 프로젝트가 종료하였는지 구독자가 있다면 이벤트를 게시합니다.

        if (EndEvalProject != null){    EndEvalProject(this, new EventArgs());    }

프로젝트 메인 창의 변화를 감지하기 위해 등록한 자동화 이벤트 핸들러를 해제합니다.

        Automation.RemoveAutomationPropertyChangedEventHandler(mae, apceh);
        Automation.RemoveAutomationEventHandler(WindowPattern.WindowClosedEvent,
                                                  mae, close_eventHandler);
    }

ToString 메서드를 재정의하여 평가 프로젝트 제목을 반환합니다.

    public override string ToString()
    {
       return Title;
    }

저장하기 메서드를 제공합시다.

    public void Save(string path)
    {

입력 인자로 받은 경로에 테이블 이름으로 폴더를 만들 것입니다.

        string dirname = path + "/" + Table.TableName;
        if (Directory.Exists(dirname))
        {

만약 같은 디렉토리명이 존재하면 일련 번호를 부여하여 존재하지 않는 디렉토리 이름이 나올 때까지 반복합니다. 이와 같은 기능은 하나의 프로세스를 평가하기 위해 생성한 프로젝트를 같은 폴더에 번호를 부여하여 보관할 수 있게 하는 기능입니다.

            int i = 1;
            dirname = path + "/" + Table.TableName + "_" + i.ToString();
            while (Directory.Exists(dirname))
            {
                i++;
                dirname = path + "/" + Table.TableName +"_" + i.ToString();
            }
        }

디렉토리를 생성합니다.

        Directory.CreateDirectory(dirname);

그리고 평가 결과를 저장하고 모든 이미지를 저장합니다.

        SaveReport(dirname);
        SaveImageAll(dirname);
    }
    public void SaveReport(string dir)
    {

현재 디렉토리를 구하여 기억해 둡니다.

        string cdir = Directory.GetCurrentDirectory();

편의성을 위해 입력 인자로 들어온 경로를 현재 디렉토리로 설정합니다.

        Directory.SetCurrentDirectory(dir);

XML로 기록하기 위한 설정 개체를 생성합니다.

        XmlWriterSettings settings = new XmlWriterSettings();

XML 선언부를 생략하지 않게 설정합니다.

        settings.OmitXmlDeclaration = false;

문자 검사를 하지 않게 설정합니다.

        settings.CheckCharacters = false;

자동 들여쓰기로 설정합니다.

        settings.Indent = true;

인코딩은 디폴트로 설정할게요.

        settings.Encoding = System.Text.Encoding.Default;

레포트를 기록할 XmlWriter 개체를 생성합니다.

        XmlWriter writer = XmlWriter.Create(dir + "/report.xml",settings);

“Report”가 XML 문서의 루트 요소로 할 것입니다. 자식 요소로 결과를 기록합니다.

        writer.WriteStartElement("Report");
        writer.WriteElementString("Title", Title);
        writer.WriteElementString("전체_개수", UICount.ToString());
        writer.WriteElementString("이름없는_UI_요소_개수", NonameCount.ToString());
        WriteControlCount(writer);
        WriteAcceleratorKey(writer);
        WriteAccessKey(writer);
        WriteUIElement(writer);
        writer.WriteEndElement();

모든 정보를 기록하였으면 XmlWriter 개체를 닫고 기억해 두었던 원래 디렉토리를 현재 디레토리로 설정합니다.

        writer.Close();
        Directory.SetCurrentDirectory(cdir);
    }

이름이 없는 자동화 요소의 개수를 가져오기 할 수 있는 속성을 제공합시다. 접근성 평가에서 가장 기본적인 절차가 UI 요소를 구분할 수 있는 이름이 적절한지 확인하는 것입니다.

    public int NonameCount
    {
        get
        {
            int count = 0;

테이블의 행 목록에서 이름 정보가 빈 것을 카운팅하여 반환합니다.

            foreach (DataRow dr in Table.Rows)
            {
                if (dr[(int)ENUM_UIProperty.NAME].ToString() == string.Empty){   count++;   }
            }
            return count;
        }
    }

Access 키 정보를 기록하는 메서드입니다.

    private void WriteAccessKey(XmlWriter writer)
    {
        writer.WriteStartElement("Access키");
        List<string[]> keylist = new List<string[]>();

먼저 Access 키를 갖고 있는 목록을 구합니다. 이 부분은 GetAccessKeyItemList 메서드로 구현합시다. GetAccessItemList 메서드에서는 키와 쌍을 원소로 하는 문자열 배열을 목록화한 컬렉션을 반환하게 구현할 것입니다.

        keylist = GetAccessKeyItemList();
        foreach (string[] key_value in keylist)
        {

각 목록의 키와 값을 기록합니다. 주의할 점은 요소 이름으로 (나 )와 같은 기호들은 사용할 수 없습니다. 따라서 문자열을 필터링해야 하는데 이 부분도 ConvertString 메서드를 별도로 정의하기로 할게요.

            writer.WriteElementString(ConvertString(key_value[0]), ConvertString(key_value[1]));
        }
        writer.WriteEndElement();
    }
    public List<string[]> GetAccessKeyItemList()
    {

먼저 반환할 키와 값을 원소로 하는 문자열 배열 개체을 목록화할 컬렉션 개체를 생성합니다.

        List<string[]> item_list = new List<string[]>();

테이블에 기록한 정보를 이용할 것입니다.

        DataTable dt = Table;
        EHAutoElem eae = null;

테이블의 행 항목마다 반복적인 작업을 수행합니다.

        foreach (DataRow dr in dt.Rows)
        {

EHAutoElem 쉘의 개체를 참조합니다.

            eae = dr["EHAutoElem"] as EHAutoElem;
            if (eae.AE.Current.AccessKey != string.Empty)
            {

만약 개체의 AccessKey가 빈 문자열이 아니면 키와 값을 문자열 배열에 설정하여 컬렉션에 추가합니다.

                string[] name_key = new string[2];
                name_key[0] = eae.ToString();
                name_key[1] = eae.AE.Current.AccessKey;
                item_list.Add(name_key);
            }
        }
        return item_list;
    }

특수 기호를 필터링하는 메서드를 구현합시다.

    private string ConvertString(string str)
    {

먼저 문자열 길이를 구합니다.

        int max = str.Length;
        for(int i = 0; i<max;i++)
        {
            if(char.IsLetterOrDigit(str[i])==false)
            {

반복해서 알파벳과 숫자 문자가 아닌 것을 발견하면 해당 요소를 제거합니다.

                str = str.Remove(i,1);
                max = str.Length;
                i--;
            }
        }

변환한 문자열을 반환합니다.

        return str;
    }
    private void WriteAcceleratorKey(XmlWriter writer)
    {

Accelerator 키의 정보를 기록하는 메서드도 Access 키를 기록하는 메서드와 원리가 같습니다.

        writer.WriteStartElement("Accelerator키");
        List<string[]> keylist = new List<string[]>();
        keylist = GetAcceleratorKeyItemList();
        foreach (string[] key_value in keylist)
        {
            writer.WriteElementString(ConvertString(key_value[0]), key_value[1]);
        }
        writer.WriteEndElement();
    }
    internal List<string[]> GetAcceleratorKeyItemList()
    {

Accelerator 키 항목을 구하는 메서드도 Access 키 항목을 구하는 메서드와 논리가 같습니다.

        List<string[]> item_list = new List<string[]>();
        ...중략...
        return item_list;
    }
    private void WriteControlCount(XmlWriter writer)
    {

컨트롤 유형 별 개수를 기록하는 메서드에서는 컨트롤 패턴 별로 개수를 구한 후에 이를 기록합니다.

        writer.WriteStartElement("컨트롤_유형_별_개수");
        for (ENUM_CONTROL ctrl = 0; ctrl < ENUM_CONTROL.MAX_CONTROL; ctrl++)
        {
            int count = GetControlPatternCount(ctrl);
            writer.WriteElementString(ctrl.ToString(), count.ToString());
        }
        writer.WriteEndElement();
    }
    public int GetControlPatternCount(ENUM_CONTROL ctrl)
    {

컨트롤 패턴 별 개수를 세기위한 변수를 선언합니다.

        int count = 0;

테이블의 행에서 NULL이 아닌 개체의 수를 구하여 반환합니다.

        DataTable dt = Table;
        foreach (DataRow dr in dt.Rows)
        {
            if (dr[ctrl.ToString()] != DBNull.Value){    count++;    }
        }
        return count;
    }
    private void WriteUIElement(XmlWriter writer)
    {

모든 UI 요소 정보를 기록하는 메서드에서는 테이블의 행 의 각 UI 요소 정보를 기록합니다.

        writer.WriteStartElement("UI_요소_목록");
        foreach (DataRow dr in Table.Rows)
        {

UI 요소 정보를 기록하는 메서드는 별도로 작성합시다.

            WriteUIInfo(writer, dr);
        }
        writer.WriteEndElement();
    }
    private void WriteUIInfo(XmlWriter writer, DataRow dr)
    {

속성 열거형의 마지막 열거값까지 반복하여 UI 요소의 속성 정보를 기록합니다. 앞에서 열거형을 정의한 이유가 이와 같은 기능을 간결하게 표현하기 위함입니다.

        writer.WriteStartElement("UI요소");
        for (ENUM_UIProperty eup = 0; eup < ENUM_UIProperty.MAX_UIPROPERTY; eup++)
        {
            string pname = eup.ToString();
            string value = ConvertString(dr[eup.ToString()].ToString());
            writer.WriteElementString(pname,value);
        }
        writer.WriteEndElement();
    }
    public void SaveImageAll(string dir)
    {

모든 이미지를 저장하는 메서드에서는 원본 이미지들을 저장하는 메서드와 흑백 이미지들을 저장하는 메서드를 호출합니다.

        SaveImage(dir);
        SaveGrayImage(dir);
    }
    public void SaveImage(string dir)
    {

현재 디렉토리를 기억해 두고 입력 인자로 받은 디렉토리로 현재 디렉토리를 설정합니다.

        string cdir = Directory.GetCurrentDirectory();
        Directory.SetCurrentDirectory(dir);

이미지 디렉토리를 생성합시다.

        Directory.CreateDirectory("이미지");

이미지 파일 이름은 일련 번호를 부여합시다.

        int fname_index = 1;
        foreach (TreeNode tn in node_dic.Values)
        {

사전 개체의 각 트리 노드마다 다음을 반복합니다. 트리 노드의 Tag에는 EHAutoElem 개체를 설정해 두었습니다.

            EHAutoElem eae = tn.Tag as EHAutoElem;
            if (eae.Image != null)
            {

EHAutoElem 개체의 이미지를 저장합니다.

                eae.Image.Save("이미지/"+fname_index.ToString() + ".bmp");
            }
            fname_index++;
        }

기억해 두었던 디렉토리로 현재 디렉토리를 설정합니다.

        Directory.SetCurrentDirectory(cdir);
    }
    public void SaveGrayImage(string dir)
    {

흑백 이미지를 저장하는 원리도 같습니다.

        string cdir = Directory.GetCurrentDirectory();
        Directory.SetCurrentDirectory(dir);
        Directory.CreateDirectory("흑백이미지");
        ...중략...
    }
}
using System;
using System.Data;
using System.Windows.Automation;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Threading;
using System.Drawing;
using System.IO;
using System.Xml;

namespace 접근성_평가_도우미
{
    public delegate void AddFindElementDele(int cnt);
    public class AccEvalProject
    {
        Dictionary<string, TreeNode> node_dic = null;
        public event EventHandler EndEvalProject = null;
        public event AutomationPropertyChangedEventHandler AEMoved = null;
        AutomationPropertyChangedEventHandler apceh = null;
        AutomationEventHandler close_eventHandler = null;
        AutomationElement mae = null;

        public string Title{    get;    private set;    }
        public int UICount
        {
            get
            {
                return Table.Rows.Count;
            }
        }
        public DataTable Table{    get;    private set;    }
        public EHProcess EHProcess{    get;    private set;    }
        public IntPtr MainWndHandle
        {
            get{    return EHProcess.MainHandle;    }
        }
        public TreeNode Root{    get;    private set;    }
        public Image Image{    get;    private set;    }
        public TreeNode this[string key]
        {
            get
            {
                return node_dic[key];
            }
        }
        internal AccEvalProject(string title, DataTable dt, EHProcess ehprocess)
        {
            Title = title;
            Table = dt;
            EHProcess = ehprocess;
            node_dic = new Dictionary<string, TreeNode>();
        }

        public void InitProcessMode(AddFindElementDele find_dele)
        {
            mae = AutomationElement.FromHandle(MainWndHandle);
            apceh = new AutomationPropertyChangedEventHandler(OnPropertyChange);
            Automation.AddAutomationPropertyChangedEventHandler(mae, 
               TreeScope.Element, apceh, AutomationElement.BoundingRectangleProperty);
            close_eventHandler = new AutomationEventHandler(OnWindowClose);
            Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, 
                              mae, TreeScope.Element, close_eventHandler);
            FindAETree(mae, find_dele);
        }

        private void OnPropertyChange(object src, AutomationPropertyChangedEventArgs e)
        {
            AutomationElement sourceElement = src as AutomationElement;
            if (e.Property == AutomationElement.BoundingRectangleProperty)
            {
                if (AEMoved != null)
                {
                    AEMoved(this, e);
                }
            }
        }
        private void OnWindowClose(object src, AutomationEventArgs e)
        {
            try
            {
                AutomationElement ae = src as AutomationElement;
            }
            catch{    return;    }
            End();
        }
        private void FindAETree(AutomationElement ae, AddFindElementDele find_dele)
        {
            try
            {
                ae.SetFocus();
                Thread.Sleep(100);
            }
            catch
            {
                try
                {
                    AutomationElement sae=ae.FindFirst(TreeScope.Subtree,
                                   new PropertyCondition
                                   (AutomationElement.IsKeyboardFocusableProperty, true));
                    sae.SetFocus();
                    Thread.Sleep(100);
                }
                catch
                {
                    MessageBox.Show(
                        "선택한 윈도우에 초점을 가질 수 있는 요소가 없습니다.");
                }
            }
            try
            {
                Image = ImageCapture.CaptureFormRect(ae.Current.BoundingRectangle);
            }
            catch { }

            if (find_dele != null)
            {
                find_dele(1);
            }
            EHAutoElem eae = new EHAutoElem(ae);
            Root = MakeUIHierarchy(eae, find_dele);
        }

        TreeNode MakeUIHierarchy(EHAutoElem eae, AddFindElementDele find_dele)
        {
            TreeNode sr = new TreeNode(eae.ToString());
            string ehid = eae.GetEHID();
            if (node_dic.ContainsKey(ehid) == false)
            {
                node_dic[ehid] = sr;
                sr.Tag = eae;
                eae.Tag = sr;
                FindedAutoElement(null, new FindAutoElemEventArgs(eae));
            }
            else{    return null;    }
            Condition con = TreeWalker.RawViewWalker.Condition;
            AutomationElement ae = eae.AE;
            AutomationElementCollection aec = ae.FindAll(TreeScope.Children, con);
            if (find_dele != null)
            {
                find_dele(aec.Count);
            }
            foreach (AutomationElement sae in aec)
            {
                TreeNode tr = null;
                if ((tr = MakeUIHierarchy(new EHAutoElem(sae), find_dele)) != null)
                {
                    sr.Nodes.Add(tr);
                }
            }
            return sr;
        }

        void FindedAutoElement(object send, FindAutoElemEventArgs e)
        {
            EHAutoElem eae = e.EAE;
            if (Table == null){    return;    }
            DataRow dr = Table.NewRow();
            for (ENUM_UIProperty uip = 0; uip < ENUM_UIProperty.MAX_UIPROPERTY; uip++)
            {
                dr[uip.ToString()] = eae.GetAEProperty(uip);
            }
            for (ENUM_CONTROL ctrl = 0; ctrl < ENUM_CONTROL.MAX_CONTROL; ctrl++)
            {
                dr[ctrl.ToString()] = eae.GetPattern(ctrl);
            }
            dr["EHAutoElem"] = eae;
            Table.Rows.Add(dr);
        }
        public void End()
        {
            if (EndEvalProject != null){    EndEvalProject(this, new EventArgs());    }
            Automation.RemoveAutomationPropertyChangedEventHandler(mae, apceh);
            Automation.RemoveAutomationEventHandler(
                      WindowPattern.WindowClosedEvent, mae, close_eventHandler);
        }
        public override string ToString(){    return Title;    }
        public void Save(string path)
        {
            string dirname = path + "/" + Table.TableName;
            if (Directory.Exists(dirname))
            {
                int i = 1;
                dirname = path + "/" + Table.TableName + "_" + i.ToString();
                while (Directory.Exists(dirname))
                {
                    i++;
                    dirname = path + "/" + Table.TableName +"_" + i.ToString();
                }
            }
            Directory.CreateDirectory(dirname);
            SaveReport(dirname);
            SaveImageAll(dirname);
        }
        public void SaveReport(string dir)
        {
            string cdir = Directory.GetCurrentDirectory();
            Directory.SetCurrentDirectory(dir);
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.OmitXmlDeclaration = false;
            settings.CheckCharacters = false;
            settings.Indent = true;
            settings.Encoding = System.Text.Encoding.Default;
            XmlWriter writer = XmlWriter.Create(dir + "/report.xml",settings);
            writer.WriteStartElement("Report");
            writer.WriteElementString("Title", Title);
            writer.WriteElementString("전체_개수", UICount.ToString());
            writer.WriteElementString("이름없는_UI_요소_개수", NonameCount.ToString());                        
            WriteControlCount(writer);
            WriteAcceleratorKey(writer);
            WriteAccessKey(writer);
            WriteUIElement(writer);
            writer.WriteEndElement();
            writer.Close();
            Directory.SetCurrentDirectory(cdir);
        }
        public int NonameCount
        {
            get
            {
                int count = 0;
                foreach (DataRow dr in Table.Rows)
                {
                    if (dr[(int)ENUM_UIProperty.NAME].ToString() == string.Empty){ count++; }
                }
                return count;
            }
        }

        private void WriteAccessKey(XmlWriter writer)
        {
            writer.WriteStartElement("Access키");
            List<string[]> keylist = new List<string[]>();
            keylist = GetAccessKeyItemList();
            foreach (string[] key_value in keylist)
            {
                writer.WriteElementString(ConvertString(key_value[0]), 
                                       ConvertString(key_value[1]));
            }
            writer.WriteEndElement();
        }
        public List<string[]> GetAccessKeyItemList()
        {
            List<string[]> item_list = new List<string[]>();
            DataTable dt = Table;
            EHAutoElem eae = null;
            foreach (DataRow dr in dt.Rows)
            {
                eae = dr["EHAutoElem"] as EHAutoElem;
                if (eae.AE.Current.AccessKey != string.Empty)
                {
                    string[] name_key = new string[2];
                    name_key[0] = eae.ToString();
                    name_key[1] = eae.AE.Current.AccessKey;
                    item_list.Add(name_key);
                }
            }
            return item_list;
        }
        private string ConvertString(string str)
        {
            int max = str.Length;
            for (int i = 0; i < max; i++)
            {
                if (char.IsLetterOrDigit(str[i]) == false)
                {
                    str = str.Remove(i, 1);
                    max = str.Length;
                    i--;
                }
            }
            return str;
        }

        private void WriteAcceleratorKey(XmlWriter writer)
        {
            writer.WriteStartElement("Accelerator키");
            List<string[]> keylist = new List<string[]>();
            keylist = GetAcceleratorKeyItemList();
            foreach (string[] key_value in keylist)
            {
                writer.WriteElementString(ConvertString(key_value[0]), key_value[1]);
            }
            writer.WriteEndElement();
        }

        internal List<string[]> GetAcceleratorKeyItemList()
        {
            List<string[]> item_list = new List<string[]>();
            DataTable dt = Table;
            EHAutoElem eae = null;
            foreach (DataRow dr in dt.Rows)
            {
                eae = dr["EHAutoElem"] as EHAutoElem;
                if (eae.AE.Current.AcceleratorKey != string.Empty)
                {
                    string[] name_key = new string[2];
                    name_key[0] = eae.ToString();
                    name_key[1] = eae.AE.Current.AcceleratorKey;
                    item_list.Add(name_key);
                }
            }
            return item_list;
        }

        private void WriteControlCount(XmlWriter writer)
        {
            writer.WriteStartElement("컨트롤_유형_별_개수");

            for (ENUM_CONTROL ctrl = 0; ctrl < ENUM_CONTROL.MAX_CONTROL; ctrl++)
            {
                int count = GetControlPatternCount(ctrl);
                writer.WriteElementString(ctrl.ToString(), count.ToString());
            }
            writer.WriteEndElement();
        }

        public int GetControlPatternCount(ENUM_CONTROL ctrl)
        {
            int count = 0;
            DataTable dt = Table;

            foreach (DataRow dr in dt.Rows)
            {
                if (dr[ctrl.ToString()] != DBNull.Value)
                {
                    count++;
                }
            }
            return count;
        }

        private void WriteUIElement(XmlWriter writer)
        {
            writer.WriteStartElement("UI_요소_목록");

            foreach (DataRow dr in Table.Rows)
            {
                WriteUIInfo(writer, dr);
            }
            writer.WriteEndElement();
        }

        private void WriteUIInfo(XmlWriter writer, DataRow dr)
        {
            writer.WriteStartElement("UI요소");

            for (ENUM_UIProperty eup = 0; eup < ENUM_UIProperty.MAX_UIPROPERTY; eup++)
            {
                string pname = eup.ToString();
                string value = ConvertString(dr[eup.ToString()].ToString());
                writer.WriteElementString(pname,value);
            }
            writer.WriteEndElement();
        }

        public void SaveImageAll(string dir)
        {
            SaveImage(dir);
            SaveGrayImage(dir);
        }

        public void SaveImage(string dir)
        {
            string cdir = Directory.GetCurrentDirectory();
            Directory.SetCurrentDirectory(dir);
            Directory.CreateDirectory("이미지");
            int fname_index = 1;

            foreach (TreeNode tn in node_dic.Values)
            {
                EHAutoElem eae = tn.Tag as EHAutoElem;
                if (eae.Image != null)
                {
                    eae.Image.Save("이미지/"+fname_index.ToString() + ".bmp");                    
                }
                fname_index++;
            }
            Directory.SetCurrentDirectory(cdir);
        }

        public void SaveGrayImage(string dir)
        {
            string cdir = Directory.GetCurrentDirectory();
            Directory.SetCurrentDirectory(dir);
            Directory.CreateDirectory("흑백이미지");
            int fname_index = 1;

            foreach (TreeNode tn in node_dic.Values)
            {
                EHAutoElem eae = tn.Tag as EHAutoElem;
                if (eae.Image != null)
                {
                    eae.GrayImage.Save("흑백이미지/" + fname_index.ToString() + ".bmp");
                }
                fname_index++;
            }
            Directory.SetCurrentDirectory(cdir);
        }
    }
}

[소스 10.7] AccEvalProject.cs