システム開発」カテゴリーアーカイブ

ClosedXMLでExcelファイルに画像を埋め込む

Excelファイルの出力でClosedXMLを使うことがありましたが、どうも公式のものは画像埋め込みをサポートしていない模様。

「ClosedXML 画像」で検索すると上位に表示されるページC#でExcelに画像データを埋め込む を参考にさせていただきClosedXMLImageSupportを入手しましたが、本家の最新版と開きがあるようなので、ClosedXMLImageSupportから画像埋め込み関係のコードを本家最新版に取り込みます。

ClosedXMLImageSupportのソリューション内にあるClosedXML_Sandboxが動けば良しとして、ざくっと追加、追記します。

  • ClosedXML\Excel\Drawings\
    • 追加:IXLMarker.cs
    • 追加:IXLPicture.cs
    • 追加:XLMarker.cs
    • 追加:XLPicture.cs
  • ClosedXML\Excel\
    • IXLWorksheet.cs
      • 追加:System.Collections.Generic.List Pictures();
      • 追加:void AddPicture(Drawings.IXLPicture pic);
    • XLWorkbook_Save.cs
      • 追加:using A = DocumentFormat.OpenXml.Drawing;
      • 追加:using Xdr = DocumentFormat.OpenXml.Drawing.Spreadsheet;
      • 追加:private static void AddPictureAnchor(WorksheetPart worksheetPart, Drawings.IXLPicture picture)
      • 内部処理追加:private static void GenerateWorksheetPartContent(WorksheetPart worksheetPart, XLWorksheet xlWorksheet, SaveContext context)
    • XLWorksheet.cs
      • 追加:private List pictures
      • 追加:public List Pictures()
      • 追加:public void AddPicture(Drawings.IXLPicture pic)

これでSandboxを実行するとExcelファイルが出力されて画像も埋め込まれています。
が、しかし。

他サイトの記事にもあるように画像の縦横比を無視してセルにフィットしてしまいます。

ClosedXMLImageSupportから取り込んだXLPictureは、AddMarker()でXLMarkerを追加して、一つ目がfrom、二つ目がtoとしてセルの範囲を指定するようになっています。このtoにあたるMarkerのセル位置とオフセットを上手いこと指定してあげれば画像の縦横比を維持して画像を埋め込むことができるようです。

しかし画像はfromで指定したセルの左上から、toで指定したセルの左上までは拡張されてしまうため、画像が収まる縦横セル数を求めた上でオフセットを指定する必要があります。十分大きな範囲を指定してからどうにかする、という手は使えません。

画像のサイズはピクセルですが、Excelのセル高さ(行高さ)はポイント、セル幅(カラム幅)は標準フォントの数字が何文字表示できるか。なので換算が必要になります。

ポイントからピクセルは簡単ですがカラム幅からピクセルはかなり面倒です。

  1. そのExcelブックの標準フォントとサイズを調べ
  2. TextRenderer.MeasureText()にTextFormatFlags.NoPaddingを指定し、0~9について描画時の最大フォント幅を求め
  3. フォント幅からセルのパディングをもとめ
    (パディング = 切り上げ(フォント幅  / 4) * 2 + 1。*2は両サイドにパディングが入るため。+1はグリッドライン分)
  4. カラムのピクセル幅 = カラム幅 * フォント幅 + パディング

となるようです。
このあたりは
ttps://social.msdn.microsoft.com/Forums/en-US/9a6a9785-66ad-4b6b-bb9f-74429381bd72/margin-padding-in-cell-excel?forum=os_binaryfile

関連ドキュメント : MS-OI29500 の 2.1.595 Part 1 Section 18.3.1.13, col (Column Width & Formatting) f. を参考にしました。

これをカラム毎に計算しながら、fromで指定されたセルから画像が上手く収まるカラム数を求めるのは正直辛いです。

ということでClosedXMLImageSupportから取り込んだXLWorkbook_Save.csのAddPictureAnchor()を弄ることでtoを指定せずに縦横比を維持して画像を埋め込むことができるようにしてみました。
 
まず、XLPictureのフィールドとしてあって使われていないNoChangeAspect, NoMove, NoResizeをIXLPictureでプロパティとして定義しておきます。XLPicture側ももちろん変更。

