C# Excel 2007 추가기능(AddIn) 만들기

 

엑셀 리본 컨트롤에 새로운 탭을 만들어서, 사용자가 선택한 셀들의 합과 평균을 메시지 창에 보여주는 작업을 진행하겠습니다.

 

 

 

우선 프로젝트를 하나 생성하겠습니다. 설치된 테블릿에서 Office를 선택후 Excel 2007 추가 기능을 선택 후 솔루션 이름을 “AboutExcelAddIn”으로 생성합니다.

 

 

 

생성 후 프로젝트에서 오른쪽 마우스를 클릭하여 새 항목 추가를 클릭합니다. 리본(비주얼 디자이너)를 선택하여 생성합니다.

 

 

 

리본을 생성 후에 솔루션 탐색기를 확인해보시면 아래와 같습니다.

 

 

 

 

먼저 Ribbon1.cs를 클릭합니다. 아래의 이미지와 같이 ButtonGroup Sum, Avg Button을 생성합니다.

 

 

Sum Name btnSum, Avg Name btnAvg로 지정합니다. 더블 클릭하여 버튼이 클릭이 되었을 때 이벤트들을 등록합니다.

 

ThisAddIn 코드를 보시면 기본적으로 아래와 같은 코드가 자동으로 추가되어 있는 것을 확인하실수 있습니다.

 

namespace AbooutExcelAddIn
{
    public partial class ThisAddIn
    {
        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            
        }
 
        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
           
        }
 
        #region VSTO generated code
 
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }
        
        #endregion
    }
}

 

 

Ribbon 개체를 생성 후 Sum Avg 버튼에 대한 Click 이벤트를 등록하였습니다. Application 개체를 통해서 Excel에 액세스 할 수 있습니다. 아래의 코드에서는 사용자가 선택한 Cell들을 가져오는 작업을 진행하였습니다. dynamic이기 때문에 Intelligence를 확인 하실 수 없습니다. 구글링을 통해 API를 검색하시거나 디버깅해서 어떤 값들이 있는지 확인하셔야 합니다.

 

 

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Office.Tools.Ribbon; using Excel = Microsoft.Office.Interop.Excel; using Microsoft.Office.Interop.Excel; namespace AbooutExcelAddIn {     public partial class Ribbon1     {         void btnAvg_Click(object sender, RibbonControlEventArgs e)         {             double sum = 0;             int count = 0;             Application app = Globals.ThisAddIn.Application;             foreach (var item in app.Selection.Cells)             {                 string text = item.FormulaLocal;                 double value = 0;                 if (double.TryParse(text, out value))                 {                     sum += value;                     count++;                 }             }             double avg = sum / count;             System.Windows.Forms.MessageBox.Show(avg.ToString());         }         void btnSum_Click(object sender, RibbonControlEventArgs e)         {             double sum = 0;             Application app = Globals.ThisAddIn.Application;             foreach (var item in app.Selection.Cells)             {                 string text = item.FormulaLocal;                 double value = 0;                 if (double.TryParse(text, out value))                     sum += value;             }             System.Windows.Forms.MessageBox.Show(sum.ToString());         }     } } 

Ribbon을 추가하는 작업을 하지 않았는데, 어떻게 추가되었는지 궁금하신분들은, Ribbon Designer Class를 확인해보시면 답이나옵니다.

 

 

완료되었습니다. 컴파일을 진행 하신후 실행하게 되면 아래와 같이 동작하는 Excel 추가기능을 작성하신겁니다.

 

 

 

Excel 옵션->추가 기능에 가보시면 작성한 추가 기능이 등록된 것을 확인 할 수 있습니다.

 

 

 

프로젝트 파일 :

AbooutExcelAddIn.zip

 

WinAPI QueryPerformanceCounter 함수와 Stopwatch 클래스를 이용해 두가지 버젼으로 구현하였다.

 

 

 

 

usSleep.zip

 

 

 

 

QueryPerformanceCounter를 이용한 us Sleep

 

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Threading;
 
namespace Some
{
    public class Sleep
    {
        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
 
        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(out long lpFrequency);
 
        private static long freq;
        private static double ufreq;
        private static double mfreq;
 
        static Sleep()
        {
            if (QueryPerformanceFrequency(out freq) == false)
            {
                throw new Win32Exception();
            }
            ufreq = freq / 1000000;
            mfreq = freq / 1000;
        }
 
        public Sleep()
        {
            USleep(0);
        }
 
        public void USleep(double us)
        {
            long startTime = 0;
            long nowTime = 0;
 
            QueryPerformanceCounter(out startTime);
 
            while (((nowTime - startTime) / ufreq) < us)
            {
                QueryPerformanceCounter(out nowTime);
            }
        }
 
        public void MSleep(double ms)
        {
            long startTime = 0;
            long nowTime = 0;
 
            QueryPerformanceCounter(out startTime);
 
            while (((nowTime - startTime) / mfreq) < ms)
            {
                QueryPerformanceCounter(out nowTime);
            }
        }
    }
}

 

 

 

 

 

Stopwatch Class를 이용한 us Sleep

 

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
 
namespace Some
{
    class SWSleep
    {
        long freq = 0;
        public SWSleep()
        {
            this.freq = Stopwatch.Frequency;
            USleep(0);
        }
 
        internal void USleep(int us)
        {
            double sec = (double)us / 1000000;
            Stopwatch sw = Stopwatch.StartNew();
            while (sw.ElapsedTicks / (double)freq < sec)
            {
            }
        }
 
        internal void MSleep(int ms)
        {
            double sec = (double)ms / 1000;
            Stopwatch sw = Stopwatch.StartNew();
            while (sw.ElapsedTicks / (double)freq < sec)
            {
            }
        }
    }
}

 

DataGridView의 특정 Column에 자동완성 기능을 추가할 때는 아래와 같이 진행하시면 됩니다.

 

특정 Column이 셀 편집을 시작한다면, 내부의 TextBox Control의 AutoCompleteMode를 지정하면 됩니다.

 

 

 

 

 

AutoCompleteStringCollection auto = new AutoCompleteStringCollection();

 
string[] hints = new string[] { "Hint0""Hint1""Hint2""Hint3" };
auto.AddRange(hints);
 
this.dgvView.EditingControlShowing += 
       new DataGridViewEditingControlShowingEventHandler(dgvView_EditingControlShowing);

 

 

 

 

void dgvView_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)

{
    int column = this.dgvView.CurrentCell.ColumnIndex;
    string headerText = this.dgvView.Columns[column].Name;
 
    if (headerText.Equals("chName"))
    {
        TextBox tb = e.Control as TextBox;
 
        if (tb != null)
        {
            tb.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
            tb.AutoCompleteCustomSource = auto;
            tb.AutoCompleteSource = AutoCompleteSource.CustomSource;
        }
 
    } 
}

 

 

C#으로 개발을 하다보면 Win32API나 Unmanaged 코드를 작성 해야 할 때가 있습니다.

 

