c++/cx metro の場合 mvvm がどのように実装されているか

というメモ書き。
metro application で grid のテンプレートを開くと、Common というフォルダが作られて、その中に ListView を使った mvvm の実装があります。WPF, Siverlight と実装が異なるので、覚え直しになるのか?とうことと、このクラスを使って実装して良いのか?ということがあるのですが、今後なし崩しに活用されそうな予感はします。

Visual Studio 2012 RCのMetro スタイル アプリのテンプレートを見てみる – かずきのBlog@Hatena
http://d.hatena.ne.jp/okazuki/20120604/1338821522

のところを参照にして、
・SampleDataSource.h
・SampleDataSource.cpp
・BindableBase.h
・BindableBase.cpp
だけ見ていきます。構造は、C# と一緒です。

■SampleDataSource.h

/// <summary>
/// Base class for <see cref="SampleDataItem"/> and <see cref="SampleDataGroup"/> that
/// defines properties common to both.
/// </summary>
 [Windows::Foundation::Metadata::WebHostHidden]
 [Windows::UI::Xaml::Data::Bindable]
public ref class SampleDataCommon : CppNotify::Common::BindableBase
{
internal:
	SampleDataCommon(Platform::String^ uniqueId, Platform::String^ title, Platform::String^ subtitle, Platform::String^ imagePath,
		Platform::String^ description);

public:
	void SetImage(Platform::String^ path);

	property Platform::String^ UniqueId { Platform::String^ get(); void set(Platform::String^ value); }
	property Platform::String^ Title { Platform::String^ get(); void set(Platform::String^ value); }
	property Platform::String^ Subtitle { Platform::String^ get(); void set(Platform::String^ value); }
	property Windows::UI::Xaml::Media::ImageSource^ Image { Windows::UI::Xaml::Media::ImageSource^ get(); void set(Windows::UI::Xaml::Media::ImageSource^ value); }
	property Platform::String^ Description { Platform::String^ get(); void set(Platform::String^ value); }

private:
	Platform::String^ _uniqueId;
	Platform::String^ _title;
	Platform::String^ _subtitle;
	Windows::UI::Xaml::Media::ImageSource^ _image;
	Platform::String^ _imagePath;
	Platform::String^ _description;
};

ListView の Item を制御するところで、実際には SampleDataCommon を継承して SampleDataItem を作っています。
実際の Notify のところは、BindableBase クラスで実装されています。
C++/CX なのでプロパティは「property」を使って作れます。

■SampleDataSource.cpp

SampleDataCommon::SampleDataCommon(String^ uniqueId, String^ title, String^ subtitle, String^ imagePath, String^ description)
{
	_uniqueId = uniqueId;
	_title = title;
	_subtitle = subtitle;
	_description = description;
	_imagePath = imagePath;
	_image = nullptr;
}

String^ SampleDataCommon::UniqueId::get()
{
	return _uniqueId;
}

void SampleDataCommon::UniqueId::set(String^ value)
{
	if (_uniqueId != value)
	{
		_uniqueId = value;
		OnPropertyChanged("UniqueId");
	}
}

御馴染みの OnPropertyChanged 呼び出しが、プロパティの set メソッドの中にあります。
OnPropertyChanged に渡すのは、C# と同じように文字列ですね。細かいことですが、C++/CX の場合は Platform::String になります。

■BindableBase.h

/// <summary>
/// Implementation of <see cref="INotifyPropertyChanged"/> to simplify models.
/// </summary>
 [Windows::Foundation::Metadata::WebHostHidden]
public ref class BindableBase : Windows::UI::Xaml::Data::INotifyPropertyChanged
{
public:
	virtual event Windows::UI::Xaml::Data::PropertyChangedEventHandler^ PropertyChanged;

protected:
	virtual void OnPropertyChanged(Platform::String^ propertyName);
};

BindableBase の中身は簡単で、Model からプロパティ更新を受け取るための OnPropertyChanged と、XAML に更新通知をするための PropertyChanged だけです。C# にある SetProperty 相当のものはありません。ちなみ、「Model が更新通知用のメソッドを呼び出す」ので「OnPropertyChanged」じゃなくて「RaisePropertyChanged」とか「ChangeProperty」とかのほうが良い気がするのですが、どうなんでしょう?いくつかの mvvm 用のサンプルを探すと OnPropertyChanged になっているので、それを踏襲した模様です。

■BindableBase.cpp

/// <summary>
/// Notifies listeners that a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners.</param>
void BindableBase::OnPropertyChanged(String^ propertyName)
{
	PropertyChanged(this, ref new PropertyChangedEventArgs(propertyName));
}

実装のほうも簡単で、プロパティの「文字列」を受け取って、PropertyChanged を実行しているだけです。この PropertyChanged は、XAML 側から登録されるものです。プロパティ名を直接渡さずに、PropertyChangedEventArgs で包むところが冗長な気がしますが(特に ref new になっているところ)、引数の型を固定したかったなのかもしれません。

■少し楽をするためにマクロを書く

という訳で、プロパティの set/get を書くところは mvvm でおなじみのところです。C# では、CallerMemberName という属性がありますが、C++/CX の場合はありません。理由はよくわかりません。

さて、ここの get/set を書くのは結構面倒です。まあ、T4 を使ってテンプレート化してしまっても良いのですが、C++/CX の場合は C/C++ 系列としてマクロを使う方法があります。

#define PROPERTY( _t, _m )				\
private:								\
	_t m_##_m;							\
