Neutral Scent このページをアンテナに追加 RSSフィード Twitter

2016-04-13

[][]Microsoftカンファレンスのセッション動画をまとめてDownloadする(Build 2016対応版)

f:id:kaorun:20160413135818p:image

以前、こんなエントリーを書きました、

Microsoftカンファレンスのセッション動画をまとめてDownloadするSession Downloader - Neutral Scent
http://d.hatena.ne.jp/kaorun/20150503/1430647159

そしてBuild 2016から一週間ほど待ってみたのですがここで取り上げたSession Downloaderが実は2015年で更新が止まっていたので更新版を...。

一言でいえば、これです。

Session Downloader Reloaded | Frank's World
http://www.franksworld.com/2015/10/07/session-downloader-reloaded/

Reloaded!

…というだけでも十分な気はしますが、もう少しだけ。
現状、Session Downloader Reloadedはバイナリを配布していないようなので、Githubからクローンしてきてビルドして実行です。

GitHub - FrankLaVigne/SessionDownloader: Simple session downloader for Build 2016 and other events on Channel9.
https://github.com/FrankLaVigne/SessionDownloader

手元で試した範囲ではVS2015以降が必要のようです。

でもって、コマンドラインから

C:\> SessionDownloader.exe [ダウンロード先ディレクトリ] https://s.ch9.ms/Events/Build/2016/RSS
f:id:kaorun:20160413135304p:image

ですね。

規定値は低解像度版なので、高解像度版なら、

C:\> SessionDownloader.exe [ダウンロード先ディレクトリ] https://s.ch9.ms/Events/Build/2016/RSS hi

PowerPointのスライドは、

C:\> SessionDownloader.exe [ダウンロード先ディレクトリ] https://s.ch9.ms/Events/Build/2016/RSS slide

Buildだけでなく、Microsoft Edge Web Summit 2016なら、

C:\> SessionDownloader.exe [ダウンロード先ディレクトリ] https://s.ch9.ms/Events/WebPlatformSummit/edgesummit2016/RSS

でしょうか。

あとは「入り口までは案内するが扉は君自身で開けろ」ってわけで。

2015-12-19

[]今、あらためて確認するWindows Installer(MSI)の基本

f:id:kaorun:20151219152544p:image

この記事は、Windows & Microsoft技術 基礎 Advent Calendar 2015 12月19日分のエントリーです。

MSIとは何か?:

デスクトップWindows用のアプリを新しく導入するためのインストーラーを作成する場合、現在、最もフォーマルなアプローチがWindows Installer形式の*.msiファイルによるインストーラーを利用する方法です。

Windows Installer自体の詳細については既にWikipediaMSDNなど様々な解説がなされているので必要に応じてご参照ください。

Microsoft Windows Installer - Wikipedia
https://ja.wikipedia.org/wiki/Microsoft_Windows_Installer
Windows Installer (Windows)
https://msdn.microsoft.com/en-us/library/cc185688

MSIファイルというけれど、最近は*.exeファイル単体で実行形式になってるインストーラーが多いんじゃ? と思われるかもしれません。
また、*.zipファイルで圧縮されたSetup.exeと*.msiファイルのセットになっているインストーラーパッケージを見かけられることも多いかもしれません。
今回のエントリーではそのあたりのMSI事情を駆け足でまとめてみたいと思います。

インストーラーのパッケージの構造:

まずは、インストーラーのパッケージ形態から考えてみましょう。

MSIを利用する場合、小型のインストールランチャーを取得してからインストーラー自体がダウンロード処理を行ういわゆるWebインストーラー形式はサポートされません。
MSIで一般的な形態は、

  1. *.msiファイル単体の配布
  2. Setup.exeと*.msiファイルの配布
  3. Setup.exeと*,msiファイルをパッキングして、実行可能な*.exeとした単一実行ファイル形式
  4. Setup.exe、*.msiに加え、インストール元ファイル群を伴いisoファイル形式などによるCD/DVDイメージ
  5. 同じく、インストール元ファイルをCab形式などでパッキングしたFD分割形式

元々、Windows Installerが登場した当時は5の形式で、多数のFDを順次読み込んで実行・インストールを行うことが、インストーラーの最も重要な役割だったのですが、現在では、4の形式もVisual Studioなどごく一部の大型アプリのみとなり、1〜3の形式や、MSIを使用しないWebインストーラーが主流になっています。
.msi以外に、.mstや.msuなどのモジュールも各種ありますが、そのあたりは使う人が知っていればいい内容なので割愛します。

SetupとMSI:

さて、ここまで何度も出てきている、Setup.exeと*,msiファイルの切り分けとはなんでしょう?

なぜ最初から単体の*.exeになっていないのか、という謎ですが、現在流通している多くのインストーラーの場合、Setup.exeはそのアプリの実行に必要となる.NET Frameworkや一部のデータアクセスライブラリなどの事前要求コンポーネントをインストールし、完了後に対になった*.msiファイルを起動するという構造になっています。

このSetup.exeの役割をセットアップ・ブートストラッパー(Setup Bootstrapper)と呼びます。

f:id:kaorun:20151219152546p:image

.msiファイルのインストールプロセス中で.NET Frameworkを利用する場合などもあり、事前に必要なコンポーネントをインストールしておこう、という仕組みです。なので、単体のMSIファイルだけを配布して、.NET Frameworkなどの環境整備は自分でやってね、という形式のアプリケーションも多くみられます。
Windows 7以降であれば、.NET 4.0程度まではOS標準でインストールされていますので、これで十分、というケースも多いでしょう。

また、歴史的経緯からみると、Windows Installerが登場した当時のWindows 95時代には、そもそもWindows Installer自体がOSに付属しておらず、インストールするためのコンポーネントをSetup.exeで組み込む、というような動作が行われていました。

MSIの単一exeパッケージ化:

とはいえ、.NET 4.6が必要である場合、とか、それぐらい勝手にやってくれるのが気が利いたインストーラーですし、いちいちZipファイルを展開して実行するのも面倒です、このため、昨今では、ブートストラッパー自体が高機能化し、.NETのインストールはもちろん、*.msiファイルや他のコンポーネントもSetup.exeのペイロードとしてパッキングしてしまい、いわゆる自己解凍形式のインストーラーとしてMSIファイルをインストールするインストーラーも多くなっています。

