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

ProjectMIDI, 第二部分:创建自定义 ProjectMIDI 程序集

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.09/5 (2投票s)

2006年1月24日

CPOL

14分钟阅读

viewsIcon

45217

本文介绍如何创建自己的 ProjectMIDI 程序集。

Image of ProjectMIDI SampleAssembly Dialog

引言

在我关于 ProjectMIDI 的第一篇文章中,我描述了它的基本架构和操作。ProjectMIDI 的优势之一在于其易于扩展性。在本文中,我将演示如何实现这一点。我假设您已经阅读了 ProjectMIDI 的第一篇文章,并且将在此文章的基础上进行扩展。

在本文中,我将按顺序介绍我用来创建 SampleAssembly 代码的过程。最终代码已包含在原始软件包中。您可以选择重新创建该文件,或者在源代码中跟随我一起学习。

快速入门

我撰写本文时创建的代码已添加到原始文章的下载包中。请从原始文章或 ProjectMIDI 网站的下载页面下载代码。

背景

第一篇文章中,我们了解到 ProjectMIDI 的主要目标之一是易于扩展。我希望能够快速轻松地创建小程序来控制我的 MIDI 设备的各种方面。在本文中,我将通过向您展示创建 SampleAssembly 代码的步骤,来演示实现这一点有多么容易。

创建新的 ProjectMIDI 程序集

好的,我们开始吧。第一步是创建基本的 .NET 程序集,然后向其中添加 ProjectMidi 接口。我想到了几种方法可以做到这一点。也许将来我会创建一个 Visual Studio 向导来完成这项工作,但目前,我们只能手动完成。我通常使用的方法是

  1. 使用 Visual Studio IDE 创建一个新的类库项目,并将其添加到现有的 ProjectMIDI 解决方案中。
  2. 将所需的接口代码从现有程序集复制到新程序集中。TraceWindow 是一个很好的来源,因为它是一个非常简单的程序集。

所以,让我们创建一个新的程序集,并将其添加到现有的 ProjectMIDI 解决方案中。

  1. 下载并安装 ProjectMIDI 源代码。

    安装时请务必包含源代码

  2. 在 Visual Studio .NET 中打开ProjectMidiAll 解决方案。
  3. 向解决方案添加新项目。

    在“解决方案资源管理器”中右键单击“解决方案 ProjectMidiAll”,然后选择“添加 -> 新建项目”。

  4. 在“Visual C# 项目”下选择“类库”模板。
  5. 为新程序集选择一个名称。在此示例中,我正在创建SampleAssembly 程序集。为您的程序集起一个新的、唯一的名称。
  6. 选择“确定”。

    将创建一个新项目并将其添加到解决方案中。新的.CS 文件现在应显示在 IDE 中。

  7. 更改默认的class1名称。

    不幸的是,IDE 创建的文件名为“class1.cs”,而不是“SampleAssembly.cs”这样更具描述性的名称。要解决此问题,请在“解决方案资源管理器”中右键单击“class1.cs”文件名,然后选择“重命名”,将其命名为您新程序集的名称。

  8. 同样,编辑新文件以将所有出现的 `Class1` 更改为与您程序集的名称匹配。

    在此示例中,我将所有出现的 `Class1` 更改为 `SampleAssembly`。

好的,现在我们的 ProjectMIDI 解决方案中已添加了一个新程序集。它应该看起来像这样:

using System;

namespace SampleAssembly
{
    /// <summary>
    /// Summary description for SampleAssembly.
    /// </summary>
    public class SampleAssembly
    {
        public SampleAssembly()
        {
            //
            // TODO: Add constructor logic here
            //
        }
    }
}

这不是 ProjectMIDI 程序集。如第一篇文章所述,所有 ProjectMIDI 程序集都需要派生自 `IProjectMidi` 接口并实现该接口。所以,我们现在就来实现它。

实现 IProjectMidi 接口

为了方便复制粘贴,我已在 IDE 中与新的 `SampleAssembly.cs` 文件一起打开了 `TraceWindow.cs` 文件。这样,我就可以轻松地在它们之间切换。