その上で、XLWorkbook_Save.csに取り込んだAddPictureAnchor()

private static void AddPictureAnchor(WorksheetPart worksheetPart, Drawings.IXLPicture picture)
{
    /* 省略 */
    var markers = picture.GetMarkers();
    Xdr.TwoCellAnchor twoCellAnchor;
    Xdr.FromMarker fMark;
    Xdr.ToMarker tMark;
    if (markers.Count == 2)
    {
    fMark = new Xdr.FromMarker
    {
        ColumnId = new Xdr.ColumnId(markers[0].ColumnId.ToString()),
        RowId = new Xdr.RowId(markers[0].RowId.ToString()),
        ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + picture.PaddingX).ToString()),
        RowOffset = new Xdr.RowOffset((markers[0].RowOffset + picture.PaddingY).ToString())
    };
    tMark = new Xdr.ToMarker
    {
        ColumnId = new Xdr.ColumnId(markers[1].ColumnId.ToString()),
        RowId = new Xdr.RowId(markers[1].RowId.ToString()),
        ColumnOffset = new Xdr.ColumnOffset((markers[1].ColumnOffset + picture.PaddingX).ToString()),
        RowOffset = new Xdr.RowOffset((markers[1].RowOffset + picture.PaddingY).ToString())
    };
    }
    else
    {
    fMark = new Xdr.FromMarker
    {
        ColumnId = new Xdr.ColumnId(markers[0].ColumnId.ToString()),
        RowId = new Xdr.RowId(markers[0].RowId.ToString()),
        ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + picture.PaddingX).ToString()),
        RowOffset = new Xdr.RowOffset((markers[0].RowOffset + picture.PaddingY).ToString())
    };
    tMark = new Xdr.ToMarker
    {
        ColumnId = new Xdr.ColumnId(markers[0].ColumnId.ToString()),
        RowId = new Xdr.RowId(markers[0].RowId.ToString()),
        ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + extentsCx + picture.PaddingX).ToString()),
        RowOffset = new Xdr.RowOffset((markers[0].RowOffset + extentsCy + picture.PaddingY).ToString())
    };
    }
    twoCellAnchor = new Xdr.TwoCellAnchor(
    fMark, tMark,
        new Xdr.Picture(
            new Xdr.NonVisualPictureProperties(
                new Xdr.NonVisualDrawingProperties { Id = nvpId, Name = picture.Name },
                new Xdr.NonVisualPictureDrawingProperties(new A.PictureLocks { NoChangeAspect = true, NoMove = true, NoResize = true })
            ),
            new Xdr.BlipFill(
                new A.Blip { Embed = drawingsPart.GetIdOfPart(imagePart), CompressionState = A.BlipCompressionValues.Print },
                new A.Stretch(new A.FillRectangle())
            ),
            new Xdr.ShapeProperties(
                new A.Transform2D(
                    new A.Offset { X = 0, Y = 0 },
                    new A.Extents { Cx = extentsCx, Cy = extentsCy }
                ),
                new A.PresetGeometry { Preset = A.ShapeTypeValues.Rectangle }
            )
        ),
        new Xdr.ClientData()
    );

    worksheetDrawing.Append(twoCellAnchor);
}

これを↓こんな感じで。

