접근성 평가 프로젝트의 자동화 요소를 조사하고 관리하는 AccEvalProject 클래스를 정의합시다. 이 부분이 전체 프로그램의 핵심적인 역할을 하는 부분입니다.
자동화 요소를 발견할 때 발견한 개수를 이벤트로 전달하기 위한 간단한 대리자를 정의합시다.
1 2 3 |
public delegate void AddFindElementDele(int cnt); public class AccEvalProject { |
AccEvalProject에서는 자동화 요소의 계층 구조를 파악할 수 있어야 합니다. 이를 위해 사전 컬렉션을 멤버로 선언합시다.
1 |
Dictionary<string, TreeNode> node_dic = null; |
평가가 끝났을 때 이를 구동하는 이벤트 핸들러를 등록하기 위한 이벤트를 선언합니다.
1 |
public event EventHandler EndEvalProject = null; |
자동화 속성이 이동할 때 프로그램 방식의 하이라이트도 이동해야 합니다. 이를 위한 이벤트를 선언합니다.
1 |
public event AutomationPropertyChangedEventHandler AEMoved = null; |
자동화 속성 변경 이벤트 핸들러와 닫힐 때 처리를 위한 이벤트 핸들러 멤버를 선언합니다.
1 2 |
AutomationPropertyChangedEventHandler apceh = null; AutomationEventHandler close_eventHandler = null; |
최상위 자동화 요소를 기억할 멤버를 선언합시다.
1 |
AutomationElement mae = null; |
평가 프로젝트 제목을 속성으로 제공합시다.
1 |
public string Title{ get; private set; } |
자동화 요소 개수를 속성으로 제공합시다.
1 2 3 4 |
public int UICount { get{ return Table.Rows.Count; } } |
평가 테이블을 속성으로 제공합시다. 평가 테이블은 평가 내용을 XML로 저장하기 위한 개체로 XML로 기록하는 메서드를 제공하는 ADO.NET 기술의 클래스 DataTable 형식을 사용합시다.
1 |
public DataTable Table{ get; private set; } |
평가 대상 프로세스도 속성으로 제공합니다.
1 |
public EHProcess EHProcess{ get; private set; } |
평가 대상 프로세스의 메인 창의 윈도우 핸들도 속성으로 제공합시다.
1 2 3 4 |
public IntPtr MainWndHandle { get{ return EHProcess.MainHandle; } } |
계층화 구조의 루트 노드를 속성으로 제공합시다.
1 |
public TreeNode Root{ get; private set; } |
대상 프로세스의 메인 창의 이미지도 속성으로 제공합시다.
1 |
public Image Image{ get; private set; } |
사전 개체에서 키로 트리 노드를 가져오기 할 수 있게 속성을 제공합시다.
1 2 3 4 |
public TreeNode this[string key] { get{ return node_dic[key]; } } |
생성자 메서드에서는 프로젝트 이름, 데이터 테이블, 평가 대상 프로세스 개체를 입력 인자로 받습니다.
1 2 |
internal AccEvalProject(string title, DataTable dt, EHProcess ehprocess) { |
생성자에서는 입력 인자로 속성을 설정합니다.
1 2 3 |
Title = title; Table = dt; EHProcess = ehprocess; |
그리고 계층화 정보에 사용할 트리 노드 개체를 기억할 사전 개체를 생성합니다.
1 2 |
node_dic = new Dictionary<string, TreeNode>(); } |
프로세스 모드를 초기화하는 메서드를 제공합시다. 이번 프로젝트는 프로세스 모드만 제공하지만 보다 효과적인 평가 도구로 만들려면 포커스 모드와 창 모드 등을 제공하는 것이 좋습니다.
1 2 |
public void InitProcessMode(AddFindElementDele find_dele) { |
프로세스의 메인 창의 자동화 요소를 찾습니다.
1 |
mae = AutomationElement.FromHandle(MainWndHandle); |
속성 변경 이벤트 핸들러를 생성합니다.
1 |
apceh = new AutomationPropertyChangedEventHandler(OnPropertyChange); |
메인 창의 사각 영역 속성이 변하는 것을 알기 위해 이벤트 핸들러를 등록합니다. 이를 통해 창의 위치가 바뀌어도 프로그램 방식의 하이라이트가 유효한 위치에 표시할 수 있습니다.
1 2 |
Automation.AddAutomationPropertyChangedEventHandler( mae, TreeScope.Element, apceh, AutomationElement.BoundingRectangleProperty); |
창의 닫히는 시점을 알기 위해 이벤트 핸들러를 등록합시다. 여기에서는 평가 대상 프로젝트의 메인 창이 닫히면 프로젝트를 끝낼 것인지 메시지 창을 띄워 확인하는 구조로 만들 것입니다.
1 2 3 |
close_eventHandler = new AutomationEventHandler(OnWindowClose); Automation.AddAutomationEventHandler( WindowPattern.WindowClosedEvent, mae, TreeScope.Element, close_eventHandler); |
그리고 메인 창의 하위 자동화 요소들을 검색합니다. 이 부분은 별도의 메서드로 구현합시다.
1 2 |
FindAETree(mae, find_dele); } |
1 2 |
private void OnPropertyChange(object src, AutomationPropertyChangedEventArgs e) { |
첫번째 입력 인자로 받은 개체를 자동화 요소 형식으로 참조합니다.
1 2 3 |
AutomationElement sourceElement = src as AutomationElement; if (e.Property == AutomationElement.BoundingRectangleProperty) { |
이벤트 발생 이유가 사각 영역의 속성 변경이면 구독자에게 By Pass합니다.
1 2 3 |
if (AEMoved != null){ AEMoved(this, e); } } } |
1 2 3 |
private void OnWindowClose(object src, AutomationEventArgs e) { try{AutomationElement ae = src as AutomationElement;}catch{ return; } |
윈도우 창 닫힘 이벤트 핸들러에서는 평가 프로젝트를 닫습니다.
1 2 |
End(); } |
계층 정보와 함께 자동화 요소를 검색하는 메서드를 구현합시다.
1 2 3 4 |
private void FindAETree(AutomationElement ae, AddFindElementDele find_dele) { try { |
입력 인자로 받은 자동화 요소에 포커스를 부여하고 약간의 시간을 지연합니다. 시간을 지연하는 이유는 SetFocus 메서드를 호출하면 바로 포커스를 소유하는 것이 아니기 때문입니다.
1 2 3 |
ae.SetFocus(); Thread.Sleep(100); } |
1 2 3 4 |
catch { try { |
하위 트리에서 키보드로 초점을 받을 수 있는 자동화 요소를 탐색합니다.
1 2 |
AutomationElement sae=ae.FindFirst(TreeScope.Subtree, new PropertyCondition ( AutomationElement.IsKeyboardFocusableProperty, true)); |
그리고 탐색한 개체에 포커스를 부여하고 지연합니다. 위에서 한 작업을 예외가 발생할 때 다시 하는 이유는 인터넷 익스플로어나 일부 응용 프로그램은 메인 창이 초점을 갖지 못할 때가 있습니다. 이러한 응용들을 위해 예외처리를 한 것입니다. 물론 이 책은 자동화 기술을 소개하는 것이 주 목적이며 상용 프로그램을 만드는 것이 아니어서 예외 처리를 최소화한 것입니다.
1 2 3 4 5 6 7 8 |
sae.SetFocus(); Thread.Sleep(100); } catch { MessageBox.Show("선택한 윈도우에 초점을 가질 수 있는 요소가 없습니다."); } } |
1 2 |
try { |
ImageCapture 정적 클래스의 CaptureFromRect 메서드를 이용하여 메인 창 이미지를 비트맵 개체로 만들어 속성에 설정합니다.
1 2 3 |
Image = ImageCapture.CaptureFormRect(ae.Current.BoundingRectangle); } catch { } |
만약 탐색 대리자를 입력 인자로 받았다면 첫번째 항목을 발견하였음을 전달합니다.
1 |
if (find_dele != null){ find_dele(1); } |
그리고 EHAutoElem 개체를 생성합니다.
1 |
EHAutoElem eae = new EHAutoElem(ae); |
계층화 정보를 만들어 최상위 노드를 설정합니다. 계층화 정보를 만드는 부분은 별도의 메서드로 구현합시다.
1 2 |
Root = MakeUIHierarchy(eae, find_dele); } |
1 2 |
TreeNode MakeUIHierarchy(EHAutoElem eae, AddFindElementDele find_dele) { |
입력 인자로 받은 요소의 ToString 메서드의 반환 문자열로 트리 노드를 생성합니다.
1 |
TreeNode sr = new TreeNode(eae.ToString()); |
구분하기 위한 ID를 구합니다.
1 2 3 |
string ehid = eae.GetEHID(); if (node_dic.ContainsKey(ehid) == false) { |
만약 사전 개체에 없다면 사전 개체에 등록합니다.
1 |
node_dic[ehid] = sr; |
트리 노드의 Tag 속성에 EHAutoElem 개체를 설정하고 반대로 EHAutoElem 개체의 Tag 속성에 트리 노드를 설정합니다. 이를 통해 트리 뷰의 트리 노드를 선택하면 태그에 기억한 정보로 대응하는 자동화 요소를 확인하거나 그 역을 수행할 수 있습니다.
1 2 3 4 |
sr.Tag = eae; eae.Tag = sr; FindedAutoElement(null, new FindAutoElemEventArgs(eae)); } |
이미 사전 개체에 있다면 더 이상 진행하지 않고 메서드를 종료합니다.
1 |
else{ return null; } |
이제 자식 요소를 탐색합니다.
1 2 3 |
Condition con = TreeWalker.RawViewWalker.Condition; AutomationElement ae = eae.AE; AutomationElementCollection aec = ae.FindAll(TreeScope.Children, con); |
탐색 대리자가 존재하면 탐색한 요소 개수를 인자로 전달합니다.
1 |
if (find_dele != null){ find_dele(aec.Count); } |
탐색한 자동화 요소 개체 항목마다 재귀적으로 MakeUIHierarchy 메서드를 호출합니다.
1 2 3 4 5 6 7 8 9 10 |
foreach (AutomationElement sae in aec) { TreeNode tr = null; if ((tr = MakeUIHierarchy(new EHAutoElem(sae), find_dele)) != null) { sr.Nodes.Add(tr); } } return sr; } |
1 2 |
void FindedAutoElement(object send, FindAutoElemEventArgs e) { |
자동화 요소를 발견하였때 인자로 전달받은 EHAutoElem 개체를 참조합니다.
1 |
EHAutoElem eae = e.EAE; |
만약 테이블이 없으면 메서드를 종료합니다.
1 |
if (Table == null){ return; } |
테이블의 NewRow 메서드를 호출하여 행 개체를 생성합니다.
1 |
DataRow dr = Table.NewRow(); |
자동화 속성 정보를 구하여 행 개체의 쉘에 설정합니다.
1 2 3 4 |
for (ENUM_UIProperty uip = 0; uip < ENUM_UIProperty.MAX_UIPROPERTY; uip++) { dr[uip.ToString()] = eae.GetAEProperty(uip); } |
지원하는 컨트롤 패턴 정보도 마찬가지로 행 개체의 쉘에 설정합니다.
1 2 3 4 |
for (ENUM_CONTROL ctrl = 0; ctrl < ENUM_CONTROL.MAX_CONTROL; ctrl++) { dr[ctrl.ToString()] = eae.GetPattern(ctrl); } |
어느 자동화 요소 개체의 정보인지 설정합니다.
1 |
dr["EHAutoElem"] = eae; |
이렇게 설정한 행 개체를 테이블의 행 항목에 추가합니다.
1 2 |
Table.Rows.Add(dr); } |
1 2 |
public void End() { |
평가 프로젝트가 종료하였는지 구독자가 있다면 이벤트를 게시합니다.
1 |
if (EndEvalProject != null){ EndEvalProject(this, new EventArgs()); } |
프로젝트 메인 창의 변화를 감지하기 위해 등록한 자동화 이벤트 핸들러를 해제합니다.
1 2 3 4 |
Automation.RemoveAutomationPropertyChangedEventHandler(mae, apceh); Automation.RemoveAutomationEventHandler(WindowPattern.WindowClosedEvent, mae, close_eventHandler); } |
ToString 메서드를 재정의하여 평가 프로젝트 제목을 반환합니다.
1 2 3 4 |
public override string ToString() { return Title; } |
저장하기 메서드를 제공합시다.
1 2 |
public void Save(string path) { |
입력 인자로 받은 경로에 테이블 이름으로 폴더를 만들 것입니다.
1 2 3 |
string dirname = path + "/" + Table.TableName; if (Directory.Exists(dirname)) { |
만약 같은 디렉토리명이 존재하면 일련 번호를 부여하여 존재하지 않는 디렉토리 이름이 나올 때까지 반복합니다. 이와 같은 기능은 하나의 프로세스를 평가하기 위해 생성한 프로젝트를 같은 폴더에 번호를 부여하여 보관할 수 있게 하는 기능입니다.
1 2 3 4 5 6 7 8 |
int i = 1; dirname = path + "/" + Table.TableName + "_" + i.ToString(); while (Directory.Exists(dirname)) { i++; dirname = path + "/" + Table.TableName +"_" + i.ToString(); } } |
디렉토리를 생성합니다.
1 |
Directory.CreateDirectory(dirname); |
그리고 평가 결과를 저장하고 모든 이미지를 저장합니다.
1 2 3 |
SaveReport(dirname); SaveImageAll(dirname); } |
1 2 |
public void SaveReport(string dir) { |
현재 디렉토리를 구하여 기억해 둡니다.
1 |
string cdir = Directory.GetCurrentDirectory(); |
편의성을 위해 입력 인자로 들어온 경로를 현재 디렉토리로 설정합니다.
1 |
Directory.SetCurrentDirectory(dir); |
XML로 기록하기 위한 설정 개체를 생성합니다.
1 |
XmlWriterSettings settings = new XmlWriterSettings(); |
XML 선언부를 생략하지 않게 설정합니다.
1 |
settings.OmitXmlDeclaration = false; |
문자 검사를 하지 않게 설정합니다.
1 |
settings.CheckCharacters = false; |
자동 들여쓰기로 설정합니다.
1 |
settings.Indent = true; |
인코딩은 디폴트로 설정할게요.
1 |
settings.Encoding = System.Text.Encoding.Default; |
레포트를 기록할 XmlWriter 개체를 생성합니다.
1 |
XmlWriter writer = XmlWriter.Create(dir + "/report.xml",settings); |
“Report”가 XML 문서의 루트 요소로 할 것입니다. 자식 요소로 결과를 기록합니다.
1 2 3 4 5 6 7 8 9 |
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 개체를 닫고 기억해 두었던 원래 디렉토리를 현재 디레토리로 설정합니다.
1 2 3 |
writer.Close(); Directory.SetCurrentDirectory(cdir); } |
이름이 없는 자동화 요소의 개수를 가져오기 할 수 있는 속성을 제공합시다. 접근성 평가에서 가장 기본적인 절차가 UI 요소를 구분할 수 있는 이름이 적절한지 확인하는 것입니다.
1 2 3 4 5 |
public int NonameCount { get { int count = 0; |
테이블의 행 목록에서 이름 정보가 빈 것을 카운팅하여 반환합니다.
1 2 3 4 5 6 7 |
foreach (DataRow dr in Table.Rows) { if (dr[(int)ENUM_UIProperty.NAME].ToString() == string.Empty){ count++; } } return count; } } |
Access 키 정보를 기록하는 메서드입니다.
1 2 3 4 |
private void WriteAccessKey(XmlWriter writer) { writer.WriteStartElement("Access키"); List<string[]> keylist = new List<string[]>(); |
먼저 Access 키를 갖고 있는 목록을 구합니다. 이 부분은 GetAccessKeyItemList 메서드로 구현합시다. GetAccessItemList 메서드에서는 키와 쌍을 원소로 하는 문자열 배열을 목록화한 컬렉션을 반환하게 구현할 것입니다.
1 2 3 |
keylist = GetAccessKeyItemList(); foreach (string[] key_value in keylist) { |
각 목록의 키와 값을 기록합니다. 주의할 점은 요소 이름으로 (나 )와 같은 기호들은 사용할 수 없습니다. 따라서 문자열을 필터링해야 하는데 이 부분도 ConvertString 메서드를 별도로 정의하기로 할게요.
1 2 3 4 |
writer.WriteElementString(ConvertString(key_value[0]), ConvertString(key_value[1])); } writer.WriteEndElement(); } |
1 2 |
public List<string[]> GetAccessKeyItemList() { |
먼저 반환할 키와 값을 원소로 하는 문자열 배열 개체을 목록화할 컬렉션 개체를 생성합니다.
1 |
List<string[]> item_list = new List<string[]>(); |
테이블에 기록한 정보를 이용할 것입니다.
1 2 |
DataTable dt = Table; EHAutoElem eae = null; |
테이블의 행 항목마다 반복적인 작업을 수행합니다.
1 2 |
foreach (DataRow dr in dt.Rows) { |
EHAutoElem 쉘의 개체를 참조합니다.
1 2 3 |
eae = dr["EHAutoElem"] as EHAutoElem; if (eae.AE.Current.AccessKey != string.Empty) { |
만약 개체의 AccessKey가 빈 문자열이 아니면 키와 값을 문자열 배열에 설정하여 컬렉션에 추가합니다.
1 2 3 4 5 6 7 8 |
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; } |
특수 기호를 필터링하는 메서드를 구현합시다.
1 2 |
private string ConvertString(string str) { |
먼저 문자열 길이를 구합니다.
1 2 3 4 5 |
int max = str.Length; for(int i = 0; i<max;i++) { if(char.IsLetterOrDigit(str[i])==false) { |
반복해서 알파벳과 숫자 문자가 아닌 것을 발견하면 해당 요소를 제거합니다.
1 2 3 4 5 |
str = str.Remove(i,1); max = str.Length; i--; } } |
변환한 문자열을 반환합니다.
1 2 |
return str; } |
1 2 |
private void WriteAcceleratorKey(XmlWriter writer) { |
Accelerator 키의 정보를 기록하는 메서드도 Access 키를 기록하는 메서드와 원리가 같습니다.
1 2 3 4 5 6 7 8 9 |
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(); } |
1 2 |
internal List<string[]> GetAcceleratorKeyItemList() { |
Accelerator 키 항목을 구하는 메서드도 Access 키 항목을 구하는 메서드와 논리가 같습니다.
1 2 3 4 |
List<string[]> item_list = new List<string[]>(); ...중략... return item_list; } |
1 2 |
private void WriteControlCount(XmlWriter writer) { |
컨트롤 유형 별 개수를 기록하는 메서드에서는 컨트롤 패턴 별로 개수를 구한 후에 이를 기록합니다.
1 2 3 4 5 6 7 8 |
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(); } |
1 2 |
public int GetControlPatternCount(ENUM_CONTROL ctrl) { |
컨트롤 패턴 별 개수를 세기위한 변수를 선언합니다.
1 |
int count = 0; |
테이블의 행에서 NULL이 아닌 개체의 수를 구하여 반환합니다.
1 2 3 4 5 6 7 |
DataTable dt = Table; foreach (DataRow dr in dt.Rows) { if (dr[ctrl.ToString()] != DBNull.Value){ count++; } } return count; } |
1 2 |
private void WriteUIElement(XmlWriter writer) { |
모든 UI 요소 정보를 기록하는 메서드에서는 테이블의 행 의 각 UI 요소 정보를 기록합니다.
1 2 3 |
writer.WriteStartElement("UI_요소_목록"); foreach (DataRow dr in Table.Rows) { |
UI 요소 정보를 기록하는 메서드는 별도로 작성합시다.
1 2 3 4 |
WriteUIInfo(writer, dr); } writer.WriteEndElement(); } |
1 2 |
private void WriteUIInfo(XmlWriter writer, DataRow dr) { |
속성 열거형의 마지막 열거값까지 반복하여 UI 요소의 속성 정보를 기록합니다. 앞에서 열거형을 정의한 이유가 이와 같은 기능을 간결하게 표현하기 위함입니다.
1 2 3 4 5 6 7 8 9 |
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(); } |
1 2 |
public void SaveImageAll(string dir) { |
모든 이미지를 저장하는 메서드에서는 원본 이미지들을 저장하는 메서드와 흑백 이미지들을 저장하는 메서드를 호출합니다.
1 2 3 |
SaveImage(dir); SaveGrayImage(dir); } |
1 2 |
public void SaveImage(string dir) { |
현재 디렉토리를 기억해 두고 입력 인자로 받은 디렉토리로 현재 디렉토리를 설정합니다.
1 2 |
string cdir = Directory.GetCurrentDirectory(); Directory.SetCurrentDirectory(dir); |
이미지 디렉토리를 생성합시다.
1 |
Directory.CreateDirectory("이미지"); |
이미지 파일 이름은 일련 번호를 부여합시다.
1 2 3 |
int fname_index = 1; foreach (TreeNode tn in node_dic.Values) { |
사전 개체의 각 트리 노드마다 다음을 반복합니다. 트리 노드의 Tag에는 EHAutoElem 개체를 설정해 두었습니다.
1 2 3 |
EHAutoElem eae = tn.Tag as EHAutoElem; if (eae.Image != null) { |
EHAutoElem 개체의 이미지를 저장합니다.
1 2 3 4 |
eae.Image.Save("이미지/"+fname_index.ToString() + ".bmp"); } fname_index++; } |
기억해 두었던 디렉토리로 현재 디렉토리를 설정합니다.
1 2 |
Directory.SetCurrentDirectory(cdir); } |
1 2 |
public void SaveGrayImage(string dir) { |
흑백 이미지를 저장하는 원리도 같습니다.
1 2 3 4 5 6 |
string cdir = Directory.GetCurrentDirectory(); Directory.SetCurrentDirectory(dir); Directory.CreateDirectory("흑백이미지"); ...중략... } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 |
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