2014年6月25日水曜日

Visual Studio 2013 SDK で拡張機能を作りたい その5

前回に引き続き、サンプルプログラムと実行結果

[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string)]
[Guid(GuidList.guidVSPackage4PkgString)]
public sealed class VSPackage4Package : Package
{
    /// <summary>Default constructor of the package...</summary>
    public VSPackage4Package()
    {
        var dte2 = Package.GetGlobalService(typeof(DTE)) as EnvDTE80.DTE2;

        var myOutWin = dte2.ToolWindows.OutputWindow.OutputWindowPanes.Add("ほげ");
        myOutWin.OutputString("♪ふが♪");

        Debug.WriteLine(string.Format("Entering constructor for: {0}", this.ToString()));
    }


どうやって動かすのか、一番悩んだ。



Visual Studio 2013 SDK で拡張機能を作りたい その4

実は、マクロとアドインとSDKの違いを知らなかった。

マクロ 既に廃止されたので知らない
アドイン DTEというCOMを相手にする・・・VS2013では非推奨
(新しいプロジェクト>その他のプロジェクトの種類>機能拡張>Visual Studio アドイン)
VSPackage
(SDK)
MEFで注入!DTEも使える
(新しいプロジェクト>その他のプロジェクトの種類>機能拡張>Visual Studio Package)

アドインは、VSPackageに変換できるようだ。(msdn: アドインを VSPackage に変換する)
というわけで、DTEから攻めることにする。

EnvDTE80(DTE2)

*EnvDTE80は、EnvDTE(_DTE)のすべてを含む。

ActiveDocument アクティブドキュメントを取得します。
ActiveSolutionProjects 現在選択されているプロジェクトの配列を取得します。
ActiveWindow 現在アクティブなウィンドウ、または他にアクティブなウィンドウがない場合は最前面に表示されたウィンドウを返します。
AddIns 現在使用できるすべてのアドインを含む AddIns コレクションを取得します。
Application インフラストラクチャ。マイクロソフト内部でのみ使用します。
CommandBars 開発環境のコマンド バーへの参照を取得します。
CommandLineArguments コマンド ライン引数を表す文字列を取得します。
Commands Commands コレクションを返します。
ContextAttributes ContextAttributes コレクションを取得します。このコレクションを使用すると、オートメーション クライアントは、[ダイナミック ヘルプ] ウィンドウで現在選択されている項目に新しい属性を追加し、追加した属性のコンテキスト ヘルプを表示できます。
Debugger デバッガー オブジェクトを取得します。
DisplayMode 表示モード (MDI またはタブ付きドキュメント) を取得します。
Documents 開発環境で開いているドキュメントのコレクションを取得します。
DTE トップレベルの機能拡張オブジェクトを取得します。
Edition 環境のエディションの説明を取得します。
Events Events オブジェクトへの参照を取得します。
FileName インフラストラクチャ。マイクロソフト内部でのみ使用します。
Find グローバル テキスト検索処理を表す Find オブジェクトを取得します。
FullName オブジェクトのファイルの完全パスと名前を取得します。
Globals ソリューション (.sln) ファイル、プロジェクト ファイル、またはユーザーのプロファイル データに保存できるアドイン値を格納する Globals オブジェクトを取得します。
IsOpenFile インフラストラクチャ。マイクロソフト内部でのみ使用します。
ItemOperations ItemOperations オブジェクトを取得します。
LocaleID 開発環境を実行しているロケールの ID を取得します。
Macros Macros オブジェクトを取得します。
MacrosIDE マクロ IDE のオートメーション モデルのルートを取得します。
MainWindow メイン開発環境ウィンドウを表す Window オブジェクトを取得します。
Mode 開発環境のモード (デバッグまたはデザイン) を取得します。
Name _DTE オブジェクトの名前を設定または取得します。
ObjectExtenders ObjectExtenders オブジェクトを取得します。
Properties [ツール] メニューの [オプション] ダイアログ ボックスで使用できるすべてのカテゴリとサブカテゴリを表す Properties コレクションを返します。
RegistryRoot Visual Studio レジストリ設定のルートへのパスを含む文字列を取得します。
SelectedItems 環境で現在選択されている項目を含むコレクションを取得します。
Solution 現在の環境のインスタンスで開いているすべてのプロジェクトを表し、ビルド オブジェクトにアクセスできる Solution オブジェクトを取得します。
SourceControl オブジェクトの背後にあるファイルのソース コード管理の状態を操作できる、SourceControl オブジェクトを取得します。
StatusBar メイン開発環境ウィンドウのステータス バーを表す StatusBar オブジェクトを取得します。
SuppressUI オートメーション コードの実行中に、UI を表示するかどうかを示す値を取得または設定します。
ToolWindows ツール ウィンドウを検索するためのショートカットとして使用されている ToolWindowsオブジェクトを取得します。
UndoContext グローバル UndoContext オブジェクトを取得します。
UserControl 環境がユーザーまたはオートメーションのどちらによって起動されたかを示す値を設定または取得します。
Version ホスト アプリケーションのバージョン番号を取得します。
WindowConfigurations 使用できるすべてのウィンドウの構成を表す WindowConfigurations コレクションを取得します。
Windows オブジェクトで表示されるウィンドウを含む Windows コレクションを取得します。

EnvDTE90

Debugger3 Debugger3 を使用すると、デバッガーの状態やデバッグ中のプログラムの状態を問い合わせたり、操作したりできます。 Debugger3 は、Debugger2 インターフェイスおよび Debugger インターフェイスよりも優先されます。
ExceptionGroups デバッガーの初回例外のダイアログで使用可能なトップレベルのグループを表します。
ExceptionSettings ExceptionSetting オブジェクトのコレクションです。各オブジェクトは、デバッガーの例外設定のセットを表します。
HTMLWindow3 Visual Studio 統合開発環境 (IDE: Integrated Development Environment) の HTML ドキュメント ウィンドウを表します。
Module デバッグ中のプロセス内のモジュールを表します。
Modules デバッグ中のプロセスで使用可能なモジュールのコレクションを表します。
Process3 Process3 オブジェクトは、プロセスのチェックおよび操作に使用されます。 Process3 オブジェクトは、Process2 オブジェクトおよび Process オブジェクトよりも優先されます。
Solution3 統合開発環境 (IDE: Integrated Development Environment) のすべてのプロジェクトとソリューション全体のプロパティを表します。 Solution および Solution2 に代わるものです。
Template 統合開発環境 (IDE: Integrated Development Environment) の現在のインスタンスで使用できる Visual Studio テンプレートを表します。
Templates 現在のプロジェクトにあるすべてのテンプレートを表します。
Thread2 Visual Studio アプリケーション内のスレッドを表します。
ToolBoxTab3 [ツールボックス] のタブとそのタブに含まれるすべてのオブジェクトを表します。 ToolBoxTab3は、ToolBoxTab インターフェイスと ToolBoxTab2 インターフェイスに代わるものです。

EnvDTE100

Debugger5 Debugger5 を使用して、デバッガーの状態やデバッグ中のプログラムの状態を問い合わせたり、操作したりできます。 Debugger5 は、Debugger4 インターフェイスよりも優先されます。
Expression2 Expression2 オブジェクトには、式の評価で返されたアイテムをチェックするプロパティが格納されます。
Solution4 統合開発環境 (IDE: Integrated Development Environment) のすべてのプロジェクトとソリューション全体のプロパティを表します。 Solution , Solution2 および Solution3 よりも優先されます。

長くなったので、ここまで。

2014年6月20日金曜日

Visual Studio 2013 SDK で拡張機能を作りたい その3

前回のウィザードで作ったプロジェクトは、まったく取っ掛かりがないので、今度はメニューコマンドにチェックを入れて、プロジェクトを作ってみた。プロジェクト名は、VSPackage2だ。

実行すると、メニュー~ツールに「My Command name」というメニューが増えている。

メニューを選択すると、こんなダイアログが出た。

ダイアログのメッセージを参考に、ソースを検索してみると、VSPackage2Package.csの、MenuItemCallbackが呼ばれている。
今度は、MenuItemCallbackで検索すると、VSPackage2PackageクラスのInitializeメソッド内で、メニューを登録しているようだ。

VSPackage2PackageクラスのVSPackage2Package, Initialize, MenuItemCallbackの3箇所にブレークポイントを張ってみると、メニューをクリックしてから、VSPackage2Packageのインスタンス生成がはじまる。つまり、メニュー登録の処理は、メニューをクリックしてから始まっている??

どうやら、VSPackage2.vsctのXAMLっぽい書式で、メニューに表示する内容とか、メニューに紐付けられたパッケージが記述されているようだ。

要するに、

