またVSIX
ほぼ備忘録になってる
まぁぼくは忘れやすいのでOKOK
それにしてもsurface3みたいなスペックだと実験インスタンス起動するのに時間かかってつらい
Visual Studioの設定ダイアログを作る
メニューバーの[ツール]
→[オプション]
で開くやつ正式名称はわからない・・・
こういうやつ
これを自分の作ったVSIX用に作る
参考はここ
プロジェクトを作る
今回は空のVSPackage
に作っていく。自分のVSIXに実装するときは新しくVSPackage
を追加する必要はない
まずは[ファイル]
-[新規作成]
-[プロジェクト]
-[Visual C#]
-[Extensibility]
-[VSIX Project]
を選んでプロジェクトを作る
できたらソリューションエクスプローラーで右クリックして[追加]
-[新しい項目]
Visual Studio Package
を選ぶ
テンプレートが展開されるまでちょっと待つ
実装する
ダイアログページを作るにはSystem.ComponentModel
名前空間が必要なのでusing
する
using System.ComponentModel;
次にオプションで設定したい値を持ってるクラスを作る。今回はOptionDialog
クラスにした。ネーミングセンス・・・・
public class OptionDialog : DialogPage { }
このクラスにはDialogPage
クラスを継承する必要があるのに気を付けよう
次にPackage
クラスを継承してるクラスに属性を追加する。たぶんこのVSIXはオプションページがありますよ~~~ってことなんだと思うたぶん
/*他の属性は略*/ [ProvideOptionPage(typeof(OptionDialog),"Test Dialog","General",0,0,true)] public sealed class Dialog : Package { /*略*/ }
ちなみにこの属性の引数はこんな感じらしい。categoryName
はオプションウィンドウのサイドバーの項目で、pageName
はその下の全般
とかのことっぽい
今のままだと設定したい項目が0なので追加していく今回はInt
を1つもたせることにした
public class OptionDialog : DialogPage { [Category("かてごりー")] [DisplayName("ひょうじめい")] [Description("せつめい")] public int Num { get; set; } }
DialogPage
ではプロパティとかにした値がそのまま設定項目として表示される。Xml
とかJson
をシリアライズしたりするのと似ていてちょっとうれしかった
実は実装はこれで終わりで、あとはビルドするだけでちゃんと動くのだ!最高!
プロパティにつけていた属性の意味は上の画像と照らし合わせればわかると思う。これは僕も驚いたところだけど、これ値を変えてから再起動しても値が変更されたままになる。どこに書き出しているかはリファレンスをちゃんと読んでないのでわからない(読む気もない)。まあとにかく簡単である
xmlに書き出してみる
xmlファイルの準備
こういうのはjsonとかxmlに書き出しておきたいっていうのがあります(あります)
なので今回はxmlに書き出すのを目標にしてみる
まずはソリューションエクスプローラーからxmlを追加しておく。そして一度自力で書くか、適当なコンソールアプリケーションを書いてこのxmlを書いておく。書くのはOptionDialog
クラスと同じようなプロパティを持ったクラスをシリアライズしたやつ。こういう時にC#インタラクティブが便利だと思うんだけどXmlSerializer
がroslynでは死ぬのでダメだった・・・ざんねん・・・
なので自分はいつも以下みたいなのを書いてる。面倒
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; namespace ConsoleApp2 { class Program { static void Main(string[] args) { var path = @"c:\users\user\source\repos\TestDialog\TestDialog\Resources\config.xml"; var xml = new XmlSerializer(typeof(Store)); using(var sw=new StreamWriter(path, false, Encoding.UTF8)) { xml.Serialize(sw, new Store { Num = 0 }); } } } public class Store { public int Num { get; set; } } }
追加したxmlはプロパティウィンドウでビルドアクションをコンテンツ
に、Include in VSIXをTrue
にしておくこと。これが分からなくてINF分失った
Load/Saveイベント
またイベントの話だけどダイアログが開かれたとき、または閉じられたときにLoadとSaveが行われる。DialogPage
にはこれをするLoad/SaveSettingsToStorage
メソッドがあるのでオーバーライドしてやるといい
パスの取得
さっきビルドした拡張機能がどこにインストールされてるのか探してみたらC:\Users\USER\AppData\Local\Microsoft\VisualStudio\15.0_29065328Exp\Extensions\xztaityozx\TestDialog\1.0
にあった。上の設定をしていればこの下にxmlも置かれるのでこのパスが分からないといけない。でもユーザー名とか変わるし決め打ちはできないのでどうにかして実行中に取得したい
ggっていたらドンピシャなstackoverflowを見つけた
stackoverflow.com
CodeBase
?ってやつを取得すると実行中のファイルがわかるっぽい
そしてどうやらPackage
クラスにも同じような値が入っているらしいけど何階層か上までしかわからなかったのでダメだった
なのでこんな風にかいた
public class OptionDialog : DialogPage { [Category("かてごりー")] [DisplayName("ひょうじめい")] [Description("せつめい")] public int Num { get; set; } public string Path { get { var codebase = typeof(Dialog).Assembly.CodeBase; var uri = new Uri(codebase, UriKind.Absolute); return System.IO.Path.GetDirectoryName(uri.LocalPath) + "\\Resources\\config.xml"; } } public override void LoadSettingsFromStorage() { using (var sr = new StreamReader(Path, Encoding.UTF8)) { var xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(Store)); var config = (Store)xmlSerializer.Deserialize(sr); this.Num = config.Num; } } public override void SaveSettingsToStorage() { var config = new Store { Num = this.Num }; using (var sw = new StreamWriter(Path, false, Encoding.UTF8)) { var xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(Store)); xmlSerializer.Serialize(sw, config); } } } public class Store { public int Num { get; set; } }
これでxmlに書き出せるようになったのでVSIX中の任意の場所から同じようにxmlにアクセスすれば色々できてうれしい
それでは最後にソースコードをはって終わります
using System; using System.ComponentModel.Design; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.InteropServices; using Microsoft.VisualStudio; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.Win32; using System.ComponentModel; using System.IO; using System.Text; namespace TestDialog { /// <summary> /// This is the class that implements the package exposed by this assembly. /// </summary> /// <remarks> /// <para> /// The minimum requirement for a class to be considered a valid package for Visual Studio /// is to implement the IVsPackage interface and register itself with the shell. /// This package uses the helper classes defined inside the Managed Package Framework (MPF) /// to do it: it derives from the Package class that provides the implementation of the /// IVsPackage interface and uses the registration attributes defined in the framework to /// register itself and its components with the shell. These attributes tell the pkgdef creation /// utility what data to put into .pkgdef file. /// </para> /// <para> /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. /// </para> /// </remarks> [PackageRegistration(UseManagedResourcesOnly = true)] [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About [Guid(Dialog.PackageGuidString)] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")] [ProvideOptionPage(typeof(OptionDialog),"Test Dialog","General",0,0,true)] public sealed class Dialog : Package { /// <summary> /// Dialog GUID string. /// </summary> public const string PackageGuidString = "3e22428e-3305-4a4b-9c3e-11c54c2bef28"; /// <summary> /// Initializes a new instance of the <see cref="Dialog"/> class. /// </summary> public Dialog() { // Inside this method you can place any initialization code that does not require // any Visual Studio service because at this point the package object is created but // not sited yet inside Visual Studio environment. The place to do all the other // initialization is the Initialize method. } #region Package Members /// <summary> /// Initialization of the package; this method is called right after the package is sited, so this is the place /// where you can put all the initialization code that rely on services provided by VisualStudio. /// </summary> protected override void Initialize() { base.Initialize(); } #endregion } public class OptionDialog : DialogPage { [Category("かてごりー")] [DisplayName("ひょうじめい")] [Description("せつめい")] public int Num { get; set; } public string Path { get { var codebase = typeof(Dialog).Assembly.CodeBase; var uri = new Uri(codebase, UriKind.Absolute); return System.IO.Path.GetDirectoryName(uri.LocalPath) + "\\Resources\\config.xml"; } } public override void LoadSettingsFromStorage() { using (var sr = new StreamReader(Path, Encoding.UTF8)) { var xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(Store)); var config = (Store)xmlSerializer.Deserialize(sr); this.Num = config.Num; } } public override void SaveSettingsToStorage() { var config = new Store { Num = this.Num }; using (var sw = new StreamWriter(Path, false, Encoding.UTF8)) { var xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(Store)); xmlSerializer.Serialize(sw, config); } } } public class Store { public int Num { get; set; } } }