인쇄용 버전       전송     
평가 및 의견을 보내려면 클릭하십시오.
MSDN
MSDN Library
기술 문서(Technical Articles)
Others
 2007 Office System에서 사용자 요구에 따른 작업창...
2007 Office System에서 사용자 요구에 따른 작업창 및 리본 만드는 방법과 기존 VBA 코드 사용 방법

소개: 잘 동작하고 있는 VBA 코드를 2007 Office System의 향상된 프로그래밍 특징으로 재활용하는 방법을 배웁니다. 이 문서에 사용된 방법론은 마이크로소프트에서 상위 의사결정권자의 비즈니스 결정을 지원하는 시스템인 Rhythm of the Business Office 2003에서 2007로 변경할 때 적용된 것입니다.

Sergei Gundorov, Microsoft Corporation

2006 년 12 월

적용 대상: 2007 Microsoft Office Suites

목차

시나리오 개요

이 문서에서는 다음의 방법에 대해 구체적으로 설명합니다.

  • 응용 프로그램을 사용자가 필요시에 실행하여 [Home] 탭 대신 사용자 지정 탭을 먼저 표시한다.

  • 리본 UI 에 사용자 지정 아이콘을 로드한다.

  • 명령 모음과 같은 방법에서 필요한 리본 UI 항목을 표시하는 추상 레이어를 기술하고 리본 UI 의 편리한 사용성과 유연성을 높인다.

  • VBA 나 Office 의 일반적인 자동화 코드를 재사용 할 수 있는 추상 레이어를 생성한다.

사용자 필요할 때 Office 응용 프로그램 실행

대부분의 Excel 추가기능은 사용자가 새로운 Excel 인스턴스를 열 때마다 로드합니다. 예를 들어, Microsoft 재무부문 일반 직원이 사용하는 데스크톱을 보면, 5 ~ 6 개의 사용자 지정 Excel 응용 프로그램이 있습니다. 이 사용자가 단지 Excel 를 사용하고 싶을 뿐, 5 개의 추가기능을 다 로드할 필요는 없습니다. Excel이 기동할 때, Excel 안에 포함되어 있는 응용 프로그램이 동시에 기동하지 않고, 사용자 해당 기능을 사용할 때 응용 프로그램이 로딩하는 방법을 제공했습니다. Microsoft Office 2003에서는 이것은 간단하게 할 수 있었습니다. 입구역할을 완수하는 통합 문서의 Open 이벤트를 포함한 VBA 추가기능을 사용하여 통합 문서를 열기 위한 바로 가기를 사용자 데스크톱에 배포했습니다. 2007 Office system 가 새로운 프로그래밍 기능 (리본 UI 콜백, 사용자를 위한 기능이 추가된 작업창 등)은 매니지 코드내에서 콜백을 사용하여 매우 효율적입니다. 이 경우, Visual Studio 2005 Tools for Office Second Edition, 매니지 COM 추가기능의 두가지 선택사항이 있습니다. 대부분의 매니지 추가기능은 응용 프로그램 실행 때에 로드되로록 설계됩니다. Visual Studio 2005 Tools for Office Second Edition 를 사용하여  매니지 COM Office 추가기능을 생성하는 경우, 이것은 기본값의 동작입니다. 한편, 매니지 코드를 사용하는 경우, 사용자가 요청했을 때에 바로 가기를 통해서 사용자 지정 Office 응용 프로그램을 실행하려면, 다음 작업이 필요합니다.

  1. 추가기능을 생성할 때, Setup 프로젝트에서 추가기능의 LoadBehavior 레지스트리 값을 1 로 설정합니다. 기본값 3 을 사용하면, Excel 세션마다 추가기능이 로드합니다.

    레지스트리 항목의 예:

    HKCU\Software\Microsoft\Office\Excel\Addins\(your add-in name)\LoadBehavior=1

    그 외의 LoadBehavior 레지스트리 값에 대한 자세한 내용은 Andrew Whitechapel 의 「Microsoft .NET Development for Microsoft Office (영어)」를 참조해 주세요.

  2. 실행 프로그램을 생성합니다. 이 실행 프로그램이 EXE 또는 VBA 추가기능 파일의 어디에서 있는지는 중요하지는 않습니다. 어느 파일에도 바로 가기를 설정할 수 있습니다. 다만, 사용자가 이 실행 프로그램을 열면, Office 추가기능의 Connect 속성이 True 로 설정되도록 합니다.

우리가 사용한 VBA 실행 프로그램의 예를 보여줍니다.

Visual Basic
Sub Workbook_Open()
   
   Dim comAddIn As Office.comAddIn
   ...
   'Set reference to COM add-in by using its ProgId property value.
   Set comAddIn = Application.COMAddIns("RhythmOfTheBusiness.Connect")
   comAddIn.Connect = True
   ...