마샬링 작업을 일일이 하기엔 너무 시간이 오래걸려서, 구글링 해보니 좋은 툴이 있었습니다.

 

http://clrinterop.codeplex.com/releases/view/14120

 

위의 링크로 이동하셔서  PInvoke Interop Assistant Installation 을 클릭해 설치하시면

 

디폴트로 진행시 C:\Program Files (x86)\InteropSignatureToolkit 의 경로에 설치가 완료됩니다.

 

SigImp Search는 Win32API에 관련 함수 및 구조체를 검색합니다.

 

 

 

 

SigImp Translate Snippet는 Native Code Snippet에 Unmannaged 코드에서 노출해주는 구조체 및 함수 등등을 작성 한 후  

 

Generate 버튼을 클릭하면 C# 또는 VisualBasic의 PInvoke 형식으로 변경해줍니다.

 

 

많은 양의 데이터를 보여줄때 깜빡임이 있을 때에는 더블버퍼링을 이용합니다.

 

Contorl 클래스의 DoubleBuffered 속성을 true로 주면 됩니다.

 

    public class ExtendGridView : DataGridView     {         public ExtendGridView()         {             base.DoubleBuffered = true;         }     }

 

 

 

 

DoubleBuffred 속성이 protected 인지라 상속을 받아야 하는데 리플렉션을 이용해서 간단히 속성을 변경 할 수 있습니다.

 

    using System.Reflection;     using System.Windows.Forms;     public static class ControlHelper     {         /// <summary>         /// 컨트롤의 DoubleBuffered 속성을 변경합니다.         /// </summary>         /// <param name="contorl"></param>         /// <param name="setting"></param>         public static void SetDoubleBuffered(this Control contorl, bool setting)         {             Type dgvType = contorl.GetType();             PropertyInfo pi = dgvType.GetProperty("DoubleBuffered"BindingFlags.Instance | BindingFlags.NonPublic);             pi.SetValue(contorl, setting, null);         }     } 

 

 

private void flowLayoutPanel1_Layout(object sender, LayoutEventArgs e)
{
    flowLayoutPanel1.Controls[0].Dock = DockStyle.None;
    for (int i = 1; i < flowLayoutPanel1.Controls.Count; i++)
    {
        flowLayoutPanel1.Controls[i].Dock = DockStyle.Top;
    }
    flowLayoutPanel1.Controls[0].Width = flowLayoutPanel1.DisplayRectangle.Width - 
           flowLayoutPanel1.Controls[0].Margin.Horizontal;
    flowLayoutPanel1.Controls[0].Height = flowLayoutPanel1.DisplayRectangle.Height/5; 
}

 

 

 

- 스크롤 없이 Dock

for (int i = 0; i < flowLayoutPanel1.Controls.Count; i++)

{

    flowLayoutPanel1.Controls[i].Width = flowLayoutPanel1.DisplayRectangle.Width - flowLayoutPanel1.Controls[0].Margin.Horizontal;

    flowLayoutPanel1.Controls[i].Height = ((flowLayoutPanel1.DisplayRectangle.Height) / this.flowLayoutPanel1.Controls.Count) 

        - (flowLayoutPanel1.Controls[0].Margin.Vertical);

}

 

1.  XmlDocument 클래스를 이용해 XML 작성하기

 

아래와 같은 XML 파일을 작성하는 예제를 진행하겠습니다.

 

<?xml version="1.0" encoding="utf-8"?>

<Students>

  <Student Number="1" Name="김동영">

    <Score>

      <Korean>10</Korean>

      <English>20</English>

      <Math>30</Math>

      <Science>40</Science>

      <Music>50</Music>

      <Art>60</Art>

    </Score>

  </Student>

</Students>

2 작성할 XML 내용

 

XmlDocument doc = new XmlDocument();
 
//"<?xml version='1.0' ?>" 생략 가능
doc.LoadXml("<Students></Students>");
 
//XmlElement rootElem = doc.CreateElement("Students");
//doc.AppendChild(rootElem);

3 루트 생성

먼저 XmlDocument 개체를 생성합니다. LoadXml 메서드를 호출하게 되면 XML 형식의 문자열을Load 할 수 있습니다. 혹은 Students Element를 직접 생성해서 자식 Element로 설정 할 수 있습니다.

 

 

 

 
XmlElement newElem = doc.CreateElement("Student");
           
XmlAttribute newAttr = doc.CreateAttribute("Number");
newAttr.Value = "1";
newElem.Attributes.Append(newAttr);
 
newAttr = doc.CreateAttribute("Name");
newAttr.Value = "김동영";
newElem.Attributes.Append(newAttr);
 
…
 
doc.DocumentElement.AppendChild(newElem);

4 Student 노드 생성 및 Attribute 정의

먼저 XmlDocument 클래스의 CreateElement 메서드를 호출하여 Student Element를 생성합니다. Student Attribute“Number”“Name”을 가지고 있기 때문에, XmlDocument 클래스의 CreateAttribute 메서드를 통해 Attribute 개체를 생성합니다. “Number” Attribute에는 “1”, “Name” Attribute에는 김동영이라는 값으로 설정하였습니다. 생성한 Attribute는 해당 Element Attributes 속성에 있는 Append 메서드를 이용해 붙이게 됩니다. 생성한 Student Element는 루트 개체인 DocumentElement 속성의 자식으로 설정합니다. DocumentElement는 첫번째 Element를 가리킵니다.

 

 

 

XmlElement subNode = doc.CreateElement("Score"); XmlElement score = doc.CreateElement("Korean"); score.InnerXml = "10"; subNode.AppendChild(score); score = doc.CreateElement("English"); score.InnerText = "20"; subNode.AppendChild(score); score = doc.CreateElement("Math"); score.InnerText = "30"; subNode.AppendChild(score); score = doc.CreateElement("Science"); score.InnerText = "40"; subNode.AppendChild(score); score = doc.CreateElement("Music"); score.InnerText = "50"; subNode.AppendChild(score); score = doc.CreateElement("Art"); score.InnerText = "60"; subNode.AppendChild(score); newElem.AppendChild(subNode);

5 Score 노드 생성 및 과목별 점수 노드 생성

Student Element를 생성하는 것과 동일하게 XmlDocument 클래스의 CreateElement 메서드를 호출하여 Score Element를 생성합니다. <Node> </Node> Node 안에 값을 작성하기 위해서는 XmlElement 개체의 InnerText 속성을 정의하면 됩니다. 생성한 Element Score Element의 자식 Element 이기 때문에 AppendChild 메서드를 이용해 뒤쪽에 붙여줍니다.

 

InnerXml 속성을 이용해 <XML></XML>” 값을 설정하면 아래와 같습니다. Node1 Element InnerXml, Node2 Element InnerText에 값을 설정 하였습니다. InnerText를 실제로 보면 “<XML></XML>” 이라는 문자열이 값으로 설정됩니다.

<Node1>

        <XML>

        </XML>

</Node1>

<Node2>&lt;XML&gt;&lt;/XML&gt;</Node2>

6 InnerText InnerXml의 차이

 

 

 

XmlDocument 클래스의 InnerXml 속성에 접근하여 작성한 XML을 확인 할 수 있습니다. 파일로 내보내기 위해서는 XmlTextWriter 클래스를 이용합니다. XmlTextWriter 개체의 Formatting 속성을 Formatting.Indented로 설정하면 XmlTextWriter 개체의 속성 Indentation IndentChar 값으로 자식 요소가 들여쓰기가 되어 저장됩니다.

 
using (XmlTextWriter writer = new XmlTextWriter(xmlFilePath, Encoding.UTF8))
{
    writer.Formatting = Formatting.Indented;
    doc.Save(writer);
}

7 XML 파일로 작성

 

 

 

2.  XmlDocument 클래스를 이용해 XML 읽기 및 수정

XmlDocument 개체를 이용해 위에서 작성한 XML 파일의 데이터를 읽기 및 수정하는 방법입니다. 예제에서는 XML 파일에 접근 하므로 XmlDocument Load 메서드를 이용하였습니다.

 

 
XmlDocument doc = new XmlDocument();
doc.Load(xmlFilePAth);

8 XmlDocument 개체 생성 및 Load 메서드 호출

Load 메서드를 호출한 후 InnerXml 속성을 확인하여 XML 내용이 제대로 메모리에 로드가 되었나 확인 할 수 있습니다.

 

 

 

XmlNodeList stuNodes = doc.SelectNodes("Students/Student");
 
foreach (XmlElement stuNode in stuNodes)
{
    Console.WriteLine("학생의 이름 : {0}", stuNode.Attributes["Name"].Value);
    Console.WriteLine("학생의 번호 : {0}", stuNode.Attributes["Number"].Value);
 
    foreach (XmlElement scoreNodes in stuNode.ChildNodes)
    {
        Console.WriteLine("영어 점수 : {0}", scoreNodes["English"].InnerText);
        scoreNodes["English"].InnerText = "-100";
        Console.WriteLine("영어 점수 -100으로 변경");
    }
}

9 Student 노드의 Attribute Child Node에 접근

XmlDocument 클래스의 SelectNodes 메서드를 이용해 Node들을 검색합니다. SelectNodes 메서드는 Xpath 식과 일치하는 노드의 목록을 검색합니다. Students의 노드에서 하위인 Student 를 검색하고 싶다면

"Students/Student" XPath를 인자로 넘깁니다. 

맨 처음에 검색될 노드만 찾고자하면 SelectSingleNode 메서드를 이용하면 됩니다. ※더 자세하게 “Xpath 탐색을 사용하여 노드를 검색하고자 한다면 를 클릭해서 MSDN에서 확인하십시오

 

 Attribute는 노드의 Attributes 개체에서 인덱싱을 통해 접근 할 수 있습니다.

 

Attribute는 노드의 Attributes 개체에서 인덱싱을 통해 접근 할 수 있습니다.

 

Student 노드의 하위 노드는 ChildNodes 속성에 인덱싱을 통해 접근 할 수 있습니다.

 

 

 

doc.Save(xmlFilePAth);

10 XML 파일 수정

Load XML파일 경로를 이용해 Save 메서드를 호출하면 변경된 내용이 저장되는 것을 확인 할 수 있습니다.

 

 

 

 

 

/// <summary>
/// Student XML 파일의 내용을 하나씩 접근하여 출력합니다.
/// </summary>
/// <param name="xmlFilePAth"></param>
private static void PrintStudentXml(string xmlFilePAth)
{
    XmlDocument doc = new XmlDocument();
    doc.Load(xmlFilePAth);
 
    XmlNodeList nodes = doc.SelectNodes("Students/Student");
    Console.WriteLine(TabInsertToHead("<Students>", 0));
 
    foreach (XmlElement node in nodes)
    {
        Console.Write(TabInsertToHead("<Student", 1));
 
        //Student Attribute
        foreach (XmlAttribute attr in node.Attributes)
        {
            Console.Write(" {0}={1}", attr.Name, attr.Value);
        }
        Console.WriteLine(">");
 
        //Student Score Element
        foreach (XmlElement stuScore in node)
        {
            //Student Score Child Element
            foreach (XmlElement score in stuScore)
            {
                Console.Write(TabInsertToHead("<", 2));
                Console.Write(score.Name);
                Console.Write(">");
 
                Console.Write(score.InnerText);
 
                Console.Write("</");
                Console.Write(score.Name);
                Console.WriteLine(">");
            }
        }
 
        Console.WriteLine(TabInsertToHead("</Student>", 1));
    }
 
    Console.WriteLine(TabInsertToHead("</Students>", 0));
}
 
/// <summary>
/// 문자열 앞 부분에 Tab을 tabCount 만큼 삽입합니다.
/// </summary>
/// <param name="str"></param>
/// <param name="tabCount"></param>
/// <returns></returns>
private static string TabInsertToHead(string str, int tabCount)
{
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < tabCount; i++)
    {
        sb.Append("\t");
    }
    sb.Append(str);
 
    return sb.ToString();
}