public:									\
	property _t _m {					\
		_t get() { return m_##_m; }		\
		void set( _t value ) {			\
			if ( m_##_m != value ) {	\
				m_##_m = value;			\
				OnPropertyChanged( #_m ); \
		}}}

これを使って、SampleDataSource.cpp を書き換えます。

/// <summary>
/// Base class for <see cref="SampleDataItem"/> and <see cref="SampleDataGroup"/> that
/// defines properties common to both.
/// </summary>
 [Windows::Foundation::Metadata::WebHostHidden]
 [Windows::UI::Xaml::Data::Bindable]
public ref class SampleDataCommon : CppNotify::Common::BindableBase
{
internal:
	SampleDataCommon(Platform::String^ uniqueId, Platform::String^ title, Platform::String^ subtitle, Platform::String^ imagePath,
		Platform::String^ description);

public:
	void SetImage(Platform::String^ path);

	PROPERTY( Platform::String^, UniqueId );
	PROPERTY( Platform::String^, Title );
	PROPERTY( Platform::String^, Subtitle );
	// PROPERTY( Windows::UI::Xaml::Media::ImageSource^, Image );
	property Windows::UI::Xaml::Media::ImageSource^ Image { Windows::UI::Xaml::Media::ImageSource^ get(); void set(Windows::UI::Xaml::Media::ImageSource^ value); }
	PROPERTY( Platform::String^, Description );
private:
	Windows::UI::Xaml::Media::ImageSource^ _image;
	Platform::String^ _imagePath;
};

private なメンバが「m_Title」のようにプロパティ名にプレフィックスが付いた形式になってしまいますが、これで良いかと。
Image プロパティの場合は、_imagePath との絡みもあるので、別途定義になっています。

■OnPropertyChangedに渡すパラメータを文字列以外にしたい。

これを踏まえて、C++のパターンと、C++/CX の別のパターンと考えていこうという訳です。
関数ポインタを使うか、自動な enum を使うか、という形で文字列を使わないように作り変えます。

カテゴリー: C++/CX | 1件のコメント

15年前のMFCあるいはMVC

15年前のMFC

View more presentations from moonmile.
2012年6月の.NETラボ勉強会のライトニングトークスで発表した資料です。

歴史をさかのぼれば、コンソールアプリケーションまでさかのぼって解説をしないと駄目なのですが、それだと時間が無くなるので、時間をさかのぼるスタイルで解説していきます。
このスライドでは、いきなりMFCのところからスタートしていますが、実はMVC(MVVMなどが生まれる前の「MVC」という用語の)やXAMLあたりからさかのぼっていきます。XAMLはSVGの派生であって、かつDHTMLの後継(とも言える)スタイルなのです。一見、動的な操作が加えられているように見えますが、本質的なところは、Viewです。Viewというのは人にとってはDisplayです。時にはInterfaceであったり、Presentationであったり(Persentする側があるという意味で)だったりします。
そこで、Viewの部分をXAMLという「言語」で書きました。現実的にはXMLの派生ということなのですが、タグフォーマットという「言語」で書きます。で、実際の内部動作はどうなっているかというと、C#やVBなどの「言語」で書きます。この2つの「言語」を使い分けるのが、XAMLというViewを使う特徴です。
(実は、内部的にXAMLにResourceなどの記述が入っているので、完全にXMLに準拠=XMLの文法だけで解析できる訳ではないので、微妙にことなるのですが)。
さて、XAML+C#あるいはXAML+VB、Windows 8 MetroStyle、あるいは、WPF、Windows Phone, Silverlight などのアプリケーションは、皆 XAML を View として扱います。
さて、View 以外の部分、MVC で言うところの、Model と Controller はどうなるかというとここではさておき(MVVMや様々なMVC実装などでimpliment自体が異なるので)、「View を異なる言語で書いたのは、WPF などが最初であったか?」という話なのですが、実は、VC++6(あたり、正確には何時だったけ?)から View の実装があります。

これが「*.rc」です。CDialog や CView という形で View を分離しておいて、その中でリソースを参照させます。このリソースの中身は、

IDD_HC_WAVE_VRETICAL_PAGE DIALOGEX 0, 0, 279, 193
STYLE DS_SETFONT | WS_CHILD | WS_DISABLED | WS_CAPTION
CAPTION "Vertical"
FONT 8, "MS Sans Serif", 0, 0, 0x0
BEGIN
    LTEXT           "Group name:",IDC_STATIC,5,10,77,8
    LTEXT           "Hatch cover no:",IDC_STATIC,5,26,77,8
    LTEXT           "Length of hatch opening (m):",IDC_STATIC,5,42,101,8
    LTEXT           "Breadth of hatch opening (m):",IDC_STATIC,5,57,101,8
    LTEXT           "Hatch center location FP (m):",IDC_STATIC,5,74,101,8
    LTEXT           "Pressure P (kN/m2):",IDC_STATIC,5,91,101,8
    EDITTEXT        IDC_E_HC_WAVE_LOADS_GROUP_NAME,114,10,92,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_E_HC_WAVE_LOADS_BREADTH,114,57,92,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_E_HC_WAVE_LOADS_HATCH_COVER_NO,114,26,92,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_E_HC_WAVE_LOADS_LENGTH,114,42,92,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_E_HC_WAVE_LOADS_CENTER,114,73,92,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_E_HC_WAVE_LOADS_PRESSURE,114,89,92,12,ES_AUTOHSCROLL
END

な感じなのです。これを見てみるとわかりますね。
実は、XAML のように構造化されているところが同じなのです。

LTEXT は静的なラベルを貼っているところ、EDITTEXT はテキストボックスです。IDC_E_HC_WAVE_LOADS_GROUP_NAME は名前です。内部的にはint形として処理されています。これを XAML 風に書くならば、

<grid font="MS Sans Serif" fontsize="8pt">
	<LTEXT content="Group name:" type="IDC_STATIC" position="5,10,77,8"/>
	<LTEXT content="Hatch cover no:" type="IDC_STATIC" position="5,26,77,8"/>
	<LTEXT content="Length of hatch opening (m):" type="IDC_STATIC" position="5,42,101,8"/>
	...
	<EDITTEXT name="IDC_E_HC_WAVE_LOADS_GROUP_NAME" position="114,10,92,12" ES_AUTOHSCROLL="true" />
    <EDITTEXT name="IDC_E_HC_WAVE_LOADS_BREADTH" position="114,57,92,12" ES_AUTOHSCROLL="true" />
	...
</grid>

のように対応付けられます。

ただし、ボタンクリックのイベントは、XAML の場合 Click 属性に設定できるのですが、*.rc の場合はできなくて ON_COMMAND などを駆使しています。このあたりは、実装の違い、かつ Click 属性などのような動的なイベント/データの扱いが、実は View とは異なる領域にあることを示しています(このあたりの話は後日)。

この *.rc の書き方ですが、その昔はどうしていたかというと、CreateWindow 関数を駆使しながら「C++」言語で書いていました。多少は、CButton, CEdit というコントロールができたものの、position を指定する(静的に設定できる値)場合には、違った書き方をしていたわけです。C++ だけで書くというやり方と、*.rc と C++ で書くというやり方ですね。
実際のところ、*.rc は Visual Studio のデザイナを使うために直接扱うことはありません。デザイナ自体がちょっと重たいので、大量に画面を作りたい場合は、直接テキストエディタで *.rc を開いて編集してます。
実は、このあたりの編集スタイルも XAML と似たようなものなのです。

となると、MVC における VC++ の *.rc は元祖なのか?というと実は違います。まだ先祖がいるのですよ。VB1.0 なんですね。かのアラン・クーパーが開発した Visual Basic は、この View と動作コードを分離させていました。

Visual Basic 6.0 のサンプルコードをダウンロードしてテキストエディタを開くと、先頭の部分はこんな風になっています。

VERSION 5.00
Object = "{648A5603-2C6E-101B-82B6-000000000014}#1.1#0"; "MSCOMM32.OCX"
Begin VB.Form Form2
   Caption         =   "Terminal"
   ClientHeight    =   3450
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   3600
   LinkTopic       =   "Form2"
   ScaleHeight     =   3450
   ScaleWidth      =   3600
   StartUpPosition =   3  'Windows の既定値
   Begin VB.PictureBox Pic_Info
      Height          =   255
      Left            =   3120
      ScaleHeight     =   195
      ScaleWidth      =   315
      TabIndex        =   9
      Top             =   3120
      Width           =   375
   End
   Begin VB.CommandButton IDB_DISCONNECT
      Caption         =   "Disconnect"
      Height          =   255
      Left            =   120
      TabIndex        =   8
      Top             =   2760
      Width           =   975
   End
...
End

見ると、*.rc と同じように「構造化」がされていますね。それもそのはずで、VC++ の *.rc は VB のフォームリソースを真似して(憧れて)作られているので当然似ていて当然なのです。
普段、Visual Basic 6.0 をデザイナで編集している場合は気が付かないのですが、これも大量のフォームを作るときには直接エディタで編集していたのです。

と、いうところで 30 分経ったのでおしまい。この先は、XLib や Motif, toolkit が続くわけですが、またの機会に。
あと、イベントに関しては、VB6、VC++, C# と発想がかなり違っているのがミソです。このあたりも別の機会に。

カテゴリー: 開発, C++ | 15年前のMFCあるいはMVC はコメントを受け付けていません

Community Open Day 2012 の補足その4

Community Open Day 2012 の補足その1 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3504
Community Open Day 2012 の補足その2 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3516
Community Open Day 2012 の補足その3 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3526

そんな訳で、デザイン編の続きです。

metro アプリを store に出店させるためには、スナップや回転に対応しないといけない…ってことになっていますが、これが結構面倒、ということを実演します。まぁ、grid を使ってタイリングで配置をさせた場合は、ざっくりと作ればよいのですが、今回のメモ帳のように背景に bitmap が貼りつけてある場合は、大変…ってことなのです。

■スナップや回転は、SizeChanged で検出

iPhone/iPad の場合は、didRotateFromInterfaceOrientation:イベントで回転が検出できるのですが、Windows 8 の場合は、SizeChanged イベントを使います。回転専用のイベントがあってもいい気がするのですが、このイベントは回転だけではなくて、スナップされたときや、スナップから元の全画面に戻ったときも発生する(と思われる)イベントなのです。また、デスクトップPCの場合は、解像度を変更した場合(ディスプレイの接続を変えた場合)にも発生すると考えられます。
なので、サイズイベントなんでしょうねぇ。実はこれが厄介なのですが。

private void Page_Loaded_1(object sender, RoutedEventArgs e)
{
    Window.Current.SizeChanged += Current_SizeChanged;
}

こんな風に SizeChanged イベントを登録しておいて、

private void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
    var state = Windows.UI.ViewManagement.ApplicationView.Value;
    VisualStateManager.GoToState(this, state.ToString(), false);
}

VisualStateManager の GoToState メソッドを使います。GoToState メソッドは、XAML の VisualStateManager.VisualStateGroups タグのほうに通じています。VisualState タグの Name 属性にマッチするところを探し当てるわけです。
なので、受け渡しする文字列は何でもいいのですが、ApplicationView.Value の値をそのまま文字列に渡してしまうのが楽そうです。

■具体的に回転に対応したレイアウトは?

メモ帳アプリのように、横置き(FullScreenLandscape)と縦置き(FullScreenPortrait)のレイアウトが大きく異なる場合は、結構大変です。
今回の対応を下に出しておくとこれだけあります。

        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState x:Name="FullScreenLandscape"/>
                <VisualState x:Name="FullScreenPortrait">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Width" Storyboard.TargetName="imageBack">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="768"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Height" Storyboard.TargetName="imageBack">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="1024"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Source" Storyboard.TargetName="imageBack">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Images/IMG_0049.PNG"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="listView1">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="imageMask">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Width" Storyboard.TargetName="textBox1">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="658"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Height" Storyboard.TargetName="textBox1">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="856"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" Storyboard.TargetName="textBox1">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="94,236,0,0"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" Storyboard.TargetName="buttonNew">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="738,152,0,0"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" Storyboard.TargetName="buttonPrev">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="254,1105,0,0"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" Storyboard.TargetName="buttonNext">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="500,1105,0,0"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" Storyboard.TargetName="buttonTrush">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="416,1105,0,0"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" Storyboard.TargetName="buttonContract">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="336,1105,0,0"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

これは、Visual Studio 2012 で提供されているテンプレートをそのまま真似しているので、この方法がベスト…ではないと思いたいのですが、通常の横置きで配置されているコントロールを、ちまちまと Storyboard を使って位置変えをしていきます。Storyboard は、通常はアニメーションをさせるためのタグなのですが、DiscreteObjectKeyFrame KeyTime=”0″ というアニメーション間隔を「0」にするという使い方をすると、位置を変えることと同じことになるのですよ。
なので、縦置き(FullScreenLandscape)で表示されていたコントロールを、横置き(FullScreenPortrait)で座標を指定するという技になります。

が、これは「技」にしか過ぎないので、本来ならばデザイナで表示させたいところですよね。ですが、Visual Studio 2012 RC 版では、この ViewState の部分が「<Base>」しか指定できなくて、手作業で横置きのデザインを作らないと駄目なのです。ここは、製品版で改善されて欲しいところです。この位のコントロールの数ならばよいのですが、沢山ある場合は、デザイナの手に負えないという感じがします。

2012/06/25 追記

もう一度確認したところ、<VisualStateGroup x:Name=”ApplicationViewStates”> のように、ApplicationViewStates を入れると、下記のようにそれぞれの画面を選択できます。

左上のビューのアイコンをクリックするとデザインを切り替えられますね。KeyTime=”0″ のあたりを、デザイナで指定できるかどうかは不明なのですが、これでデザインを確認しながら配置ができます。この話はまた書きます。

■回転アニメーションがない?

あと、これがデザイン的に致命的だと思うのですが、iPad や Android にあるような回転アニメーションがありません。スレートPCを回転させると、パタパタと画面が切り替わるのです。
自前で、回転アニメーションを作ろうと思ってはみたのですが、回転イベント自体が「SizeChanged」という、「回転が終わったときに発生する」イベントなので、metro アプリの回転描画が始まる前ではないのですよ。妙なレイアウトで回転してしまったあとに、SizeChanged イベントが発生するために手が付けようがありませんでした。

このあたりは、背景に bitmap を貼りつけているからだと思います。grid を利用したタイリングの場合には、あまりこのパタパタが気にならないと思います。
そうそう、Windows Phone 7 の場合はどうなんでしょうか?Android も回転アニメーションがあるぐらいなので、あるとは思うのですが実物で試したことがないもので…、この度、.NET ラボあたりで試して貰おう。

ひとまず、Windows Phone 7 の場合は、iPhone 並みに滑らかに回転するそうです。そうなると、Windows 8 metro アプリの場合も綺麗に回転される、というのが想像できそうですね。いまのところ、Acer w500 に windows 8 rp を入れた段階ではバタバタとするので「残念」な気がしていたのですが、製品版にちょっと期待。

カテゴリー: C#, windows 8 | Community Open Day 2012 の補足その4 はコメントを受け付けていません

Community Open Day 2012 の補足その3

Community Open Day 2012 の補足その1 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3504
Community Open Day 2012 の補足その2 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3516

の続き、デザイン編です。

COD のために作成したメモ帳アプリですが、スレートPC上で「回転」を実装したかったので、画面のサイズは acer w500 に合わせていたのです。
通常 Visual Studio 2012 で metro アプリを作ると「1366×768」のサイズがデフォルトなのですが、acer の場合は「1280×800」な訳です。

grid タグを使ってタイリングで配置をする場合には、画面サイズには影響されないのですが(デスクトップを含めると、解像度自体にmetroアプリが影響されては困るのですが)、ゲームアプリの場合や、固定サイズを対象にした業務アプリの場合は、このあたり対象機種の解像度にぴっちりと合わせる方が操作的にはよいのです…ということを想定して、acer の「1280×800」にあわせます。

■デザイナのサイズを変更する

デザイナを表示しているときに、「デバイス」ウィンドウを表示させると「表示」のところで切り替えができます。

ここで「1280×800」を選択するわけです。

View のところで縦置き、横置きが設定できるので、これを使って VisualState を設定できるのか、と思いきや現状の Visual Studio 2012 rc 版ではうまく動きません…つーか、多分、製品版でも駄目なような雰囲気が。

まぁ、とりあえず、これで「1280×800」サイズの横置きのデザインはできるのです。
さて、「縦置き」のほうはどうするのかというと…実は、結構面倒なのです、WDD の話でもそうだったのですが、結構ややこしい、と言いますか妙な作り方になります。この話は後日。

■シミュレータのサイズを変える

あわせて、シミュレータの解像度も変える必要があります。デフォルトでは「1366×768」となっているので、これを「1280×800」にあわせます。

当然、標準の「1366×768」の場合は、こんな風にレイアウトが崩れてしまいます。

■Blend で開くと標準解像度以外の場合はレイアウトが崩れる?

実は、Blend でプロジェクトを開くと、標準解像度が「1366×768」となっているので、Visual Studio と同じようにデバイスウィンドウで解像度を変更しておきます。

で、これでデザインが完了と思いきや、「回転」を含めると(デスクトップもスナップもそうなんですが)、色々やらないといけないですよね。
という訳で、この話はまだ続く。

カテゴリー: C#, windows 8 | Community Open Day 2012 の補足その3 はコメントを受け付けていません

Community Open Day 2012 の補足その2

Community Open Day 2012 の補足その1 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3504

の続き。

サンプルコード
win8/MetroMemoPadiPadLike at master ・ moonmile/win8 ・ GitHub
https://github.com/moonmile/win8/tree/master/MetroMemoPadiPadLike

を実行しようとすると分かるのですが、サーバーを実行する時にエラーがでます。これは、サーバーとして使っている HttpListener クラスを実行する権限…というか、サーバーのポートを開ける権限は Windows 8 では制限されているからなのです。Windows 7 でも制限されているので、IIS ではなくて、HttpListener を使う時には覚えておくとい良いテクニックです(って定番か?)。

■サーバーのポートを許可する

手っ取り早いのは、HttpListener クラスを使っているログサーバーアプリを「管理者権限」で立ち上げてしまうことです。実験をしているときはこれでよいのですが、ログサーバーアプリからファイルに書き込みをすると、Administrators になってしまうのでちょっと問題があります(一般ユーザーでファイルを消せなくなってしまうので)。

普通に起動した場合には、以下のようなエラーが出てきます。

という訳で、ポートの設定は、「netsh http add urlacl url=http://*:8083/metro/ user=masuda」のように予め空けておくという訳です。

■metroアプリのマニフェストの変更(機能の追加)

もうひとつ、ネットワークを使うときには設定が必要です。今回のメモ帳アプリからログサーバーにメッセージを飛ばす場合に、インターネットを使う場合には通常設定されている「インターネット(クライアント)」で十分なのですが、ローカルネットワーク(イントラネットワーク)を使う場合には、「自宅または勤務先のネットワーキング」というものにチェックをいれます。
いわゆる、「win8rp-pc」のようなローカルだけで通用するホスト名を相手にする場合は、別な設定が必要という訳です。ネットワークセキュリティにあわせてというところでしょう。

■Windows 8 上で、ログサーバーとメモ帳アプリを同時に動かす場合

更に云うと、Windows 8 上の metro アプリから、同じコンピュータにあるログサーバーにアクセスをする場合には、もうひとつ設定が要るのです。metro アプリを Visual Studio 2012 上でデバッグモードで動かしている場合は大丈夫なのですが、普通に metro アプリをインストールして動かそうとするとログサーバーにメッセージが飛ばないという現象です。

metro-desktop 間のループバックには CheckNetIsolation コマンドで設定する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3414

Windows 8 ではデフォルトで「ループバック」が不許可になっています。なので、metro アプリから同マシンで動いている desktop アプリ(この場合はログサーバー)にアクセスする場合には、特殊な設定が必要なのですね。

CheckNetIsolation.exe LoopbackExempt -a -n= パッケージファミリ名指定でループバックを許可

という形でパッケージファミリ名を使ってループバックを許可します。
パッケージファミリ名は、マニフェストの「パッケージ化」→「パッケージファミリ名」で取得できます。

ちなみに、パッケージ名は変更できるんですが、パッケージファミリ名は変更できません。Visual Studio 2012 が自動的に付加します。まぁ、どこかの設定ファイルに書かれていると思うのですが、ちょっと分かりませんね。パッケージ名を変更してもパッケージファミリ名は変更されないので、まあ、そういう仕様なのかと。

カテゴリー: C#, windows 8 | Community Open Day 2012 の補足その2 はコメントを受け付けていません

Community Open Day 2012 の補足その1

Community Open Day 2012
http://cod.ms/
Community Open Day 2012 の準備 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3476

なところで、発表したものの、事前準備が足りない…つーか、コード自体を見せるの忘れていたので補足しておきます。
スライド自体は、

Cod2012 デバッグ講座
http://www.slideshare.net/moonmile/cod2012

なところを参照してください。概要しか書いて無くて、実際は、スレートpc へのリモートデバッグが主なところです。
開発マシンからスレートPCへリモート接続する場合は、Visual Studio から接続すればよいのですが、テストをしていくといくつか問題があったのです。

  • Visual Studio からデバッグ実行していると、サスペンドしない。
  • スレートPCへの接続は「無線LAN」なので、スピード自体に問題がでそう。

多分、サスペンドしないのはデバッグモードで動いているために、権限が desktop アプリ側にあるからだと思います。デバッグ時にサスペンドして貰っても困るし、というのもありますが。
が、実機で動かす場合、サスペンドする時、サスペンドから復帰する時、というのが metro アプリでは結構重要で、このタイミングを実機でみるのは重要かなと思ったわけです。

で、通常はローカルファイルに書き込むわけですが、metro アプリの場合は desktop 側への制限が厳しいために、

  • マイドキュメントなどのファイルアクセスに、マニフェストを変えないといけない。
  • metro アプリ内のサンドボックスに書き込むとアンインストールした時にログファイルも一緒に消えてしまう。

という欠点があります。なので、ひとつの手段として、metro アプリから HttpClient を使って開発機にログを飛ばそうという試みです。最終的には、log4net のように使えるといいんでしょうねえ。

デモで使ったサンプルコード自体は、

win8/MetroMemoPadiPadLike at master ・ moonmile/win8 ・ GitHub
https://github.com/moonmile/win8/tree/master/MetroMemoPadiPadLike

に一式があります。

■ metro アプリからログ出力

App.xaml.cs の中に次のようなログ関数を作っておきます。単純に「win8rp-pc.local」というマシンに HttpClinet を飛ばしているだけです。
本来ならば、きちんと Web REST 形式にすれば良いのですが、まあサンプルということで。
ログ自体は、WCF サービスにするときっちりと作れるのでしょうが、WCF だといちいちサービスを立ち上げないといけないので、結構面倒です。
なので、適当に GET コマンドで送れるパターンにしておきます。

        public async static void DebugLog(string text)
        {
            string url = "http://win8rp-pc.local:8083/metro/method";
            HttpClient client = new HttpClient();
            try
            {
                HttpResponseMessage res = await client.GetAsync(url + "/" + text);
            }
            catch
            {
            }
            // string response = await res.Content.ReadAsStringAsync();
        }

サーバーに接続できない場合もあるので、try-catch は必須ですね。また、このパターンだといちいち new HttpClient をしているので、本当は別に privete 変数で保持したほうがいいと思います。

        private void OnSuspending(object sender, SuspendingEventArgs e)
        {
            App.DebugLog("OnSuspending");
            var deferral = e.SuspendingOperation.GetDeferral();
            //TODO: Save application state and stop any background activity
            deferral.Complete();
        }

metro アプリ自体のサスペンドは、App.OnSuspending になるので、ここに仕込みます。

■回転時にログを出力する

スレートPCを回転させたときのログ出力は、MainPage.xml.cs の Current_SizeChanged メソッドになります。
このイベントは、あらかじめ Window.Current.SizeChanged += Current_SizeChanged; のように回転イベントに仕込んでおきます。

        private void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
        {
            var state = Windows.UI.ViewManagement.ApplicationView.Value;
            VisualStateManager.GoToState(this, state.ToString(), false);
            App.DebugLog("Current_SizeChanged " + state.ToString());
        }

こうすると、実機を回転した時にログが出力されます。

■簡易ログサーバー

metro アプリからログ受信をするためのサーバーを作ります。
HttpListener クラスという目的そのままのクラスがあるので、これを利用します。これは non-blocking で動くので、background スレッドを作らなくてよいので、非常に便利です。

namespace MetroLogServer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        HttpListener listener;

        private void button1_Click(object sender, EventArgs e)
        {
            string url = "http://*:8083/metro/";
            // 開始
            listener = new HttpListener();
            listener.Prefixes.Add(url);

            listener.Start();
            listBox1.Items.Add("サーバー開始");
            listener.BeginGetContext(ListenerCallback, listener);
        }

        public void ListenerCallback(IAsyncResult result)
        {
            // HttpListener listener = (HttpListener)result.AsyncState;
            HttpListenerContext context;
            try
            {
                context = listener.EndGetContext(result);
            }
            catch
            {
                // stop メソッドで例外が発生するので、対処
                return;
            }
            // var content = listener.GetContext();
            var req = context.Request;
            var url = req.RawUrl;
            var res = context.Response;

            string text = url.Replace("/metro/method/", "");
            text = System.Web.HttpUtility.UrlDecode(text);
            listBox1.Items.Add("受信:" +  text);

            var output = new StreamWriter(res.OutputStream);
            output.WriteLine(string.Format("called {0}", url));
            output.Close();

            // 次の受信の準備
            listener.BeginGetContext(ListenerCallback, listener);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            // 終了
            listener.Stop();
            listBox1.Items.Add("サーバー終了");
        }
    }
}