如果您的新程序集不打算实现用户界面,那么事情会更简单一些,您只需要直接派生您的类自 `IProjectMidi`。由于 `SampleAssembly` 将实现一个窗口,因此它需要同时派生自 `System.Windows.Form.Form` 类和 `IProjectMidi` 接口(或其派生接口)。

  1. 添加相应的 using 语句。

    复制 `using ProjectMidiNS` 语句。这将使我们能够访问 `ProjectMIDI` 命名空间。

    如果您的程序集实现了用户界面窗口,请复制 `using System.Windows.Forms` 和 `using System.Drawing` 语句。

  2. 为上面步骤中复制的每个 `using` 语句添加引用

    在“解决方案资源管理器”中右键单击新项目的“引用”,然后选择“添加引用”。

    在 `using ProjectMidiNS` 引用的“项目”选项卡下选择 `ProjectMidiInterfaces`。

    在 `using System.Windows.Forms` 引用的“.NET”选项卡下选择 `System.Windows.Forms.dll`。

    在 `using System.Drawing` 引用的“.NET”选项卡下选择 `System.Drawing.dll`。

  3. 为您的类添加 `ProjectMidiAssemblyAttribute`。

    将 `ProjectMidiAssemblyAttribute` 语句复制到类声明之前。更改指定的名称以匹配您程序集的名称。ProjectMIDI 将在图标菜单的程序集列表中使用此名称。

  4. 从 `System.Windows.Forms.Form` 和 `IProjectMidi` 派生您的类。

    复制 `public class TraceWindow` 右侧的代码,并将其粘贴到您的类名称的右侧。请注意,在此示例中,接口不是 `IProjectMidi`,而是 `IProjectMidiHideShow`。这是一个派生自 `IProjectMidi` 的接口,并添加了一个方法,允许 ProjectMIDI 通过状态栏图标菜单隐藏和显示此程序集的窗口。

  5. 复制 `ProjectMidiParent` 属性代码

    ProjectMidiParent 属性用于允许 ProjectMidi 将一个反向指针传递给每个程序集。此反向指针使每个程序集能够访问 ProjectMidi 核心可执行文件提供的几个有用的属性和方法。

  6. 复制 `projectMidiParent` 成员变量。

    此变量由上面步骤中复制的 `ProjectMidiParent` 属性使用。

  7. 复制 `PerformStartProcessing` 方法代码。

    此方法在 ProjectMidi 启动期间的战略点被调用。

  8. 复制 `IProjectMidiHideShow` 成员部分。

    ProjectMidi 核心可执行文件调用此方法来切换每个程序集的可见状态。

此时,您的代码应如下所示,并且可以编译而无错误:

using System;
using System.Windows.Forms;
using System.Drawing;
using ProjectMidiNS;

namespace SampleAssembly
{
    /// <summary>
    /// Summary description for SampleAssembly
    /// </summary>
    [ProjectMidiAssemblyAttribute(AssemblyType.Applet, "Sample" )]
    public class SampleAssembly : System.Windows.Forms.Form, IProjectMidiHideShow
    {
        private IProjectMidiParent projectMidiParent;

        public SampleAssembly()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        #region ProjectMidiParent
        // This will happen immediately after construction
        public IProjectMidiParent ProjectMidiParent 
        {
            set 
            { 
                projectMidiParent = value; 
            }
        }
        #endregion
        #region PerformStartProcessing
        public void PerformStartProcessing( StartPhase phase )
        {
            switch(phase)
            {
                case StartPhase.FirstTimeEver:
                    break;
                case StartPhase.AfterLoading:
                    break;
                case StartPhase.AllAssembliesLoaded:
                    break;
                case StartPhase.StartConnecting:
                    break;
                case StartPhase.AllAssembliesConnected:
                    break;
                case StartPhase.Terminating:
                    break;
                default:
                    Console.WriteLine("Unknown StartPhase");
                    break;
            }
        }
        #endregion

        #region IProjectMidiHideShow Members
        // Toggle Hide/Show state of window
        [ProjectMidiActionAttribute("Hide/Show",0, 
           "","Hide or Show Trace window.")]
        public void OnHideShow(object sender, EventArgs e) { HideShow(); }
        public void HideShow()
        {
            this.Visible = !this.Visible;
        }
        #endregion
    }
}

添加 InitializeComponent 方法

由于我们使用的是类库模板,因此我们的类不是作为窗体创建的。要添加此功能,我们需要添加一个 `InitializeComponent` 方法并从构造函数中调用它。

  1. 在构造函数中添加对 `InitializeComponent()` 的调用。

    这应该是我们构造函数中唯一的一行。

    #region CTOR
    public SampleAssembly()
    {
        // Required for Windows Form Designer support
        InitializeComponent();
    }
    #endregion
  2. 打开我们新文件的“查看设计器”,并分配一些属性。

    这将导致“查看设计器”自动为我们创建 `InitializeComponent` 方法。至少,为窗口分配一个 `Text` 值,如果您愿意,还可以分配一个 `Icon`。

    切换回代码视图后,您应该会看到 `InitializeComponent` 方法已被添加,并且看起来大致如下:

    private void InitializeComponent()
    {
        System.Resources.ResourceManager resources = 
          new System.Resources.ResourceManager(typeof(SampleAssembly));
        // 
        // SampleAssembly
        // 
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
        this.ClientSize = new System.Drawing.Size(292, 266);
        this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
        this.Name = "SampleAssembly";
        this.Text = "SampleAssembly";
    }