11 Attribute Element에 접근

XmlDocument 개체의 InnerXml 속성에 접근하면 쉽게 출력 할 수 있지만, XML 노드 및 속성에 각각 접근할 수 있음을 나타내기 위해 작성하였습니다.

 

 

 

internal 접근자는 현재 어셈블리에서만 접근 가능합니다.

 

솔루션에 프로젝트를 생성하여 테스트를 진행하고자 하면 internal로 설정된 Class 및 개체에는 접근이 불가합니다.

 

public으로 변경하면 배포시 접근 가능하다는 찝찝함이 존재해버립니다.

 

 

InternalsVisibleTo라는 Attribute Class를 이용해서 특정 어셈블리에서만 internal로 접근 가능하게 설정 할 수 있습니다.

 

테스트하고자 하는 프로젝트의 AssemblyInfo 클래스에 아래의 값을 작성해주 시면 되겠습니다.

 

 

 

 //해당 Assembly에는 internal 접근 가능

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("LogAnalyzerTests")]

 

 

 

우측에 IWebService 인터페이스의 접근자는 internal인데 테스트 프로젝트에서 접근하고 있는것을 확인 하실 수 있습니다.

 

 

 

 

 

 

 

일반적으로는 위와같이 진행하면 문제가없지만, 현재 어셈블리와 지정할 어셈블리 모두 서명이 되지 않아야 합니다.

 

둘중에 하나라도 서명이 되어있다면 강력한 이름으로 서명되어야합니다. 강력한 이름은 Sn.exe를 이용하여 생성합니다.(.snk파일에서 추출)

 

  1. 강력한 이름의 키 파일에서 별도의 파일로 공개 키를 추출합니다.

    Sn -p snk_file outfile

  2. 전체 공개 키를 콘솔에 표시합니다.

    Sn -tp outfile

  3. 전체 공개 키 값을 복사하여 소스 코드에 붙여 넣습니다.

 

 