本当は、これを説明しないと駄目だったんですけどね…なんか、いきおいデモのほうに進んでしまって、前提のコードの紹介が抜けてしまいました。時間配分は10分ごとに確認していたのですが、回転時の動作とかリモート接続とかの解説に力を入れ過ぎてしまったという感じです。

こんな風にシミュレータ上と開発機で通信もできます。

という訳で、もうちょっと cod2012 の話が続く。

カテゴリー: C#, windows 8 | 1件のコメント

Visual Studio Experss 2012 RC for Windows 8 を試してみる

msdn を持っている(というか、MVPなので借りている状態)ので、正式版をリリース後でも Visual Studio 2012 Professional を使えるので特に問題はないのですが、一応、Express Edition のほうも試しておきましょうということで。

ダウンロード | Microsoft Visual Studio 2012
http://www.microsoft.com/visualstudio/11/ja-jp/downloads#express

なところで、無償版である Experss Edition がダウンロードできます。真ん中の「Express for Windows 8」をクリックしてダウンロードページに進みます。
ちなみに、「Express for Web」のほうは、ASP.NET MVC のような Web アプリケーション開発のためだけの環境です。「Express for Windows 8」のほうは、metro アプリケーションの開発環境ですね。どちらも無償で使える Visual Studio の環境です。ええと、注意しないといけないのは、Windows 8 の上でしか動かないのですよね。いままでの Windows 7 では動かないので、別途 Windows 8 のコンピュータを用意することになります。あるいは、仮想環境(VMWareなど)を使うとか。