此时,我们现在拥有一个可正常工作的 ProjectMIDI 程序集。它实际上并不做什么,只是显示一个空白窗口。不过,核心 ProjectMIDI 可执行文件提供了一些功能:

  • 窗口的大小和位置将在会话之间保存和恢复。
  • 窗口的名称将显示在系统托盘图标菜单的程序集菜单中。

    选择此项将交替隐藏和显示窗口。

请注意,必须将程序集复制到 ProjectMIDI 的程序集文件夹才能运行它。最简单的方法是修改新项目的属性,使其在运行时程序集文件夹中创建程序集 DLL。

  1. 在“解决方案资源管理器”中右键单击新项目。
  2. 选择“属性”。
  3. 在“配置属性”下选择“生成”。
  4. 将“输出路径”更改为 `..\Runtime\Assemblies`。

    这假定您在与 ProjectMIDI 其他项目相同的目录下创建了项目。

现在,当您生成项目时,它将自动放置到运行时文件夹中。

这可能也是更改 `ProjectMidiInterfaces` 引用的属性的好时机。Visual Studio .NET 默认会将复制本地设置设置为 True。这将在每次生成代码时导致 `ProjectMidiInterfaces.dll` 的副本复制到输出目录。在更改输出目录之前,这不成问题。但是,现在它会将该 DLL 的副本放入 `RunTime\Assemblies` 目录,这并不是我们想要的。要更改此设置:

  • 在“解决方案资源管理器”的“引用”部分下选择 `ProjectMidiInterfaces`。
  • 复制本地设置从True更改为False

测试运行新的空白程序集

好的,让我们对代码进行一次测试运行,以确保我们到目前为止所做的一切都正常工作。在继续之前,请检查以下几点:

  1. 删除先前的 ProjectMidi 注册表设置。

    如果您之前安装和/或运行过 ProjectMidi,它将设置 `FoldersPath` 注册表设置。这可能指向与您现在放置新生成程序集的目录不同的目录。您可以更改此设置,或者删除它或从注册表中删除所有 ProjectMidi 设置。要执行此操作,请启动RegEdit(开始 -> 运行 -> RegEdit)并找到以下项:

    HKEY_LOCAL_MACHINE\SOFTWARE\ProjectMidi\FoldersPath

    此项需要指向您的程序集文件夹所在的父文件夹。如果不是,则需要更改或删除它。

  2. 确保您正在处理ProjectMidi 解决方案

    您不能直接执行 ProjectMidi 程序集。您需要生成程序集并将其复制到程序集文件夹(如上所述)。然后,您需要启动ProjectMidi.exe。ProjectMidi.exe 将加载并执行程序集。

观察以下行为是否正常工作:

  1. 您的新程序集的窗口已显示。
  2. 您的程序集的名称现在出现在图标菜单程序集菜单中。

    使用此菜单项,您可以隐藏或恢复您的窗口。

  3. ProjectMidi 将保存和恢复您窗口的大小和位置。

    如果您移动然后关闭窗口,稍后恢复它时,它将显示在相同的位置和大小。

按照惯例,我将每个 ProjectMidi 程序集窗体的 `ShowInTaskbar` 设置为 false。我这样做是因为 ProjectMidi 窗口通常很多,我不喜欢弄乱任务栏。您可以在“查看设计器”中查看窗体时,从窗体的属性中设置此参数。

我还为每个窗口的 `Icon` 属性分配了我标准的ProjectMidi.ico图标。也许有一天,我会更有创意,为每个窗口分配一个独特的图标。

添加功能

好了,现在我们可以开始做一些有趣的事情了。到目前为止,我们只是在创建样板代码。现在,我们将开始添加实际功能。

注意:我意识到上面的样板代码可以由 Visual Studio Wizard 插件或其他类似的东西自动创建。那是我以后要解决的项目。

我将添加一些控件来演示如何执行此操作。我不期望这会形成逻辑上的控件分组,但它应该很有趣,并给我们一些东西来玩。