//강력한 이름으로 접근

[assembly: InternalsVisibleTo("LogAnalyzerTests, PublicKey=002400000480000094" +

                              "0000000602000000240000525341310004000" +

                              "001000100bf8c25fcd44838d87e245ab35bf7" +

                              "3ba2615707feea295709559b3de903fb95a93" +

                              "3d2729967c3184a97d7b84c7547cd87e435b5" +

                              "6bdf8621bcb62b59c00c88bd83aa62c4fcdd4" +

                              "712da72eec2533dc00f8529c3a0bbb4103282" +

                              "f0d894d5f34e9f0103c473dce9f4b457a5dee" +

                              "fd8f920d8681ed6dfcb0a81e96bd9b176525a" +

                              "26e0b3")]

  

 

참조 : MSDN

 

제 PC의 IE 버젼은 9입니다.

 

개발시에  WebBrowser 컨트롤이 Default로 현재 PC에 설치되어있는 IE를 따라갈줄 알았는데..

 

버젼 확인결과 7이더라구요..ㅠ.ㅠ

 

사용 PC에 레지스트를 수정해주어야합니다.

 

 

코드를 사용해도되고 직접 레지스트를 수정하셔도됩니다.

 

 

 

Registry.SetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION",

  Application.ProductName + ".exe", 10001); 

 

 

 

레지스트리 편집기(Regedit)로 이동하여서 아래의 경로로 이동합니다.

 

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION

 

 

HKEY_LOCAL_MACHINE (or HKEY_CURRENT_USER)
   SOFTWARE
      Microsoft
         Internet Explorer
            Main
               FeatureControl
                  FEATURE_BROWSER_EMULATION
                     contoso.exe = (DWORD) 00009000 

 

 

프로그램명과 데이터를 작성합니다.

 

데이터에 관한 설명은 MSDN에 나와있습니다. 저는 IE10으로 설정하기위해 10001로 설정하였습니다.

 

MSDN 바로가기

 

 

 

Value Description
10001 (0x2711) Internet Explorer 10. Webpages are displayed in IE10 Standards mode, regardless of the !DOCTYPE directive.
10000 (0x02710) Internet Explorer 10. Webpages containing standards-based !DOCTYPE directives are displayed in IE10 Standards mode. Default value for Internet Explorer 10.
9999 (0x270F) Windows Internet Explorer 9. Webpages are displayed in IE9 Standards mode, regardless of the !DOCTYPE directive.
9000 (0x2328) Internet Explorer 9. Webpages containing standards-based !DOCTYPE directives are displayed in IE9 mode. Default value for Internet Explorer 9.
8888 (0x22B8) Webpages are displayed in IE8 Standards mode, regardless of the !DOCTYPE directive.
8000 (0x1F40) Webpages containing standards-based !DOCTYPE directives are displayed in IE8 mode. Default value for Internet Explorer 8
7000 (0x1B58) Webpages containing standards-based !DOCTYPE directives are displayed in IE7 Standards mode. Default value for applications hosting the WebBrowser Control.

 

 

 

 

서학수 tech2001@hitel.net|마이크로소프트 VB와 ASP.NET의 MVP였으며, 데브피아 ASP.NET과 WPF, Silverlight 시삽으로 활동 중이며, 현재 Silverlight와 WPF 기술과 관련하여 개발을 하고, 틈틈이 사진도 찍고 있다. 최근 Transfer Windows 7 모임과 함께 Windows7과 관련된 개발 기술을 알리고 있다.

 

 

 

Windows 7에는 이전까지 없었던 다양한 형태의 변화가 생겼다. 어떤 것은 혁신적이라고 생각되어질 만큼 눈에 띄는 것도 있다. 하지만 이 글에서는 가장 사용자의 피부에 와 닿는 부분을 살펴보게 될 것이다. 처음 Windows 7에 대한 여러 가지 정보를 접해 보면 대부분 멀티터치 라든지, 센서 기술 등의 화려한 것에 이끌릴 수 있지만 단 며칠만, 아니 단 몇 시간만 지속적으로 사용해 본 사람이라면 단 두 가지가 떠오를 것이다. ‘빠르다’, ‘테스크바(Task-bar) 정말 편하다’

 

 

정말 테스크바는 좋아졌다. 이렇게 영리하고 편리하며, 예쁘기까지 하다. 여기서 잠시 추억(?)을 떠 올려 보자.

 

 

 

 

 

Windows 7 이전까지 운영체제의 테스크바는 현재 실행되고 있는 응용프로그램을 표시하는 것이 주요 임무였고, Windows XP로 넘어오면서 응용프로그램을 빠르게 실행할 수 있는 바로가기 역할의 아이콘(Quick-Launch)이 생겼으며, 같은 응용프로그램이 실행되면 그룹화(Grouping)되는 정도의 역할을 담당했다. 그런 테스크바가 Windows 7에서는 스마트해진 것이다. 그러나 개발자에게 “무엇인가 스마트하게 변화되었다”라는 말은 또 하나를 배우고 익혀서 코드에 넣고 테스트해야 하는 골칫거리(?)가 생겨났다고 느낄 수도 있다. 하지만 걱정할 필요는 없다. 사용자를 위한 것 뿐 아닌, 개발자를 위한 것도 준비되어 있다. 이제 하나씩 그 베일을 벗겨보자.

 

 

테스크바(Taskbar)의 구성

 

 

Windows 7의 새로운 테스크바는 <화면 4>와 같이 빠른실행 부분과 현재 실행중인 응용프로그램을 따로 분리하여 나타내지 않고 하나로 표현한다. 실행 중이면 아이콘에 사각형 테두리가 생기고 현재 활성화되어 있다면 불투명한 배경이 보여 진다. 또한 <화면 4>처럼 여러 창이 겹쳐 보이기도 한다. 즉, 각각의 아이콘이 빠른실행이면서도 동시에 현재 실행 중인지도 함께 알려 주는 것이다. <화면 4>에서 보여지는 부분은 Windows 7에서 모두 처리되므로 굳이 개발자가 어떠한 코딩을 해야 하는 부분은 전혀 없다.

 

 

<화면 5-1>은 SQL Server Management Studio에서 마우스 오른쪽 버튼을 클릭했을 때이며, <화면 5-2>는 SQL Manage ment Studio가 실행 중일 동안 마우스 오른쪽 버튼을 클릭했을 때이다. <화면 5-3>은 실행 중인 SQL Management Studio에 마우스를 올려 놓으면 보여지는 작은 썸네일 화면이다. <화면 5>에서는 새로운 Windows 7 테스크바를 지원하지 않을 경우에 나타는 것을 확인할 수 있다.