End Sub

Office 응용 프로그램 세션마다 추가기능을 로드하는 표준적인 동작 대신에 응용 프로그램 실행 프로그램을 사용하는 장점은 그 밖에도 있습니다. 예를 들어, Connect 속성을True 로 설정하고, 응용 프로그램의 로드를 시작하기 전에 코드 업데이트가 있는지 조사하여 사용자 컴퓨터에 설치된 구성요소나 변경을 스캔할 수 있습니다. 또, Excel 가 크래쉬 했기 때문에 추가기능이 무효로 되어 있는지를 체크해 다시 유효하게 하기 위해, 사용자에 적절한 경고메시지를 표시하는 것도 가능합니다. 그 결과, 지원 호출을 큰 폭으로 감소시킬 수 있습니다.  Excel 사용자는 응용 프로그램을 사용할 때, 동작 확인되지 않은 VBA 자동화에 의해서 0.5 GB 라는 피벗 테이블 제한을 극복하는 경우가 자주 있습니다.

응용 프로그램 로드 때에 사용자 지정 리본 탭 "활성화"

이 테크닉은 위의 응용 프로그램 실행 프로그램과 같은 접근 방식에 의하는 것입니다. 명령 모음을 사용하는 환경에서 응용 프로그램을 로드할 때, 단지 사용자 지정 메뉴 항목과 명령 모음을 생성하여, 그러한 표시 가능 속성이 True 로 설정되도록 했습니다. 이것은 리본 UI 에는 들어맞지 않습니다. 리본 UI에서는 활성화하는 탭을 프로그램에 의해서 제어할 수 없습니다. 2007 Office system에서 레거시 코드에 의한 사용자 지정 명령 모음 또는 자동화용 코드의 추가 메뉴의 사용을 시도하면, 이러한 사용자 지정 컨트롤은 다른 사용자 지정 응용 프로그램과 함께 [추가 기능] 탭에 표시되는 알 수 있습니다. 비즈니스 사용자는 응용 프로그램을 실행 했을 때 먼저 표시되는 것은 응용 프로그램 탭에서 하면 좋겠다고 바로 주장했습니다. 지금까지 사용자 지정 응용 프로그램의 탭을 먼저 표시하기 위한 유일한 간단한 방법은 기본값 [Home] 탭보다 전에 그 탭을 삽입하는 것이었습니다. 사용자 지정 XML 를 정의하는 방법은 다음과 같습니다.

Xml
<?xml version="1.0" encoding="utf-8" ?>
<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" 
         onLoad="GetRibbonXControl">
   <ribbon startFromScratch="false">
   <tabs>
      <tab id="ROB_Tab" label="Rhythm of the Business" 
   getVisible ="GetItemVisible" 
   insertBeforeMso="TabHome">
   . . .

다음은 이 구조를 설명합니다. 실행 프로그램이 추가기능 Connect 속성을 True 로 설정할 때, Excel 은 이미 열려 있고, 다른 모든 사용자 지정 추가기능이 로드됩니다. 그 때문에 응용 프로그램의 사용자 지정 UI 정의는 로드 시퀀스의 마지막에 로드됩니다. [Home] 탭보다 전에 다른 추가기능 탭이 삽입되어 있지 않다는 사실을 목표로 합니다. 이것은 기업 이외의 환경에서는 제어가 어려운 일일지도 모릅니다. 그러나 Microsoft 의 IT 그룹에서는 매우 간단한 규칙을 실시합니다. 필요에 따라서 바로 가기를 사용하여  사용자 지정 응용 프로그램을 로드할때, 그 응용 프로그램의 사용자 지정 탭은 [Home] 탭보다 먼저 삽입해야 한다는 규칙입니다. Excel 세션마다 추가기능을 로드하는 경우는 그 추가기능 탭을 리본의 뒤에 이동하는지, 기존의 탭을 용도 변경해야 합니다. 기존의 탭에 사용자 지정 단추를 추가하여, 기본 제공의 아이콘을 재사용하는 것은 간단하게 할 수 있습니다. 사용자 지정 이미지를 추가하는 것도 어렵지는 않습니다.

리본 UI 에서 사용자 지정 이미지 로드

리본 UI 의 사용자 지정 이미지는 Office 응용 프로그램에 고유한 개성을 줍니다. 또, 현재 사용하는 응용 프로그램이 단순한 Excel이 아니라는 것도 사용자에게 알려줍니다. 리본 UI에서는 독자적인 사용자 지정 아이콘 및 이미지를 간단하게 로드 할 수 있습니다. 다음은 우리의 프로젝트로 가장 잘 된 예를 보여줍니다. 여기에서는 리본 UI 컨트롤에 사용자 지정의 비트맵 및 아이콘을 사용합니다.

