65.9K
CodeProject 正在变化。 阅读更多。
Home

Lexware Assembly Reference Tool for Visual Studio 2005 / 2008

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (12投票s)

2007年12月21日

CPOL

6分钟阅读

viewsIcon

77668

downloadIcon

563

Visual Studio 2005 / 2008 都缺乏一种有效的方法来为解决方案配置(“Debug”或“Release”)定义程序集引用。此工具填补了这一空白。

Sample Image - maximum width is 600 pixels

引言

Visual Studio 2005 / 2008 都缺乏一种有效的方法来为解决方案配置(“Debug”或“Release”)定义程序集引用。

背景

有几种方法可以定义程序集引用,这些引用会随着解决方案配置的变化而变化,但对于大型项目来说,它们并不足够。Visual Studio 2005 / 2008 的以下功能对于小型项目来说是很好的:

  • 将一个项目引用到另一个项目。项目必须都在同一个解决方案中。当许多开发人员在处理位于不同解决方案的多个项目时,这并不总是可行的。
  • 引用位于项目输出路径的程序集。Visual Studio 2005 首先在项目的输出路径中搜索程序集引用。一种按解决方案配置定义程序集引用的方法是将所有程序集放在一个公共输出目录中(MSDN 文章)。这是不灵活的。

Lexware Assembly Reference Tool 填补了这一空白,它在 Visual Studio 2005 / 2008 中提供了一个新的工具窗口,允许您将“硬编码”的程序集引用路径更改为灵活的引用路径,这些路径会根据解决方案配置而变化。该工具会检测程序集引用路径中的“Debug”或“Release”字样,并以红色标记程序集,以显示当您在其他配置中生成项目时可能出现的问题。您只需按一下按钮,所有这些路径都将被转换为依赖于项目配置的路径。

ToolWindow

此外,该工具还提供了以下功能:

  • 更改“Copy Local”属性
  • 操作“Specific Version”属性
  • 编辑程序集引用路径(“HintPath”)
  • 将项目引用转换为程序集引用
  • 删除程序集引用

Context menue

安装并启动插件后,您可以通过 Visual Studio 2005 / 2008 的工具菜单打开程序集引用工具。

VisualStudioMenue

工作原理

其背后的总体思路

该工具通过将引用路径中的“\Debug\”或“\Release\”替换为“\$Configuration\”(Visual Studio 2005 / 2008 中解决方案配置的占位符)来实现灵活的程序集引用路径。Visual Basic 和 CSharp 项目在尝试解析程序集路径时,能够将占位符替换回“\Debug\”或“\Release\”。

由于 Visual Studio 2005 / 2008 对象模型不允许对引用路径进行任何更改,因此该工具会更改底层项目文件中的路径。它会操作标签,即被引用的程序集的路径。

Hnit path

当工具保存项目文件时,Visual Studio 会注意到此更改并要求您重新加载项目。

对于某些功能,该工具会保留对程序集引用(VSLangProj80.Reference3)的内部 Visual Studio 表示的引用,这允许操作诸如“CopyLocal”、“SpecificVersion”或删除引用的属性。对这些属性的更改会直接反映在 Visual Studio 中程序集引用的原始属性窗口中。

工具解决方案本身

该解决方案包含一个 C# 项目和一个安装项目。安装项目是使用 Setup Project 模板创建的。C# 项目最初是使用 Visual Studio 2005 的 Visual Studio Add-In 项目模板创建的。

New project

如果您在项目向导中选择这样做,Visual Studio 会自动为插件创建一个“Tool”菜单项。如果您选择“I would like my Add-In to load, …”,Visual Studio 会在启动时直接加载您的插件。

Projectwizard

在新项目中,Visual Studio 会创建两个扩展名为 'AddIn' 的文件。一个位于项目文件夹中(例如 'Lexware.Tools.AssemblyReferences.AddIn'),用于插件部署。另一个(例如 'Lexware.Tools.AssemblyReferences - for Testing.AddIn')放置在 Visual Studio Add-In 文件夹中。这是在调试插件时使用的文件。Visual Studio Add-In 文件夹包含在您的“文档”文件夹中。对于 Windows Vista,它应该在这里找到:C:\Users\’Your User Name’\Documents\Visual Studio 2005\Addins。Add-In 文件包含插件的完整描述。它提供了友好名称、加载行为和其他 Visual Studio 在“Add-in Manager”中显示插件所需的信息。

Addin Manager Menue

Addin Manager