  1. Visual Studioの起動時にパッケージが読み込まれ、VSPackage2.vsctで定義された内容を、メインメニューに追加する。
  2. メニューをクリックしたら、(まだ無ければ)Packageのインスタンスを生成し、Initializeメソッドを呼ぶ。
  3. Initializeメソッドは、コマンド機能テーブルに、コマンド番号とコールバックを登録する。
  4. コールバックが呼ばれて、ダイアログが出る。

こんな感じだな。

コレクションの初期化 Repeat or Range

参照型のコレクションを、インスタンス込みで初期化したい時がある。

なんか動きがおかしいので、デバッグしていたら、以下の様なことがわかった。

    int numOfFoo = 3;

    // newが1回だけ実行され、その結果が3回使われる。
    // コレクションの要素数は3だが、全て同じインスタンスが入っている。
    var listX = Enumerable.Repeat(new Foo(), numOfFoo).ToList();

    // newが3回実行される。
    // コレクションの要素数は3で、別々のインスタンスが入っている。
    var listO = Enumerable.Range(0, numOfFoo).Select((x) => new Foo()).ToList();

配列の初期化にRepeatを使っていたので、同じように書いたらダメダメだった。
気をつけよう。

2014年6月19日木曜日

Visual Studio 2013 SDK で拡張機能を作りたい その2

出力ウィンドウに割り込みたいので、まずは調査から。

This example shows how to parse a standard build message for errors, and add an item to the Error window, if appropriate, before the message is sent to the Output window.
この例では、エラーの標準のビルドメッセージを解析し、適切な場合には、メッセージが出力ウィンドウに送信される前に、エラーウィンドウに項目を追加する方法を示しています。

void OutputTaskItemStringExExample(string buildMessage,
    IVsOutputWindowPane buildPane, IVsLaunchPad launchPad)
{
    uint[] priority = new uint[1], lineNumber = new uint[1];
    string[] fileName = new string[1], taskItemText = new string[1];
    int[] taskItemFound = new int[1];

    // buildMessageにエラーが含まれているかどうかを判断する
    launchPad.ParseOutputStringForTaskItem(
        buildMessage, 
        priority, 
        fileName, 
        lineNumber, 
        taskItemText, 
        taskItemFound);


    // buildMessageにエラーがある場合は、エラーウィンドウと[出力]
    // ウィンドウの両方に送信します。
    // そうでなければ、唯一の[出力]ウィンドウに送信します。
    if (taskItemFound[0] != 0)
    {
        buildPane.OutputTaskItemStringEx(
            buildMessage, 
            (VSTASKPRIORITY)priority[0], 
            VSTASKCATEGORY.CAT_BUILDCOMPILE, 
            null, 
            0, 
            fileName[0], 
            lineNumber[0], 
            taskItemText[0], 
            null);
    }
    else
    {
        buildPane.OutputString(buildMessage);
    }

    buildPane.OutputString("\n");
}

でも、どうやって動かすのか、検討もつかない・・・orz

Visual Studio 2013 SDK で拡張機能を作りたい その1

Microsoft Visual Studio 2013 SDKをインスコすると、機能拡張のプロジェクトが作れるようになる。



言語はC# を選択する。


空のプロジェクトを作りたいので、何も選択しない。

とりあえずなので、ユニットテストも不要

ソリューションができたので、実行してみる。

VisualStudioがもうひとつ起動すればOK

今日はここまで。

2014年6月13日金曜日

NET Framework のバージョンを表すシンボル定義

色々探したが、よい物が見つからなかった。
なので、作ってみた。

<None Include="App.config" />
  </ItemGroup>
  <Import Project="$(SolutionDir)NetFrameworkVersion.targets" />
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <PropertyGroup>
  <PreBuildEvent>

これで、以下のシンボルが定義される。