private static void AddPictureAnchor(WorksheetPart worksheetPart, Drawings.IXLPicture picture)
{
    /* 省略 */
    var xdrPicture = new Xdr.Picture(
                new Xdr.NonVisualPictureProperties(
                    new Xdr.NonVisualDrawingProperties { Id = nvpId, Name = picture.Name },
                    // ここでIXLPictureに追加したプロパティを使用。
                    new Xdr.NonVisualPictureDrawingProperties(new A.PictureLocks { NoChangeAspect = picture.NoChangeAspect, NoMove = picture.NoMove, NoResize = picture.NoResize })
                ),
                new Xdr.BlipFill(
                    new A.Blip { Embed = drawingsPart.GetIdOfPart(imagePart), CompressionState = A.BlipCompressionValues.Print },
                    new A.Stretch(new A.FillRectangle())
                ),
                new Xdr.ShapeProperties(
                    new A.Transform2D(
                        new A.Offset { X = 0, Y = 0 },
                        new A.Extents { Cx = extentsCx, Cy = extentsCy }
                    ),
                    new A.PresetGeometry { Preset = A.ShapeTypeValues.Rectangle }
                )
            );

    var markers = picture.GetMarkers();

    var fMark = new Xdr.FromMarker
    {
        ColumnId = new Xdr.ColumnId(markers[0].ColumnId.ToString()),
        RowId = new Xdr.RowId(markers[0].RowId.ToString()),
        ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + picture.PaddingX).ToString()),
        RowOffset = new Xdr.RowOffset((markers[0].RowOffset + picture.PaddingY).ToString())
    };

    if (markers.Count == 2)
    {
        var tMark = new Xdr.ToMarker
        {
            ColumnId = new Xdr.ColumnId(markers[1].ColumnId.ToString()),
            RowId = new Xdr.RowId(markers[1].RowId.ToString()),
            ColumnOffset = new Xdr.ColumnOffset((markers[1].ColumnOffset + picture.PaddingX).ToString()),
            RowOffset = new Xdr.RowOffset((markers[1].RowOffset + picture.PaddingY).ToString())
        };

        var twoCellAnchor = new Xdr.TwoCellAnchor(fMark, tMark, xdrPicture, new Xdr.ClientData());

        worksheetDrawing.Append(twoCellAnchor);
    }
    else
    {
        var ext = new Xdr.Extent { Cx = extentsCx, Cy = extentsCy };
        var oneCellAnchor = new Xdr.OneCellAnchor(fMark, ext, xdrPicture, new Xdr.ClientData());

        worksheetDrawing.Append(oneCellAnchor);
    }
}

元のコードはMarker一つの場合にfromをtoとしても使い、常にTwoCellAnchorを使っていますが、これをOneCellAnchorを使うよう変更しています。あとnew PictureLocks()の箇所ですね。
これでExcelで普通に画像を挿入した場合と同じように埋め込まれるようになりました。

using (FileStream strm = new FileStream(@"sample.png", FileMode.Open))
{
    XLPicture pic = new XLPicture { ImageStream = strm, Name = "test image" };
    pic.AddMarker(new XLMarker { ColumnId = 2, RowId = 3 });
    worksheet.AddPicture(pic);
}

ついでにXLPictureのResize()も少し手を加えると拡大縮小して埋め込めるようになります。

IXLPictureのプロパティにしたNoMoveをtrueにすると埋め込んだ画像が移動できなくなります。NoResizeをtureにするとサイズが変更できなくなります。

XLPictureのPddingX, PddingY(またはEMUOffsetX, EMUOffsetY)にピクセル単位で値を指定するとXLMarkerで指定したセルからのオフセットとして画像が埋め込まれます。XLMarkerのColumnOffset、RowOffsetでもオフセットによる位置指定ができますが、こちらはEMUで指定する必要があるので面倒です。

EEMUOffsetX, EMUOffsetYは設定した値がPaddingX、PaddingYから取得される際にConvertToEmu()を通るので、「設定したピクセル値がEMUに変換されるオフセット」のようです。EMUのつもりで設定するとおかしなことになります。

「ポケモンGO」のアップデート

世間を騒がせたあのポケモンGOにアップデートが行われ、
新たなポケモンが出現するようになるそうです。

去年は会社の周りでもプレイしている人を多く見かけました。
今回のアップデートでまたプレイヤーが増えそうですね。

会社の周りは人混みが多いので、
出歩くときはまた注意が必要になりそうです。

Mac OS 10.12.2 Sierra でSilverlight が動作できない現象について

MacでOSのアップグレードにより、SafariのSilverlightプラグイン仕様に関する設定が「オフ」になっているため、Silverlightのプラグインが使用できない状態となっています。
Silverlightのプラグイン設定を「オン」に変更することでSilverlightが正常に動作させることができます。

作業手順は、次のとおりです。

①「Safari」を起動します。
②「Safari」メニューをクリックします。
③「環境設定」をクリックします。
④「セキュリティ」を選択します。
⑤「プラグイン設定」をクリックします。
⑥「Silverlight」を選択し、チェックを入れます。
⑦ 画面右側の「現在開いているWebサイト」を「オン」に設定します。
⑧ 画面右側の「ほかのWebサイトを閲覧しようとしたとき」を「オン」に設定 します。
⑨「完了」をクリックします。