また、もうどうせそこまでやるなら、とUI自体もブートストラッパーに内蔵し、古典的なWindows Installerのウィザード形式のUIを利用せず、*.msiをサイレントモードでインストールする手法なども登場しています。

f:id:kaorun:20151219152545p:image

もちろん、そのような高度な手法を採用し、GUIさえも独自実装してしまうのならば、MSIなど使わずにより高機能で独自形式のインストーラーを開発したほうがいいのでは? という考え方もあり、実際にそのようなインストーラー開発キットも多く流通していますが、アプリケーションのシステムへの組み込み、というデリケートな問題を扱う点や、将来サポートの継続性なども考慮すると、依然としてMSIを応用した形式のインストーラーを使用するメリットは十分にあるといっていいのではないかと思います。

Project Centennieal:

また、直近では、Windows 10のリリースとともに、Project Centennialという新しい形式のパッケージ技術がマイクロソフトから予告されており、この形式では、古典的なWin32や.NETを利用したWindowsデスクトップアプリケーションを、アプリケーション仮想化技術を応用したコンポーネント化し、隔離したサンドボックス内で実行することで、安全な配置・実行を可能にし、これによってWidows Storeからデスクトップアプリケーションを配布・販売することを実現することが謳われています。

f:id:kaorun:20151219152547p:image

このProject Centennial上で実行されるアプリケーションパッケージの作成においてもMSIインストーラーの使用が推奨されています。MSI形式のインストーラーを持つデスクトップアプリであれば、容易にWidnwos Storeへ登録が可能になるかもしれないよ、という状況も生まれつつあるわけです。

まとめ:

以上、昨今のWindows Installer事情をざっくりとまとめてみました。
各種インストーラー開発ツールの利用方法などについては、様々なエントリーが書かれていると思いますので、各自ググってみてください。(このエントリーもだいぶやっつけなので、後日更新するかもしれません)

2015年にもなって、インストーラーをゴリゴリ書くことになるとは思っていませんでしたが、2-in1のWindowsタブレットの普及も進み、Windows用のクリエイティブ デスクトップアプリを作ってみるのも、意外と面白いかもしれませんよ。それでは、今年も残り少なくなってまいりましたが、良いWindowsライフをお過ごしください。

続く...

[]めとべや東京 #10で「今風なデスクトップアプリの モダンインストーラー開発」について発表しました

...というわけで、本日開催された「めとべや東京 #10」でイマドキなモダンUIを利用したWindows Installer開発について登壇させていただきました。
会場では少々グダグダなデモになってしまい参加者の方々には申し訳ありませんでした。スライドの方は資料として使いやすいギョーミーな箇条書きにまとめさせていただいております。

正直、今年後半はこのあたりのインストーラー周りに追いまくられて全力でバックダッシしていた感があるのですが、Project Centennialなど今後の展開も期待できますので、資料としてご活用いただければ幸いです。

また、あわせて、より基礎的なMSIの話をWindows & Microsoft技術 基礎 Advent Calendar 2015向けのエントリーとしてまとめましたので、合わせてご参照ください。

今、あらためて確認するWindows Installer(MSI)の基本
http://d.hatena.ne.jp/kaorun/20151219/1450511313

2015-08-12

[][]Windows 10でUSB接続のLANやオーディオデバイスが正常に動作しないときは(DisplayLink編)

DisplayLink製チップを搭載したUSB接続のマルチファンクションデバイス(ディスプレイ、ネットワーク、オーディオ等)がWindows 10でうまく認識されない場合の対策。
現在市販されているUSBディスプレイアダプタやUSBポートリプリケーターのチップは大概DisplayLink製です。

で、これらのデバイスをWindows 10で使う場合、現時点(10240)ではドライバの認識に失敗することがあるようです。いずれはWindows Updateでスムースに入るようになると思うのですが…。
というわけで、公式サイトによる対処方法をざっくりと日本語でまとめ。

まずは、DisplayLinkのドライバをクリーンナップしWindows 10対応ドライバを再インストールします。

それでも治らない場合

  1. デバイスマネージャーを開きます
  2. 「その他のデバイス」「ネットワークアダプター」「オーディオの入力および出力」などで認識していないデバイスを選択
  3. 右クリックして「ドライバーソフトウェアの更新」を選択
  4. 「コンピューターを参照してドライバーソフトウェアを検索します」を選択
  5. 「次の場所でドライバー ソフトウェアを探索します:」に C:\Program Files\DisplayLink Core Software を入力して「次へ」

ざっとこんな感じです。

via:
Troubleshooting software installation / upgrade – DisplayLink Support
http://support.displaylink.com/knowledgebase/articles/679177-troubleshooting-software-installation-upgrade

2015-06-18

[]今だから入れたい! おすすめ Windows Phoneアプリ (2015夏)

f:id:kaorun:20150525232553p:image:w240

やー、色々ありましたが、ほんと色々ありましたが、もーね...、というわけでようやく日本のWindows Phone 8端末事情にも遅すぎた春がやってきつつあるようです。マウスコンピューターさんMADOSMA発売おめでとうございます。
さて、みなさま、新しいWP端末を手に入れることができたでしょうか? このエントリーを見ている時点でもう手に入れられているのではないか、と...、手に入れましたよね? (しつこい)

そんなわけで、以前まとめた頃から時が経ち、国内外で新作がいくつも登場し、KrileWPが公開終了になるなど、WPアプリ界隈にも多少の変化がありました。
なので、前回のまとめに追加する形で、初めてWPを手に入れた方々への紹介も含めて、今ならコレというオススメなアプリをいくつかご紹介。