  • NET_20
  • NET_30
  • NET_35
  • NET_40
  • NET_45
  • NET_451
  • NET_20_OR_GREATER
  • NET_30_OR_GREATER
  • NET_35_OR_GREATER
  • NET_40_OR_GREATER
  • NET_45_OR_GREATER


このファイルを、ソリューションフォルダに置くのを忘れずに。
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    
    <!-- NET Frameworkのバージョンに関する定数を定義する。 -->
    <!-- NET_20, NET_30, NET_35, NET_40, NET_45, NET_451, -->
    <!-- NET_20_OR_GREATER, NET_30_OR_GREATER, NET_35_OR_GREATER, NET_40_OR_GREATER, NET_45_OR_GREATER -->

    <!-- メジャーバージョン、マイナーバージョン、ビルド番号の結果を入れる -->
    <VerMajor>0</VerMajor>
    <VerMinor>0</VerMinor>
    <VerBuild>0</VerBuild>

    <!-- Pos1:最初のピリオドの位置 / Pos2:次のピリオドの位置 / Pos1n,Pos2nは、それぞれのPosに+1している -->
    <Pos1>-1</Pos1>
    <Pos1n>-1</Pos1n>
    <Pos2>-1</Pos2>
    <Pos2n>-1</Pos2n>

    <!-- v4.5.1とかv2.0とか入っているので、まずは'v'を除去 -->
    <Tmp>$(TargetFrameworkVersion.Replace('v', ''))</Tmp>