Excelをファイルをダブルクリックして開いたときに同時に空のウィンドウが開かれる現象

Excelをファイルをダブルクリックして開いたときに同時に空のウィンドウが開かれる現象が

前のバージョンで使用したエクセルの個人用マクロブックがある場合におこります。

(若しくは個人用マクロブックの一部が破損している)

使用アプリケーションはWindows7上のExcel2013です。

無題

回避策:個人用マクロブックを作り直す。

1.個人用マクロブックを開く。(PERSONAL.XLSB)

  場所 C:\Users\ユーザー名\AppData\Roaming\Microsoft\Excel\XLSTART\

2.標準モジュールなどを任意の場所にエクスポートして個人用マクロブックを閉じる。

3.個人用マクロブックを削除。(PERSONAL.XLSB)

4.エクセルブックを新規に作成。

5.マクロを記録する。記録先は「個人用マクロブック」にする。

6.保存してエクセルブックを閉じる。

7.個人用マクロブックを開く。(PERSONAL.XLSB)

8.標準モジュールなどをインポートして個人用マクロブックを閉じる。

クロスプラットフォーム開発

iOSやAndroidのアプリをクロスプラットフォーム開発するのに、Delphi なのかXamarinなのか。

当然ながら、ちまたにはC#開発者が多いのでRADツールにVisualStudioを使うならXamarinなのかな。

しかし、Xamarinは有償(エクセルソフト)なんだよね。

その点、Delphi は最初からクロスプラットフォーム開発ができる。 優秀です。

しかし、Delphi コード(Pascal言語)を書くプログラマは滅多にいないので開発者捜しに、ひと苦労しそうだ。

もっとDelphi の魅力が伝われば、Delphi 開発者も増えるんですけどね。

エンバカデロ・テクノロジーズ社(すでにボーランド社ではない)の今後に期待したい。

http://www.embarcadero.com/jp

 

クラウドコンピューティングが教務システムという分野で思うより普及しない理由

クラウドコンピューティングが教務システムという分野で普及しない理由は何なのでしょうか?

その理由は大きく、カスタマイズ不可と実行環境そしてセキュリティという3つの問題に分けられます。
これらの問題をいかにクリアできるかが、今後の課題となっているものと思われます。

【カスタマイズが出来ないもしくは限られる点について】※問題→大

教務事務システムは、入試・学籍・履修・出欠・成績・保健・就職・証明書などのサブシステムで構成されることがほとんどです。 この中で特に入試・履修・出欠・成績に関しては、会計などの一般的な業務システムと趣が大きく異なります。 それらは、学務規則・履修規程に大きく影響を受けており、また学校の歴史・地域性も関係しており、多くの学校ではそれらの実情に合うように教務システムをカスタマイズしながら運用しています。 しかしながら、クラウドシステムはカスタマイズをせずに使用することが基本です。 すなわち、現時点でそれなりのカスタマイズを施した教務システムを運用している学校様では、カスタマイズが出来ないシステムでは、そもそも運用が出来ないのです。

【実行環境について】※問題→中

学校の現場において、グループウエアなど、いろんなブラウザベースのシステムが運用されています。 システムによってはブラウザのバージョンを指定するものもあります。 ブラウザのバージョン指定が異なるシステムは、1台のPCでは実行できないという状態になります。 これは困ります。

【セキュリティ】※問題→学校様次第

昨今のコンピュータウィルスは巧妙化しており、情報漏洩を防ぐ目的で教務システムを稼働しているパソコンからのインターネットアクセス自体を制限している学校様も多く見受けられます。  サービス向上に比例してリスクは伴うものです。 ローカルクラウドという考え方もありますが、それでは今までの学内WEBシステムと何ら変わりはありません。

CampusMagic Windows8.1

CampusMagicがWindows8.1(RTではないです)で動作するのか検証を行っていますが、
その一環ではないですが、個人的興味も含めてWindowsタブレットでも動くかどうか実験です。
※まだ正式対応していません。

まあ、思った通り、デスクトップでもタブレットでも同じWindows8.1ですので、動いているようです。