ちなみに、従来型の desktop の開発環境は有償版である Visual Studio 2012 Professional でしか使えません。7万円程度と結構高価なので趣味に使うにはちょっとと思う場合には、Visual Studio 2010 Express のほうを使ってみてください。

Microsoft Visual Studio Express
http://www.microsoft.com/japan/msdn/vstudio/express/

.NET Framework のバージョンは 4.0 ですが、基本的なところは十分使えます。Visual Studio 2012 は 4.5 対応なので、その差分に気を付けないといけませんが、まぁ、趣味のツールを作る分には十分かと。

御馴染みの「新しいプロジェクト」を開くと、見事に「Windows Metro style」しか作れません。metro アプリを作るとなると、Windows Store に登録しなくちゃ駄目、という先入観がありますが(実際、microsoft社はそういう薦め方なのですが)、個人で使う分に「開発ライセンス」というものを取得すれば、別に Windows Store にアップしなくても使えます。
この開発ライセンスは

  • windows live id を取得して
  • 1ヶ月毎に開発環境で更新
  • たぶん、1ヶ月毎に実行環境で更新

すれば、ローカルコンピュータで使えます。普段使いのコンピュータであれば、1か月ごとの更新ぐらいならば、あまり手間ではないでしょう。
ただし、友達にアプリを配布ってことになると、ちょっと面倒ですよね。