반면 Windows 7에 있는 새로운 MS Media Player는 Windows 7 테스크바를 지원하기 때문에 마우스 오른쪽 버튼을 눌러서 보는 메뉴가 확연하게 다르다<화면 6-1>. 또한 실행중인 화면에서도 새로운 명령 버튼을 확인할 수가 있다<화면 6-2>.

이 중에서 <그림 6-1>에서 보는 실행 중이 아닌 상태에서도 기존에 열어보았던 파일에 바로 접근을 가능케 하는 목록을 점프리스트(Jump List)라고 부른다. 잘 생각해 보면 응용프로그램이 실행되고 있지 않은 상태에서 단 두 번의 클릭만으로 최근에 작업했던 또는 열어보았던 파일을 열어서 실행할 수 있다는 것이다. 당연히 이러한 기능을 구현하기 위해서는 개발자의 노력이 필요하다. SQL Management Studio처럼 개발자들은 별 다른 노력 없이 Windows 7에서 실행하는 것은 아무런 수고가 필요하지 않다. 하지만 이제 새로운 운영체제에 맞도록 추가 개발해야 하는 요건이 생겨난 것이다. 이 얼마나 귀찮고 힘든 일일까.

그러나 귀찮을지는 몰라도 적어도 어렵지는 않다. 그 이유는 개발을 준비하면서 알아보자.

테스크바 개발 준비
새로운 Windows 7 테스크바를 지원하는 응용프로그램을 개발하기 위해서는 다음과 같은 준비물이 필요하다. Windows 7는 물론 당연히 필요하다. 너무 당연한 말인가?

Microsoft Windows SDK for Windows 7 and .NET Framework 3.5 sp1
Windows 7과 관련된 기능을 사용하기 위해서 제공되는 SDK(SW Development Kit) 7
http://www.microsoft.com/downloads/details.aspx?FamilyID=c17ba869-9671-4330-a63e-1fd44e0e2505&displaylang=en

Visual Studio 2008 or 2010
최신 .NET을 포함하여 개발 하기 때문에 Visual Studio 2008 이상의 버전은 필수적이다. Visual Studio 2010에 대해서는 다음에서 정보를 얻을 수 있다.

http://www.microsoft.com/downloadS/details.aspx?familyid=75CBCBCD-B0E8-40EA-ADAE-85714E8984E3&displaylang=en

Windows API code pack

과거에는 Windows의 시스템과 그와 관련된 기능들을 이용하여 개발을 하기 위해서는 사실상 Visual C++과 같은 네이티브 개발 환경이 적합했고 .NET 개발자들은 쉽게 접근할 수가 없었다. 다행하게도 Windows 7이 발표되고 베타버전 때부터 이러한 개발자들의 고충을 알고 제공되는 것이 있었다. 그것은 Windows Code Pack이라 불리는 것으로 현재 Windows 7의 정식 버전에 맞추어 정식 1.0 버전이 공개되어 있다. Windows 7 기반의 기능들을 모두 .NET 버전으로 구현하는 오픈 소스 프로젝트로 관련된 소스뿐 아니라 샘플까지 모두 통째로 다운로드 받을 수 있다.

http://code.msdn.microsoft.com/WindowsAPICodePack

Windows API Code Pack (Ver 1.0)에서  지원하는 항목

● Windows 7 Taskbar Jump Lists, Icon Overlay, Progress Bar, Tabbed Thumbnails, and Thumbnail Toolbars.
● Windows 7 Libraries, Known Folders, non-file system containers.
● Windows Shell Search API support, a hierarchy of Shell Namespace entities, and Drag and Drop functionality for Shell Objects.
● Explorer Browser Control.
● Shell property system.
● Windows Vista and Windows 7 Common File Dialogs, including custom controls.
● Windows Vista and Windows 7 Task Dialogs.
● Direct3D 11.0, Direct3D 10.1/10.0, DXGI 1.0/1.1, Direct2D 1.0, DirectWrite, Windows Imaging Component (WIC) APIs. (DirectWrite and WIC have partial support)
● Sensor Platform APIs
● Extended Linguistic Services APIs
● Power Management APIs
● Application Restart and Recovery APIs
● Network List Manager APIs
● Command Link control and System defined Shell icons.

테스크바 지원 초간단 프로젝트

실제로 간단한 응용프로그램을 제작해 보자.

우선 Windows API Code Pack을 다운을 받고, 압축을 풀어 보면, 다음과 같은 폴더 구조가 있다.

 

 

앞서서 말했듯이 Windows API Code Pack은 전체 소스가 공개되어 있어 Visual Studio 솔루션 파일을 통째로 들어있음을 확인할 수 있다. 그 중에서 Core 부분과 Shell 부분이 기본적으로 사용해야 할 부분이다. 이름에서 보면 짐작할 수 있듯, 센서나 DirectX 등은 현재 사용할 필요가 없다. 사용하는 방법은 Core와 Shell을 컴파일하여 DLL을 직접 참조하는 방법과 이것 또한 Visual Studio Project의 전체 소스이므로 프로젝트 통째로 참조하는 방법이 있다. 공부도 하고 디버깅에 어떤 원리가 있는지 참고도 할 수 있으므로 여기서는 프로젝트 전체를 참조해 보도록 하겠다.

<화면 7>에서 박스가 되어 있는 Core 폴더와 Shell 폴더를 복사해 놓고, Visual Studio로 새로운 Windows 응용프로그램 프로젝트를 만들고 필요한 위치에 Core 폴더와 Shell 폴더를 복사한다.

(참고) Windows API Code Pack을 사용하기 위해서 무조건 참조해야 하는 것은 Core뿐이다. 하지만, Windows의 공통 기능이나, 대화상자, 라이브러리 등의 여러 가지 기본 기능을 Shell에서 포함하고 있기 때문에, 보통의 경우에는 Core부분과 Shell부분을 기본적으로 참조해 놓고 시작하는 경우가 대부분이다.

 

복사한 Core와 Shell을 솔루션에 추가하고, 초간단 프로젝트에 “프로젝트 참조”로 참조 추가를 하여 <화면 8>과 같은 구조가 되도록 한다. 정상적으로 추가되었는지 확인을 위해서 빌드해 보면 정상적으로 빌드 됨을 확인할 수 있다. 이것으로 Windows 7의 새로운 테스크바를 지원할 준비가 완료 되었다. 물론 기존에 이미 Windows 응용프로그램이나, WPF Windows 응용프로그램으로 만들어진 프로젝트가 있다면 위와 같이 Windows API Code Pack 중에서 Core와 Shell 부분을 참조할 수 있도록 프로젝트 참조 또는 완성된 DLL 참조를 통해서 참조만 추가해 주어도 .NET 프로젝트에서 손쉽게 Windows 7의 테스크바에 접근할 준비는 끝난 것이다. 이제 간단하게 테스크바에 메모장을 실행할 수 있는 작업(Task)을 하나 만들어 보자. 우선 다음과 같이 기본적으로 생겨진 Form1에서 이벤트중 Shown을 선택하고 <리스트 1>과 같이 코딩하자.