タブレットでCM

TypeScriptエディタとLESSエディタが便利! Web Essentials 2013 & TypeScript 1.0RC

Visual Studio 2013がリリースされてから早半年が経とうとしています。

VS2013では新コンパイラであるRoslyn(ロザリン)の搭載は見送られましたが、その分Web関連の機能が強化されました。LESSエディタや、TypeScriptエディタ、デバッグ時のブラウザの選択実行、OWINやSignalRなどなど、Web開発をラクにするためのツールが沢山追加されています。
しかし、これらの多くはVisual Studioの標準機能に含まれているわけではありません。アドインとして提供されているため、拡張機能マネージャ(もしくはNuGet)から別途自分でインストールする必要があります。

「VS2013でLESSがコンパイルできない」もしくは「VS2013でTypeScriptがコンパイルできない」と悩んでいる方は、Visual Studio 2013の上部のメニューから[ツール]->[拡張機能と更新プログラム]->[オンライン]->[右上の検索ボックス]と画面を遷移し、「Web Essentials 2013」と検索して出てきた拡張機能をインストールすると幸せになれます。もしくはこちらのリンクからインストールしても良いです。

私も今日はじめてWeb Essentials 2013をインストールしたのですが、LESSエディタが超便利でびっくりしました。右クリックでベンダプリフィックスを簡単に追加したり、クラスのネストを自動的に認識してくれたり、至れり尽くせりとはまさにこのことでしょう。
下はLESSエディタでLESSを編集しているところですが、なんとCtrl+Sで保存した直後にCSSもリアルタイムで生成されるのです。分かりやすすぎます…。
なお、TypScriptも同様にエディタがあり、保存すると自動的にJavaScriptが生成されます。

LESSエディタ

LESSエディタでLESSを編集しているところ。自動的にCSSが生成されます。

生成されたCSSファイルはLESSのツリーの下にくっつきます。CSSを意識せずにLESSを編集できますし、探すのも簡単で助かります。

LESSの下にCSSが生成されました。

LESSの下にCSSが生成されました。mapファイルも生成されてますね!

先日TypeScript 1.0RCがアナウンスされたので、おそらく2ヶ月以内にはTypeScript 1.0がお披露目されることでしょう。
TypeScript 0.9からGenericsもサポートされているようですし、普段お仕事で静的言語をメインに使われている方にとっては手に馴染むツールになりそうです。私も、Genericsがサポートされていることを知ってから、ワクワクしながらTypeScript 1.0のリリースを待っています。

TypeScriptはECMAScript 6の仕様であるclass, moduleなどが実装されており、JavaScriptとの互換性もそれなり考慮されているので安心して使えますね。言語的には割と洗練されていると思いましたが、多くの人にとって気になる点があるとすれば、開発元がMicrosoftということでしょうか?(笑)
一応オープンソースで開発されている言語なので、Microsoftが開発していると言って神経質になる必要もなさそうです。きっとAnders Hejlsbergがうまくやってくれるはずです。Microsoftの社内でも本気でTypeScriptをドッグフーディングしているようですし、好感が持てます。

たとえばVisual Studio Onlineには今、30万行あまりのTypeScriptコードがあり、Windows 8.1用のXbox Musicの新しい機能はすべて、この言語で書かれた。またBing Maps、Photos、Microsoft Dynamicsなどそのほかのアプリケーションも部分的にTypeScriptで書かれている。

MicrosoftのVisual StudioがTypeScriptを正式サポート

ということで、いよいよ本格的にWeb開発に乗り出し始めたMicrosoft。
今後の動向に注目ですね!

TypeScriptについて、少し前に私が見て良くまとまってるなーと感じたスライドも紹介しておきます。

VisualStudio2013の拡張機能を作る4 -サブメニューとしてコマンドを追加する-

前回でコマンドを増やすことができましたので、これらのコマンドをサブメニューの中に入れたいと思います。

公式サイトの「サブメニューを追加する」をみると

  1. vsctファイルを開いて
  2. IDSymbolを追加して
  3. Menusセクションにサブメニュー用のMenuを追加して
  4. GroupsセクションにGroupを追加して
  5. Buttonsセクションにサブメニューコマンド用のButtonを追加しろ