f:id:kaorun:20150525232612p:imageMapFan
相変わらず、「Windows Phoneといえば残念な地図」でおなじみのHERE Mapsの酷い地図を補ってくれる形でインクリメントPがやってくれました、Windows 8.x/Windows Phone 8.x対応のユニバーサルアプリとしてMapFanがリリースされました!
機能的には、時層地図的な古地図やドラクエ風のRPG地図など面白機能がある反面、実用的な機能はまだまだ控えめでこれからに期待、という感じですが、これで無料アプリですし、なにしろユニバーサルアプリとしてリリースしてくれただけでも感謝感激なのです。
f:id:kaorun:20150525232813p:image:w160f:id:kaorun:20150525232811p:image:w160f:id:kaorun:20150525232809p:image:w160f:id:kaorun:20150525232807p:image:w160
f:id:kaorun:20150525232944p:image
f:id:kaorun:20150525233122p:image
ジョルダン乗換案内 (新) (旧)
ジョルダンも乗換案内アプリをリニューアルしてくれました! ...が、正直使い勝手的には古い方がいいかな...? 検索条件により時々落ちるし?
なぜか新しい方に死にbug放置されてるので早く治してください〜!
f:id:kaorun:20150525233127p:image:w160f:id:kaorun:20150525233126p:image:w160f:id:kaorun:20150525233125p:image:w160
f:id:kaorun:20150525233124p:image:w160f:id:kaorun:20150525233123p:image:w160
f:id:kaorun:20150525233507p:image乗換NAVITIME
乗換アプリといえばジョルダンと双璧なのがNAVITIMEですね。このあたりは使い勝手のお好みで。
f:id:kaorun:20150525233532p:image:w160f:id:kaorun:20150525233531p:image:w160
f:id:kaorun:20150525233557p:image:mediumMSN天気
前回紹介したAccuWeatherのロック画面がお天気画像からストック画像に切り替わってしまい、個人的に気に入っていたAmazing Weather HDのデータ更新が謎のエラーになってしまったので、現時点でのおすすめお天気ロックスクリーンアプリは純正のMSN天気です。
インストールするまでもなく標準で入っている気がしますが、アプリを起動してやればロックスクリーン設定が出ると思います。ロックスクリーンの選択は 設定/ロック画面 で。
f:id:kaorun:20150525233637p:image:w160
f:id:kaorun:20150525234519p:image2Day
クラウド同期可能なタスク・ToDoアプリの2DayはユニバーサルアプリでWindowsストア版とのデータ同期も可能なのが素敵です。
見た目もよく、タスクアプリ的な設定も一通りそろっていて過不足なく使えるクラウドタスクアプリといったところ。
f:id:kaorun:20150525234615p:image:w160f:id:kaorun:20150525234614p:image:w160f:id:kaorun:20150525234612p:image:w160f:id:kaorun:20150525234610p:image:w160
f:id:kaorun:20150525234743p:imagePictures Lab
正直、WPでも写真編集アプリは沢山あるのです、あるのですが、なんかこー突き抜けないというか一長一短で、これという大定番がありません。
ただ、その中でも、そこそこ使い勝手が良くて気に入っているのはPictures Labです。とりあえず、写真画像の編集なに使ったらいいん? という時点ではこれをおすすめしてます。
f:id:kaorun:20150525234819p:image:w160f:id:kaorun:20150525234817p:image:w160
f:id:kaorun:20150525234939p:imagePushile
この一年で、個人的に最大のヒットだったWebサービスPushBulletです。複数端末持ちのガジェオタ的にはこれなしにはもう暮らせない感じですね。
で、デスクトップWindows向けには公式アプリがあるのですが、WP版はリリースされておらず、非公式アプリが沢山出ています。Pushileはその中でも公認非公式アプリの一つ。
このあたりは、使い勝手と見た目の問題なので、Windows Phone Storeから検索してみてお好みで。
もちろん、Windows Phoneの共有コントラクトにも対応してますので、NextGenReaderやTweetiumからリンクをPCや他の端末にプッシュするのも簡単になります。
f:id:kaorun:20150525234954p:image:w160f:id:kaorun:20150525234952p:image:w160
f:id:kaorun:20150525235102p:imageTweetium
Krile WP亡き後、個人的に最強のTwitterクライアントだと思っているのはTweetiumです。
高機能かつ、着々と更新されていて、TwitterとWPの最新機能に着実に対応してくれているのがうれしいところ。
Windowsストア版もありますが、こちらはフローレイアウト表示で好みの分かれるところ。
f:id:kaorun:20150525235058p:image:w160f:id:kaorun:20150525235100p:image:w160
f:id:kaorun:20150618222730p:imageAristea
WP用国産Twitterクライアントの筆頭Amelloidesの作者つもりんによるユニバーサルアプリ版TwitterクライアントがAristea、柔軟なフィルタ式とマルチアカウントでヘビーユーザー御用達な使い勝手も健在です。ユニバーサルアプリとして購入すれば合わせて利用できるWindowsストアアプリ版の完成度も高く、こちらはマルチカラムで使いやすいのでお試しあれ。
f:id:kaorun:20150618222728p:image:w160f:id:kaorun:20150618222726p:image:w160f:id:kaorun:20150618222725p:image:w160f:id:kaorun:20150618222724p:image:w160
f:id:kaorun:20150525235205p:imageHyperlapse Mobile
昨年、気持ち悪いぐらいなめらかなタイムラプス動画とともに公開されたHyperlapse技術のアプリ版です。
正直、昨年公開されたビデオほどなめらかにはならないのですが、手軽に使えるスタンドアロンのタイムラプスアプリとして。Android版も公開されてますね。
f:id:kaorun:20150525235202p:image:w240
f:id:kaorun:20150525235340p:imageMicrosoft How-Old
これはもう、Twitterで見かけた方にはおわかりですね。例のアレのアプリ版です。
画像ファイルやカメラで撮影した顔写真から年齢を推定する、ただそれだけのネタアプリです。ブラウザ版ならどこでも使えますが、アプリ版はWPエクスクルーシブ(笑)。
ただそれだけのことに画像認識や機械学習など高度な技術が投入された、まさに技術の無駄遣いですね。
f:id:kaorun:20150525235338p:image:w160
f:id:kaorun:20150525235451p:imageMS-DOS Mobile
そして、シメもネタアプリ。
今年のApril Foolに公開された、Microsoft純正のMS-DOS for Windows Phoneです。
なにができる、ってものでもありませんが、細かい作りこみや仕込みネタが沢山あるので、往年のMS-DOSコマンドが今頭をよぎった方々はれっつチャレンジ、思ったよりもちゃんとDOSです(笑)。
f:id:kaorun:20150525235450p:image:w160f:id:kaorun:20150525235448p:image:w160