<리스트 1>

01:     public partial class Form1 : Form
02:     {
03:         private const string PROG_ID = "TestApplication001";
04:         private JumpList myList;
05:         private TaskbarManager winTaskbarManager = TaskbarManager.Instance;
06:
07:         public Form1()
08:         {
09:             InitializeComponent();
10:
11:             this.winTaskbarManager.ApplicationId = PROG_ID;
12:         }
13:
14:         private void Form1_Shown(object sender, System.EventArgs e)
15:         {
16:             myList = JumpList.CreateJumpList();
17:           
18:             string systemFolder = Environment.GetFolderPath(Environment.SpecialFolder.System);
19:             myList.AddUserTasks(new JumpListLink(Path.Combine(systemFolder,
20:                 "notepad.exe"), "메모장 열기")
21:                {
22:                    IconReference
23:                    = new IconReference(Path.Combine(systemFolder,
24:                        "notepad.exe"), 0)
25:                });
26:             myList.Refresh();
27:         }
28:     }

<리스트 1>에서 myList는 WindowsAPICodePack에서 제공하는 JumpList 타입이고, winTaskbarManager는 Windows APICodePack에서 제공하는 TaskbarManager 타입이다. 03번줄의 상수값은 테스크바가 아이콘을 식별하기 위한 ProgramID로 사용될 문자열 상수값이다. Windows 7의 테스크바는 응용프로그램의 실행파일로 같은 프로그램을 구별하는 것이 아니라 <리스트 1>의 11번줄에서 처럼 ApplicationId 값을 가지고 프로그램을 식별한다. 즉 실행파일이 달라도 ApplicationId가 같으면 테스크바에서는 겹쳐진 아이콘 형태로 표시된다.

<리스트 1>에서는 프로그램이 처음 실행할 때, 정해진 상수값으로 테스크바 매니저에게 ApplicationId 값을 알려놓은 다음, 프로그램이 화면에 보여지는 시점에 점프리스트를 가져오게 된다. 16번줄의 CreateJumgList() 메소드는 테스크바에서 현재 응용프로그램에 해당하는 점프리스트를 반환 받는다. 18번줄~25번줄은 시스템 폴더의 위치를 찾아서 메모장의 링크를 만들고 그 링크를 점프리스트의 사용자 작업줄로 추가하는 코드다. 이때 아이콘도 메모장의 아이콘을 얻어오도록 Shell의 도움을 받고 있다.

아직 화면에 아무런 작업을 하지 않았으나 이것을 실행하고, 테스크바에서 마우스 오른쪽 버튼을 클릭해 보면 <화면 9>와 같은 화면을 볼 수 있다.

 

 

실제로 ‘메모장 열기’를 클릭하면 메모장이 실행되는 것을 확인할 수 있다. 필자는 프로그램에 아이콘을 설정했기 때문에 파란색 원 모양의 아이콘이 표시되어 있다.

단, 몇 줄의 코드만으로 이런 작업이 가능한 것이다. 물론 Windows API Code Pack의 소스코드를 따라가다 보면 어떻게 이러한 동작이 가능한지 알아볼 수도 있고, 그 반대로 쉽게 사용만 할 수도 있다. 단, 위 소스코드는 위험천만하게도 이 응용프로그램이 Windows 7에서만 실행된다는 것을 전제로 한다. 만일 Windows Vista나 Windows XP와 같은 곳에서 이 프로그램을 실행했다면 당연히 프로그램은 오류를 나타내며 종료되고 말 것이다. 따라서 점프리스트와 같은 것은 현재 환경이 Windows 7 환경인지 검사할 필요가 있다. 물론 이 또한 Code Pack에서 제공한다. 다음 코드 한 줄이면 해결이다.

<리스트 2>
if (TaskbarManager.IsPlatformSupported == true)

위 <리스트 2>의 단 한 줄의 코드는 현재 TaskbarManager가 지원되는 환경인지 확인을 해주는 bool값을 가지고 있다. 조금 응용해 보자. <리스트 1>의 26번 줄 다음 라인에 <리스트 3>을 추가한다.

<리스트 3>
this.winTaskbarManager.SetProgressState(TaskbarProgressBarState.Normal);
TaskbarManager.Instance.SetProgressValue(50, 100);

소스코드만으로 짐작이 가능했겠지만 테스크바의 아이콘에 진행표시 배경을 추가하는 코드이다. <리스트 3>의 첫 번째 라인은 진행표시를 나타내도록 설정하는 것이며, 그 두 번째 라인은 0~100 중에서 50에 그 상태를 맞추는 것을 의미한다. 이렇게 <리스트 3>을 추가하여 실행하면 <화면 10>과 같은 화면을 얻을 수 있다.

 

 

진행률이 50%인 배경화면을 포함한 아이콘, 이것이 바로 Windows 7에서 탐색기로 파일을 복사하거나, Internet Explorer 8로 다운로드를 받는 동안 보이는 진행표시를 테스크바에서 볼 수 있었던 그 기능이다. 너무나도 쉽게 구현된다.

점프리스트 추가 프로젝트
상태표시를 하거나, 메모장을 링크로 연결하는 등은 별다른 수고 없이 개발할 수 있음을 알았다. 이제 워드나 엑셀처럼 파일을 열고 닫은 후 다시 파일을 빠르게 열 수 있도록 최근 작업했던 목록을 점프리스트로 표시해 보자.

이렇게 파일을 연결하는 것은 메모장의 링크를 거는 것과는 조금 다른 것이 있다. 그것은 파일을 현재 개발하고 있는 응용프로그램과 함께 등록 절차가 한 번 필요하다는 점이 있다는 것이다. 등록되지 않은 파일은 점프리스트의 최근목록에 나타나지 않거나, 사용자 정의 카테고리 등에 새롭게 등록하려 시도한다면 <화면 11>과 같은 오류가 나타날 것이다.

 

 

 

이런 파일을 등록하는 것은 Windows API Code Pack의 Sample에서 그 해결책 소스코드를 제공받을 수 있다. Windows APICodePack\Samples\Shell\TaskbarDemo 경로를 따라가면 RegistrationHelper 라는 콘솔응용프로그램이 있다. 이것을 사용하여 사용하고자 하는 파일과 현재의 응용프로그램을 등록하는 과정을 수행하면 된다. 등록하는 과정 자체가 중요한 것이므로 어떤 원리로 등록되는지는 생략하고 RegistrationHelper 프로젝트까지 현재 프로젝트에 포함시켜서 코드를 고쳐보자.

<리스트 4>
01:         public Form1()
02:         {
03:             InitializeComponent();
04:
05:             this.winTaskbarManager.ApplicationId = PROG_ID;
06:             executablePath = Assembly.GetEntryAssembly().Location;
07:             CheckFileRegistration();
08:         }
09:
10:         private void Form1_Shown(object sender, System.EventArgs e)
11:         {
12:             myList = JumpList.CreateJumpList();
13:
14:             myList.AddToRecent(@"C:\TEST1.txt");
15:             myList.Refresh();
16:         }