と書いてあります。

実は社内用の拡張機能を作った際にはこの部分でひっかかり、1,2ヵ月放置していました。

この公式ページの説明どおりにTopLevelMenuという名前のVSPackageを作って、説明どおりにIDSymbolやらMenuやらを追加すれば良かったのですが、説明を斜め読みのまま、

  1. VSPackage1という名前で作ったプロジェクトのvsctファイルの内容を書き換えよう。
  2. とりあえずIDSymbleを追加。
  3. MenusセクションへのMenu追加はコピペしてguidを書き換えよう。
  4. GroupsセクションのGroupを書き換えよう。
  5. 最後のコマンド用Buttonは既に作ったコマンドのParentのidを書き換えて使おう。

ってな感じで進めた結果、まったくサブメニュー化できませんでした。

どこを間違えていたかというと、GroupsセクションにGroupを追加する部分です。「追加」しなければいけないのですが、説明ページの内容に置き換えてしまったわけです。

<Groups>
    <Group guid="guidVSPackage1CmdSet" id="MyMenuGroup" priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
    </Group>
</Groups>

これを

<Groups>
    <Group guid="guidVSPackage1CmdSet" id="MyMenuGroup" priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
    </Group>
    <Group guid="guidVSPackage1CmdSet" id="SubMenuGroup" priority="0x0000">
        <Parent guid="guidVSPackage1CmdSet" id="SubMenu"/>
    </Group>
</Groups>

こうしないといけませんでした。

ちなみにid=”SubMenu”はMenuなのでこんな感じです。

<Menus>
    <Menu guid="guidVSPackage1CmdSet" id="SubMenu" priority="0x0100" type="Menu">
        <Parent guid="guidVSPackage1CmdSet" id="MyMenuGroup"/>
        <Strings>
            <ButtonText>Sub Menu</ButtonText>
            <CommandName>Sub Menu</CommandName>
        </Strings>
    </Menu>
</Menus>

で、各コマンド用ButtonのParentをid=”SubMenuGroup”に変更します。

<Button guid="guidVSPackage1CmdSet" id="cmdidMyCommand" priority="0x0100" type="Button">
    <Parent guid="guidVSPackage1CmdSet" id="SubMenuGroup" />
    <Icon guid="guidImages" id="bmpPic1" />
    <Strings>
        <ButtonText>My Command name</ButtonText>
    </Strings>
</Button>
  1. メニュー「ツール」の中にサブメニュー(id=”SubMenu”)を配置するために必要なのがParentを id=”IDM_VS_MENU_TOOLS” にしたGroup(id=”MyMenuGroup”)。
  2. サブメニューの中にコマンドを配置するために必要なのがParentを id=”SubMenu”にしたGroup(id=”SubMenuGroup”)。
  3. 配置するコマンドのParentはGroup(id=”SubMenuGroup”)にする。

サブメニューを親にしてコマンド配置用のGroupを作らないといけないんですね。よく読まずにコマンドの親をそのままサブメニューにしていたためにサブメニュー化できずに放置していました。

こうしてめでたくサブメニューになりました。

2014-01-17_193310

同じようにMenuやGroup、Buttonをもうワンセット作成し、一番上の親になるGroupのParentを以下のようにすればコンテキストメニューへ追加も完了です。

<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/>

ショートカットキーの設定は簡単でKeyBindingsセクションにKeyBindingを追加してidに目的のコマンドIDを指定するだけです。修飾キーとして使えるものは入力補完で表示されます。

<KeyBindings>
<KeyBinding guid="guidVSPackage1CmdSet" id="cmdidMyCommand" editor="guidVSStd97" key1="M" mod1="Control Shift" key2="A" mod2="Control Shift" />
    <KeyBinding guid="guidVSPackage1CmdSet" id="cmdidMyCommand2" editor="guidVSStd97" key1="M" mod1="Control Shift" key2="B" mod2="Control Shift" />
</KeyBindings>

こうしてメンバから要望のあったサブメニュー化、コンテキストメニュー化が実現できて、めでたく社内公開できました。

VisualStudio2013の拡張機能を作る3 -コマンドを追加-

引き続きVisual Studioの拡張機能を作ります。

