프로그래밍 기술/소프트웨어 접근성, UI 자동화

[S/W 접근성] 테이블 정보 탐색기 실습

언제나휴일 2016. 4. 19. 16:02
반응형

테이블 정보 탐색기 실습 



테이블 정보 탐색기 솔루션(Visual Studio 2010).zip


 이번에는 TablePattern을 이용하여 실행하고 있는 다른 프로그램의 테이블 정보를 얻어오는 프로그램을 작성하는 실습을 진행할게요.

 

 실습에서는 간단한 회원 정보를 관리하는 타겟 데모를 만들고 테이블 정보를 탐색하는 탐색기 프로그램을 만듭시다.


테이블 정보 탐색기 실습 시연화면

[그림] 시연화면

(인덱스가 0부터 시작합니다. 그리고 행과 열이 반대로 표시하고 있습니다. 데이터가 없을 때 예외는 처리하지 않았습니다.)


 먼저 데모 타겟을 Windows Forms 응용 프로그램 프로젝트로 생성하세요. 그리고 컨트롤을 배치합니다.

 [그림] 데모 타켓 컨트롤 배치

 

번호

컨트롤 타입

컨트롤 명

설명

1

ListView

lv_member

회원 정보 리스트 뷰

2

ColumnHeader

ch_id

아이디 컬럼 헤더

3

ColumnHeader

ch_name

이름 컬럼 헤더

4

TextBox

tbox_id

아이디 입력 텍스트 박스

5

TextBox

tbox_name

이름 입력 텍스트 박스

6

Button

btn_add

회원 추가 버튼

[] Form1의 컨트롤 배치

 

 회원 추가 버튼 클릭 이벤트 핸들러를 추가하여 입력 텍스트 상자의 정보로 회원 리스트 뷰에 항목 추가하는 코드를 작성하세요. 데모 타겟은 실습을 위한 더미 프로젝트이므로 다른 기능은 구현하지 않기로 합시다.

private void btn_add_Click(object sender, EventArgs e)

{

    string[] strs = new string[] { tbox_id.Text, tbox_name.Text };

    lv_member.Items.Add(new ListViewItem(strs));

    tbox_id.Text = tbox_name.Text = string.Empty;

}

 

using System;

using System.Windows.Forms;

 

namespace 데모_타겟

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

 

        private void btn_add_Click(object sender, EventArgs e)

        {

            string[] strs = new string[] { tbox_id.Text, tbox_name.Text };

            lv_member.Items.Add(new ListViewItem(strs));

            tbox_id.Text = tbox_name.Text = string.Empty;

        }

    }

}

[소스 5.5] 데모 타겟의 Form1.cs

 

 테이블 정보 탐색기 프로젝트를 추가하세요. 그리고 컨트롤을 배치합시다.


테이블 정보 탐색기 컨트롤 배치

[그림] 테이블 정보 탐색기 컨트롤 배치

 

번호

컨트롤 타입

컨트롤 명

설명

1

Button

btn_target_start

타켓 프로그램 실행 버튼

2

Label

lb_msg

메시지 표시

3

Label

lb_countdown

대기 시간 표시

4

NumericUpdown

nud_col

행 선택

5

NumericUpdown

nud_row

열 선택

6

Label

lb_header

헤더 정보 표시

7

Label

lb_shell

셀 정보 표시

8

Button

btn_refresh

, 열 정보 새로 고침

9

Button

btn_header

헤더 정보 가져오기

10

Button

btn_shell

셀 내용 가져오기(헤더 정보 포함)

[] 테이블 정보 탐색기의 Form1 컨트롤 배치



UI 자동화 기술에 관한 .NET 어셈블리를 참조 추가하세요. (솔루션 탐색기의 프로젝트 참조 노드에서 마우스 오른쪽 버튼을 클릭하여 컨텍스트 메뉴의 참조 추가를 선택하세요.)

 UI 자동화 기술에 관한 .NET 어셈블리 참조 추가

 [그림] UI 자동화 기술에 관한 .NET 어셈블리 참조 추가


 멤버 필드로 데모 타겟 프로그램의 실행 파일명을 설정합니다.

string target = "타겟 데모.exe";

 데모 타겟 프로그램이 실행하여 Main 윈도우가 뜨고 난 후에 자동화 요소를 수집할 것입니다. 차후에는 자동화 이벤트를 이용하지만 여기에서는 의도적으로 10초간 대기하기로 할게요. 이를 위해 카운트 다운하기 위한 멤버 필드를 선언하고 10으로 초기화합니다.

int cd_value=10;

 

 타겟 프로그램의 프로세스 개체를 참조할 멤버 필드를 선언합니다.

Process process=null;

 제어할 컨트롤의 RangeValuePattern 개체를 참조할 멤버 필드를 선언합니다.

TablePattern tp=null;

 데모 타겟 프로그램 실행 버튼의 클릭 이벤트 핸들러를 추가합시다.