插件

Add-In 项目包含 'connect.cs' 文件,其中包含连接到 Visual Studio 的代码。当 Add-In 启动时,会调用 'Connect' 类。事实上,它是在下面描述的 '.Addin' 文件中的 'FullClassName' 标签中定义的。

Connect

Connect 类

Connect 类实现了 Visual Studio 接口 IDTExtensibility2,该接口定义了 OnConnection 方法。当 Visual Studio 将 Add-In 加载到内存时,会调用此方法。这是您可以添加菜单项的地方,在这种情况下,可以创建一个工具窗口。

public void OnConnection(object application, ext_ConnectMode connectMode,
    object addInInst, ref Array custom)
{
    _applicationObject = (DTE2)application;
    _addInInstance = (AddIn)addInInst;

    CreateToolWindow();

    if(connectMode == ext_ConnectMode.ext_cm_UISetup)
    {
        object[] contextGUIDS = new object[] {};
        Commands2 commands = (Commands2)_applicationObject.Commands;
        string toolsMenuName;

        try
        {
            //If you would like to move the command to a different menu, change the
            //word "Tools" to the English version of the menu. This code will take
            // the culture, append on the name of the menu then add the command to
            // that menu. You can find a list of all the top-level menus in the file
            //  CommandBar.resx.
            ResourceManager resourceManager = new ResourceManager(
                "Lexware.Tools.AssemblyReferences.CommandBar",
                Assembly.GetExecutingAssembly());
            CultureInfo cultureInfo = new CultureInfo(_applicationObject.LocaleID);
            string resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName,
                LocalResources.ToolbarName);
            toolsMenuName = resourceManager.GetString(resourceName);
        }
        catch
        {
            //We tried to find a localized version of the word Tools, but one was
            // not found.
            //  Default to the en-US word, which may work for the current culture.
            toolsMenuName = LocalResources.ToolbarName;
        }

        //Place the command on the tools menu.
        //Find the MenuBar command bar, which is the top-level command bar holding
        //all the main menu items:
        Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = (
            (CommandBars)_applicationObject.CommandBars)["MenuBar"];

        //Find the Tools command bar on the MenuBar command bar:
        CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
        CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;

        //This try/catch block can be duplicated if you wish to add multiple commands
        // to be handled by your Add-in,
        //  just make sure you also update the QueryStatus/Exec method to include
        //  the new command names.
        try
        {
            //Add a command to the Commands collection:
            Command command = commands.AddNamedCommand2(_addInInstance,
                "AssemblyReferences", "Assembly Reference Tool",
		"Checks and fixes assembly references. 
		Uses placeholder for debug and release directory.",
                 true, 0, ref contextGUIDS,
                 (int)vsCommandStatus.vsCommandStatusSupported +
                 (int)vsCommandStatus.vsCommandStatusEnabled,
                 (int)vsCommandStyle.vsCommandStyleText,
                 vsCommandControlType.vsCommandControlTypeButton);

            //Add a control for the command to the tools menu:
            if((command != null) && (toolsPopup != null))
            {
                command.AddControl(toolsPopup.CommandBar, 1);
            }
        }
        catch(ArgumentException)
        {
            //If we are here, then the exception is probably because a
            // command with that name
            // already exists. If so there is no need to recreate the command and 
            // we can safely ignore the exception.
        }
    }
}

当用户单击您的菜单项时,将调用同一类中的 Exec 方法。您可以过滤 commandName,以确定被调用的菜单项是否是您的。

public void Exec(string commandName, vsCommandExecOption executeOption,
    ref object varIn, ref object varOut, ref bool handled)
{
    handled = false;
    if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
    {
        if(commandName == "Lexware.Tools.AssemblyReferences.Connect.AssemblyReferences")
        {
            // Open Toolwindow
            CreateToolWindow();

            handled = true;
            return;
        }
    }
}

工具窗口包含此 Add-In 中的所有重要代码,它是一个 UserControl。要创建它,您可以调用 CreateToolWindow2。此方法会创建一个新的 Visual Studio 工具窗口,并在其中托管一个用户控件。