리본 컨트롤 용무의 사용자 지정 이미지를 취득하는 콜백 함수 정의방법은 글로벌 또는 컨트롤 단위의 두 가지가  있습니다. 다음의 사용자 지정 UI 의 XML 정의에서는 글로벌이미지 콜백 함수를 사용합니다.

Xml
<?xml version="1.0" encoding="utf-8" ?>
<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" 
         onLoad="GetRibbonXControl" 
         loadImage="GetItemIcon">
   <ribbon startFromScratch="false">
   <tabs>
      <tab id="ROB_Tab" label="Rhythm of the Business" 
   getVisible ="GetItemVisible" 
   insertBeforeMso="TabHome">
      <group id="ROBGroup1" label="ROB Tools" >
         <button id="ROB_Button" 
         size="large" 
         image="ROB_Button.bmp" 
         supertip="Launches ROB Team intranet site"/>

이 예로, ROB_Button.bmp 는 어셈블리내의 포함(embedded) 리소스입니다. 이 리소스 이름이 loadImage 콜백 함수에게 건네져 있습니다. 사용자 지정 UI 탭에 이 아이콘을 로드하는 모든 컨트롤 함수 글로벌 콜백은 다음과 같습니다.

C#
internal ResourceManager Resources = new ResourceManager();
. . .
public stdole.IPictureDisp GetItemIcon(string image_id)
{
return Resources.GetIcon(image_id);
}
Note메모 :

매니지 COM 추가기능의 경우, 이 함수는 Connect 클래스에서 정의해야 합니다.

다음은 어셈블리에서  미지의 배경을 투명하게 만들어 리본 UI 에서 필요한 형식 IPictureDisp 에 이미지를 변환하는 코드의 예를 보여줍니다.