<리스트 2>를 <리스트 4>로 변경했다. 06번 라인과 07번 라인은 Windows API Code Pack의 Sample에서 자세한 내용을 살펴보면 되겠다. 확장자가 ‘.txt’로 된 파일을 사용하기 위해 등록되는 코드로 실행할 때에 단 한 번만 실행되는 부분이다. 중요한 점은 14번 라인이다. 이것을 실행한 다음 테스크바에서 마우스 오른쪽 버튼을 누르면 <화면 12>와 같은 화면을 볼 수 있다.

 

 

점프리스트에 단지 AddToRecent() 메소드의 호출만으로 최근에 작업했던 파일을 등록시킬 수 있다. 물론 다른 코드를 작성하지 않았기 때문에 실제로 <화면 12>에서 ‘TEST1’을 클릭하면 단지 응용프로그램이 하나 더 실행될 뿐 아무런 작동을 하지는 않는다. 이것은 등록할 때 사용했던 실행문의 파라미터의 해석을 통해서 처리될 수 있다. 지면 관계상 파라미터를 사용하여 최근 파일의 다시 로드를 살펴보지는 않겠다. 소스를 또 바꿔보자.

<리스트 5>

01:         private JumpListCustomCategory category1 = new JumpListCustomCategory("내맘대로 카테고리");
02:         private JumpListCustomCategory category2 = new JumpListCustomCategory("또하나의 카테고리");   
03:
04:         private void Form1_Shown(object sender, System.EventArgs e)
05:         {
06:             myList = JumpList.CreateJumpList();
07:
08:             myList.AddCustomCategories(category1, category2);
09:             category1.AddJumpListItems(new JumpListItem(@"C:\TEST1.txt"));
10:             category1.AddJumpListItems(new JumpListItem(@"C:\TEST2.txt"));
11:             category2.AddJumpListItems(new JumpListItem(@"C:\TEST1.txt"));
12:             category2.AddJumpListItems(new JumpListItem(@"C:\TEST2.txt"));
13:             myList.Refresh();
14:         }

위 <리스트 5>를 실행한 다음 테스크바에서 마우스 오른쪽 버튼을 누르면 <화면 13>과 같은 화면을 볼 수 있다.

 

 

 

다른 설명을 하지 않아도 사용자가 지정한 카테고리를 만들고 그 항목에 내용을 추가함을 알 수 있다. 08번 라인의 JumpList의 AddCustomCategories() 메소드는 여러 가지를 동시에 등록할 수 있기 때문에 이번 예제에서는 두 개의 카테고리를 동시에 파라미터로 넘겨서 등록했다. 각각의 카테고리에 해당하는 JumpListCustomCategory에는 JumpListItem을 만들어서 추가해 주는 것으로 항목을 하나씩 만들 수 있다. 주의해야 할 점은 중복을 스스로 체크해 주지 않기 때문에 개발자가 같은 항목이 추가되면 최근 것만 남기고 갱신할 수 있도록 신경 써 주어야 한다는 것이다.

 

 

썸네일 툴바 프로젝트

 

 

<화면 14>는 좌측에 이미지가 있는 폴더의 이미지들이 목록으로 나타나고 그 중에 선택된 이미지가 우측 영역에 확대되어 나타나는 간단한 WPF 응용프로그램이다. 이 응용프로그램을 Windows 7의 새로운 썸네일툴바 기능을 이용해서 <화면 15>처럼 보이도록 개발해 보자.

 

 

 

<화면 15>를 보면 썸네일로 나타나는 부분이 <화면 14>에서 보이는 좌측 목록은 보이지 않고, 큰 이미지만 보이고 있다. 또한 4개의 버튼이 있고, 각각 클릭했을 때, 이미지 목록 중 처음, 이전, 다음, 마지막 이미지를 선택할 수 있는 기능까지 구현되고 있다. 개발을 위해서 Windows API Code Pack에서 Core와 Shell을 DLL참조 또는 프로젝트 참조로 참조하는 WPF Windows 응용프로그램 프로젝트를 선택한다. Windows1.xaml에 다음과 같이 XAML을 코딩한다.

<리스트 6>

01: <Window x:Class="Microsoft.WindowsAPICodePack.Samples. ImageViewerDemo.Window1"
02:     xmlns="http://schemas.microsoft.com/winfx/2006/ xaml/presentation"
03:     xmlns:x="http://schemas.microsoft.com/winfx/ 2006/xaml"
04:     Title="Image Viewer WPF Demo (with Taskbar Thumbnail toolbar)" Height="600" Width="800" WindowStartupLocation="CenterScreen">
05:     <Window.Resources>
06:         <DataTemplate x:Key="MyImageTemplate">
07:             <StackPanel>
08:                 <Image Source="{Binding Path}" Width="100" Height="100"/>
09:                 <TextBlock Text="{Binding Name}" Width="100"/>
10:             </StackPanel>
11:         </DataTemplate>
12:     </Window.Resources>
13:     <DockPanel Name="dockPanel">
14:         <ListBox
15:       Name="ImageList"
16:       DockPanel.Dock="Left"
17:       ItemsSource="{Binding AllImages}"
18:       ItemTemplate="{StaticResource MyImageTemplate}"/>
19:         <Image DockPanel.Dock="Right" Name="pictureBox1" Source="{Binding ElementName=ImageList,Path=SelectedItem.Path}">
20:         </Image>
21:     </DockPanel>
22: </Window>

<리스트 6>에서 그림 목록을 채우기 위하여 17번 라인의 AllImages를 Windows1.xaml.cs 파일에 <리스트 7>처럼 작성한다.

<리스트 7>

01: private List<ShellFile> picturesList;
02:
03: public List<ShellFile> AllImages
04: {
05:     get
06:     {
07:         ShellContainer pics = (ShellContainer)KnownFolders.Pictures;
08:
09:         if (ShellLibrary.IsPlatformSupported)
10:             pics = (ShellContainer)KnownFolders.PicturesLibrary;
11:
12:         if (picturesList == null)
13:             picturesList = new List<ShellFile>();
14:         else
15:             picturesList.Clear();
16:
17:         GetPictures(pics);
18:         return picturesList;
19:     }
20: }
21:
22: private void GetPictures(ShellContainer folder)
23: {
24:     foreach (ShellFile sf in folder.OfType<ShellFile>())
25:     {
26:         string ext = Path.GetExtension(sf.Path).ToLower();
27:
28:         if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp")
29:             picturesList.Add(sf);
30:     }
31:
32:     foreach (ShellContainer subFolder in folder.OfType<ShellContainer>())
33:         GetPictures(subFolder);
34: }