さて、面白いことに Express Edition の場合には、Professional 版ではなかった「ストア」というメニューがあります。これは、有償版(いまではテスト的に無償でダウンロードできますが)の Visual Studio 2012 には無かったメニューです。

開発ライセンスの取得は、「ストア」→「開発ライセンスの取得」でできます。Windows Live ID は無料なので、無い場合は適当に取ってください。



良く見ると有効期限が「2012/07/07」になっていますね。今日が「2012/06/07」なので、丁度1ヶ月の有効期限となっているのです。1か月後にどうなるかというと、単に最初の開発ライセンスを要求するダイアログが出るだけです。再び、Windows live Id を入力して登録すれば ok です。「期限切れになる前に~」と書かれていますが、期限が切れてしまった場合でも最初に起動すると「開発ライセンス」を要求されるので大丈夫です。ただ、その時にネットワークに繋がっていないと、(多分)ライセンスの更新ができなくてアプリを起動できないってことになるので、ノートブックにインストールしている場合は早めに更新しておくのが吉かと。

開発ライセンスを取得した後に「ストア」→「開発者アカウントを開く」を選択すると、デベロッパーセンターに接続します。


Windows Store 用の登録者コードを貰って、年間4,900円払えば公開できますよ、ってな感じです。現在登録者コードは、microsoft さんの dev camp に参加する(だったっけ?)ことになっています。まあ、個人の場合は正式公開を待ってもよいかなと。