C#
class ResourceManager
   {
      Assembly thisAssembly = Assembly.GetExecutingAssembly();
      string[] resources=
         Assembly.GetExecutingAssembly().GetManifestResourceNames();
      //Load custom icon for Ribbon control from embedded assembly resources
      public stdole.IPictureDisp GetIcon(string resourceName)
      {
         foreach (string resource in resources)
         {
            if (resource.EndsWith(resourceName))
            {
               try
               {
                  System.IO.BinaryReader customIconReader =
                     new System.IO.BinaryReader(
                     thisAssembly.GetManifestResourceStream(resource));
                  if (customIconReader != null)
                  {
                     if (resourceName.ToUpper().EndsWith(".ICO"))
                     {
                        System.Drawing.Icon customIcon =
                           new System.Drawing.Icon(
                             customIconReader.BaseStream);
                        return 
            ConvertImage.Convert(customIcon.ToBitmap());
                     }
                     if (resourceName.ToUpper().EndsWith(".BMP"))
                     {
                        System.Drawing.Bitmap customBitmap =
                           new System.Drawing.Bitmap(
                           customIconReader.BaseStream);   
                        customBitmap.MakeTransparent(
                           customBitmap.GetPixel(0,0));
                        return ConvertImage.Convert(customBitmap);
                      }
                  }
               }
               catch (Exception e)
               {
                  Connect.RobApp.appFuncCaller.MakeLogEntry("Error 
         Loading " + resourceName + "-" + e.Message);
               }
            }
         }
         return null;
      }
   }

   //Helper class to convert image to IPictureDisp -
   //OLE type is the only type recognized by callback function
   //SOURCE: http://msdn2.microsoft.com/en-us/library/ms268747.aspx
   sealed internal class ConvertImage : System.Windows.Forms.AxHost
   {
      private ConvertImage(): base(null)
      {
      }
      public static stdole.IPictureDisp Convert(System.Drawing.Image image)
      {
         return (stdole.IPictureDisp)System.Windows.Forms.AxHost
            .GetIPictureDispFromPicture(image);
      }
   }

어셈블리 이미지내의 사용자 지정포함(embedded) 리소스를 취득하려면, 다른 방법도 있습니다. IPictureDisp 을 반환하는 도우미 클래스에 주목합니다. 이것은 리본 UI 에서 기본 제공의 Office 아이콘 이외에 받아 들여지는 사용자 지정 이미지의 종류로서는 유일한 것입니다. 위의 예로, 그 외에 도움이 되는 코드행은 비트맵 이미지의 투명한 배경을 설정하는 행입니다. 비트맵 이미지의 배경을 투명하게 하지 않으면 검은 사각형이 표시됩니다.

실행시에 리본 UI 속성을 명령 모음과 같이 변경

리본 UI 를 처음으로 본 Office 개발자가 맨 먼저에 찾는 것은 CustomRibbonUI.Items 과 같은 요소이지만, 이 생각은 더 이상 통용되지 않습니다. IRibbonUI 를 반환하고 onLoad 콜백을 찾아내면, 누구나가 매우 기뻐합니다. 그러나 IRibbonUI 유형이 Invalidate() InvalidateControl(string) 의 2 개 메서드 밖에 없고, 게다가 리본 항목의 컬렉션 액세스를 전혀 제공하지 않는 것에 알면 실망합니다. 리본 UI에서는 콜백과 사용자의 "풀(Pull)" 에 모든 것을 의존합니다. 그러나 간단한 추상형에 의해서 일반적인 리본 UI 컨트롤의 대부분의 속성을 친숙함이 있는 방법으로 표시할 수 있습니다. 그것은 MyRibbonButton1.Visible=true 라는 테크닉입니다. 이것은 오래된 방법을 고집하는 개발자나 고급 언어 콜백을 어렵다고 느끼는 개발자에게 Office 개발에 도움이 됩니다.

리본 UI 콜백 함수는 모두 Connect 클래스에 있어야 합니다.  고유한 콜백을 사용하는 컨트롤이 많으면, Connect 클래스가 커집니다. 그러나 Connect 클래스에 모든 것을 담는 것은 뛰어난 개체 지향형 프로그래밍 원칙에 반합니다. 우리가 사용하는 테크닉이라면, 필연으로 여겨지는 정밀결합을 회피해 리본 콜백을 Connect 클래스 이외의 고유의 클래스에서 실제로 구현할 수 있습니다. 또, 우리는 콜백과 플래그 대신에 속성을 사용하여, 실행시에 액세스 가능한 리본 UI 항목 컬렉션을 취득합니다.

사용자 지정 UI 의 XML에서 제네릭 콜백 정의

우리는 추가기능의 로드 때에 사용자 지정 리본 UI 의 XML 를 프로그램에 의해서 생성합니다. 구체적인 예로서 코드가 생성하는 XML 단편(fragment)의 일례를 보여줍니다.

Xml
<?xml version="1.0" encoding="utf-8" ?> 
   <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" 
   onLoad="GetRibbonXControl" 
   loadImage="GetItemIcon">
   <ribbon startFromScratch="false">
   <tabs>
   <tab id="ROB_Tab" 
      label="Rhythm of the Business" 
      getVisible="GetItemVisible" 
      insertBeforeMso="TabHome">
      <group id="ROBGroup1" label="ROB Tools">
      <button id="ROB_Button" 
      size="large" 
      image="ROB_Button.ico"      
      getLabel="GetItemLabel" 
      onAction="ExecuteItemOnAction" 
      getVisible="GetItemVisible" 
      getEnabled="GetItemEnabled" /> 
      <button id="ROB_Tree_Button" 
      image="DeckTree.bmp" 
      size="large" 
      getLabel="GetItemLabel" 
      onAction="ExecuteItemOnAction" 
      getVisible="GetItemVisible" 
      getEnabled="GetItemEnabled" /> 
. . .
     </group>
   </tab>
   </tabs>
   </ribbon>
   </customUI>

이 예로 특히 주목해야 할 점은 일련의 리본 UI 항목 콜백이 같다는 사실입니다. 그 때문에 가능한 한 일반적인으로 하고 싶은 Connect 클래스내에서 콜백 함수 집합을 매우 작게 할 수 있습니다.

일반 사용자 지정 UI 콜백 지원

위의 예로 나타내 보인 일반 사용자 지정 UI 콜백 함수 정의를 지원하기 위해 Connect 클래스가 어떻게 되는지 보여줍니다. 리본 UI 에 관련된 모든 것을 하나의 ConnectRibbonX.cs 파일에 포함하기 위해 우리는 Microsoft .NET Framework 2.0 의 partial 클래스 지원을 적극 활용합니다.

C#
public partial class Connect
   {
      //RibbonX reference for callbacks
      internal IRibbonUI ribbonX;
      //Application reference to enable all event handlers access to application state  
      internal static Connect RobApp;
      //ROB Ribbon Items collection
      internal Dictionary<string, IRibbonBasicItem> AllRibbonItems = new 
      Dictionary<string, IRibbonBasicItem>();

      region Ribbon CustomUI callbacks
      //Callback to set RibbonX control reference 
      public void GetRibbonXControl(IRibbonUI ribbon)
      {
         //Reference to enable access to RibbonX
         //So we can call Invalidate and InvalidateControl methods inside this code
         ribbonX = ribbon;
      }

      //Generic calls that expose Ribbon items in a command bar-like way
      public string GetItemLabel(IRibbonControl control)
      {
         return AllRibbonItems[control.Id].Label;
      }

      public bool GetItemVisible(IRibbonControl control)
      {
         return AllRibbonItems[control.Id].Visible;
      }

      public bool GetItemEnabled(IRibbonControl control)
      {
         return AllRibbonItems[control.Id].Enabled;
      }
      
      //More complex Ribbon items with type-specific test
      public void ExecuteItemOnAction(IRibbonControl control)
      {
         if (AllRibbonItems[control.Id] is IRibbonItemWithAction)
         {
            ((IRibbonItemWithAction)AllRibbonItems[control.Id]).ExecuteOnAction();
         }
      }
. . .

AllRibbonItems 은 사용자 지정 유형 IRibbonBasicItem 의 일반적인 Dictionary 컬렉션입니다. 이 컬렉션은 이 어셈블리내의 모든 클래스에 보여지고, 컨트롤 이름을 가진 모든 리본 항목 및 그 속성에 접근할 수 있습니다. 여기에서는 리본 UI 의 네이티브인 control.id 속성이 이용됩니다.

IRibbonBasicItem 및 그 외의 확장된 유형 설정

이 유형에는 실행시에 쉽게 관리할 수 있는기본적인 속성 정의를 포함해야 합니다. 전에 나타낸 사용자 지정 UI 의 XML 예에서는 GetItemLabel,GetItemVisible,GetItemEnabled ExcuteItemOnAction 을 정의했습니다. 다음은 이 인터페이스가 코드에서 어떻게 설정되는지 보여줍니다.

C#
interface IRibbonBasicItem
   {
      string ControlID { get; }
      bool Enabled { get; set; }
      string Label { get; set; }
      bool Visible { get; set; }
   }

   interface IRibbonItemWithAction
   {
      void ExecuteOnAction();
   }

여기에서는 컨트롤 ID 와 사용 빈도가 높은 세가지 속성을 추적합니다. 다음의 예에서는 액션 매핑 단추 등의 리본 컨트롤을 보여줍니다.

단추 콜백 생성

모든 기본적인 리본 항목의 유형은 다음과 같습니다.

C#
internal class RibbonBasicItem : IRibbonBasicItem
   {
      string controlID = "";
      string label = "Label not set!";
      bool visible = false;
      bool enabled = false;
      internal RobRibbonItemType ItemType;

      internal RibbonBasicItem(string ControlID)
      {
         this.controlID = ControlID;
         Connect.RobApp.AllRibbonItems.Add(controlID, this);
      }

      region RibbonItem properties

      //Read-only control ID
      public string ControlID
      {
         get
         {
            return controlID;
         }
      }

      public string Label
      {
         get
         {
            return label;
         }

         set
         {
            //Restricting the label length
            if (value.Length <= 50)
            {
               label = value;
            }
            else
            {
               label = value.Substring(0, 50);
            }

            if (Connect.RobApp.ribbonX != null)
            {
               Connect.RobApp.ribbonX.InvalidateControl(controlID);
            }
         }
      }

      public bool Visible
      {
         get
         {
            return visible;
         }

         set
         {
            this.visible = value;
            if (Connect.RobApp.ribbonX != null)
            {
               Connect.RobApp.ribbonX.InvalidateControl(controlID);
            }
         }
      }

      public bool Enabled
      {
         get
         {
            return enabled;
         }
         set
         {
            this.enabled = value;
            if (Connect.RobApp.ribbonX != null)
            {
               Connect.RobApp.ribbonX.InvalidateControl(controlID);
            }
         }
      }

      endregion
      }

응용 프로그램의 단추 상태를 추적하기 위해서 클래스내에 여러 개의 플래그를 설정할 필요가 없습니다. 또, 특정 컨트롤 상태를 추적하는 플래그가 변화했을 때, 원하는 효과를 트리거 하기 위해서 즉시 InvalidateControl 콜백을 실행하는 필요도 없습니다. 대신에 이 리본 항목 속성의 정의를 사용하면, 다음의 1 행을 지정하면 됩니다. AllRibbonItems["ROB_Button"].Visible=true.

단추 컨트롤의 일반적인 추상형

IRibbonBasicItem 을 확장하는 사용 빈도의 가장 높은 "단추" 유형의 UI 컨트롤 추상형은 다음과 같습니다.

C#
//Ribbon item dynamic onAction method signature
   internal delegate void RibbonAction(RibbonBasicItem ribbonItem);

   internal class RibbonButtonItem : RibbonBasicItem, IRibbonItemWithAction
   {
      //Control-specific
      internal RibbonAction OnControlAction;

      internal RibbonButtonItem(string ControlID)
         : base(ControlID)
      {
      }

      //Using a dedicated method to perform test and ensure delegate is not null
      //instead of a try-catch block
      public void ExecuteOnAction()
      {
         if (OnControlAction != null)
         {
            try
            {
               OnControlAction(this);
            }

            catch (Exception e)
            {
               MessageBox.Show("Error executing action for: "+this.ControlID+"\n"+
                  e.Message+"\nCheck application error log. "+
                  "You might need to restart the application.","Rhythm of the 
                   Business");
            }
         }
      }
   }

   //All other specific implementations of Ribbon items 
   //must derive from RibbonBasicItem
}

RibbonAction 은 특정 컨트롤의 액션을 실행할 때, 그 컨트롤 상태에 액세스할 수 있도록 RibbonBasicItem 유형을 매개 변수로서 사용하는 대리자(delegate)입니다.

명령 모음과 같은 리본 UI 항목 생성

다음은 명령 모음과 같은 리본 UI 항목을 생성합니다 (이번 경우에서는 대부분이 단추입니다).

C#
  internal class RibbonItemsCreator
   {
      OnControlAction ribbonActions = new OnControlAction();
      
      internal void CreateRibbonItems()
      {
         //Temporary type-specific variables used to construct
         //application-level collection
         RibbonBasicItem itemBasic;
         RibbonButtonItem itemButton;
         . . .
         //ROB_Tab
         itemBasic = new RibbonBasicItem("ROB_Tab");
         itemBasic.Label = "Rhythm of the Business";
         itemBasic.Enabled = true;
         itemBasic.Visible = true;

         //ROB_Button
         itemButton = new RibbonButtonItem("ROB_Button");
         itemButton.Label = "Link to ROB Homepage";
         itemButton.Enabled = true;
         itemButton.Visible = true;
         itemButton.OnControlAction = new 
            RibbonAction(ribbonActions.ROB_Button_OnAction);
         
         //ROB_Tree_Button
         itemButton = new RibbonButtonItem("ROB_Tree_Button");
         itemButton.Label = "Show GEO Tree View";
         itemButton.Enabled = true;
         itemButton.Visible = true;
         itemButton.OnControlAction = new 
            RibbonAction(ribbonActions.ROB_Tree_Button_OnAction);
            . . . 
}
   }
Note메모 :

RibbonBasicItem 생성자는 AllRibbonItems 에 자동적으로 새로운 컨트롤을 추가합니다. 즉, 사용자 지정 UI 의 XML 를 사용자 필요할 때 생성합니다. UI 정의가 두곳에 때문에, 사용자 지정 UI 의 XML 정의와 그 정보 및 상태를 저장하는 유형 사이에 충돌을 일으킬 가능성이 있습니다. 유효성 검사를 위한 각종 규칙과 컨트롤의 XML 를 생성하는 메서드가 이런한 충돌이 일어나지 않도록 합니다.  이 예에서는 알기 쉽도록 사용자 지정 XML 를 사용자가 필요할 때 생성하는 코드는 의도적으로 제외했습니다.

OnControlAction 사용

위의 예에서  아직 설명하지 않는 유일한 유형이 OnControlAction 입니다. OnAction 메서드의 매핑에서는 멀티 캐스트 대리자(delegate)를 사용하여 처리 메서드 추가 또는 삭제는 실행시에 간단하게 실행가능합니다. 이것은 추상형의 장점 중 하나입니다.

C#
   class OnControlAction
   {
      Connect thisApp = Connect.RobApp;
      . . .
      public void ROB_Button_OnAction(RibbonBasicItem ribbonItem)
      {
         thisApp.appFuncCaller.OpenHomepage();
      }

      public void ROB_Tree_Button_OnAction(RibbonBasicItem ribbonItem)
      {
         thisApp.ShowTreeViewTaskPane();
      }
   . . .
   }

appFuncCaller 는 레거시 코드의 호출을 저장하는 특수한 유형입니다. 다음의 섹션에서는 이 유형에 대해 자세히 설명합니다.

2007 Office Suites 레거시 코드 재사용

 모든 사용자 지정 솔루션이 2007 Office system에서도 계속해서 사용할 수 있습니다. 2005 년 8 월에 처음 발표된 Excel 의 pre-Beta 버전에서도 응용 프로그램을 로드하여 실행할 수 있었습니다. [추가 기능] 탭에 메뉴나 명령 모음이 표시되는 것은 특별히 요구하지 않았지만, 이것이 레거시 코드에 대한 기본값 동작입니다. 우리는 3 년간에 매우 기능 풍부한 VBA 응용 프로그램을 개발하여, 2007 Office Suites에서 리본 UI 및 사용자를 위한 기능이 추가된 작업창을 사용하기를 원했습니다. 레거시 VBA 코드에는 아무것도 문제는 없으며, 안정성도 우수합니다. 과제는 매니지 코드를 완전하게 고쳐 쓰지 않고 사용자를 위한 기능이 추가된 작업창에서 레거시 VBA  함수를 리본 UI 및 Microsoft .NET 기반의 사용자 지정 컨트롤에  결합시킵니다. 거기서 VBA 코드의 선두에 매우 단순한 추상형을 추가 기입했습니다. 그 결과, 2007 Office Suites 마이그레이션 비용을 최소한으로 하고, 새로운 도구 기능 개발에 전념할 수 있었습니다.

우리는 VBA 코드 파일을 로드하는 다음과 같은 유형을 생성했습니다.

C#
 internal class XlaAddinLoader
   {
      internal Excel._Workbook xla;
      object mv = Missing.Value;

      /// <summary>
      /// Opens the specified VBA file
      /// </summary>
      /// <param name="xl">Excel application instance in which to open the file 
      /// specified</param>
      /// <param name="addInName">Add-in file name</param>
      /// <returns></returns>
      internal bool OpenAddin(Excel._Application xl, string addInName)
      {
         if (!addInExists(addInName)) return false;

         try
         {
            xla = xl.Workbooks.Open(addInName, mv, mv, mv, mv, mv, mv, mv, mv, mv, 
              mv, mv, mv, mv, mv);
            return true;
         }
         //Add specific error trapping
         catch
         {
            throw;
         }
      }

      private bool addInExists(string addInName)
      {
         try
         {
            FileInfo addinFile = new FileInfo(addInName);
            return addinFile.Exists;
         }
         //Add specific error trapping
         catch
         {
            throw;
         }
      }
   }

계속해서, VBA에서 실행하는 하위 항목 및 함수를 규정하여 몇 가지 오버로드를 추가했습니다.

C#
   /// <summary>
   /// Base class for executing VBA code call-throughs
   /// </summary>
   public class XlaFunctionsWrapper
   {
      protected Excel.Application xl;
      protected Excel._Workbook xla;
      protected object mv = Missing.Value;
      protected string addInName = "";
      XlaAddinLoader AddIn;
      string addInPath = "";

      public XlaFunctionsWrapper(Excel.Application xlInstance, string addInName, 
         string addInPath)
      {
         xl = xlInstance;
         AddIn = new XlaAddinLoader();
         if (AddIn.OpenAddin(xl, addInPath))
         {
            xla = AddIn.xla;
            this.addInName = addInName;
            this.addInPath = addInPath;
         }
         else
         {
            MessageBox.Show("Unable to open: \n" + addInPath);
         }
      }

      //Parameterless void VBA call-through overload
      public void ExecuteCall(string funcName)
      {
         if (xla != null)
         {
            xl.Run(addInName + "!" + funcName, mv, mv, mv, mv, mv, mv, mv, mv, mv, 
               mv, mv, mv, mv, mv, mv, mv, mv, mv, mv, mv, mv, mv, mv, mv, mv, mv, 
               mv, mv, mv, mv);
         }
      }
      
      //Parameterized VBA call-through overload
      public void ExecuteCall(string funcName, params object[] parameters)
      {
         //Initializing optional parameters
         //Application.Run takes 30 parameters
         object[] par = GetVBAParameters(parameters);

         if (xla != null)
         {
            xl.Run(addInName + "!" + funcName, par[0], par[1], par[2], par[3], 
               par[4], par[5], par[6], par[7], par[8], par[9], par[10], par[11],
               par[12], par[13], par[14], par[15], par[16], par[17], par[18],
               par[19], par[20], par[21], par[22], par[23], par[24], par[25], 
               par[26], par[27], par[28], par[29]);
         }
      }

      //Parameterized VBA call-through overload with object return type
      //NOTE: Returned object needs to be cast to its proper type in the calling 
      //function
      public object ExecuteGetValue(string funcName, params object[] parameters)
      {
         //Initializing optional parameters
         //Application.Run takes 30 parameters
         object[] par = GetVBAParameters(parameters);

         if (xla != null)
         {
            return xl.Run(addInName + "!" + funcName, par[0], par[1], par[2], par[3],
               par[4], par[5], par[6], par[7], par[8], par[9], par[10], par[11], 
               par[12], par[13], par[14], par[15], par[16], par[17], par[18],
               par[19], par[20], par[21], par[22], par[23], par[24], par[25], 
               par[26], par[27], par[28], par[29]);
         }

         return null;
      }

      //Parameters builder
      internal object[] GetVBAParameters(params object[] parameters)
      {
         //Initializing optional parameters
         //Application.Run takes 30 parameters
         object[] par = new object[30];
         for (int i = 0; i <= 29; i++)
         {
            if (parameters!=null && parameters.Length - 1 >= i)
            {
               par[i] = parameters[i];
            }
            else
            {
               par[i] = mv;
            }
         }
         return par;
      }
   }

Application.Run은 30 개의 매개 변수를 취합니다. Microsoft Visual C#에서는 생략 가능한 매개 변수가 지원되지 않지만, GetVBAParameters 를 사용하여 해결했습니다. 이것에 의해 임의의 VBA 하위 항목 또는 함수를 호출 가능하여,  C# 로 30 개의 Missing.Values 를 입력을 걱정할 필요는 없습니다.

우리는 또한 최종적으로는 모든 레거시 VBA 코드를 매니지 코드에서 변환을 목표로 응용 프로그램과 VBA 의 사이에 본격적인 추상 레이어를 포함했습니다. 이 추상 레이어에 의해 응용 프로그램에 영향을 미치지 않고 메서드 구현을 VBA에서 매니지 코드에서 변경할 수 있습니다. 그래서 RobFunctionsWrapper 는 메인 응용 프로그램 DLL 와는 다른 장소에 배포됩니다. 다음에 예를 보여줍니다.

C#
   /// <summary>
   /// ROB-specific extended VBA call-through methods
   /// </summary>
   public class RobFunctionsWrapper : XlaFunctionsWrapper
   {
      public RobFunctionsWrapper(Excel.Application xlInstance, string robAddInName, 
         string robAddInPath):base(xlInstance, robAddInName, robAddInPath)
      {
      }

      //Using facade design pattern
      //Legacy code calls to be replaced by managed code without impact
      //on core application
      . . .
      public void ShowAboutForm()
      {
         ExecuteCall("ShowAboutForm");
      }

      public void OpenHomepage()
      {
         ExecuteCall("OpenHomepage");
      }

      public void MakeLogEntry(string logEntry)
      {
         ExecuteCall("MakeLogEntry", "[Managed Code]: " + logEntry);
      }

      //Global variables in VBA handlers
      public object GetGlobalVarValue(string varName)
      {
         return ExecuteGetValue("GetGlobalVarValue", varName);
      }

      public void SetGlobalVarValue(string varName, object varValue)
      {
         ExecuteCall("SetGlobalVarValue", varName, varValue);
      }

      . . .
      //Explicit unmanaged resources clean up to prevent password 
      //prompt on Excel session shutdown
      ~RobFunctionsWrapper()
      {
         if (xla != null)
         {
            try
            {
               xla.Close(false, mv, mv);
            }
            catch
            {
               throw; //TODO: add proper error handler here
            }

            finally
            {
               System.Runtime.InteropServices.Marshal.ReleaseComObject(xla);
               xla = null;
            }
         }
      }
   }

이 코드 예는소멸자 (descrubtor)에 대한 것입니다. 매니지 COM 추가기능 또는 Visual Studio 2005 Tools for Office Second Edition 응용 프로그램으로 VBA 를 사용하는 경우는 모든 COM 리소스를 확실히 릴리스해야 합니다.

추가기능을 로드할때, VBA call-through object가 어떻게 인스턴스화 되는지 보여줍니다.

C#
  public partial class Connect : Object, Extensibility.IDTExtensibility2,
      ICustomTaskPaneConsumer, IRibbonExtensibility
   {
      internal Excel.Application excelApp = null;
      //application-specific VBA call wrapper/facade
      internal RobFunctionsWrapper appFuncCaller = null;
      
      Public void OnConnection(object application,
         Extensibility.ext_ConnectMode connectMode, 
         object addInInst, ref System.Array custom)
      {
         . . .
         excelApp = (Excel.Application)application;
         appFuncCaller=
          new RobFunctionsWrapper(excelApp, "ROB.XLA", userAppsPath + "\\ROB.XLA");
         . . .
      }

      . . .
      public void OnBeginShutdown(ref System.Array custom)
      {
         //releasing COM resources
         //IMPACT: If COM objects are not properly released, get 
         //password prompts
         //implemented by using destructor in the respective class. 
         //Unavoidable coupling
         //to ensure proper resources clean up
         appFuncCaller = null;
         
         GC.Collect();
         GC.WaitForPendingFinalizers();
         GC.Collect();
         GC.WaitForPendingFinalizers();
      }
         }

appFuncCaller 에 의해 매니지 코드가 RobFunctionsWrapper 에 랩된 VBA 코드를 실행할 수 있습니다.


© 2009 Microsoft Corporation. All rights reserved. 사용약관 | 상표 | 개인정보취급방침 및 청소년보호정책
Page view tracker