    <!-- ピリオドがなければPos1に-1が入るので、Pos2に突入させない-->
    <Pos1>$(Tmp.IndexOf("."))</Pos1>
    <Pos1n>$([MsBuild]::Add($(Pos1), 1))</Pos1n>
    <Pos2 Condition="0 &lt;= $(Pos1)">$(Tmp.IndexOf(".", $([MsBuild]::Add($(Pos1), 1))))</Pos2>
    <Pos2n>$([MsBuild]::Add($(Pos2), 1))</Pos2n>
    
    <!-- 最初のピリオドがあれば、そこまでを VerMajor にする。ピリオドがなければ、全てを VerMajor にする。-->
    <VerMajor  Condition="0 &lt;= $(Pos1)">$(Tmp.SubString(0, $(Pos1)))</VerMajor>
    <VerMajor  Condition="0 &gt;  $(Pos1)">$(Tmp)</VerMajor>

    <!-- 次のピリオドがあれば、 最初のピリオドから次のピリオドまでを、VerMinorにする。なければ、残りをVerMinorにする-->
    <VerMinor  Condition="0 &lt;= $(Pos2)">$(Tmp.SubString($(Pos1n), $([MsBuild]::Subtract($(Pos2), $(Pos1n)))))</VerMinor>
    <VerMinor  Condition="0 &gt;  $(Pos2) And 0 != $(Pos1n)">$(Tmp.SubString($(Pos1n)))</VerMinor>
    
    <!--2つ目のピリオドがあれば、そこから後ろを VerBuild にする-->
    <VerBuild  Condition="0 &lt;= $(Pos2)">$(Tmp.SubString($(Pos2n)))</VerBuild>