Visual Studio 2012 Pro で作った metro アプリを Experss Edition で開くと、きちんと開けます。ざっと見たところ、metro アプリ開発に関しては有償版の Visual Studio と変わらないようです。多分、機能制限もないでしょう。

最終的には windows store を利用するんでしょうが、ツールを使ったり単純に無償で配布する場合は、ソースコードを github などで公開するか、パッケージを作る良いです。アプリパッケージについては後日。

追記 2012/06/09

とか思ったらこんなものが、desktop 版も出すような記述が。

Visual Studio Express 2012 for Windows Desktop – The Visual Studio Blog – Site Home – MSDN Blogs
http://blogs.msdn.com/b/visualstudio/archive/2012/06/08/visual-studio-express-2012-for-windows-desktop.aspx

 

カテゴリー: 開発, windows 8 | 1件のコメント

[win8] SurfaceImageSource を使って、metro アプリに部分的に DirectX 描画する

[win8] 画像加工をDirectXに任せて、UIはC#にする技 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3454

なところで、metro アプリで DirectX を使おうとしていたのですが、もっと良い方法がありました。既に、SurfaceImageSource ということで DirectX を使えるように用意がされているのですね。

わんくま大阪#49の資料
http://jyurimaru.info/data/20120602wankuma_osaka49/20120602_sao.pdf
SurfaceImageSource
http://blogs.msdn.com/b/windowsappdev_ja/archive/2012/03/22/xaml-directx.aspx
DirectX and XAML interop (Metro style apps using C++ and DirectX) (Preliminary)
http://64.4.10.18/ja-jp/library/hh825871.aspx

なところに、SurfaceImageSource を使って、DirectX で描画できます。と書いてあるのです。

SurfaceImageSource^ surfaceImageSource =
    ref new SurfaceImageSource(rectangle1->Width, rectangle1->Height, true);
ImageBrush^ brush = ref new ImageBrush();
brush->ImageSource = surfaceImageSource;
rectangle1->Fill = brush;

ImageBrush オブジェクトに設定して、XAML の UI コントロール(ここでは、Rectangle コントロール)の Fill プロパティに設定しています。簡単に言えば、背景で使う bitmap 型のブラシを作って rectangle コントロールに設定している訳です。bitmap オブジェクトへの描画を DirectX でやるということです。リアルタイムに描画、とは違うのですが、まぁ普通の画像を描画するだけならばこれで十分です。コントロール同士の重なりや、透明度も指定できるので相当汎用的に使えます。

が…問題はですね、この surfaceImageSource ってのにどうやって描画するんですか?というのが、なかなかぶち当たりません。つーか、「省略」してるだろッ!!!(意図的か無意識的かわかりませんが)ってな具合です。DirectX に詳しければ ok なんだけど、詳しくないと途端に分からんという(私を含む)有様 orz

■どうやって、SurfaceImageSource に描画するのか?

結論から言うと、

Direct2D Quickstart for Windows 8 Release Preview
http://msdn.microsoft.com/en-us/library/windows/desktop/hh780340(v=vs.85).aspx

あたりに書いてありました。直接ではないのですが、「Step 3: Create an ID2D1Device and an ID2D1DeviceContext」の「ID2D1DeviceContext::CreateBitmapFromDxgiSurface」の記述が肝です。
SurfaceImageSource オブジェクトを、描画コンテキスト(ID2D1DeviceContext)に結びつけるために、CreateBitmapFromDxgiSurface メソッドを使う訳ですね。なるほど。知らないと分かりませんがな orz