添加一个按钮

按钮可用于触发单个值操作,例如“递增页码”

  1. 将一个按钮添加到窗体。
  2. 创建按钮单击事件处理程序。

    通过单击按钮属性窗口中的小闪电图标来显示按钮的事件属性。然后,双击Click事件。

  3. 按如下方式编辑新的事件处理程序代码:
    [ProjectMidiEventAttribute("Button",1,"SetNextSong", 
       ConnectionType.Normal,"Sent whenever the button is pressed.")]
    public event ProjectMidiEventHandler OnSampleButton;
    private void sampleButton_Click(object sender, System.EventArgs e)
    {
        try
        {
            // Fire the event (if connected).
            if(OnSampleButton!=null) OnSampleButton( this, e );
        }
        catch(Exception err)
        {
            Console.WriteLine("SampleButtonClick error: {0}",err.Message);
        }
    }

    您的代码将根据您为按钮指定的名称而有所不同。

添加一个文本框

添加一个文本框以显示连接到其动作节点的事件的值。

  1. 将一个文本框控件添加到窗体。
  2. 创建以下动作代码:
    // Here is a sample action. 
    // The name specified in the attribute will be displayed in the connections list. 
    // The 2nd parameter specifies how many data values it expects; 128 in this case.
    // The optional 3rd parameter can specify an auto-connect name.
    // This action will be invoked whenever any events connected to it are fired.
    // It will display the value passed in the list box.
    [ProjectMidiActionAttribute("TextBox",128,
      "GetSongNum","String or number to be displayed in the textbox.")]
    public void textBoxAction( object source, EventArgs e )
    {
        StringEventArgs eString = e as StringEventArgs;
        IntEventArgs eInt = e as IntEventArgs;
        if(eString != null)
        {
            TraceMessage( "Text Box Action: "+eString.str );
            sampleTextBox.Text = eString.str;
        }else if(eInt != null)
        {
            TraceMessage( "Text Box Action: "+eInt.data );
            sampleTextBox.Text = eInt.data.ToString();
        }
    }

添加一个轨道条

一个 `TrackBar` 很有趣,因为它既可以作为动作,接收来自连接的位置信息,也可以作为事件,将用户选择的位置信息发送到连接。

  • 将一个 `Trackbar` 控件添加到窗体。
  • 添加滚动事件处理程序。

    创建滚动事件处理程序和 ProjectMidi 事件,如下所示:

    // This is the event associated with the track bar.
    // It will be fired every time the track bar is moved.
    // It is "auto-connected" to set the song #, but the user can change this.
    [ProjectMidiEventAttribute("TrackBar",16,"SetSongNum",
      ConnectionType.Normal,"Sent whenever the TrackBar is moved.")]
    public event ProjectMidiEventHandler OnTrackBar;
    private void sampleTrackBar_Scroll(object sender, System.EventArgs e)
    {
        int newvalue = sampleTrackBar.Value;
        try
        {
            IntEventArgs eInt = new IntEventArgs( newvalue );
            if(OnTrackBar!=null) OnTrackBar(this,eInt);
        }
        catch(Exception err)
        {
            Console.WriteLine("Trackbar error: {0}",err.Message);
        }
    }
  • 添加一个 ProjectMidi动作

    添加代码,如下所示,以实现 ProjectMidi动作

    // Here is the action associated with the TrackBar.
    // This allows having the TrackBar set by other controls.
    // It is auto-connected to Song # so that it will track it if changed elsewhere.
    [ProjectMidiActionAttribute("TrackBar",128, 
      "GetSongNum","Sets the position of the trackbar.")]
    public void trackBarAction( object source, EventArgs e )
    {
        IntEventArgs eInt = e as IntEventArgs;
        if(eInt != null)
        {
            TraceMessage( "Sample TrackBar Action: "+eInt.data );
            sampleTrackBar.Value = eInt.data;
        }
    }

添加一个上下文菜单

上下文菜单是我最喜欢的功能之一。通过向您的窗体添加上下文菜单,您可以允许用户链接到他们喜欢的任何操作。当用户右键单击窗体时,将显示上下文菜单。菜单中显示的项是通过使连接从我们为上下文菜单创建的事件任何其他 ProjectMidi 程序集中的任何其他动作来指定的!这是一个非常强大且有用的功能。

