Visual Studio 2005 插件编程(代码行数统计插件)之一
原文:http://www.codeproject.com/useritems/LineCounterAddin.asp
注意:1.本文中的提到的“外接程序”等同與“插件程序”
2.本文提供的源碼是在原作的基礎(chǔ)上有過修改。
代碼行數(shù)統(tǒng)計器(二):http://sifang2004.cnblogs.com/archive/2006/06/26/436178.html
代碼行數(shù)統(tǒng)計器(一)
--Visual Studio 2005插件開發(fā)
???????? 前段日子也寫篇關(guān)于開發(fā)Visual Studio 2005插件的文章,《用Visual Studio 2005 開發(fā)VB.NET-C#語言轉(zhuǎn)換插件》,對這個也產(chǎn)生了點興趣,后又在codeproject上看到了這篇文章,開發(fā)一個可以統(tǒng)計整個項目代碼行數(shù)的插件,覺得還是挺不錯的,準備介紹給E文不是很好的朋友,當然沒有必要對文章進行整體翻譯,只要達到我們學(xué)習(xí)的目的就行了,而且代碼也是經(jīng)過我做了些修改(我現(xiàn)在還有點納悶,為什么他的源碼編譯后可以正常運行,我是修改了幾個小時才能讓它正確運行的,特別是在中文環(huán)境中,需要修改的地方更多),如果你覺得本文讓你受益,那就向原著致以謝意吧!
背景
?????? 其實這種統(tǒng)計工程中各個文件及其整體的代碼行數(shù)的插件在WndTabs.com已經(jīng)出現(xiàn)很久了,只是直到現(xiàn)在它還沒有出現(xiàn)支持VS2005的插件,那插件的運行效果顯示如下:
?
這插件的用處不用我說了,也許你說你用不上,但有的卻是喜歡至極了。
???????? 下面就是本文要完成的插件,運行后的效果圖:
是不是覺得更加漂亮點呢,至于功能,基本上差不多,如果你還需要更多的功能,完全可以自己去擴展,沒有看本文之前,也許還一頭霧水,但看完本文,你就有點蠢蠢欲動了。
Visual Studio自動化和擴展
?????? Visual Studio最大的特性之一就是其擴展性,對此我們中很多人應(yīng)該都有所了解。Visual Studio 提供了三種不同級別的擴展:宏、外接程序和向?qū)?#xff08;就是我們本文中到的插件程序)以及 Visual Studio行業(yè)合作伙伴 (VSIP) 計劃。
???????? 若要創(chuàng)建自動化應(yīng)用程序(如外接程序),則必須執(zhí)行一些步驟獲取對自動化成員的訪問權(quán)。首先必須引用必需的自動化程序集,然后必須獲取對頂級對象 DTE2 的引用。
在 Visual Studio .NET 2002 和 Visual Studio .NET 2003 中,所有的核心自動化項均位于名為 EnvDTE 的程序集中,并且其最高分層對象為 DTE 對象。該對象是所有核心自動化對象、集合及其成員的父對象。DTE2 是從 DTE 派生的。
對于 Visual Studio 2005,只對這些核心自動化對象、集合及成員的一部分進行添加和更新。所有新的更新功能都位于名為 EnvDTE80 的程序集中(EnvDTE 8.0 版),而不會更新現(xiàn)有的程序集和危及現(xiàn)有外接程序和自動化項目的向后兼容性。EnvDTE80 中大多數(shù)更新的函數(shù)保留與早期版本相同的名稱,但是在函數(shù)名后加上數(shù)字 2。例如,在新版本中,TaskItems 集合名為 TaskItems2,Solution 對象名為 Solution2。由于新成員比早期版本的功能更強健,并且包含最新功能,因此推薦在編寫新的自動化應(yīng)用程序時使用新對象和新集合。
雖然新項位于 EnvDTE80 中,但是大多數(shù)核心自動化功能仍然位于 EnvDTE 中。因此,在編寫新的自動化應(yīng)用程序(如外接程序)時,一定要引用 EnvDTE 和 EnvDTE80 兩者。另外,如果使用 EnvDTE 程序集中的成員,也必須設(shè)置對 DTE 對象和 DTE2 對象的引用。這使您可以訪問所有的項。
???????? 外接程序是一些可以為您節(jié)省時間和精力的應(yīng)用程序,可附加到 Visual Studio 集成開發(fā)環(huán)境 (IDE) 中使用。外接程序是 COM 對象,它們實現(xiàn) IDTExtensibility2 接口,并通過 EnvDTE 和 EnvDTE80 程序集中包含的核心自動化對象模型與 IDE 通信。工具欄模型包含在 Microsoft.VisualStudio.CommandBars 中。
??? 使用到的主要對象就是DTE對象,利用它我們能編程控制在Visual Studio中的任何東西,從工具欄,裝卸工具窗口,甚至編輯文件和初始化編譯。
?
創(chuàng)建一個插件工程
??????
???????? Visual Studio 2005插件能使用任何編程語言寫,當你在運行Add-In向?qū)?#xff08;也就是插件工程向?qū)?#xff09;時,你可以選擇使用何種語言編寫,向?qū)н€會向你顯示其它的選項,具體每個選項的含義與作用,請參考MSDN,我只列舉如下(外接程序就是本文所說的插件):
1.???????????????
該項目類型位于“其他項目類型”下的“擴展性”文件夾中。
2.??????????????? 在“選擇編程語言”頁選擇一種語言。
這使您可以選擇將用來編寫外接程序的編程語言。
3.??????????????? 在“選擇應(yīng)用程序宿主”頁選擇一個或多個應(yīng)用程序,如 Visual Studio。
這使您可以選擇需要的應(yīng)用程序以便創(chuàng)建外接程序后可在其中運行該外接程序,如 Visual Studio 或 Visual Studio“宏 IDE”。
4.??????????????? 在“輸入名稱和說明”頁中輸入外接程序的名稱和說明。
創(chuàng)建外接程序后,此名稱和說明顯示在“外接程序管理器”對話框的“可用外接程序”列表中,它告訴用戶外接程序的用途和工作方式等。
5.??????????????? 在“選擇外接程序選項”頁可以指定:
2??????? 是否希望外接程序顯示在“工具”菜單中。
2??????? 希望啟動外接程序的時間。
2??????? 外接程序是否不使用模型用戶界面 (UI),以便可以和命令行生成一起安全地使用。
此頁使您可以指定外接程序的某些行為選項。
6.??????????????? 在“選擇‘幫助’中的‘關(guān)于’信息”頁指定是否要將外接程序的信息顯示在 Visual Studio“幫助”中的“關(guān)于”窗口中,如果是這樣,就會顯示所需信息。
可以添加到 Visual Studio“幫助”中的“關(guān)于”窗口的信息包括版本號、支持詳細信息和授權(quán)數(shù)據(jù)等。
完成步驟 1 - 6 后,選定的選項會顯示在“摘要”頁中。
7.??????????????? 選擇“完成”創(chuàng)建外接程序。
現(xiàn)在,您已經(jīng)擁有了一個功能齊全的基本外接程序。若要使外接程序能夠執(zhí)行一些有用的操作,則必須添加相應(yīng)代碼。
?
根據(jù)“Add-In Wizard”創(chuàng)建一個外接程序,它具有全面的功能,但是只有基本框架,創(chuàng)建完該程序后可立即運行它。向?qū)詣由梢粋€Connect.cs文件,這個文件就是任何Visual Studio插件的起動點,它實現(xiàn)了一些關(guān)鍵接口,例如IDTExtensibility2, IDTCommandTarget,在幾個關(guān)鍵方法中提供一些啟動代碼,最重要的一個方法就是OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)。當啟動一個插件時,第一個調(diào)用的方法就是它,你可以在其中增加一些初始化代碼,以及一些技術(shù)性的處理代碼,只要這些是工作在Visual Studio自動模型所公開的范圍之內(nèi)。通常,該方法被插件向?qū)傻拇a填充,它實現(xiàn)你當時所做出的選擇(例如,增加一個工具菜單項)。
???????? 在Onconnection方法中許多代碼都有很好注釋說明,我們就不詳細解釋了,首先我們要注意的這三行代碼:
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
if (connectMode == ext_ConnectMode.ext_cm_AfterStartup || connectMode == ext_ConnectMode.ext_cm_Startup)
第一行就是緩存一個DTE對象,這是在Visual Studio啟動插件時由它提供的,第二行就是緩存插件自己的一個實例,在你的編寫的插件代碼中你將會經(jīng)常調(diào)用它,第三行是一個if語句,當插件啟動后,涉及到的條件的處理,Visual Studio通常會兩次啟動插件,一次就是設(shè)置自己的UI,如菜單項,菜單欄按鈕等等;另外,當插件真正運行后,插件被再次啟動,這可以發(fā)生在兩種不同情況下(第一是當VS啟動后自動運行,或者是在VS啟動后通過其它進程來運行)。If語句中兩個條件含義你可以參照下表:
| 成員名稱 | 說明 |
| ext_cm_AfterStartup | 外接程序是在 Visual Studio 啟動后加載的。? |
| ext_cm_CommandLine | 外接程序是從命令行加載的。? |
| ext_cm_External | 外接程序是由外部客戶端加載的。(Visual Studio 不再使用此設(shè)置。)? |
| ext_cm_Solution | 外接程序是在解決方案中加載的。? |
| ext_cm_Startup | 外接程序是在 Visual Studio 啟動時加載的。? |
| ext_cm_UISetup | 外接程序是為用戶界面設(shè)置而加載的。? |
枚舉ext_ConnectMode的成員?
??? OnConnection方法中的其它代碼都有注釋,根據(jù)你當初在向?qū)е凶龀龅倪x擇,代碼會有所不同,對于該Line Counter插件來說,我們?nèi)サ袅怂械淖詣由傻拇a,完全用自己寫的代碼代替了,如果你希望跟著本文聽我解釋如果創(chuàng)建一個工具欄插件,那么按如下設(shè)置創(chuàng)建一個新的工程:
工程名稱:LineCounterAddin
語言:c#
插件名稱:Line Counter
插件描述:Line Counter 2005 - Source Code Line Counter
其它選項:默認
工程創(chuàng)建后,添加如下引用:
System.Drawing
System.Windows.Forms
最后,添加一個用戶控件LineCounterBrowser,該用戶控件是本插件的主要的交換接口,它就像普通的Widows Form那樣工作,當然這不是本文討論的重點,你可以下載源碼,從源碼中查看該用戶控件的詳細細節(jié),現(xiàn)在,我們向新的用戶控件中加入下面的代碼:
private DTE2 m_dte;??????????????????????
public DTE2 DTE
{
set
{
???? ???? m_dte = value;
}
}
暫時在用戶控件代碼中我們不需要任何其它東西,這個屬性以及相應(yīng)的變量為我們提供了一種從Connect類向我們的UI類傳遞DTE對象引用的方法。我們在Connect類的OnConnection方法中設(shè)定該屬性,OnConnection方法中的全部代碼就如下面的,已經(jīng)注釋的非常清楚,在這我就不做深入解釋了。
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
??????? {
??????????? // Cache the DTE and add-in instance objects
??????????? _applicationObject = (DTE2)application;
??????????? _addInInstance = (AddIn)addInInst;
?
??????????? // Only execute the startup code if the connection mode is a startup mode
??????????? if (connectMode == ext_ConnectMode.ext_cm_AfterStartup || connectMode == ext_ConnectMode.ext_cm_Startup)
??????????? {
??????????????? try
??????????????? {
??????????????????? // Declare variables
??????????????????? string ctrlProgID, guidStr;
??????????????????? EnvDTE80.Windows2 toolWins;
??????????????????? object objTemp = null;
?
??????????????????? // The Control ProgID for the user control
??????????????????? ctrlProgID = "LineCounterAddin.LineCounterBrowser";
?
??????????????????? // This guid must be unique for each different tool window,
??????????????????? // but you may use the same guid for the same tool window.
??????????????????? // This guid can be used for indexing the windows collection,
??????????????????? // for example: applicationObject.Windows.Item(guidstr)
??????????????????? guidStr = "{2C73C576-6153-4a2d-82FE-9D54F4B6AD09}";
?
??? ??????????????? // Get the executing assembly...
??????????????????? System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
?
??????????????????? // Get Visual Studio's global collection of tool windows...
??????????????????? toolWins = (Windows2)_applicationObject.Windows;
?
??????????????????? // Create a new tool window, embedding the LineCounterBrowser control inside it...
??????????????????? m_toolWin = toolWins.CreateToolWindow2(_addInInstance, asm.Location, ctrlProgID, "Line Counter", guidStr, ref objTemp);
?
??????????????????? // Pass the DTE object to the user control...
??????????????????? LineCounterBrowser browser = (LineCounterBrowser)objTemp;
??????????????????? browser.DTE = _applicationObject;
?
??????????????????? // and set the tool windows default size...
??????????????????? m_toolWin.Visible = true;?????? // MUST make tool window visible before using any methods or properties,
??????????????????? // otherwise exceptions will occurr.
??????????????????? //toolWin.Height = 400;
??????????????????? //toolWin.Width = 600;
??????????????? }
??????????????? catch (Exception ex)
??????????????? {
??????????????????? Debug.WriteLine(ex.Message);
??????????????????? Debug.WriteLine(ex.StackTrace);
??????????????? }
??????????? }
?
??????????? // Create the menu item and toolbar for starting the line counter
??????????? if (connectMode == ext_ConnectMode.ext_cm_UISetup)
??????????? {
??????????????? try
??????????????? {
??????????????????? // Get the command bars collection, and find the MenuBar command bar
??????????????????? CommandBars cmdBars = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars);
??????????????????? CommandBar menuBar = cmdBars["MenuBar"];
?
??????????????????? // Add command to 'Tools' menu
??????????????????? CommandBarPopup toolsPopup = (CommandBarPopup)menuBar.Controls["Tools"];
??????????????????? AddPopupCommand(toolsPopup, "LineCounterAddin", "Line Counter 2005", "Display the Line Counter 2005 window.", 1);
?
??????????????????? // Add new command bar with button
??????????????????? CommandBar buttonBar = AddCommandBar("LineCounterAddinToolbar", MsoBarPosition.msoBarFloating);
??????????????????? AddToolbarCommand(buttonBar, "LineCounterAddinButton", "Line Counter 2005", "Display the Line Counter 2005 window.", 1);
??????????????? }
????????? ??????catch (Exception ex)
??????????????? {
??????????????????? string error = ex.Message;
??????????????? }
??????????? }
??????? }
在Visual Studio的執(zhí)行期間的不同點,OnConnection方法會運行幾次,我們關(guān)注的是方法被調(diào)用的可能原因中的兩個,其一是UI Setpup,另外就是Startup,當因為UI Setpup OnConnection方法被調(diào)用時,為了這個插件,我們想用一個菜單項以及菜單欄按鈕更新Visual Studio的用戶接口,我們是在OnConnection方法中的第二個if語句中完成的。當OnConnection方法因為Startup(有兩種不同的方法-當VS啟動時,VS啟動后)調(diào)用時我們顯示我們的插件。
???????? 當執(zhí)行UI Setup時,我們已經(jīng)創(chuàng)建了幾個private輔助方法來簡化處理,下面,你能發(fā)現(xiàn)不少方法能幫助你在Visual Studio中建立新的CommandBar,還可以向這些CommandBar中添加命令。這些方法還包含向菜單中添加菜單項,這些方法也都注釋得非常清楚了。關(guān)于這些方法,要注意的一件事情就是他們認為你的插件項目中有一個包含你所有你希望為你命令使用的圖片(包括菜單項以及你工具條上的按鈕。等下我就解釋如何添加自定義的圖標。
/// <summary>
??????? /// Add a command bar to the VS2005 interface.
??????? /// </summary>
??????? /// <param name="name">The name of the command bar</param>
??????? /// <param name="position">Initial command bar positioning</param>
??????? /// <returns></returns>
??????? private CommandBar AddCommandBar(string name, MsoBarPosition position)
??????? {
??????????? // Get the command bars collection
??????????? CommandBars cmdBars = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars);
??????????? CommandBar bar = null;
?
??????????? try
??????????? {
??????????????? try
??????????????? {
??????????????????? // Create the new CommandBar
??????????????????? bar = cmdBars.Add(name, position, false, false);
??????????????? }
??????????????? catch (ArgumentException)
??????????????? {
??????????????????? // Try to find an existing CommandBar
??????????????????? bar = cmdBars[name];
??????????????? }
??????????? }
??????????? catch
??????????? {
??????????? }
?
??????????? return bar;
??????? }
?
??????? /// <summary>
??????? /// Add a menu to the VS2005 interface.
??????? /// </summary>
??????? /// <param name="name">The name of the menu</param>
??????? /// <returns></returns>
??????? private CommandBar AddCommandMenu(string name)
??????? {
??????????? // Get the command bars collection
??????????? CommandBars cmdBars = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars);
??????????? CommandBar menu = null;
?
??????????? try
??????????? {
??????????????? try
??????????????? {
??????????????????? // Create the new CommandBar
??????????????????? menu = cmdBars.Add(name, MsoBarPosition.msoBarPopup, false, false);
??????????????? }
??????????????? catch (ArgumentException)
??????????????? {
??????????????????? // Try to find an existing CommandBar
??????????????????? menu = cmdBars[name];
??????????????? }
??????????? }
??????????? catch
??????????? {
??????????? }
?
??????????? return menu;
??????? }
?
??????? /// <summary>
??????? /// Add a command to a popup menu in VS2005.
??????? /// </summary>
??????? /// <param name="popup">The popup menu to add the command to.</param>
??????? /// <param name="name">The name of the new command.</param>
??????? /// <param name="label">The text label of the command.</param>
??????? /// <param name="ttip">The tooltip for the command.</param>
??????? /// <param name="iconIdx">The icon index, which should match the resource ID in the add-ins resource assembly.</param>
??????? private void AddPopupCommand(CommandBarPopup popup, string name, string label, string ttip, int iconIdx)
??????? {
??????????? // Do not try to add commands to a null menu
??????????? if (popup == null)
??????????????? return;
?
??????????? // Get commands collection
??????????? Commands2 commands = (Commands2)_applicationObject.Commands;
??????????? object[] contextGUIDS = new object[] { };
?
??????????? try
??????????? {
??????????????? // Add command
??????????????? Command command = commands.AddNamedCommand2(_addInInstance, name, label, ttip, false, iconIdx, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
??????????????? if ((command != null) && (popup != null))
??????????????? {
??????????????????? command.AddControl(popup.CommandBar, 1);
??????????????? }
??????????? }
??????????? catch (ArgumentException)
??????????? {
??????????????? // Command already exists, so ignore
??????????? }
??????? }
?
??????? /// <summary>
??????? /// Add a command to a toolbar in VS2005.
??????? /// </summary>
??????? /// <param name="bar">The bar to add the command to.</param>
??????? /// <param name="name">The name of the new command.</param>
??????? /// <param name="label">The text label of the command.</param>
??????? /// <param name="ttip">The tooltip for the command.</param>
??????? /// <param name="iconIdx">The icon index, which should match the resource ID in the add-ins resource assembly.</param>
??????? private void AddToolbarCommand(CommandBar bar, string name, string label, string ttip, int iconIdx)
??????? {
??????????? // Do not try to add commands to a null bar
??????????? if (bar == null)
??????????????? return;
?
??????????? // Get commands collection
??????????? Commands2 commands = (Commands2)_applicationObject.Commands;
??????????? object[] contextGUIDS = new object[] { };
?
??????????? try
??????????? {
??????????????? // Add command
??????????????? Command command = commands.AddNamedCommand2(_addInInstance, name, label, ttip, false, iconIdx, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePict, vsCommandControlType.vsCommandControlTypeButton);
??????????????? if (command != null && bar != null)
??????????????? {
??????????????????? command.AddControl(bar, 1);
??????????????? }
??????????? }
??????????? catch (ArgumentException)
??????????? {
??????????????? // Command already exists, so ignore
??????????? }
??????? }
現(xiàn)在我們有了必要的代碼,讓插件正確地集成到Visual Studio用戶接口中去的代碼,并當?shù)玫秸埱髸r顯示插件,我們需要增加命令處理,在插件中處理命令是一件相當簡單的事情,這個 IDTCommandTarget接口,我們的Connect 類實現(xiàn)了它,提供了一些必要的方法從Visual Studio中正確地處理命令。你將需要根據(jù)下面的代碼更新QueryStatus和Exec 方法,下面的代碼的含義是,當它的菜單項或者工具條按鈕被點擊后顯示出Line Counter插件。
?
public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)
??????? {
??????????? if(neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
??????????? {
??????????????? // Respond only if the command name is for our menu item or toolbar button
??????????????? if (commandName == "LineCounterAddin.Connect.LineCounterAddin" || commandName == "LineCounterAddin.Connect.LineCounterAddinButton")
??????????????? {
??????????????????? // Disable the button if the Line Counter window is already visible
??????????????????? if (m_toolWin.Visible)
??????????????????? {
??????????????????????? // Set status to supported, but not enabled
??????????????????????? status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported;
??????????????????? }
??????????????????? else
??????????????????? {
??????????????????????? // Set status to supported and eneabled
??????????????????????? status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
??????????????????? }
??????????????????? return;
??????????????? }
??????????? }
??????? }
?
??????? public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
??????? {
??????????? handled = false;
??????????? if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
??????????? {
??????????????? // Respond only if the command name is for our menu item or toolbar button
??????????????? if (commandName == "LineCounterAddin.Connect.LineCounterAddin" || commandName == "LineCounterAddin.Connect.LineCounterAddinButton")
??????????????? {
??????????????????? // Only display the add-in if it is not already visible
??????????????????? if (m_toolWin != null && m_toolWin.Visible == false)
??????????????????? {
??????????????????????? m_toolWin.Visible = true;
??????????????????? }
?
??????????????????? handled = true;
??????????????????? return;
??????????????? }
??????? ??? }
??????? }
???????? 隨著OnConnection方法的完成,你就已經(jīng)建立了一個漂浮工具窗口的插件,完整的用戶控件能讓你計算你的解決方案中總共的代碼行數(shù),各個工程的代碼行數(shù),以及各個文件的代碼行數(shù)。
代碼行數(shù)統(tǒng)計器(二):http://sifang2004.cnblogs.com/archive/2006/06/26/436178.html
總結(jié)
以上是生活随笔為你收集整理的Visual Studio 2005 插件编程(代码行数统计插件)之一的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 都在说TDD开发,那到底TDD是什么?
- 下一篇: 大班教案《红黄蓝》反思