結びつける先は、bitmap でなくても良いらしくテクスチャを使っているサンプルもあるのですが、ComPtr<ID2D1Bitmap1> を使うのが一番楽っぽいので、こんな風に書きます。
Native の surface で、BeginDraw/EndDraw を使うのですが、その間にコンテキストの方の BeginDraw/EndDraw を突っ込めば ok … というか、このコンテキストを使った描画部分は別に、surface の描画の中にある必要はなくって、bitmap に書き込んであるので別のところで描画しても大丈夫です。そのあたりは後程。

	ComPtr<IDXGISurface> surface;
	RECT updateRect = { 0,0,400,300 };
	POINT offset = { 0,0 };

	HRESULT beginDrawHR = m_sisNative->BeginDraw(updateRect, &surface, &offset);
	// draw to IDXGISurface (the surface paramater)

	ComPtr<ID2D1Bitmap1> bitmap;
	m_d2dContext->CreateBitmapFromDxgiSurface( surface.Get(), NULL, &bitmap );
	m_d2dContext->SetTarget( bitmap.Get());

	m_d2dContext->BeginDraw();
	m_d2dContext->Clear(D2D1::ColorF(D2D1::ColorF::Green));
	
	Platform::String^ text = "日本語";
    IDWriteTextFormat *pTextFormat = NULL;
    ID2D1SolidColorBrush *pBlackBrush = NULL;

		// static const WCHAR sc_fontName[] = L"Calibri";
		static const WCHAR sc_fontName[] = L"HGP明朝E";
        static const FLOAT sc_fontSize = 32;

        m_dwriteFactory->CreateTextFormat(
            sc_fontName,
            NULL,
            DWRITE_FONT_WEIGHT_NORMAL,
            DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL,
            sc_fontSize,
            L"", //locale
            &pTextFormat
            );

        m_d2dContext->CreateSolidColorBrush(
            D2D1::ColorF(D2D1::ColorF::Black),
            &pBlackBrush
            );

        m_d2dContext->DrawText(
			text->Data(),
			text->Length(),
            pTextFormat,
            D2D1::RectF(0, 0, 400,300),
            pBlackBrush);
	
	m_d2dContext->EndDraw();

	m_sisNative->EndDraw();
	// The SurfaceImageSource object's underlying ISurfaceImageSourceNative object contains the completed bitmap.
	ImageBrush^ brush = ref new ImageBrush();
	brush->ImageSource = surfaceImageSource;

	// fill;
	rect1->Fill = brush;

これだと、再描画するたびに bitmap を作成するので処理の無駄ですよね。あらかじめ別のところで bitmap を作成しておくのがベターです。

実行すると、こんな風に扱えます。きちんと透明度(Opacity)も実行できているし、コントロールのz-index(描画順序)も有効なので、かなり使えます。

さて、グラフ描画のような静的な画像はこれでいいのですが、少しアニメーションをしようとするとどうなるのか?が問題ですよね。このあたりは、後で調べることにしましょう。
定期的に ImageBrush の中身を変えて、再描画をすれば変わるような気もするけど…どうなのかな?

カテゴリー: C++/CX, windows 8 | 2件のコメント

Community Open Day 2012 の準備

Community Open Day 2012
http://cod.ms/
東京会場
http://cod.ms/Pages/place_tokyoa.aspx

6/9(土)の東京会場の13:00から「Visual Studio 11 beta とスレートPCを使ったデバッグ講座」ということで発表をするのですが、そのアプリの準備。
基本は「スレートPCにインストールした windows 8 に metro アプリをインストールして、リモートデバッグをするよ」という話です。

将来的に顧客が「スレートPC」を使ったときには、開発環境を入れないので、テスト用環境として機器を1台購入すると考えられます。普通のPCでも最終的にはインストール対象マシンを用意しますよね。この場合、通常の PC と異なる点は、

  • Visual Studio からのリモートデバッグはどうやるのか?
  • デバッグ出力は何処に出すのか?
  • スレートPC特有のテスト/デバッグ項目は何か?

ってところです。そのあたりを、

  • acer w500 にインストールした windows 8 consumer preview 版
  • ノートPC のvmare 上にインストールした windows 8 + visual studio 11 beta

で実演するというお話になります。ノートPCに vmware を入れるのは、ベースが windows 7 なので将来的にはノートPCなりデスクトップマシンに windows 8 + visual studio 11 という組み合わせになると思います。ノートPCに入れておくと、こういった実演が楽というのがありますが、ちょっとパワー不足なんですよね。

そこで今週はデモアプリ作り、来週にはざっとプレゼン作りをするわけですが、例によってプレゼン資料はチープなものになりそうです。毎度、.NET ラボ勉強会でのは発表ではチープすぎて、場合によってはテキストファイルだったりする訳で、そのあたりはご容赦。

ご覧の通り、ネタアプリはこんな感じ。iPad のメモ帳を丸ごと画面キャプチャして使っています(笑)。

スレートPC 特有の回転にも対応しています

ってことで、デバッグ実演用にもう少し機能を追加しないと駄目なのですが、ひとまずネタとしては面白いかと。
ええ、このままだ意匠関係で windows store にアップできませんよね~。どうせならば、少し意匠をクリアさせて完成したものをアップしてみたいと思ったり、どうなんでしょうねぇ。

カテゴリー: C#, windows 8 | Community Open Day 2012 の準備 はコメントを受け付けていません

ボタンの hover 切り替えのための画像を作成するツール

チープなツールシリーズ第1弾ッ!!! ということで(多分、第1弾でおしまい。単なるアクセス解析のためのブログ更新だし)。

マウスをホバーさせる時の画像の作り方/スクリプトの書き方は色々あるわけですが、CSS を使って、

hoverでリンク画像を切り替える – スタイルシートTIPS ふぁくとりー
http://www.nishishi.com/css/link-image-hoverchange.html

な方法が使えます。

.menu#menu1 a {
	display: block;
	width: 160px;
	height: 60px;
	text-indent: -4000px;
	background-image: url(./image/menu1.png);
}
.menu a:hover {
	text-decoration: none;
	background-position: top right;
}

昔は JavaScript で切り替えたのですが、CSS だとこれが便利かなと。ホバー前の画像とホバー時の画像がひとつにまとまっているのでロード時に読み込まれるのがいいのです。ホバー時に読み込むと、読み込みが遅かったりしてなかなか画像がでなかったりするので。
で、このホバー用の画像、photoshop とかを持っていると作るのは簡単なんでしょうが(多分、Blend でも良いかと)、適当な画像ツールがない私としてはひと苦労で、大抵は Excel のワードアートを使っているという始末。

これを横に並べるのが面倒…なので、それ専用のツールを作りました。

構想はお昼の散歩時に15分ぐらいで、製作は30分ぐらい。前後合わせると1時間ちょっとというところでしょうか。この手のツールは、画像加工をするときのイライラ度合を考えると、手元で作っていたほうが精神的によいのです。
で、ソースの全文はこんな感じ。約100行位。