如下面的代码所示,通过以下步骤实现上下文菜单:

  1. 将一个 `ContextMenu` 控件添加到窗体。
  2. 创建上下文菜单弹出处理程序方法。

    选择 `ContextMenu` 控件,然后双击 `Popup` 属性。此方法在显示上下文菜单之前被调用。我们将添加代码来调用连接到我们上下文菜单事件的每个动作,并将指向 `ContextMenu` 对象的引用传递给它们。

  3. 使用 `ProjectMidiEventAttribute` 添加一个新事件。
    private void SampleAssembly_Load(object sender, System.EventArgs e)
    {
        contextMenu.Popup += new System.EventHandler(this.contextMenu_Popup);
    }
    
    [ProjectMidiMenuEventAttribute("ContextMenu",1,"Menu",ConnectionType.Normal,
            "Displays connected action items on the Sample assmebly's context menu.")]
    public event ProjectMidiEventHandler ContextMenuEvent;
    
    // This event is fired before the icon menu is displayed
    private void contextMenu_Popup(object sender, System.EventArgs e)
    {
        Console.WriteLine("Sample FireContextMenuEvent");
        contextMenu.MenuItems.Clear();
        MenuEventArgs e2 = new MenuEventArgs( contextMenu, "Sample" );
        if(ContextMenuEvent != null) ContextMenuEvent( this, e2 );
    }

玩转 SampleAssembly

现在代码已完成,编译它,然后将 `SampleAssembly.dll` 复制到 ProjectMidi 的程序集目录。启动 `ProjectMidi.exe`,并验证 SampleAssemblies 窗口是否显示。现在,让我们玩玩它。

确保连接对话框、Sample对话框和SongSet对话框都可见。如果不可见,请右键单击任务栏中的 Project MIDI 图标以弹出图标菜单。选择“程序集”子菜单中列出的任何名称将导致相应的程序集交替显示或隐藏。

首先,让我们建立一个从 SongSet 的歌曲编号到 SampleAssembly 文本框的连接。这样,每当任何内容更改歌曲编号时,它都会显示在 SampleAssembly 文本框中。

  1. 右键单击“连接”对话框中任何连接的ID列。选择“新建连接”菜单项。这将创建一个新连接。
  2. 单击ID列标题,按 ID 号对连接列表进行排序。然后向下滚动到列表底部以显示新创建的连接。
  3. 右键单击新连接的每个列,然后选择SongSet程序集、歌曲编号事件以及Sample目标程序集的文本框动作。

通过在 Song Set 对话框中选择不同的歌曲来测试连接。每选择一首歌曲,其编号都应显示在文本框中。

现在,让我们将 TrackBar 连接到歌曲编号。

  1. 右键单击“连接”对话框中任何连接的ID列。选择“新建连接”菜单项。这将创建一个新连接。
  2. 单击ID列标题,按 ID 号对连接列表进行排序。然后向下滚动到列表底部以显示新创建的连接。
  3. 右键单击新连接的每个列,然后选择SongSet程序集、歌曲编号事件以及Sample目标程序集的Track Bar动作。
  4. 使用相同的程序,创建另一个新连接,并将Sample程序集的TrackBar事件连接到SongSet程序集的Song Number动作。

现在,TrackBar 同时作为事件动作连接到 Song Number。

菜单连接

让我们创建一些 Sample Assembly 上下文菜单项来选择下一首或上一首歌曲。

菜单项可以直接添加到菜单中。或者,可以创建一个子菜单,然后将菜单项添加到子菜单中。

在此示例中,我们将创建一个“歌曲”子菜单,并添加用于选择下一首上一首歌曲的项。

  1. 创建一个新连接。
  2. 将此连接的源设置在Sample程序集的ContextMenu事件中。
  3. 现在,通过右键单击目标程序集并选择SubMenu来将其制成子菜单。
  4. 右键单击此新的子菜单连接项,然后选择“编辑上下文菜单”。这将允许您编辑此项的名称。将其名称更改为“歌曲”。
  5. 关闭菜单编辑器。
  6. 现在,从Sample程序集的Song事件创建新的连接。此事件实际上是您刚刚创建的子菜单。从它到SongSet程序集的Prev SongNext Song动作创建连接。

Image of Sample Context Menu

现在,当您右键单击 Sample Assembly 对话框时,将显示一个上下文菜单,其中包含两个菜单项:Set next songSet previous song,它们位于“歌曲”子菜单中。

关注点

我希望本文展示了如何轻松地扩展 ProjectMIDI 以适应您独特的个人需求。我希望许多人会受到启发,创建自己的 ProjectMidi 程序集来控制您的 MIDI 设备。

历史

  • 2006年1月21日 - 初始文档创建。
© . All rights reserved.