<리스트 7>의 10번 라인에서 라이브러리에 이미지로 등록된 폴더들을 얻어오고 17번 라인에서 GetPictures를 호출하게 된다. 호출된 프로시저는 각각의 폴더에서 이미지들을 추출하여 picturesList에 추가하는 코드이다. <리스트 7>에서는 Windows 7의 라이브러리의 개념과 그것을 위하여 Windows API Code Pack을 활용하는 것을 알고 있어야 한다.

<리스트 8>01: pictureBox1.LayoutUpdated += new EventHandler(pictureBox1_LayoutUpdated);
02:
03: void pictureBox1_LayoutUpdated(object sender, EventArgs e)
04: {
05:     Vector v = VisualTreeHelper.GetOffset(pictureBox1);
06:
07:     TaskbarManager.Instance.TabbedThumbnail. SetThumbnailClip(
08:         (new WindowInteropHelper(this)).Handle,
09:         new System.Drawing.Rectangle((int)v.X,
10:                    (int)v.Y,
11:                    (int)pictureBox1.RenderSize.Width,
12:                                 (int)pictureBox1.RenderSize.Height));
13: }

<리스트 8>의 01번라인은 Window1() 생성자에서 이벤트 핸들러의 등록코드이며, 이미지의 레이아웃의 변화가 생길 때 picturBox1_LayoutUpdated가 실행 됨을 의미한다. 05번 라인에서 현재의 이미지가 되는 pictureBox1의 오프셋 값을 얻어오게 되고, 이것을 가지고 테스크바에서 리스트를 제외한 본 이미지만을 썸네일로 반영하는 것이 07번 라인부터 12번 라인까지이다. 길어 보이지만 실제로는 한 줄이다.

이제 Windows 7 테스크바의 썸네일 하단에 네 개의 아이콘으로 표시되는 버튼을 달아두어야 할 것이다. 이 썸네일 밑에 달려있는 버튼ThumbnailToolbarButton이라고 한다. 미리 아이콘파일(.ico)을 만들어 둔 다음, 프로젝트에 추가해 놓으면 준비는 끝났다.

<리스트 9>

01: private ThumbnailToolbarButton buttonPrevious;
02: private ThumbnailToolbarButton buttonNext;
03: private ThumbnailToolbarButton buttonFirst;
04: private ThumbnailToolbarButton buttonLast;
05:
06:  void Window1_Loaded(object sender, RoutedEventArgs e)
07: {
08:     buttonFirst = new ThumbnailToolbarButton(Properties.Resources.first, "First Image");
09:     buttonFirst.Enabled = false;
10:     buttonFirst.Click += new EventHandler<ThumbnailButtonClickedEventArgs>(buttonFirst_Click);
11:
12:     buttonPrevious = new ThumbnailToolbarButton(Properties.Resources.prevArrow, "Previous Image");
13:     buttonPrevious.Enabled = false;
14:     buttonPrevious.Click += new EventHandler<ThumbnailButtonClickedEventArgs>(buttonPrevious_Click);
15:
16:     buttonNext = new ThumbnailToolbarButton(Properties.Resources.nextArrow, "Next Image");
17:     buttonNext.Click += new EventHandler<ThumbnailButtonClickedEventArgs>(buttonNext_Click);
18:
19:     buttonLast = new ThumbnailToolbarButton(Properties.Resources.last, "Last Image");
20:     buttonLast.Click += new EventHandler<ThumbnailButtonClickedEventArgs>(buttonLast_Click);
21:
22:     TaskbarManager.Instance.ThumbnailToolbars.AddButtons(new WindowInteropHelper(this).Handle,
23:         buttonFirst, buttonPrevious, buttonNext, buttonLast);
24:
25:     // 이하 생략

 

<리스트 9>의 01번 라인부터 04번 라인까지 네 개의 버튼에 해당하는 객체를 정의하고, Window1이 Load될 때에 각각의 버튼을 생성하고 이벤트 핸들러를 등록한다. 필요에 따라서는 맨 처음 이미지를 보여줄 것이므로 09번 라인과 13번 라인처럼 버튼을 비활성화 시키기도 한다. 이렇게 만들어진 네 개의 썸네일버튼은 22번 라인에서처럼 한꺼번에 등록시킨다. 이렇게만 해도 <화면 15>처럼 네 개의 버튼이 제 기능을 갖도록 화면에 나타나게 되고, 각각의 이벤트 핸들러 프로시저에 이미지를 선택하는 코드를 넣어두게 되면, 마치 리모컨처럼 원격으로 테스크바에서 응용프로그램을 제어할 수가 있게 된다.

 

<리스트 10>

01: void ImageList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
02: {
03:     if (ImageList.SelectedIndex == 0)
04:     {
05:         buttonFirst.Enabled = false;
06:         buttonPrevious.Enabled = false;
07:     }
08:     else if (ImageList.SelectedIndex > 0)
09:     {
10:         buttonFirst.Enabled = true;
11:         buttonPrevious.Enabled = true;
12:     }
13:     if (ImageList.SelectedIndex == ImageList.Items.Count - 1)
14:     {
15:         buttonLast.Enabled = false;
16:         buttonNext.Enabled = false;
17:     }
18:     else if (ImageList.SelectedIndex < ImageList.Items.Count - 1)
19:     {
20:         buttonLast.Enabled = true;
21:         buttonNext.Enabled = true;
22:     }
23: }

이미지 목록에서 선택된 이미지가 바뀔 때 마다 <리스트 10>의 코드가 실행되면, 각각의 썸네일버튼들은 상황에 맞추어 활성과 비활성 상태가 변화되어 적절히 버튼의 상태가 조절된다.

 

정리하며…
테스크바에는 지금까지 설명했던 것 이외에, Tab으로 구성된 윈도우들의 표현 방식이나 전환에 대한 부분도 있고, 메신저 프로그램처럼 현재 상태를 작은 아이콘으로 추가하여 덧붙이는 기능도 있다. 하지만 지면 관계상 많은 부분을 모두 설명할 수는 없었기에 가장 기본적인 것과 흥미 있을 것 같은 부분을 간략하게나마 살펴보았다. 아마도 Windows 7을 계속 사용하다 보면 테스크바가 모양만 좋아진 것이 아니라 얼마나 편리해졌는지 실감하게 될 것이다. 개발을 위해서 Windows XP와 2003 Server 그리고 Vista도 늘 함께 사용하고 있지만 Windows 7의 테스크바의 편리함은 더욱 더 빛을 발한다. 문제는 언제나 새로운 것을 활용하려면 개발자의 노력이 필요하게 되는데 지금껏 살펴보았듯이 기존 응용프로그램에 약간의 수고를 더해서 이제 막 날개를 펴고 비상하려는 Windows 7까지 완벽하게 지원되는 모습을 보여준다면 어떨까 싶다.

마지막으로 언제나 늦은 밤까지 일과 공부에 열중인 개발자들에게 박수를 보낸다.

 

출처 :  마이크로소프트웨어

+ Recent posts