private void btn_target_start_Click(object sender, EventArgs e)

{

 타겟 프로세스를 시작합니다.

    process = Process.Start(target);

 그리고 카운트 다운 멤버 필드 값을 10으로 설정한 후 타이머를 가동합니다.

    cd_value = 10;

    timer1.Start();

 카운트 다운 시간과 대기 메시지를 설정하고 버튼을 비활성화합니다.

    lb_countdown.Text = cd_value.ToString();

    lb_msg.Text = "잠시 대기해 주세요.";

    btn_target_start.Enabled = false;

}

 타이머 이벤트 핸드러를 등록합니다.

private void timer1_Tick(object sender, EventArgs e)

{

 타이머 이벤트 핸들러에서는 카운트 다운을 감소하고 이를 표시하는 작업을 수행합니다.

    cd_value--;

    lb_countdown.Text = cd_value.ToString();

    if (cd_value == 0)

    {

 만약 카운트 다운 타밍이 만료하면 타이머를 멈추고 메시지를 빈 문자열로 설정합니다. 그리고 RangeValuePattern 의 자동화 요소를 탐색합니다. 탐색 부분은 메서드로 정의하여 사용합시다.

        timer1.Stop();

        lb_msg.Text = string.Empty;

        lb_countdown.Text = string.Empty;

        SearchTablePattern();

    }

}

SearchTablePattern 을 탐색하는 메서드를 정의합시다.

private void SearchTablePattern()

{

 만약 프로세스 개체가 null이거나 Main 창의 Handle Zero일 때는 제대로 프로세스를 동작하지 않거나 아직 메인 창이 뜨지 않을 때입니다. 이 때는 오류 메시지를 메시지 창을 통해 사용자에게 알려줍시다.

    if ( (process == null)||(process.MainWindowHandle == IntPtr.Zero) )

    {

        MessageBox.Show("죄송합니다. 다시 실행해 주세요.");

        btn_target_start.Enabled =false;

        return;

    }

 이제 프로세스의 Main Handle을 인다로 자동화 요소를 참조합니다. 이를 위해 자동화 기술에서는 AutomationElement 형식에 정적 메서드 FromHandle을 제공하고 있습니다.

    AutomationElement ae = AutomationElement.FromHandle(process.MainWindowHandle);

 탐색 조건 개체를 생성합니다. 여기에서는 TablePattern을 찾습니다. 자동화 기술에서는 자동화 요소의 정적 속성 IsTablePatternAvailableProperty를 제공하여 조건 개체를 만들 때 인자로 사용할 수 있습니다.

    Condition cond = new PropertyCondition(

              AutomationElement.IsTablePatternAvailableProperty, true);

 자동화 요소의 FindAll 멤버 메서드에 탐색 범위를 서브 트리로 지정하고 앞에서 생성한 조건 개체를 전달합니다. FindAll 멤버 메서드에서는 조건에 맞는 개체들을 찾아 컬렉션을 반환합니다.

     AutomationElementCollection aec = ae.FindAll(TreeScope.Subtree, cond);

 

    try

    {

 이번 실습에서는 타겟 프로그램에 의도적으로 TablePattern인 컨트롤을 하나 추가하였습니다. 컬렉션에 있는 개체를 전수 조사하지 않고 인덱스 0에 있는 개체에 직접 접근하기로 할게요.

 

 탐색 결과로 받은 자동화 요소 컬렉션의 인덱스 0에 있는 개체가 TablePattern으로 참조합니다. 이를 위해 자동화 요소 개체에서는 GetCurrentPattern 메서드를 통해 컨트롤 패턴 개체를 참조할 수 있게 기능을 제공합니다. 원하는 패턴을 접근하기 위해 TablePattern의 정적 속성인 Pattern을 입력 인자로 사용합니다. 그리고 실제 자동화 요소 개체가 요청한 컨트롤 패턴이 아닐 수 있기 때문에 as 연산을 통해 참조합니다.

        tp = aec[0].GetCurrentPattern(TablePattern.Pattern) as TablePattern;

 그리고 테이블 패턴의 정보를 구하여 컨트롤의 속성을 설정합니다. 이 부분은 별도의 메서드로 작성합시다.

        GetTPInfo();

    }

    catch { }

}

 

 새로 고침 버튼 클릭 이벤트 핸들러를 등록하여 마찬가지로 테이블 패턴의 정보를 다시 구하여 컨트롤의 속성을 설정합니다. 이 부분은 GetTPInfo 메서드로 구현할 부분이므로 단순 호출 구문을 작성합니다.

private void btn_refresh_Click(object sender, EventArgs e)

{

    GetTPInfo();

}

 

 테이블 패턴의 정보를 구하여 컨트롤 속성을 설정하는 GetTPInfo 메서드를 구현합시다.

private void GetTPInfo()