namespace MakeMenuImage
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private Bitmap bmpNormal;
        private Bitmap bmpHover;
        private Bitmap bmpMenu;

        private void Form1_Load(object sender, EventArgs e)
        {
			Properties.Settings.Default.Reload();
			textLeft.Text = Properties.Settings.Default.ImageLeft.ToString();
			textTop.Text = Properties.Settings.Default.ImageTop.ToString();
			textWidth.Text = Properties.Settings.Default.ImageWidth.ToString();
			textHeight.Text = Properties.Settings.Default.ImageHeight.ToString();
        }

		private void Form1_FormClosed(object sender, FormClosedEventArgs e)
		{
			Properties.Settings.Default.ImageLeft = int.Parse(textLeft.Text);
			Properties.Settings.Default.ImageTop = int.Parse(textTop.Text);
			Properties.Settings.Default.ImageWidth = int.Parse(textWidth.Text);
			Properties.Settings.Default.ImageHeight = int.Parse(textHeight.Text);
			Properties.Settings.Default.Save();
		}

		private void btnNormal_Click(object sender, EventArgs e)
        {
            bmpNormal = new Bitmap(Clipboard.GetImage());
            picNormal.Image = bmpNormal;
        }

        private void btnHover_Click(object sender, EventArgs e)
        {
            bmpHover = new Bitmap(Clipboard.GetImage());
            picHover.Image = bmpHover;
        }

        private void btnToClip_Click(object sender, EventArgs e)
        {
            Rectangle rect = new Rectangle(
                int.Parse(textLeft.Text),
                int.Parse(textTop.Text),
                int.Parse(textWidth.Text),
                int.Parse(textHeight.Text));

            bmpMenu = new Bitmap(rect.Width*2, rect.Height);
            Graphics g = Graphics.FromImage(bmpMenu);
            g.FillRectangle(Brushes.White, new Rectangle(0, 0, rect.Width * 2, rect.Height));
            g.DrawImage(bmpNormal,
                new Rectangle(0, 0, rect.Width, rect.Height),
                rect,
                GraphicsUnit.Pixel);

            g.DrawImage(bmpHover,
                new Rectangle(rect.Width,0, rect.Width, rect.Height),
                rect,
                GraphicsUnit.Pixel);
			Clipboard.SetImage(bmpMenu);

            picMenu_MouseLeave(sender, e);
        }

        private void picMenu_MouseEnter(object sender, EventArgs e)
        {
            if (bmpMenu == null) return;
            Rectangle rect = new Rectangle(
                int.Parse(textLeft.Text),
                int.Parse(textTop.Text),
                int.Parse(textWidth.Text),
                int.Parse(textHeight.Text));
            Bitmap bmp = new Bitmap(rect.Width, rect.Height);
            Graphics g = Graphics.FromImage(bmp);
            g.DrawImage(bmpMenu,
                new Rectangle(0, 0, rect.Width, rect.Height),
                new Rectangle(rect.Width, 0, rect.Width, rect.Height),
                GraphicsUnit.Pixel);
            picMenu.Image = bmp;
        }

        private void picMenu_MouseLeave(object sender, EventArgs e)
        {
            if (bmpMenu == null) return;
            Rectangle rect = new Rectangle(
                int.Parse(textLeft.Text),
                int.Parse(textTop.Text),
                int.Parse(textWidth.Text),
                int.Parse(textHeight.Text));
            Bitmap bmp = new Bitmap(rect.Width, rect.Height);
            Graphics g = Graphics.FromImage(bmp);
            g.DrawImage(bmpMenu,
                new Rectangle(0, 0, rect.Width, rect.Height),
                new Rectangle(0, 0, rect.Width, rect.Height),
                GraphicsUnit.Pixel);
			picMenu.Image = bmp;
        }
    }
}

本来ならば、画像ファイルの保存や、Excel との連携、ドラッグアンドドロップなどを考えたりするのですが、そんなのは面倒なのでクリップボード経由で Clipboard.GetImage() で画像を取り込んでいきます。保存先もクリップボードなので、IrfanView を使うかペイントに貼りつけます。

MouseEnter/MouseLeave イベントのところは、試しにマウスをhoverさせて確認できるようにしてあるところですね。このところ、hover されるたびに new Bitmap をしているので、hover するたびにメモリががんがん増えるという…苦笑レベルなのですが、まあツールなので良しとしましょう。本来ならば、Bitmap オブジェクトを使い廻すようにすればよいのです。

さて、これを作っているときに思ったのですが、この操作は metro アプリでは苦手な部類になります。
手順を簡単に書いてみると、

  1. Excel で通常画像とホバー画像の二種類の画像を作成する。
  2. Excel で通常画像を選択して、クリップボードへコピー
  3. 画像加工ツールを起動して、「Set Normal」ボタンをクリックしてペースト
  4. Excel に戻って、ホバー画像を選択して、クリップボードへコピー
  5. 画像ツールに戻って、「Set Hover」ボタンをクリックしてペースト
  6. 「ToClip」ボタンを押して、加工画像をクリップボードへ
  7. ペイントに、クリップボードがから画像をペースト
  8. ペイントで、ファイル名を付けて保存する。

という具合。ユースケースから見ると、Excel、画像ツール、ペイントを行き来します。
metro アプリの場合、新しいアプリを起動させてしまうと元のアプリに戻るのが結構面倒です。左からタスクを出して元のアプリを選択して、って具合になります。デスクトップアプリだと複数の Window を並べておいてクリックします。あるいは、Ctrl+Tab で移動します。まぁ Ctrl+Tab 自体は metro でも有効なのですが、「Window を並べておいて、mouse でクリックする」という動作はできないわけです。

この不便さは、実は iPhone/iPad にもあって、twitter アプリでブラウザを起動すると元に戻ることが面倒なので、アプリ内ブラウザで凌いでいます。本来ならばメールアプリやその他のツールと行き来できたほうがいいのですが、以前はクリップボードが無かった時代があったり、シングルタスクであったりしたものですから、このあたりの連携は微妙なところがあります。

windows 8 の metro app の場合は「プロトコル」ということで「mailto://」みたいなのが自由に作成できます。ただし、このプロトコルや通知は、事前にデータ形式を決めておかないと駄目という微妙なところがあって、なんか妙なところでハマりそうなんですよねぇ。どうなんですかね?

クリップボードの場合は、複数の形式のデータを同時に置くことができるので、例えば Excel の場合は、

  • テキストデータ
  • HTML形式のデータ
  • リッチテキスト形式のデータ
  • 画像データ
  • Excel 独自の書式付きのデータ

などが、いちどにクリップボードに送られます。で、クリップボードからデータを取り出す側で、適当な形式のデータのみ取り出せるという方式になっているのです。metro app の通知の場合には、どうなんでしょう。共有コントラクトあたりを使うか、それとも通知だけプロトコルを使って後はクリップボード経由とか?

「コントラクト」でMetroスタイル・アプリのサンドボックスを乗り越える! - @IT
http://www.atmarkit.co.jp/fdotnet/chushin/readyforwin8app_02/readyforwin8app_02_02.html

カテゴリー: ツール | ボタンの hover 切り替えのための画像を作成するツール はコメントを受け付けていません