例によってざっと駆け足で、雑なまとめですいません。スクリーンショットぐらいストアから持ってこずに自分で撮れよ、って感じですよね。いや、ほんと暇がなくて...、自分でもガツガツアプリを書きたい気持ちはあるのですが...。
...と、それはともかく、ユニバーサルアプリのおかげで、個性派アプリも徐々に増え、Windows 10Windows 10 Mobileのリリースが楽しみになってまいりました。

初めての方も、お久しぶりの方も、色々と過剰な期待は禁物ですが、独特な快適さを持つ今どきのWindows Phoneの世界を感じる一助になれば幸いです。

関連:
Windows Phone 8 とりあえず入れておきたいオススメ定番アプリ25選 (2014年夏) - Neutral Scent
http://d.hatena.ne.jp/kaorun/20140801/1406872071

2015-05-19

[][]IE11のアクセラレーターにGoogle検索プロバイダーが追加できない問題

原因はよくわからないのですが、最新Windows Updateが適用された環境でIE11に検索プロバイダーのアクセラレーターを追加できない状況が発生しているようです。IE Galleryのアクセラレーターの追加ページでインストールボタンを押しても何も起きません。
直近で会社のPCを入れ替えているのですが、GoogleWikipediaの検索プロバイダを追加できずに困りました。

で、調べて分かったのはこれ、

IE11 patch KB 3038314 blocks search engines and may fail with error 80092004 | InfoWorld
http://www.infoworld.com/article/2911704/microsoft-windows/ie-11-patch-kb-3038314-blocks-adding-search-providers-install-may-fail-with-error-80092004.html

ようするにKB3038314が適用されると、検索プロバイダの追加が無効になってしまうようなのです...。これは廉価版「Windows 8.1 with Bing」がらみの仕様なのでしょうか?

まぁ、それはともかくアドレスバーからGoogleで検索できないのは猛烈に不便なので(Bingは日本語じゃねぇ...)、どうにからないのか? KBを削除してアクセラレーターを追加して戻せなどと解説されていますがメンドクサイ...、と件の記事のコメント欄まで読んでみたところ、レジストリに直書きしてしまえばOkよ、といってる人がいたので試してみたら成功しました。件のコメントではgoogle.co.ukに行ってしまうので、他のPCから同じregエントリーを引っ張ってきてgoogle.comへ飛ばします。

具体的には、
ie_google_search_provider.reg 直

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\SearchScopes\{CA052273-9E19-4525-A22B-8CC4BA5AD5C1}]
"DisplayName"="Google"
"URL"="http://www.google.com/search?q={searchTerms}&sourceid=ie7&rls=com.microsoft:{language}:{referrer:source}&ie={inputEncoding?}&oe={outputEncoding?}"
"ShowSearchSuggestions"=dword:00000001
"SuggestionsURL"="http://clients5.google.com/complete/search?hl={language}&q={searchTerms}&client=ie8&inputencoding={inputEncoding}&outputencoding={outputEncoding}"
"OSDFileURL"="http://www.iegallery.com/en-US/AddOns/DownloadAddOn?resourceId=813"
"FaviconURL"="http://www.google.com/favicon.ico"
"SuggestionsURLFallback"="http://clients5.google.com/complete/search?hl={language}&q={searchTerms}&client=ie8&inputencoding={inputEncoding}&outputencoding={outputEncoding}"
"FaviconURLFallback"="http://www.google.com/favicon.ico"
"ShowTopResult"=dword:00000001
"SortIndex"=dword:00000005

ほかの検索プロバイダも、
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\SearchScopes\
から下で探してきてエクスポートすればOkの様子。faviconのキャッシュはC:\Users\<username>\AppData\LocalLow\Microsoft\Internet Explorer\Servicesの下にありますが、regから削れば勝手に取得してきてくれるかもしれません。

新規インストールのPCなどでお困りなら試してみてください。

2015-05-13

[][][][]WPFでマウスで選択した範囲をスクリーンキャプチャーする

f:id:kaorun:20150513155325p:image

どうも、クラスのprivateメンバー変数はlowerCamelCaseで、_は付けない派 id:kaorunです。
最近は間違った事を書いて質問に答えてもらうメソッドが巷で流行っているらしいですが、それはともかく、今回はWPFでさらっとスクリーンキャプチャをするためのサンプルを作ってみました。

まぁ、なんとなくそんな難しくないよね、と思っていたのですが、そういえばRxでマウスイベントの処理を自分で書いたことないんだよね、と、そのあたりを調べつつ試しに書いてみたらちょっと手こずりまして、助言をいただいたりもしてそれなりな感じになったのでエントリーにしておきます。

サンプルプロジェクトのダウンロードはこちら。
https://github.com/kaorun/kaorun_samples/archive/master.zip

問題のキャプチャー部分のコードはこんな感じです。
https://github.com/kaorun/kaorun_samples/blob/master/WpfScreenCaptureTest/WpfScreenCaptureTest/CaptureWindow.xaml.cs

using System;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Drawing;
using System.Windows.Interop;

namespace kaorun.samples
{
    /// <summary>
    /// Screen capture window class by selecting rectangle by Mouse with Rx event handling
    /// </summary>
    public partial class CaptureWindow : Window
    {
        public CaptureWindow()
        {
            InitializeComponent();
            
            Cursor = Cursors.Cross;
            var origin = new System.Windows.Point();
            
            var mouseDown = Observable.FromEventPattern<MouseEventArgs>(this, "MouseLeftButtonDown");
            var mouseMove = Observable.FromEventPattern<MouseEventArgs>(this, "MouseMove");
            var mouseUp = Observable.FromEventPattern<MouseEventArgs>(this, "MouseLeftButtonUp");

            mouseDown
                .Do(e => { origin = e.EventArgs.GetPosition(LayoutRoot); })
                .SelectMany(mouseMove)
                .TakeUntil(mouseUp)
                .Do(e =>
                {
                    var rect = BoundsRect(origin.X, origin.Y, e.EventArgs.GetPosition(LayoutRoot).X, e.EventArgs.GetPosition(LayoutRoot).Y);
                    selectionRect.Margin = new Thickness(rect.Left, rect.Top, this.Width - rect.Right, this.Height - rect.Bottom);
                    selectionRect.Width = rect.Width;
                    selectionRect.Height = rect.Height;
                })
                .LastAsync()
                .Subscribe(e =>
                {
                    this.Hide();

                    // Offsetting selection boundery, because transpalent WPF window shifted few pixel from screen coordinats
                    MainWindow.Captured = CaptureScreen(Rect.Offset(BoundsRect(origin.X, origin.Y, e.EventArgs.GetPosition(LayoutRoot).X, e.EventArgs.GetPosition(LayoutRoot).Y), this.Left, this.Top));
                    
                    Cursor = Cursors.Arrow;
                    this.Close();
                });
        }