{

 컬럼 수와 수가 0보다 때만 설정할게요.

    if((tp.Current.ColumnCount>0)&&(tp.Current.RowCount>0))

    {

         nud_col.Maximum = tp.Current.ColumnCount - 1;

         nud_row.Maximum = tp.Current.RowCount - 1;

         nud_col.Enabled = nud_row.Enabled = true;

    }

}

 헤더 버튼 클릭 이벤트 핸들러를 추가합니다.

private void btn_header_Click(object sender, EventArgs e)

{

 선택한 번호를 얻어와서 헤더 정보를 얻어옵니다. 헤더 정보를 얻어와서 컨트롤의 속성을 설정하는 기능은 정보를 구하는 부분에서도 필요하므로 별도의 메서드로 정의합시다.

    int col = (int)nud_col.Value;

    GetHeaderInfo(col);

}

 

 행의 헤더 정보를 얻어와서 컨트롤의 속성을 설정하는 GetHeaderInfo 메서드를 구현합시다.

private void GetHeaderInfo(int col)

{

 테이블 패턴 개체의 Current 속성의 GetColumnHeaders 메서드로 자동화 요소 컬렉션을 얻어옵니다. 입력 인자로 받은 번호로 자동화 요소 이름을 얻어와 레이블에 표시합니다.

    AutomationElement[] aec = tp.Current.GetColumnHeaders();

    lb_header.Text = aec[col].Current.Name;

}

 

  정보 가져오기 버튼 클릭 이벤트 핸들러를 등록하세요.

private void btn_shell_Click(object sender, EventArgs e)

{

 선택한 행과 열 번호를 얻어옵니다.

    int col = (int)nud_col.Value;

    int row = (int)nud_row.Value;

 먼저 헤더 정보를 구하여 컨트롤 속성에 표시하는 GetHeaderInfo 메서드를 호출합니다.

    GetHeaderInfo(col);

 테이블 패턴 개체의 GetItem 메서드에 열과 행 번호를 전달하여 자동화 요소를 구합니다.

    AutomationElement ae = tp.GetItem(row, col);

 그리고 얻어온 자동화 요소의 이름으로 레이블에 표시합니다.

    lb_shell.Text = ae.Current.Name;

}

 

using System;

using System.Windows.Forms;

using System.Diagnostics;

using System.Windows.Automation;

namespace 테이블_정보_탐색기

{

    public partial class Form1 : Form

    {

        string target = "데모 타겟.exe";

        int cd_value = 10;

        Process process = null;

        TablePattern tp = null;

        public Form1()

        {

            InitializeComponent();

        }

        private void btn_target_start_Click(object sender, EventArgs e)

        {

            process = Process.Start(target);

            cd_value = 10;

            timer1.Start();

            lb_countdown.Text = cd_value.ToString();

            lb_msg.Text = "잠시 대기해 주세요.";

            btn_target_start.Enabled = false;

        }

        private void timer1_Tick(object sender, EventArgs e)

        {

            cd_value--;

            lb_countdown.Text = cd_value.ToString();

            if (cd_value == 0)

            {

                timer1.Stop();

                lb_msg.Text = lb_countdown.Text = string.Empty;

                SearchTablePattern();

            }

        }

        private void SearchTablePattern()

        {

            if ((process == null) || (process.MainWindowHandle == IntPtr.Zero))

            {

                MessageBox.Show("죄송합니다. 다시 실행해 주세요.");

                btn_target_start.Enabled = false;

                return;

            }

            AutomationElement ae = AutomationElement.FromHandle(

                                          process.MainWindowHandle);

            Condition cond = new PropertyCondition(

                     AutomationElement.IsTablePatternAvailableProperty, true);

            AutomationElementCollection aec = ae.FindAll(TreeScope.Subtree, cond);

            try

            {

                tp = aec[0].GetCurrentPattern(TablePattern.Pattern) as TablePattern;

                GetTPInfo();

            }

            catch { }

        }

        private void btn_refresh_Click(object sender, EventArgs e)

        {

            GetTPInfo();

        }

        private void GetTPInfo()

        {

            if((tp.Current.ColumnCount>0)&&(tp.Current.RowCount>0))

            {

                nud_col.Maximum = tp.Current.ColumnCount - 1;

                nud_row.Maximum = tp.Current.RowCount - 1;

                nud_col.Enabled = nud_row.Enabled = true;

            }

        }

        private void btn_header_Click(object sender, EventArgs e)

        {

            int col = (int)nud_col.Value;

            GetHeaderInfo(col);

        }

        private void GetHeaderInfo(int col)

        {

            AutomationElement[] aec = tp.Current.GetColumnHeaders();

            lb_header.Text = aec[col].Current.Name;

        }

        private void btn_shell_Click(object sender, EventArgs e)

        {

            int col = (int)nud_col.Value;

            int row = (int)nud_row.Value;

            GetHeaderInfo(col);

            AutomationElement ae = tp.GetItem(row, col);

            lb_shell.Text = ae.Current.Name;

        }

    }

}

[소스 5.6] 테이블 정보 탐색기의 Form1.cs


반응형