    <!-- VerMajor,VerMinor,VerBuildを用いて、定数を作成する。 -->
    <DefineConstants>$(DefineConstants);NET_$(VerMajor)$(VerMinor)$(VerBuild)</DefineConstants>
    <DefineConstants Condition="2 &lt;= $(VerMajor)">$(DefineConstants);NET_20_OR_GREATER</DefineConstants>
    <DefineConstants Condition="3 &lt;= $(VerMajor)">$(DefineConstants);NET_30_OR_GREATER</DefineConstants>
    <DefineConstants Condition="3 &lt;= $(VerMajor) And 5 &lt;= $(VerMinor)">$(DefineConstants);NET_35_OR_GREATER</DefineConstants>
    <DefineConstants Condition="4 &lt;= $(VerMajor)">$(DefineConstants);NET_40_OR_GREATER</DefineConstants>
    <DefineConstants Condition="4 &lt;= $(VerMajor) And 5 &lt;= $(VerMinor)">$(DefineConstants);NET_45_OR_GREATER</DefineConstants>

  </PropertyGroup>

</Project>

csproj (MsBuild) のデバッグ

csprojファイルをいじるときに、他の人はどうやってデバッグしているのだろうか?

サンプルとして、TargetFrameworkVersionの先頭に"NET_"をつけて、TestPropertyに入れて、DefineConstantsに追加した。
  <None Include="App.config" />
  </ItemGroup>
  
  <PropertyGroup>
    <TestProperty>NET_$(TargetFrameworkVersion.Replace("v","").Replace(".",""))</TestProperty>
    <DefineConstants>$(DefineConstants);$(TestProperty)</DefineConstants>
  </PropertyGroup>
  
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- To modify your build process, add your task inside one of the ta .... 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
  <Target Name="Build">
    <Message Text="テストプロパティ = $(TestProperty)" />
    <Message Text="ビルド定数       = $(DefineConstants)" />
  </Target>
</Project>

このように、Visual Studioのコマンドプロンプトから、MsBuildを実行すると、Messageタスクが出力される。


このNET_45というシンボルは、ちゃんとVisualStudioで使える。


今度はターゲットフレームワークをNet4.0にすると、シンボル名もNET_40になる。

当然、NET_45シンボルは無いので、#if で括った部分は無効になる。

2014.06.18追記
csprojで使えるメソッドとかの説明
http://msdn.microsoft.com/ja-jp/library/dd633440.aspx

2014年6月4日水曜日

Collection クラス

コレクションの雛形なんだけど、実はC# 2.0からあったんだね。

/// <summary>上限付きコレクション</summary>
public class LimitedCollection<T> : Collection<T>
{
    private int limit = int.MaxValue;

    public int Limit
    {
        get
        {
            return this.limit;
        }

        set
        {
            this.limit = value;
            this.CheckLimit();
        }
    }

    private void CheckLimit()
    {
        if (this.Limit <= this.Count)
        {
            throw new InvalidOperationException();
        }
    }

    protected override void SetItem(int index, T item)
    {
        this.CheckLimit();
        base.SetItem(index, item);
    }

    protected override void InsertItem(int index, T item)
    {
        this.CheckLimit();
        base.InsertItem(index, item);
    }
}

Visual Studio 2013の拡張機能

やっとVisual Studio 2013のExpressEditionを卒業したので、拡張機能を入れて遊んでいる。
ちなみにTechNetが9月で終わるので、Visual Studio Onlineで月々2000円を3ヶ月間使い、MSDNを申し込む予定。

まずは定番の、Productivity Power Tools 2013
ColumnGuidesは、Indent Guidesを使うのでOFF
Middle-click scrollingはなんだかわからないのでOFF
Organize VB ImportとStructure Visualizerは使わないのでOFF

あと、Align AssignmentsをONにするとは、エディタの設定を変更する。
これは、代入文とかのイコールを揃える機能なので、
エディタの設定を変えておかないと、せっかく揃えた部分が、
ソース整形により、元に戻ってしまう。

ほかには、こんなものを入れている。
  • Resource Translator
  • VSColorOutput
  • Indent Guides
  • Snippet Designer

ReSharperを更新するかはビミョーだな。
むしろ、OpenCover UIの出来によっては、
DotCoverを買うほうがいいかもね。