正味の話、前々回のウィザードにしたがって作る部分は公式サイトをみれば一発です。

さて今回は公式サイトの「VisualStudioのメニューバーにメニューを追加する」や「サブメニューを追加する」あたりを参考にしてメニューコマンド増やしたいと思います。

メニューコマンドを追加するにはvsctファイルに記述する。

公式サイトをみると「TopLevelMenu.vsctを開いて編集~」とあり、vsctファイルに色々と書き込むことでメニューを追加したりしています。なので VSPackage1.vsct を編集します。

ちなみにvsctファイルのXMLスキーマリファレンスはこちら

2014-01-16_192532

実行すると、VisualStudioのメニュー「ツール」に My Command name という項目が追加されますが、Buttons にある Button で設定されます。追加される位置は Parent で指定されます。

id=MyMenuGroupを探してみると Groups にid=MyMenuGroupのGroupがあります。そしてその Parent は guid=”guidSHLMainMenu” id=”IDM_VS_MENU_TOOLS” 。

これはvsctファイルに記述が無いですが、VisualStudio上での位置を指定しています。これを変えることで好きな位置にコマンド追加することができます。

例えば、idをIDM_VS_MENU_EDITに変えるとメニューの「編集」にコマンドが追加されます。

<Group guid="guidVSPackage1CmdSet" id="MyMenuGroup" priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_EDIT"/>
</Group>

その他のguidやidはこちらをご参考に。

さて、サクッとコマンド「My Command name 2」を追加しましょう。

SymblosのGuidSymbolの中に My Command name のid、cmdidMyCommandがありますのでこれを参考に「My Command name 2」用のcmdidMyCommand2を追加します。

<GuidSymbol name="guidVSPackage1CmdSet" value="{0347ac17-ebae-4ec3-b6bb-72a5ba8a702c}">
    <IDSymbol name="MyMenuGroup" value="0x1020" />
    <IDSymbol name="cmdidMyCommand" value="0x0100" />
    <IDSymbol name="cmdidMyCommand2" value="0x0102" />
    <IDSymbol name="cmdidMyTool" value="0x0101" />
</GuidSymbol>

Buttonsにコマンドのボタンを追加。Iconのidを変えてアイコンも変更してみました。ButtonのpriorityはParentに追加される順番になります。試しにcmdidMyCommandのpriorityよりも前になるように設定しています。

<Button guid="guidVSPackage1CmdSet" id="cmdidMyCommand2" priority="0x0001" type="Button">

    <Parent guid="guidVSPackage1CmdSet" id="MyMenuGroup" />
    <Icon guid="guidImages" id="bmpPic2" />
    <Strings>
        <ButtonText>My Command name2</ButtonText>
    </Strings>
</Button>

vsctファイルを弄っただけでコールバックメソッド等を記述していませんが、実行してみます。

2014-01-16_200936

バッチリです。
ちなみにクリックしても何も起こりません。まずは「My Command name」と同じ動作をするようにしてみます。

コールバックメソッドが記述されている VSPackage1Package.cs を開いて Initialize() メソッドに追加。

CommandID menuCommandID2 = new CommandID(GuidList.guidVSPackage1CmdSet, 0x0102);
MenuCommand menuItem2 = new MenuCommand(MenuItemCallback, menuCommandID2);
mcs.AddCommand(menuItem2);

CommandIDに渡すIDはPkgCmdIDListに定義するのが正しいのでしょうが、とりあえずベタ書き。変数もこんなネーミングをしていると殴られます。気をつけましょう。コールバックメッソッドとしては「My Command name」と同じにしています。

前回、エディタにHello, World.と追加するように変更していましたが、メッセージボックスを表示するように戻しておきましょう。

実行して、「My Command name2」をクリック。

2014-01-16_202340

上手くいきました。

MenuCommandに渡すコールバックメソッド(イベントハンドラ)を変えればめでたくコマンドの追加完了です。

でも、メニュー「ツール」に自作コマンドがポコポコ追加されても邪魔なのでサブメニュー化したいものです。

というわけで次回はサブメニュー化とコンテキストメニューへの追加、ショートカットキーの設定を行いたいと思います。vsctファイルとの格闘です。