private void CreateToolWindow()
{
    if(_toolWindow != null)
    {
        _toolWindow.Activate();
    }
    else
    {
        //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)
        Windows2 windows2 = (Windows2)_applicationObject.Windows;
        Assembly asm = Assembly.GetExecutingAssembly();

        object customControl = null;
        string className = "Lexware.Tools.AssemblyReferences.ToolWindowControl";
        string caption = "Assembly References";
        _toolWindow = windows2.CreateToolWindow2(_addInInstance, asm.Location, className, 
                                                 caption, _toolWindowGuid,
                                                 ref customControl);

        //Set the picture displayed when the window is tab docked (this causes
        //problems in Visual Studio 2008)
        try
        {
            _toolWindow.SetTabPicture(LocalResources.LexwareBmp.GetHbitmap());
        }
        catch
        {
        }

        //When using the hosting control, you must set visible to true before calling
        // HostUserControl, otherwise the UserControl cannot be hosted properly.
        _toolWindow.Visible = true;

        if (customControl != null)
        {
            _toolWindowControl = (ToolWindowControl)customControl;
            _toolWindowControl.ApplicationObject = _applicationObject;
            _toolWindowControl.ParentToolWindow = _toolWindow;
        }
    }
}

工具窗口

工具窗口会注册 Visual Studio 解决方案、文档和命令对象的某些事件,以便 Add-In 能够注意到解决方案中的更改。

private void RegisterEvents()
{
    if (_solutionEvents != null)
    {
        UnregisterEvents();
    }
    _solutionEvents = _applicationObject.Events.SolutionEvents;

    // register new events
    _solutionEvents.Opened += new _dispSolutionEvents_OpenedEventHandler(
         _solutionEvents_Opened);
    _solutionEvents.ProjectAdded += new _dispSolutionEvents_ProjectAddedEventHandler(
         _solutionEvents_ProjectAdded);
    _solutionEvents.ProjectRemoved += new _dispSolutionEvents_ProjectRemovedEventHandler(
         _solutionEvents_ProjectRemoved);
    _solutionEvents.ProjectRenamed += new _dispSolutionEvents_ProjectRenamedEventHandler(
         _solutionEvents_ProjectRenamed);
    _solutionEvents.AfterClosing += new _dispSolutionEvents_AfterClosingEventHandler(
         _solutionEvents_AfterClosing);
    _documentEvents.DocumentSaved += new _dispDocumentEvents_DocumentSavedEventHandler(
         _documentEvents_DocumentSaved);

    _commandEvents.AfterExecute += new _dispCommandEvents_AfterExecuteEventHandler( 
         _commandEvents_AfterExecute);
}

private void _commandEvents_AfterExecute(string Guid, int ID, object CustomIn,
    object CustomOut)
{
    //Command name: File.SaveSelectedItems
    //Command GUID/ID: {5EFC7975-14BC-11CF-9B2B-00AA00573819}, 331

    //Command name: File.SaveAll
    //Command GUID/ID: {5EFC7975-14BC-11CF-9B2B-00AA00573819}, 224

    //Command name: File.SaveSelectedItemsAs
    //Command GUID/ID: {5EFC7975-14BC-11CF-9B2B-00AA00573819}, 226

    //Command name: Build.SolutionConfigurations
    //Command GUID/ID: {5EFC7975-14BC-11CF-9B2B-00AA00573819}, 684

    //Command name: Project.Addreference
    //Command GUID/ID: {1496A755-94DE-11D0-8C3F-00C04FC2AAE2}, 1113
    if (((Guid == "{5EFC7975-14BC-11CF-9B2B-00AA00573819}") && (ID == 331)) ||
        ((Guid == "{5EFC7975-14BC-11CF-9B2B-00AA00573819}") && (ID == 224)) ||
        ((Guid == "{5EFC7975-14BC-11CF-9B2B-00AA00573819}") && (ID == 226)))
    {
        ReadAllReferences();
    } 
    else if ((Guid == "{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}") && (ID == 1113))
    {
        ParentToolWindow.Activate();
    }
}

当解决方案发生变化时,Add-In 会遍历解决方案中的项目,并读取每个项目的引用。它将每个引用添加到列表视图中,并将 AssemblyReferenceInformation 类的实例放置在 ListViewItem 的标签中。此实例包含有关项目文件(.csproj.vbproj)和程序集引用(VSLangProj80.Reference3)的信息,这些信息将在操作引用时使用。

private void ReadAllReferences()
{
    ClearHintLists();

    if ((_applicationObject != null) && (_applicationObject.Solution != null))
    {
        // Walk through the projects of the solution and search for assembly references
        foreach (Project currentProject in _applicationObject.Solution.Projects)
        {
            ReadProjectReferences(currentProject);
        }
    }
}