        private static Rect BoundsRect(double left, double top, double right, double bottom)
        {
            return new Rect(Math.Min(left, right), Math.Min(top, bottom), Math.Abs(right - left), Math.Abs(bottom - top));
        }

        public static BitmapSource CaptureScreen(Rect rect)
        {
            using (var screenBmp = new Bitmap((int)rect.Width, (int)rect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
            {
                using (var bmpGraphics = Graphics.FromImage(screenBmp))
                {
                    bmpGraphics.CopyFromScreen((int)rect.X, (int)rect.Y, 0, 0, screenBmp.Size);
                    return Imaging.CreateBitmapSourceFromHBitmap(screenBmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
                }
            }
        }
    }
}

今回は、ほぼ透明な全面ウインドウを張ってマウスイベントを取得し、選択後ウインドウを隠してからスクリーンショットを撮る、というアプローチで実装してみました。
Rxの練り方はもう一声エクセレントな書き方がありそうな気がしますが、今日はこれで精いっぱい。よりエレガントなRxの書き方は随時募集中です。
Rxを使わずにイベントハンドラでやった方が黒魔術っぽくなくてサンプルとしてわかりやすい気もしますが、習作もかねているのでこんなもんで。
細かい飾りとか、画像の保存もなにもしていないし、やっつけ実装で、タスクスイッチなど異常系の処理やマルチdpi・マルチスクリーン・DirectXなどは特にケアしていないので、ちゃんと実装するならもっと頑張った方がいい気がします。

まぁ、C#でざっくり書いて68行ですかね(何がだ)。上にも書いたようにもっとケアしないといけないことが沢山あるわけですが、細かい要請に答えたり挙動を調整していけるのがネイティブアプリの肝であり醍醐味でもあります。

ところで、Windows 10ではデスクトップアプリもAppX化することでWindows Storeに登録できるようになるようです。この手のちょっとしたツールアプリでもストアで販売できるようになると思うと楽しみですね。

Special thanks emomonさん!

2015-05-03

[]Microsoftカンファレンスのセッション動画をまとめてDownloadするSession Downloader

f:id:kaorun:20150503185001p:image

どうも、ご無沙汰しております。id:kaorunです。
日々の生活に押し流されている毎日で、久々のエントリー更新です。

今年もBuild2015が幕を閉じましたね!
現地で参加された遠征組も、2日連続で夜半に3時間以上のキーノートに挑んだ半徹組も本当にお疲れさまでした。
Windows 10にHoloLensにAzureにと盛りだくさんな当たり年だったのではないでしょうか?

さて、例によって、いつものごとく、カンファレンスが終われば、大量のセッション資料と動画が放出されるのが昨今のマイクロソフトの習わし、ということで、Channel9には開催当日からザクザクと資料がUploadされています。
とはいえ、こんなんいちいち仕事中に観ていられんわ、ということで、ちまちまダウンロードしてスマフォやタブレットに詰め込んで持ち歩いたりするわけですが、今年のように目新しい情報山盛りの年はダウンロードだけでも難儀です。

というわけで、前置きが長くなりましたが、Buildや続いて開催されるIgniteなど、マイクロソフトのカンファレンス資料をまとめてガツンとダウンロードできるツールをご紹介。Tim Nilimaa氏のリリースしているその名もずばりSession Downloaderです。

Session Downloader  Infoworks
http://infoworks.tv/session-downloader/

ClickOnceアプリなので、Windowsマシンでリンクをクリックして開くだけ。
使い方もシンプルです。

プルダウンのリストからイベント名を選び、

f:id:kaorun:20150503185000p:image

ダウンロードしたいフォーマットを選びます。

f:id:kaorun:20150503184959p:image

で、Refreshボタンを押せば...

f:id:kaorun:20150503184957p:image

サムネール付きの一覧がずらっとでるので、あとは、選択してDownloadするだけ。

f:id:kaorun:20150503184955p:image

f:id:kaorun:20150503184954p:image

ちなみに、Filterでインクリメンタルサーチできると思うのですが、手元の環境ではキー入力するとばきっと死にます。また、ちまちま沢山選択してDownload実行しても死んだりします(Select AllだとOkでした)。W10TPだからか日本語だからか...。とりあえず、そのあたりはお試ししながら確認してみてください

ちまちまブラウザでやってるときりがない作業を、ストレージ容量さえあれば大人げなくSelect AllしてすべてDownloadしてしまうことさえも可能です。

さぁさぁ、次世代のMicrosoftテクノロジーの数々が無料で見放題。動画をがつがつ落としてもよし、PowerPointのスライドをまるっと取得して検索しても良し、です。
まぁ、たいがいまるっと落としてきても全部は観きれないんですけどね。

2014-12-02

[][][][]サンプルコードを見ながら理解するMVVMの基礎的な実装

f:id:kaorun:20141202213049p:image

WPFがWinFormsより敷居が高い?
そりゃ、最初に掛け違ってるからですよ、きっと。

というわけで、これはXAML Advent Calendar 2014の3日目、12月3日分のエントリーです。WPF? XAML? MVVM? そんなにムズカシクないよ? というお話。
もちろんWinRTのWindowsストアアプリやWindows Phoneアプリ等、XAMLベースのプラットフォームにもほとんどすべて共通した内容です。

ここではあえて理論とか観念は説明しません。とにかくコードを見ながら仕組みと動きを理解していきます。
俺は、コードが読める、長い説明エントリーなんてめんどくせぇ、と思ったら、コード部分だけを実際に動かしながら見ていくだけでも基本的な構造が十分に理解できるのではないか、と。

f:id:kaorun:20141202215300p:image:w240

サンプルプロジェクト
Download: SimpleMVVM.zip 直
github: https://github.com/kaorun/kaorun_samples/tree/master/SimpleMVVM

ビルドして実行すると表示されるこちらのインデックスから順にサンプルとして紹介していきます。


Simple Model Binding

まずは、おなじみのPersonクラスから。
Person.cs

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

まぁ、よくあるサンプルですよね。MVVMやMVCパターンで言うところのいわゆるModelです。

これをXAMLを使って表示すると、だいたいこんな実装になります。
SimpleModelPage.xaml

<StackPanel>
    <TextBlock Text="First Name:"/>
    <TextBox Text="{Binding FirstName}"/>
    <TextBlock Text="Last Name:"/>
    <TextBox Text="{Binding LastName}"/>
    <TextBlock Text="Age:"/>
    <TextBox Text="{Binding Age}"/>
</StackPanel>

こちらが表示側にあたる、いわゆるViewですね。
TextBoxコントロールのText属性を{Binding 〜}というマークアップ拡張構文でオブジェクトに紐づけています。
<TextBox Text="{Binding FirstName}"/>であればTextBoxコントロールのTextプロパティにFirstNameプロパティを紐づけ(Binding)するよ、という定義です。

このようにViewをXMLで定義して実装するためのマークアップ言語として開発されたのがXAMLです(実際には汎用的に様々なオブジェクトをXMLで定義することができるため、多くの用途にXAMLが利用されるようになっています)。
追記: いちおう.NET界隈でない人に説明しておくと、SimpleModelPage.xamlSimpleModelPage.xaml.csの2つはいわゆるpartialクラス定義です。XAMLはオブジェクト定義に変換されるので、クラスの定義を複数の構文に分割できるpertialクラスにより*.xamlと*.xaml.csで一つのViewオブジェクトクラスを定義しているわけですね。*.xaml.csはHTMLでいうところのscriptタグみたいなイメージで。

そのままでは出ないので、とりあえず、Pageのコンストラクターインスタンスを生成して、DataContextに突っ込んでいます。このViewのコンテキスト、つまりは表示するためのデータはこれ、というわけですね。これは、とりあえず、お約束ということで。
SimpleModelPage.xaml.cs

public partial class SimpleModelPage : Page
{
    public SimpleModelPage()
    {
        InitializeComponent();

        var p = new Person()
        {
            FirstName = "Tadafumi",
            LastName = "Iriya",
            Age = 17,
        };

        this.DataContext = p;
    }
}

入谷君をイジって楽しむよ(何)。

実行してみましたか? はい、出ましたね。
f:id:kaorun:20141202215115p:image:w320

  1. 表示するためのクラスを定義
  2. インスタンスを生成
  3. ページ(View)のDataContextに割り当て
  4. {Binding}構文を使ってコントロールのプロパティに割り当てる

これが、XAMLを利用したMVVMの礎の一つDataBindingの基本です。

しかし、これだけではとりあえず出るだけで、編集してもなにも起こりません。


Field Member Model Binding

それから、先のPersonクラス、なんでgetter/setterの付いたプロパティやねん? と思いませんでしたか? そう、シンプルなクラスだったら、フィールド変数なんじゃないの? と。

では、試してみましょう。
PersonFieldMember.cs

public class PersonFieldMember
{
    public string FirstName;
    public string LastName;
    public int Age;
}

どうでしょう? 表示されませんね。
f:id:kaorun:20141202215940p:image:w320

つまり、Bindingを使うためのDataContextとなるクラスのメンバ変数は常にgetter/setterを持ったプロパティでなければならないという点です。後々でてくる話に繋がるのですが、とりあえずまず、これは約束です。大概、わかっていても実装を進めていると後述するCommandプロパティなどをうっかりフィールド変数で定義してしまい、「よ...呼ばれない、なんで!?」などとうろたえることになります。
とにかくBindする変数はプロパティで。


Update Model Binding

さて、クラスオブジェクトをViewで表示しているのなら、これをコードで変更したらView側も更新して表示してくれんの? と思いませんか?

早速、ボタンを置いて、Ageを加算してみましょう。
ModelIncrementAgePage.xaml

<StackPanel>
    <TextBlock Text="First Name:"/>
    <TextBox Text="{Binding FirstName}"/>
    <TextBlock Text="Last Name:"/>
    <TextBox Text="{Binding LastName}"/>
    <TextBlock Text="Age:"/>
    <TextBox Text="{Binding Age}"/>
    <Button x:Name="cmdIncrementAge" Click="cmdIncrementAge_Click" >Age++</Button>
</StackPanel>

Personインスタンスをクラスメンバー化して、とりあえず、ボタンの処理はえいやでコードビハインドのClickイベントで実装です。
ModelIncrementAgePage.xaml.cs

public partial class ModelIncrementAgePage : Page
{
    private Person p;

    public ModelIncrementAgePage()
    {
        InitializeComponent();

        p = new Person()
        {
            FirstName = "Tadafumi",
            LastName = "Iriya",
            Age = 17,
        };

        this.DataContext = p;
    }

    private void cmdIncrementAge_Click(object sender, RoutedEventArgs e)
    {
        p.Age++;
    }
}

さて、実行してボタンを押してみます。
f:id:kaorun:20141202220508p:image:w320

うーん、何も起きませんね。デバッグしてみるとわかりますが、コードは実行されているはずです。つまりPersonクラスのAgeプロパティは増えているのに、View側の画面には反映されません。

WinFormsなどコードビハインドで実装していたら、

private void cmdIncrementAge_Click(object sender, RoutedEventArgs e)
{
    p.Age++;
    textAge.Text = p.Age.ToString()
}

などと実装するところですが、今このサンプルで更新するためのコードは書いてない、つまりDataContextに割り当てたオブジェクトの変更をViewがBindingで勝手に更新してくれたりはしないのです。


Update ViewModel Binding

では、どうするか?
クラス側で、どの変数が変更されたよ、という情報を通知するイベントを用意してやります。
PersonViewModel.cs

public class PersonViewModel : INotifyPropertyChanged
{
    private string firstNameVal;
    public string FirstName
    {
        get { return firstNameVal; }
        set
        {
            firstNameVal = value;
            NotifyPropertyChanged("FirstName");
        }
    }

    private string lastNameVal;
    public string LastName
    {
        get { return lastNameVal; }
        set
        {
            lastNameVal = value;
            NotifyPropertyChanged("LastName");
        }
    }

    private int ageVal;
    public int Age
    {
        get { return ageVal; }
        set
        {
            ageVal = value;
            NotifyPropertyChanged("Age");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

実行すると、ボタンの押下で入谷君の年齢がどんどん上がっていきます。下げる機能はまだありません。
f:id:kaorun:20141202221427p:image:w320

さて変わったところはなんでしょう?
INotifyPropertyChangedというインターフェイスを継承して、各プロパティになにやらがしゃがしゃとコードが追加され、最後に見知らぬメンバーとしてイベントとプライベートメソッドが追加されています。

追加されたINotifyPropertyChangedインターフェイスとはなんでしょう? 定義を見てみます。

namespace System.ComponentModel
{
    // 概要:
    //     プロパティ値が変更されたことをクライアントに通知します。
    public interface INotifyPropertyChanged
    {
        // 概要:
        //     プロパティ値が変更されたときに発生します。
        event PropertyChangedEventHandler PropertyChanged;
    }
}

ようするに、クラスにイベントを一つ追加実装しなさいよ、という決まりです。

なにをやってるかはコードを読めばわかると思いますが、今どの変数が変更されたよ、という通知を送ってるわけですね。いわゆるObserverパターンのオブザーバー実装です。

シンプルなPersonクラスに変更通知機能が付いた、これがViewのためのModelオブジェクト、ViewModelの基礎の基礎です。実際のプロパティやNotifyPropertyChanged()実装では様々なテクニックや実装が使われることになりますが、基本的な動作としてはここがベースになります。


Edit Model Binding

さて、今度はよりViewModelらしい動きを実装するためにフォームの編集機能を追加します。FirstName, LastNameときたらFullNameですよね。ということで、まずは試しにModelクラスのPersonクラスにFullNameプロパティを実装してみます。
PersonFullName.cs

public class PersonFullName
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName
    {
        get { return FirstName + " " + LastName; }
        set
        {
            var names = value.Split(new[] {' '});
            if (names.Length == 2)
            {
                FirstName = names[0];
                LastName = names[1];
            }
            else
            {
                FirstName = value;
                LastName = "";
            }
        }
    }
    public int Age { get; set; }
}

とうぜん、編集したところで更新されません。
f:id:kaorun:20141202230303p:image:w320


Edit ViewModel TwoWay Binding

ではViewModelでは?
PersonFullNameViewModel.cs

public class PersonFullNameViewModel : INotifyPropertyChanged
{
    private string firstNameVal;
    public string FirstName
    {
        get { return firstNameVal; }
        set
        {
            firstNameVal = value;
            NotifyPropertyChanged("FirstName");
            NotifyPropertyChanged("FullName");
        }
    }

    private string lastNameVal;
    public string LastName
    {
        get { return lastNameVal; }
        set
        {
            lastNameVal = value;
            NotifyPropertyChanged("LastName");
            NotifyPropertyChanged("FullName");
        }
    }

    public string FullName
    {
        get { return FirstName + " " + LastName; }
        set
        {
            var names = value.Split(new[] { ' ' });
            if (names.Length == 2)
            {
                FirstName = names[0];
                LastName = names[1];
            }
            else
            {
                FirstName = value;
                LastName = "";
            }
            NotifyPropertyChanged("FullName");
        }
    }

...
}

いよいよクラスがだいぶ長くなってきたので途中は割愛、先ほどのViewModelと基本的には同じ仕組み、変更されたら通知する、です。

FirstNameを変更したらFullNameの変更も通知しているのがキモですね。

それから、XAML側も実はいろいろ変わっています。
EditFullNamePage.xaml

<StackPanel>
    <TextBlock Text="Press [Tab] key or change focus to Update"/>
    <TextBlock Text="First Name:"/>
    <TextBox Text="{Binding FirstName,Mode=TwoWay}"/>
    <TextBlock Text="Last Name:"/>
    <TextBox Text="{Binding LastName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
    <TextBlock Text="Full Name:"/>
    <TextBox Text="{Binding FullName,Mode=TwoWay}"/>
    <TextBlock Text="Age:"/>
    <TextBox Text="{Binding Age,Mode=TwoWay}"/>
</StackPanel>

f:id:kaorun:20141202230443p:image:w320

{Binding FirstName}
から
{Binding FirstName,Mode=TwoWay}
へ、

{Binding LastName}
の方には、なにやらさらに長く、
{Binding LastName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
とついていますね。

Mode=TwoWayは、View側での変更をViewModelへ反映するよ、という属性です。規定値はMode=OneWayなんですね。
そして、UpdateSourceTrigger=PropertyChangedは、ViewModelへの反映をするタイミングの設定です。

試しに、FirstNameとLastNameをそれぞれ変更してみてください。FirstNameは入力してもすぐにはFullNameが変わらず、タブキーなどでFirstNameコントロールからフォーカスが外れると更新されます。LastNameコントロールは入力している傍から変更が反映されますね、そういう属性です。
Binding属性にはこれ以外にもStringFormatなど様々な設定が可能なので、色々変更して試してみるといいでしょう。


Command Binding

さて、View側のフォームを変更してViewModelに反映させることができるようになりました。

ただ、先ほどの加齢ボタンのように、コードビハインドを書いてView側で値を出し入れしたりするのはなんだかカッコ悪い、ボタンクリックなどのコマンドもViewModel側で実装してBindingできないの?
...というわけで、今度はCommandバインディングです。

f:id:kaorun:20141202230744p:image:w320

ViewModelがさらににぎやかになりました。

PersonCommandViewModel.cs

public class PersonCommandViewModel : INotifyPropertyChanged
{
    public ICommand IncrementAge { get; set; }
    public ICommand DecrementAge { get; set; }
    public ICommand Submit { get; set; }

    public PersonCommandViewModel()
    {
        IncrementAge = new RelayCommand(() => { Age++; });
        DecrementAge = new DecrementAgeCommand(this);
        Submit = new RelayCommand(SubmitNow);
    }

    private void SubmitNow()
    {
        MessageBox.Show(string.Format("User: {0}\nAge: {1}\nSubmitted!", FullName, Age));
    }
...
}

IncrementAge, DecrementAge, Submitコマンドが定義されました。謎のICommandインターフェイスインスタンスが定義されています。
また、View側のXAMLでは、ボタンのCommand属性にそれらのコマンドがBindingされています。

CommandPage.xaml

<StackPanel>
    <TextBlock Text="First Name:"/>
    <TextBox Text="{Binding FirstName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
    <TextBlock Text="Last Name:"/>
    <TextBox Text="{Binding LastName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
    <TextBlock Text="Full Name:"/>
    <TextBox Text="{Binding FullName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
    <TextBlock Text="Age:"/>
    <TextBox Text="{Binding Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
    <StackPanel Orientation="Horizontal">
        <Button Command="{Binding IncrementAge}">Age++</Button>
        <Button Command="{Binding DecrementAge}">Age--</Button>
    </StackPanel>
    <Button Command="{Binding Submit}">Submit!</Button>
</StackPanel>

しかし、そのおかげで、CommandPage.xaml.csのコードビハインドはインスタンスの生成だけですっきりしました。最初のサンプルと同じですね。
CommandPage.xaml.cs

public partial class CommandPage : Page
{
    public CommandPage()
    {
        InitializeComponent();

        var p = new PersonCommandViewModel()
        {
            FirstName = "Tadafumi",
            LastName = "Iriya",
            Age = 17,
        };

        this.DataContext = p;
    }
}

ViewModelがICommandを実装して、そのプロパティがViewのCommand属性にBindingされると、ViewがViewModelを呼び出せるようになるわけです。

ICommandはちとややこしいので、3つのボタンに対してそれぞれ違う実装をしてみています。

public PersonCommandViewModel()
{
    IncrementAge = new RelayCommand(() => { Age++; });
    DecrementAge = new DecrementAgeCommand(this);
    Submit = new RelayCommand(SubmitNow);
}

一番最初のIncrementAgeコマンドは単純ですね、ラムダ式でAge++をしているだけです。割り当てているRelayCommandについては後述。

だったら、コードビハインドみたいに単にメソッドを渡せばいいんじゃないの? と思われるかもしれませんが、次のDecrementAgeコマンドに割り当てられているDecrementAgeCommandクラスの実装を見てみてください。

public class DecrementAgeCommand : ICommand
{
    private PersonCommandViewModel vm;

    public DecrementAgeCommand(PersonCommandViewModel viewmodel)
    {
        vm = viewmodel;
    }

    public bool CanExecute(object parameter)
    {
        return vm.Age > 0;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        vm.Age--;
    }
}

ICommandをシンプルに実装するとこういう感じになります。
単に、コマンドが実行された場合だけでなく、ボタンの状態が今有効かどうか(無効なら自動的にグレる)、そして(ここでは未実装ですが)その状態が変わったことをイベントとして通知する仕組みも持っているわけです。

このICommandを都度々実装するのがめんどくせぇ、というのがRelayCommandクラスなわけですが、なぜか、こいつは.NET Frameworkの標準クラスではありません。Commonフォルダに入っているRelayCommand.csの実装は、Windowsストアアプリテンプレートに自動的に入ってくるRelayCommandのほぼ丸写し実装です。この辺りがなんで標準でないかは今回は割愛。RelayCommandもわりと高性能なので、引数で有効無効の実装などもできます。


今回作成したサンプルは以上です。

出来上がったコードを見てみると、MVVMによるアプリ実装とは、表示するためのViewModelクラスを定義して、XAMLで定義したViewにBindingしていくことが基本である、ということがご理解いただけると思います。
そして、表示するオブジェクトがViewModelに集約され、動作の流れが見えてくると、手続き型のコードビハインド実装が生み出すスパゲティコードがどのように回避されるのかもなんとなくわかってくるのではないでしょうか?

ただのプロパティなのにNotifyPropertyChanged()等をいちいち実装するのがめんどくせぇ、と思うかもしれませんが、そこはIDEパワーでコードスニペットを定義して力技で行きましょう。

本当は、INotifyPropertyChangeによるViewModelとはまた別にDipendencyObject(依存関係オブジェクト)とDependencyProperty(依存関係プロパティ)による通知システムも存在したりするのですが、ひじょーに長くてめんどい話になりますので、これまた割愛いたします。
さぁWPFWindowsストアアプリの勉強を始めよう、と、最初にVisual StudioのデザイナでUIを作り、コードビハインドを止めていざMVVMへ、ViewModelやらBindingやらを実装しようとすると、誤ってそちらの密林に足を踏み込んでしまい、厄介なことになることもあるのではないかと思うわけです。

さて、以上で駆け足で実装してきた、とにかく基礎的なMVVMアプリの説明は終了です。え? ViewModelがあるならModelいらないんじゃ? ModelとViewModelはどう切り分けるの? いやいや、そのあたりまで突っ込んで解説すると怖い人から鉞(マサカリ)が飛んできますからね...(まぁ、ここまででも十分飛んできそうだけども)。
まずは、動きとそれぞれの役割を把握してから、理論や応用に関して理解を深めてみるのもよいのではないでしょうか?

そういうわけで、くだくだと長い説明になってしまいましたが、MVVMってなんじゃい? という基本的なイメージを実装から理解していただければ幸いです。

それでは、今年も残り少なくなりましたが、皆さんに良いXAMLライフが訪れますように。

まとめ:

  • ViewをXMLで定義して実装するためのマークアップ言語として開発されたのがXAML
  • XAMLで定義したViewのDataContextに、Bindingを利用してViewModelを紐づけるのが(Windowsプラットフォームにおける)MVVMの基本
  • Bindingする変数はフィールド変数ではなく必ずget/setのプロパティで実装すること、これ約束な
  • ModelにINotifyPropertyChangedによる通知機能を追加してViewModelに
  • プロパティの更新をViewに反映するためにPropertyChangedイベントで通知する
  • ViewModelとは、通知機能を持ったObserverパターンの実装
  • ICommandインターフェイスを実装して、クリックなどの操作イベントもViewModelにBinding