이제 마지막으로 MainForm을 구현할 차례입니다. 프로젝트 생성할 때 디폴트로 제공한 Form1을 MainForm으로 이름을 변경하세요. 솔루션 탐색기에서 변경하면 클래스 이름을 자동으로 변경합니다.
MainForm에는 탭 컨트롤을 배치합니다. 탭 컨트롤은 두 개의 탭 페이지로 구성하고 첫번째 탭 페이지는 프로젝트 탭입니다. 프로젝트 탭은 좌측에 프로젝트 생성 버튼과 프로젝트 목록을 표시할 리스트 박스를 배치합니다. 그리고 우측에는 프로젝트 생성이나 정보를 표시할 영역을 남겨둡니다.
번호 | 컨트롤 형식 | 컨트롤 이름 | 특이 사항 |
1 | TabControl | tc_main | tp_project, tp_acc_evaluate 탭 추가 |
2 | Button | btn_new_project | |
3 | ListBox | lbox_project | |
4 | SplitContainer | sc_project | 2,3은 4의 왼쪽 Panel에 배치한 것임 |
5 | MenuStrip | ms_main | 메인 메뉴 |
6 | FolderBrowserDialog | fbdlog |
[표 10.7] MainForm의 자식 컨트롤1
접근성 평가 탭 페이지에는 선택한 평가 프로젝트의 다양한 정보를 확인할 수 있는 자식 컨트롤을 배치합니다.
번호 | 컨트롤 형식 | 컨트롤 이름 | 특이 사항 |
7 | Button | btn_view_image | |
8 | TreeView | tv_hierarchy | |
9 | ListView | lv_property | ch_name, ch_value 열 추가,View 속성을 Details로 지정 |
10 | ListBox | lbox_pattern |
[표 10.8] MainForm의 자식 컨트롤2
Main 폼의 메뉴를 [그림 10.17]처럼 편집하세요.
번호 | 컨트롤 형식 | 컨트롤 이름 | 특이 사항 |
12 | ToolStripMenuItem | ms_file | |
13 | ToolStripMenuItem | ms_file_new | |
14 | ToolStripMenuItem | ms_file_save_Click | Enabled 속성을 False로 지정 |
15 | ToolStripMenuItem | ms_file_end | Enabled 속성을 False로 지정 |
16 | ToolStripMenuItem | ms_file_exit | |
17 | ToolStripMenuItem | ms_view | |
18 | ToolStripMenuItem | ms_view_highlight | Checked 속성을 True로 지정CheckState 속성은 Checked |
19 | ToolStripMenuItem | ms_view_image | Enabled 속성을 False로 지정 |
20 | ToolStripMenuItem | ms_view_invoke | Enabled 속성을 False로 지정 |
21 | ToolStripMenuItem | ms_search | Enabled 속성을 False로 지정 |
22 | ToolStripMenuItem | ms_search_first | Enabled 속성을 False로 지정 |
23 | ToolStripMenuItem | ms_search_last | Enabled 속성을 False로 지정 |
24 | ToolStripMenuItem | ms_search_after | Enabled 속성을 False로 지정 |
25 | ToolStripMenuItem | ms_search_before | Enabled 속성을 False로 지정 |
26 | ToolStripMenuItem | ms_search_parent | Enabled 속성을 False로 지정 |
[표 10.9] MainForm의 자식 컨트롤3
메인 폼에는 다른 폼을 띄우는 역할을 수행합니다. 이를 위해 멤버 필드를 선언합니다.
ImageForm image_form = null; InvokePatternForm invokepattern_form = null;
메인 폼의 첫번째 탭 페이지의 우측에 표시할 사용자 정의 컨트롤을 위한 멤버를 선언합니다.
UserControl sc_pro_panel2_child=null;
프로그램 방식으로 하이라이트를 표시하기 위한 멤버를 선언합니다.
EHHighlight eh = null;
사용하기 편하게 EvalManager 클래스의 단일체를 가져오기 속성을 추가합니다.
EvalManager EM { get{ return EvalManager.Manager; } }
메인 폼의 Load 이벤트 핸들러를 등록하세요.
private void MainForm_Load(object sender, EventArgs e) {
프로그램 방식으로 하이라이트를 표시할 개체를 생성합니다.
eh = new EHHighlight(); eh.Visible = true;
프로젝트를 생성하였을 때와 선택 변경하였을 때의 이벤트를 구독하기 위해 이벤트 핸들러를 등록합니다.
EM.MakedProject += new MakeProjectEventHandler(EM_MakedProject); EM.ChangedProject += new MakeProjectEventHandler(EM_ChangedProject); }
void EM_MakedProject(object sender, MakeProjectEventArgs e) {
프로젝트 생성 이벤트를 통보 받았을 때 이벤트 핸들러는 크로스 문제가 발생할 수 있습니다. 따라서 이를 해결합니다.
if (this.InvokeRequired) { this.Invoke(new MakeProjectEventHandler(EM_MakedProject), new object[] { sender, e }); }
평가 프로젝트 목록에 추가하고 생성한 평가 프로젝트를 선택 프로젝트로 선택합니다. 그리고 평가 대상 프로젝트의 메인 창이 움직일 때의 이벤트를 구독하는 이벤트 핸들러를 등록합니다.
else { lbox_project.Items.Add(e.AccEvalProject); lbox_project.SelectedItem = e.AccEvalProject; e.AccEvalProject.AEMoved += new AutomationPropertyChangedEventHandler(AccEvalProject_AEMoved);
프로젝트를 평가 시작하는 메서드를 호출합니다. 이 부분은 별도로 만들어서 사용합니다.
EvaluationStart(e.AccEvalProject);
메뉴의 활성화를 설정하는 메서드를 호출합니다. 이 부분도 별도로 만들어서 사용합니다.
SetEnableMenu(true); } }
파일의 메뉴 아이템의 Enabled 속성을 설정합니다.
private void SetEnableMenu(bool flag) { ms_file_save.Enabled = flag; ms_file_end.Enabled = flag; ms_view_image.Enabled = flag; ms_view_invoke.Enabled = flag; btn_view_image.Enabled = flag; }
시작할 프로젝트 개체가 없으면 프로젝트 목록에서 선택 항목을 제거합니다.
private void EvaluationStart(AccEvalProject aeproject) { if (aeproject == null) { lbox_project.Items.Remove(lbox_project.SelectedItem); return; }
선택한 프로젝트의 정보를 표시할 컨트롤을 생성하여 배치합니다.
ProjectInfoControl pictrl = new ProjectInfoControl(aeproject); ChangeProjectPanel2Child(pictrl);
선택한 평가 프로젝트 개체의 래핑한 프로세스 개체를 참조하고 계층화 트리의 항목도 선택한 평가 대상 프로세스의 루트 노드로 설정합니다.
EHProcess ehprocess = aeproject.EHProcess; tv_hierarchy.Nodes.Clear(); tv_hierarchy.Nodes.Add(aeproject.Root); tv_hierarchy.ExpandAll(); }
현재 자식 컨트롤이 존재하면 먼저 Dispose 메서드를 호출하여 해제합니다.
private void ChangeProjectPanel2Child(UserControl ctrl) { if (sc_pro_panel2_child != null) { sc_pro_panel2_child.Dispose(); }
입력 인자로 들어온 사용자 정의 컨트롤을 자식으로 배정합니다.
sc_pro_panel2_child = ctrl; if (ctrl != null) { ctrl.Parent = sc_project.Panel2; ctrl.Dock = DockStyle.Fill; } }
크로스 스레드 문제가 발생할 수 있으니 이를 해결합니다.
void AccEvalProject_AEMoved(object sender, AutomationPropertyChangedEventArgs e) { if (this.InvokeRequired) { this.Invoke(new AutomationPropertyChangedEventHandler(AccEvalProject_AEMoved), new object[] { sender, e }); return; }
선택한 노드를 확인해 사각 영역을 구합니다.
TreeNode tn = tv_hierarchy.SelectedNode; if (tn != null) { EHAutoElem eae = tn.Tag as EHAutoElem; eh.Rect = eae.GetBoundaryRect(); } }
크로스 스레드 문제가 발생할 수 있으니 이를 해결합니다.
void EM_ChangedProject(object sender, MakeProjectEventArgs e) { if (this.InvokeRequired) { this.Invoke(new MakeProjectEventHandler(EM_ChangedProject), new object[] { sender, e }); }
평가 프로젝트의 정보를 설정하는 EvaluationStart 메서드를 호출합니다.
else { EvaluationStart(e.AccEvalProject);
만약 이벤트 인자로 전달받은 프로젝트 개체가 없으면 평가를 끝냅니다.
if (e.AccEvalProject == null) { MessageBox.Show("평가를 끝냅니다."); EndProject(); } } }
void EndProject() {
탭 페이지 우측 패널에 자식 컨트롤을 null로 설정합니다.
ChangeProjectPanel2Child(null);
서브 폼들이 떠 있으면 닫아주고 현재 프로젝트도 닫습니다.
if (invokepattern_form != null){ invokepattern_form.Close(); } if (image_form != null){ image_form.Close(); } AccEvalProject aeproject = EM.CurrentProject; if (aeproject != null){ aeproject.End(); } tv_hierarchy.Nodes.Clear(); lv_property.Items.Clear(); lbox_pattern.Items.Clear(); }
프로젝트 생성 버튼의 클릭 이벤트 핸들러를 등록하세요.
private void btn_new_project_Click(object sender, EventArgs e) {
프로젝트 생성에 필요한 사용자 정의 컨트롤을 생성하여 탭 페이지 우측 패널에 배치합니다.
ProjectMakerControl pmctrl = new ProjectMakerControl(); ChangeProjectPanel2Child(pmctrl); }
트리 뷰의 AfterSelect 이벤트 핸들러를 등록합니다.
private void tv_hierarchy_AfterSelect(object sender, TreeViewEventArgs e) {
선택 노드가 없으면 이벤트 핸들러를 종료합니다.
if (tv_hierarchy.SelectedNode == null){ return; }
선택한 노드로 UI 속성 트리큐와 계층 관계, 패턴 등을 변경합니다. 이들은 모두 별도의 메서드로 작성합시다.
TreeNode tn = tv_hierarchy.SelectedNode; EHAutoElem eae = tn.Tag as EHAutoElem; ChangeUIPropertyTreeView(eae); ChangeFamily(tn); ChangePattern(eae);
사각 영역을 가져옵니다.
eh.Rect = eae.GetBoundaryRect(); }
private void ChangeUIPropertyTreeView(EHAutoElem eae) {
속성 리스트 뷰의 항목을 모두 지웁니다.
lv_property.Items.Clear(); string[] vals = new string[2];
자동화 속성의 목록을 리스트 뷰의 항목에 추가합니다.
for (ENUM_UIProperty i = 0; i < ENUM_UIProperty.MAX_UIPROPERTY; i++) { vals[0] = i.ToString(); vals[1] = eae.GetAEPropertyByIndex((int)i); lv_property.Items.Add(new ListViewItem(vals)); } }
private void ChangeFamily(TreeNode tn) {
부모 자식 관계에 따른 메뉴 스트립의 Enabled 속성을 설정합니다.
ms_search_parent.Enabled = (tn.Parent != null); ms_search_after.Enabled = (tn.NextNode != null); ms_search_before.Enabled = (tn.PrevNode != null); ms_search_first.Enabled = (tn.FirstNode != null); ms_search_last.Enabled = (tn.LastNode != null); }
private void ChangePattern(EHAutoElem eae) {
object obj = null;
패턴 리스트 박스의 모든 항목을 지웁니다.
lbox_pattern.Items.Clear();
패턴 항목을 조사하여 리스트 박스의 항목에 추가합니다.
for (ENUM_CONTROL dt = 0; dt < ENUM_CONTROL.MAX_CONTROL; dt++) { obj = eae.GetPattern(dt); if (obj != null){ lbox_pattern.Items.Add(obj); } } }
평가 프로젝트 리스트 박스의 SelecctedIndexChanged 이벤트 핸들러를 등록하세요.
private void lbox_project_SelectedIndexChanged(object sender, EventArgs e) {
선택한 항목이 없으면 이벤트 핸들러를 종료합니다.
if (lbox_project.SelectedIndex == -1){ return; }
선택한 평가 프로젝트를 현재 프로젝트로 설정합니다.
AccEvalProject aeproject = lbox_project.SelectedItem as AccEvalProject; EM.SetCurrentProject(aeproject.ToString()); }
하이라이트 보이기 메뉴 스트립의 클릭 이벤트 핸드러를 등록하여 시각화 상태를 토글합니다.
private void ms_view_highlight_Click(object sender, EventArgs e) { ms_view_highlight.Checked ^= true; eh.Visible = ms_view_highlight.Checked; }
이미지 보기 메뉴 스트립의 클릭 이벤트 핸들러를 등록하세요.
private void ms_view_image_Click(object sender, EventArgs e) {
이미지 폼이 떠 있지 않다면 이미지 폼을 띄웁니다.
if (image_form == null) { image_form = new ImageForm();
이미지 폼을 두 번 띄우지 않기 위해 image_form 멤버에 개체를 기억하게 한 것입니다. 여기에서는 이미지 폼이 닫히는 이벤트를 구독하기 위한 이벤트 핸들러도 등록합니다.
image_form.FormClosed += new FormClosedEventHandler(image_form_FormClosed); image_form.Show(); } }
void image_form_FormClosed(object sender, FormClosedEventArgs e) {
이미지 폼이 닫혔다는 이벤트를 통보받으면 image_form을 null로 설정합니다. 이 작업을 하지 않으면 한 번 이미지 폼이 뜨고 난 이후에는 이미지 폼을 닫아도 다시 뜨지 않습니다.
image_form = null; }
Invoke 가능하 요소 보기 메뉴 스트립의 Click 이벤트 핸들러를 등록하세요.
private void ms_view_invoke_Click(object sender, EventArgs e) {
InvokePattern 폼이 떠 있지 않거나 현재 선택 프로젝트가 있을 때만 작업을 수행합니다.
if ((invokepattern_form == null) && (EM.CurrentProject != null))
현재 선택 프로젝트의 Root 노드를 가져오고 매핑하는 EHAutoElem 개체를 참조합니다.
TreeNode root = EM.CurrentProject.Root; EHAutoElem eae = root.Tag as EHAutoElem;
InvokePatternForm 개체를 생성하면서 EHAutoElem 개체를 전달합니다.
invokepattern_form = new InvokePatternForm(eae);
InvokePatternForm 이 닫히는지 알 수 있게 이벤트 핸들러를 등록합니다.
invokepattern_form.FormClosed += new FormClosedEventHandler(ip_form_FormClosed); invokepattern_form.Show(); } }
void ip_form_FormClosed(object sender, FormClosedEventArgs e) { invokepattern_form = null; }
저장 메뉴 스트립의 Click 이벤트 핸들러를 등록합니다.
private void ms_file_save_Click(object sender, EventArgs e) {
폴더 브라우저 대화상자를 띄워서 폴더를 선택하면 평가 프로젝트 개체의 Save 메서드를 호출합니다.
if (fbdlog.ShowDialog() == DialogResult.OK) { AccEvalProject aeproject = EM.CurrentProject; aeproject.Save(fbdlog.SelectedPath); } }
프로젝트 끝내기 메뉴 스트립의 Click 이벤트 핸들러를 등록하여 EndProject를 호출합니다.
private void ms_file_end_Click(object sender, EventArgs e) { EndProject(); }
종료 메뉴 스트립의 Click 이벤트 핸들러를 등록하여 Close 메서드를 호출합니다.
private void ms_file_exit_Click(object sender, EventArgs e) { Close(); }
탐색의 각 메뉴 스트립의 Click 이벤트 핸들러도 등록하여 선택 항목의 계층 구조에 맞게 설정하세요.
private void ms_search_first_Click(object sender, EventArgs e) { TreeNode tr = tv_hierarchy.SelectedNode; tv_hierarchy.SelectedNode = tr.FirstNode; } private void ms_search_last_Click(object sender, EventArgs e) { TreeNode tr = tv_hierarchy.SelectedNode; tv_hierarchy.SelectedNode = tr.LastNode; } private void ms_search_after_Click(object sender, EventArgs e) { TreeNode tr = tv_hierarchy.SelectedNode; tv_hierarchy.SelectedNode = tr.NextNode; } private void ms_search_before_Click(object sender, EventArgs e) { TreeNode tr = tv_hierarchy.SelectedNode; tv_hierarchy.SelectedNode = tr.PrevNode; } private void ms_search_parent_Click(object sender, EventArgs e) { TreeNode tr = tv_hierarchy.SelectedNode; tv_hierarchy.SelectedNode = tr.Parent; }
using System; using System.Windows.Forms; using System.Windows.Automation; using System.IO; namespace 접근성_평가_도우미 { public partial class MainForm : Form { ImageForm image_form = null; InvokePatternForm invokepattern_form = null; UserControl sc_pro_panel2_child=null; EHHighlight eh = null; EvalManager EM { get { return EvalManager.Manager; } } public MainForm() { InitializeComponent(); } private void MainForm_Load(object sender, EventArgs e) { eh = new EHHighlight(); eh.Visible = true; EM.MakedProject += new MakeProjectEventHandler(EM_MakedProject); EM.ChangedProject += new MakeProjectEventHandler(EM_ChangedProject); } void EM_MakedProject(object sender, MakeProjectEventArgs e) { if (this.InvokeRequired) { this.Invoke(new MakeProjectEventHandler(EM_MakedProject),new object[]{sender,e }); } else { lbox_project.Items.Add(e.AccEvalProject); lbox_project.SelectedItem = e.AccEvalProject; e.AccEvalProject.AEMoved += new AutomationPropertyChangedEventHandler(AccEvalProject_AEMoved); EvaluationStart(e.AccEvalProject); SetEnableMenu(true); } } private void SetEnableMenu(bool flag) { ms_file_save.Enabled = flag; ms_file_end.Enabled = flag; ms_view_image.Enabled = flag; ms_view_invoke.Enabled = flag; btn_view_image.Enabled = flag; } private void EvaluationStart(AccEvalProject aeproject) { if (aeproject == null) { lbox_project.Items.Remove(lbox_project.SelectedItem); return; } ProjectInfoControl pictrl = new ProjectInfoControl(aeproject); ChangeProjectPanel2Child(pictrl); EHProcess ehprocess = aeproject.EHProcess; tv_hierarchy.Nodes.Clear(); tv_hierarchy.Nodes.Add(aeproject.Root); tv_hierarchy.ExpandAll(); } private void ChangeProjectPanel2Child(UserControl ctrl) { if (sc_pro_panel2_child != null) { sc_pro_panel2_child.Dispose(); } sc_pro_panel2_child = ctrl; if (ctrl != null) { ctrl.Parent = sc_project.Panel2; ctrl.Dock = DockStyle.Fill; } } void AccEvalProject_AEMoved(object sender, AutomationPropertyChangedEventArgs e) { if (this.InvokeRequired) { this.Invoke(new AutomationPropertyChangedEventHandler( AccEvalProject_AEMoved), new object[] { sender, e }); return; } TreeNode tn = tv_hierarchy.SelectedNode; if (tn != null) { EHAutoElem eae = tn.Tag as EHAutoElem; eh.Rect = eae.GetBoundaryRect(); } } void EM_ChangedProject(object sender, MakeProjectEventArgs e) { if (this.InvokeRequired) { this.Invoke(new MakeProjectEventHandler(EM_ChangedProject),new object[]{sender,e}); } else { EvaluationStart(e.AccEvalProject); if (e.AccEvalProject == null) { MessageBox.Show("평가를 끝냅니다."); EndProject(); } } } void EndProject() { ChangeProjectPanel2Child(null); if (invokepattern_form != null) { invokepattern_form.Close(); } if (image_form != null) { image_form.Close(); } AccEvalProject aeproject = EM.CurrentProject; if (aeproject != null) { aeproject.End(); } tv_hierarchy.Nodes.Clear(); lv_property.Items.Clear(); lbox_pattern.Items.Clear(); } private void btn_new_project_Click(object sender, EventArgs e) { ProjectMakerControl pmctrl = new ProjectMakerControl(); ChangeProjectPanel2Child(pmctrl); } private void tv_hierarchy_AfterSelect(object sender, TreeViewEventArgs e) { if (tv_hierarchy.SelectedNode == null){ return; } TreeNode tn = tv_hierarchy.SelectedNode; EHAutoElem eae = tn.Tag as EHAutoElem; ChangeUIPropertyTreeView(eae); ChangeFamily(tn); ChangePattern(eae); eh.Rect = eae.GetBoundaryRect(); } private void ChangeUIPropertyTreeView(EHAutoElem eae) { lv_property.Items.Clear(); string[] vals = new string[2]; for (ENUM_UIProperty i = 0; i < ENUM_UIProperty.MAX_UIPROPERTY; i++) { vals[0] = i.ToString(); vals[1] = eae.GetAEPropertyByIndex((int)i); lv_property.Items.Add(new ListViewItem(vals)); } } private void ChangeFamily(TreeNode tn) { ms_search_parent.Enabled = (tn.Parent != null); ms_search_after.Enabled = (tn.NextNode != null); ms_search_before.Enabled = (tn.PrevNode != null); ms_search_first.Enabled = (tn.FirstNode != null); ms_search_last.Enabled = (tn.LastNode != null); } private void ChangePattern(EHAutoElem eae) { object obj = null; lbox_pattern.Items.Clear(); for (ENUM_CONTROL dt = 0; dt < ENUM_CONTROL.MAX_CONTROL; dt++) { obj = eae.GetPattern(dt); if (obj != null) { lbox_pattern.Items.Add(obj); } } } private void lbox_project_SelectedIndexChanged(object sender, EventArgs e) { if (lbox_project.SelectedIndex == -1) { return; } AccEvalProject aeproject = lbox_project.SelectedItem as AccEvalProject; EM.SetCurrentProject(aeproject.ToString()); } private void ms_view_highlight_Click(object sender, EventArgs e) { ms_view_highlight.Checked ^= true; eh.Visible = ms_view_highlight.Checked; } private void ms_view_image_Click(object sender, EventArgs e) { if (image_form == null) { image_form = new ImageForm(); image_form.FormClosed += new FormClosedEventHandler(image_form_FormClosed); image_form.Show(); } } void image_form_FormClosed(object sender, FormClosedEventArgs e) { image_form = null; } private void ms_view_invoke_Click(object sender, EventArgs e) { if ((invokepattern_form == null) && (EM.CurrentProject != null)) { TreeNode root = EM.CurrentProject.Root; EHAutoElem eae = root.Tag as EHAutoElem; invokepattern_form = new InvokePatternForm(eae); invokepattern_form.FormClosed += new FormClosedEventHandler(ip_form_FormClosed); invokepattern_form.Show(); } } void ip_form_FormClosed(object sender, FormClosedEventArgs e) { invokepattern_form = null; } private void ms_file_save_Click(object sender, EventArgs e) { if (fbdlog.ShowDialog() == DialogResult.OK) { AccEvalProject aeproject = EM.CurrentProject; aeproject.Save(fbdlog.SelectedPath); } } private void ms_file_end_Click(object sender, EventArgs e) { EndProject(); } private void ms_file_exit_Click(object sender, EventArgs e) { Close(); } private void ms_search_first_Click(object sender, EventArgs e) { TreeNode tr = tv_hierarchy.SelectedNode; tv_hierarchy.SelectedNode = tr.FirstNode; } private void ms_search_last_Click(object sender, EventArgs e) { TreeNode tr = tv_hierarchy.SelectedNode; tv_hierarchy.SelectedNode = tr.LastNode; } private void ms_search_after_Click(object sender, EventArgs e) { TreeNode tr = tv_hierarchy.SelectedNode; tv_hierarchy.SelectedNode = tr.NextNode; } private void ms_search_before_Click(object sender, EventArgs e) { TreeNode tr = tv_hierarchy.SelectedNode; tv_hierarchy.SelectedNode = tr.PrevNode; } private void ms_search_parent_Click(object sender, EventArgs e) { TreeNode tr = tv_hierarchy.SelectedNode; tv_hierarchy.SelectedNode = tr.Parent; } } }
[소스 10.15] MainForm.cs
이상으로 UI 자동화 기술에 관한 소개를 마칠게요. 교재의 예제 코드들은 상용화를 목적으로 만든 것이 아니라 UI 자동화 기술을 소개하기 위함입니다. 충분한 테스트를 거친 것이 아니므로 버그가 발생할 수 있습니다. 수고하셨습니다.
모두가 행복한 사회를 만드는데 동참하길 기원하며 글을 마칠게요.