private void ReadProjectReferences(Project currentProject)
{
    try
    {
        if (currentProject != null)
        {
            VSProject2 visualStudioProject = currentProject.Object as VSProject2;

            // The current project can be a 'real' project, but it can also be a
            // folder (see else if)
            if (visualStudioProject != null)
            {
                string projectFullName = currentProject.FullName;

                if (!string.IsNullOrEmpty(projectFullName))
                {
                    FileInfo projectFileInfo = new FileInfo(projectFullName);

                    // If it is a csproj or a vbproj, add it the list view
                    if (projectFileInfo.Exists &&
                        ((projectFileInfo.Extension == _csProjectFileExtension) || 
                            (projectFileInfo.Extension == _vbProjectFileExtension)))
                    {
                        // Add a group for this project
                        ListViewGroup projectGroup = GetProjectGroup(currentProject);

                        AddAssemblyHintsToListView(currentProject, projectFullName,
                            projectGroup, visualStudioProject);
                    }
                }
            }
            else if ((currentProject.ProjectItems != null) && (
                currentProject.ProjectItems.Count > 0))
            {
                // Project Item Type       GUID
                // Physical File         {6BB5F8EE-4483-11D3-8BCF-00C04F8EC28C}
                // Physical Folder       {6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}
                // Virtual Folder        {6BB5F8F0-4483-11D3-8BCF-00C04F8EC28C}
                // Subproject            {EA6618E8-6E24-4528-94BE-6889FE16485C}

                // The projects contains a sub folder -> search for projects in
                // these folders
                foreach (ProjectItem currentProjectItem in currentProject.ProjectItems)
                {
                    if (currentProjectItem.SubProject != null)
                    {
                        ReadProjectReferences(currentProjectItem.SubProject);
                    }
                }
            }
        }
        // Enable fixit button, if there is something to fix
        toolStripButtonFixIt.Enabled = (_needsToBeSaved.Count > 0);
    }
    catch (Exception ex)
    {
        ShowMessage(ex);
    }
}

当用户单击“FixIt”按钮时,工具会更改底层项目文件。它会保存所有需要保存的项目,因为存在不正确的程序集路径。

Convert to configuration based assembly paths

private void SaveDirtyProjects()
{
    try
    {
        foreach (KeyValuePair<string> project in _needsToBeSaved)
        {
            XmlDocumentHolder documentHolder = project.Value;

            SaveProject(documentHolder);
        }
    }
    catch (Exception ex)
    {
        ShowMessage(ex);
    }
}

private void SaveProject(XmlDocumentHolder documentHolder)
{
    string projectName = documentHolder.Project.FullName;

    if (!documentHolder.Project.Saved)
    {
        MessageBox.Show("Please save all projects before you fix the problems.");
        return;
    }

    // Check out the project
    if ((_sourceControl != null) && (_sourceControl.IsItemUnderSCC(projectName)) &&
        (!_sourceControl.IsItemCheckedOut(projectName)))
    {
        _sourceControl.CheckOutItem(projectName);
    }

    using (XmlTextWriter writer = new XmlTextWriter(documentHolder.ProjectFileName,
        Encoding.UTF8))
    {
        writer.Formatting = Formatting.Indented;
        documentHolder.XmlDocument.Save(writer);
    }
}

Visual Studio 会注意到工具更改了项目文件,并要求您重新加载项目。

Reload the project

安装项目

安装项目将 Add-In 部署到 Visual Studio 2005 和 Visual Studio 2008 的 AddIns 文件夹中。

它会自动检测新旧版本,在安装新版本之前会移除旧版本。要为 Add-In 创建新的安装程序版本,只需更改安装程序的版本。Visual Studio 会为您生成一个新的 ProductCode,但不会触及 UpgradeCode。通过代码组合,Windows Installer 能够检测并更新 Add-In 的旧版本或新版本。

Setupproject

构建解决方案的要求

要构建解决方案,您需要 Visual Studio 2008。此 Add-In 已在 Visual Studio 2005 和 2008 Team Developer 和 Team Suite 上进行了测试。

历史

我们将 Add-In 的源代码移植到了 Visual Studio 2010。Visual Studio 2010 引入了扩展的概念。对于扩展,部署比 Visual Studio 2005 / 2008 容易得多,因此不再需要 MSI 安装程序。其余代码至少保持不变。新代码托管在 http://assemblyreftool.codeplex.com,Visual Studio 2010 的扩展可以从 Visual Studio Code Gallery 下载。

© . All rights reserved.