16) 실습: 테이블 정보 탐색기

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

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

[그림 5.7] 시연화면
[그림 5.7] 시연화면

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

[그림 5.8] 데모 타켓 컨트롤 배치
[그림 5.8] 데모 타켓 컨트롤 배치
번호컨트롤 타입컨트롤 명설명
1ListViewlv_member회원 정보 리스트 뷰
2ColumnHeaderch_id아이디 컬럼 헤더
3ColumnHeaderch_name이름 컬럼 헤더
4TextBoxtbox_id아이디 입력 텍스트 박스
5TextBoxtbox_name이름 입력 텍스트 박스
6Buttonbtn_add회원 추가 버튼

[표 5.22] 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

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

[그림 5.9] 테이블 정보 탐색기 컨트롤 배치
[그림 5.9] 테이블 정보 탐색기 컨트롤 배치
번호컨트롤 타입컨트롤 명설명
1Buttonbtn_target_start타켓 프로그램 실행 버튼
2Labellb_msg메시지 표시
3Labellb_countdown대기 시간 표시
4NumericUpdownnud_row행 선택
5NumericUpdownnud_col열 선택
6Labellb_header헤더 정보 표시
7Labellb_shell셀 정보 표시
8Buttonbtn_refresh행, 열 정보 새로 고침
9Buttonbtn_header헤더 정보 가져오기
10Buttonbtn_shell셀 내용 가져오기(헤더 정보 포함)

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

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

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 = true;
}

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

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();
    try
    {
        lb_header.Text = aec[col].Current.Name;
    }
    catch
    {
        lb_header.Text = "알 수 없음";
    }
}

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

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