MS-C++ では __declspec(property()) でプロパティを作れるよ

__declspec(property())
http://blogs.wankuma.com/melt/archive/2007/03/12/66223.aspx
プロパティ (C++)
http://msdn.microsoft.com/ja-jp/library/yhfk0thd(v=vs.110).aspx

VC++2005 の頃からあったのですが、MS-C++の場合はプロパティを作れたのですね…なるほど。
__declspec(property())な構文を作れば、VB/C# のようにプロパティでアクセスできるという便利さ。これだと、Model を作る時に良さそうってことで、早速 PROPERTY マクロを作成。

#include "stdafx.h"
#include <afxwin.h>
#include <iostream>
#include <string>

class Model
{
private:
	int _value;
public:
	__declspec(property(get=GetValue, put=SetValue)) int Value;
public:
	int GetValue() { return _value; }
	void SetValue( int value ) { _value = value; }

// マクロで定義する
#define PROPERTY( _t, _m )	\
private: _t m_##_m ;		\
public:						\
	__declspec(property(get=get##_m, put=set##_m)) _t _m ;	\
	_t get##_m() { return m_##_m; }						\
	void set##_m( _t value ) { m_##_m = value; };

public:
	PROPERTY( int, Number );
	PROPERTY( CString, Name );
	PROPERTY( double, Pressure );
	PROPERTY( std::string, MyName );
};

int _tmain(int argc, _TCHAR* argv[])
{
	Model model;
	model.Value = 10;

	model.Name = L&quot;masuda tomoaki&quot;;
	model.Number = 10;
	model.Pressure = 1.2f;
	model.MyName = &quot;tomoaki&quot;;

	std::cout << &quot;name: &quot; << model.Name << std::endl
		      << &quot;num:  &quot; << model.Number << std::endl;
	std::cout << &quot;myname: &quot; << model.MyName << std::endl;

	return 0;
}

このコードでは CString を cout しているときには文字化けしていますが、まぁ大抵のところはok。
これで、なんらかの形でプロパティ名とバインドさせれば、INotify を実装できるかな。

カテゴリー: C++ | MS-C++ では __declspec(property()) でプロパティを作れるよ はコメントを受け付けていません

グラフィック系のModel-Viewを考える

手元のコードでは、以前の通りに作る訳ですが、metro application も含めて少し考えてみます。なぜ、metro も入れるかというとサスペンドがあるからなんですよね~。

image

まず、データ保持のほうの Model から考えてみます。矩形あるいは三角形などを書くための Point クラスと面を作るための Mesh クラスを準備します。

これをデータとして保存する場合は、XMLでもバイナリデータでも良いのですが、配列な感じで保存します。逆に読み出す時も配列として読み出します。このとき、矩形なり三角形なりの頂点は「すでに出来上がったもの」を保存するのが普通です。

このデータをもとにして、描画をする訳です。便宜上、同じクラス名になっていますが、ここは共有しても良いし、違うクラスにしても良いということです。共有する場合は、一部のプロパティ(statusプロパティとcolorプロパティ)が異なるのでコンバートが必要です。コンバート自体は、View の描画時に判断すると思います。逆にModel-Viewで異なるPointクラスを用意する場合は、Viewでは直接Colorプロパティの値を参照することができます。

// クラスを共有する場合は
View.Color = Point.Color
// クラスを共有しない場合は
View.Color = StatusToColor(Point.Status)

これは、Color 自体の可搬性によって共有するか共有しないかを決めます。あるいは、可搬性を良くする場合は、Status を Color と別ものにすることで、DarkStyle や LightStyle のように色相を変えたときにも対応できるのです。なので少なくとも、Color 自体は共有しないほうが良い、と考えられます。

ただし、共有しない場合は、2つの Point クラスを管理せねばならず結構面倒です。なので、概念的には2種類のPointクラスを扱い、実装的にはひとつのPointクラスを扱う(あるいは自動でコンバートする)ほうが良いと考えらます。

先のファイルへの永続化を考えたとき XML の場合にはクラスでもよいのですが、バイナリファイルとして直接保存する場合は、C++の構造体を使うと便利です。

struct Point {
int x, y, z ;
int status ;
};

class Point {
int x, y, z ;
int status;
};

C#/VB で作る場合には、sturctもclassもあまり代わりがないのですが(実際は、structにするほうがメモリ効率がよいのですが)、C/C++ の場合は構造体のほうが明らかに永続化に関しては便利です。

このような場合、Model 側では、struct Point を使い、View 側では利便性を考えて class Point を使うという方法があります。そして、それぞれのデータを定義するのはメモリ効率的に冗長であるので、class Point では struct のほうを内包させます。

class Point {
struct Point *_pt ;
getX() { return _pt->x ; }
setX( int x ) { _pt->x = x; }

};

この記述は、前回書いた通り、Model の構造に密着し過ぎているために、Model の変更に対して過敏になりすぎます。また、View での変更に対して過敏すぎます。なので、実際には、struct Point と class Point のメモリの冗長性を諦めます。最近のメモリ容量から考えると、二重化させても問題ないでしょうし、既に永続化がされているのですから Model のメモリ領域は解放してしまうことも可能です。これは場合によりけりでしょう。

ただし、Model の Point の構造は View ほど変えないほうが無難です。Model の構造イコール可搬性を意味するので、どのシステム、アーキテクチャに持って行っても「同じ Model の構造を使える」ということは重要です。なので、実際に永続化された構造(ファイルに保存されたバイナリデータ、XMLデータ、データベースの構造)と対になるように Model を作ります。

さて、Model の Point と View の Point のコンバートは静的な構造の変換でしかすぎませんが、Pointの集合体である Mesh は、Model と View とでは随分と違います。

どちらも複数のPointを持つという点では同じなのですが、Viewの場合には「作業途中である中途半端な」Meshデータを保持する必要があります。このMeshデータは、Viewが再描画するときに再利用されます。XAMLの場合には、再描画を直接要求されることは稀なのですが(自前でBitmapを書く場合は別)、Windows アプリケーションを C++で書く、DirectXで書くという場合には、Paintイベント時に発生するためにこれが必要になります。

しかし、Model で持つ Mesh データは、この中途半端なデータを持つ必要があるのか?という疑問が出来ます。簡単に考えると、Modelのデータは「できるだけ欠損してない」データ望ましいでしょう。しかし、Metro Application を作る場合には若干異なるのです。

Metro Application の場合には、ユーザーがアプリケーションをバックグラウンドに置くことができます。と言いますか、画面を切り替えると、表のアプリはバックグラウンドに追いやられてしまうわけです。このバックグラウンドに追いやらられたアプリは、なんらかのタイミングでサスペンド(一時停止)となります。更に、メモリが足りなくなるとサスペンドからアプリは終了に移行させられます。

ユーザーから見ると、バックグラウンドに移動したアプリをもう一度開いたときには「元の状態」に戻ってほしいものです。これは、紙に途中まで書きつけていたメモを、一旦わきに追いやって、もう一度手元に戻したときに「書いていた途中のものが消えない」現実と同じことです。

ですが、これを実現するためには、アプリケーションが一時停止あるいは terminate されたときに、「作業途中の状態」=「バックグラウンドに追いやられた状態」を永続化する必要がでてきます。これは、いままで、「Model は完全なデータである」という習慣とは、かなり違ったものになります。

さて、作業途中の状態は、View の Mesh クラスが持っています。これをそのまま Model の Mesh に移行さて、そのまま永続化すれば「作業途中」の状態が保存されることが分かります。

が、逆に作業途中のデータを「作業途中」の状態へ復元することを考えてみましょう。作業途中である不完全な Mesh データ(1点や直線でしかないデータ)は、永続化された Model の Mesh として復元可能なのですが、そのままでは復元不可能なものが出てきます。

  • View が保持している、カレントの Point 位置
  • View が保持している、視点や回転、描画範囲

これらのデータは、Point は Mesh クラスには表れてこない View 独自のパラメータです。作業途中を復元しようとすると、これらのデータも永続化しないといけない、という難関が待っているのです。

これはメモ帳のような簡単なものであっても、

  • 挿入箇所のカーソル位置
  • スクロール位置
  • リストの表示位置

などがあります。検索途中であれば、検索しているときの文字列も保存しないと復元ができません。となると完全に復元するという現実が、結構困難なものだということがわかります。

さて、iPhone/iPad の場合は、どうなっているのでしょうか?というのを確認してみましょう。実は簡単なルールがあるらしく(apple自体は、この点に関しては明記していないような気がします)、

  • 元に戻すかどうかは、アプリケーション依存
  • 駄目だったら、最初から起動

という動きになっています。いやいや、実に単純明快。これだと途中の Point, Mesh かつ View 独特のデータを永続化しないで済みます。

で、果たして Metro Application の場合はどうなるのかというと、実際のところはよく分かりません。復元せよということにはなっていますが、People などのいくつかの標準機能を途中で落としてみると、スクロール位置までは保管していないようです。まあ、実際に Windows Store が動き出すと、そのあたりの規約もゆるゆるになるかと。

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

c++ で INotifyProperyChanged をどう実現するか?

c++/cx metro の場合 mvvm がどのように実装されているか | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3542

の続きで考察を30分ほど。

mvvm, mvc などの現在の実装に分け入ってしまうと混乱するので「view を分離する」という点に絞って実現してみる。
前回解説した通り、XAML 側には、文字列を Notation として受け取る仕組みがあるので、それを使うとすると、PropertyChangedEventHandler に沿って作る必要がある。これは実装れべるでそうなっているので、XAML を View として使う限りはこれを使う。

さて、今度は XAML を使わない場合、あるいは XAML の提供する PropertyChangedEventHandler を使わずに実装する場合を考えてみると、

  • Model: データプロパティのまとまり
  • View: 画面に表示する部分

となる場合、間に INotifyProperyChanged を入れる。Model のプロパティを変更した時に、INotifyProperyChanged を通じて、View に伝えるという訳です。
何故「伝えない」といけないかというと、

  • View は単独で、画面の再描画を行っている

という前提になっているためで、View が描画するときに Model の値を直接参照していれば、何も「伝える」ことをしなくても良いわけです。
これをコードで表すと、以下のような View::value, Model::value と分離されている場合(実装的にそうなっている場合)は、なんらかの形で、Model -> View への伝達が必要になります。

class View {
	int value;
	void Display() {...}
};
class Model {
	int value;
};

これが、INotify であったり、Listener パターンであったりします。このあたりの実装は、

誰かが使ったらアリスに知らせろ | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2017
誰かが使ったら誰かが使ったらアリスに知らせろ(2) | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2023

なところで、C++ の実装があります。(2)のほうは関数ポインタ(クラスのメソッドのポインタ)を使っているのですが、Model と View が密着し過ぎていてあまり良くありません。どうせならば、多対多の Model-View に合わせたいですよね。

話を元に戻すと、先の実装では、View::value を Model::value のポインタとすると、INotify が省けます。

class View {
	int *value;
	void Display() {...}
	void SetModel( Model *model ) {
		this->value = &(model->value);
	}
};
class Model {
	int value;
};

これで、View::value は常に Model::value と同じ値を持ちます。ポインタですからね。View から設定した vlaue の値も、常に Model::value に設定される(というかアドレスが同じ)なので、万々歳…てな感じなのですが、実は問題があります(C#/VBには、値を直接参照できる「参照/ポインタ」の概念がないので、これができません。もっとも、int を適当にオブジェクト化して参照させることも可能ですが、ちょっと冗長ですね)。

  • SetModel のところで、Model の型に縛られている。
  • View から入力された value に、Model::value が過敏すぎる

という問題があります。View と Model が密接過ぎるという点では、asp.net mvc では、dynamic という型を使って動的にプロパティを解決する手段を用意しています。mvvm の場合は、model の型依存になっているところもありますが、中間の vm で吸収する方法があります。あまりやりませんが、複数の model をひとつの viewmodel で扱えるようにして(model の本来の「データ」の位置に落とし込む)view との距離を保ちます。

なので、C# の INotifyPropertyCanged の実装までは調べていないのですが、View からは、リフレクションなどを使って「名前」を使って Model のプロパティ(アクセスメソッド)を呼び出していると考えられます。あるいは、com の場合は、invoke です。だから、Model::value -> View::value への伝達は、View からの pickup に「名前/文字列」を使っているのかなと。
ただし、「文字列」=「プロパティ名」(アクセッサ)というは、C#/VB の場合は文字列の生成が発生するので、ちょっと GC 的にどうなのかなぁ。と思うのですがどうなんでしょうねぇ? const で定義されていると一意なアドレスを取るのですが、引数に直接指定した「文字列」の扱いは不明ですね。。。と思って、調べてみると GetHashCode で同じ値が返ってきました。同一文字列を何度も生成されている訳ではなさそうです。という訳で、GC 的にも大丈夫。

もうひとつの問題点として、View の反応に過敏すぎる Model::value が出来てしまいます。このあたりは、view の実装依存なのですが、例えば Model::value の値の範囲を「0から100」と制限しているとき(validationしているとき)に、View では「0から100」以外の値が入らなってしまいます。この実装は、一見良いように見えますが、ユーザーのオペレーション(ユースケース)として、

  1. 最初に「99」が入っている。
  2. 「90」に変更しようと思う。
  3. 「990」と一時的に「0」を入力した後に「9」を消して、「90」と入れたいと思った。
  4. しかし、100以上になるので「0」が入力できない。

これは、一度「9」を消してから、というオペレーションになるのですが、人としてはちょっと面倒です。なので、view での検証と model での検証(特に永続化時の検証)を別にしたい時が多々あります。このためにも、

class View {
	int value;
	void Display() {...}
};
class Model {
	int value;
};

な風に、View::value と Model::value は分離しておきたい、しかし緩く結合しておきたいという意味合いがあります。

さて、c++ で INotify を実装する場合には、リフレクションや com の Invoke がないので、単純にプロパティの文字列では使えません。まぁ、c++/cx の場合は使えそうなのですが、これは別途。
INotify に文字列を渡す意味としては

  • model::value を一意に表す記号

の意味が強いので、c++ の場合は、

  • model のプロパティを、文字列から変換する map を作る
  • model のプロパティを、一意に認識する enum を作る
  • model のプロパティを、直接指定する関数ポインタを使う。

という方法があります。「文字列から変換する map を作る」のが、既存の PropertyChangedEventHandler を使う方法と近いのですが、いちいち map を作るのも面倒だし、model のプロパティが追加されると map も修正しないといけないという保守性の悪さがあります。このあたりは、#define マクロを使ってもいけそうな気がするのですが、ちょっと他を探りたいですね。
クラスメソッドのポインタを使おうと思ったのですが、クラスの型に過敏なために既に指摘したように model と view が密着しすぎます。できることならば、model と view とは全く独実して開発しても大丈夫な位に分離させたいものです。XAML の場合は、Model のプロパティ名を指定することが可能なので(実際のプロパティ名とプロパティの文字列は同一なのですが)、View の側から見れば ViewModel を媒介させることにより分離が可能です。ただし、この分離部分は、(現在の)間にある INotifyProperyChanged の実装に依り複数の Model への対応が難しくなっています。まぁ、手作業でちまちまということで。

目標とするところは、Multi doc-view を継承した形で、 multi model-view ってところなのですが、少しずつぼちぼちと考察してきますか。

カテゴリー: 開発, C++ | 2件のコメント

[資料] iPadに真似ぶ windows 8 タブレットプログラミング

 

発表時にはネタを削除しましたが、こっちは元のままで。いままで slideshare って使ったことがなかったんですよね~。プレゼン資料を作るよりも「その場の喋り」に重きを置くので(場の雰囲気にあわせるとも言う、あるいは、事前準備に力を入れないように注意しているとも言う)、資料を見ると「曲解」されるかもしれませんんが、まぁ、HDD のこやしになってしまうよりは良いかと思い直し。いえいえ、単純に VC++, Fortran のコンパイル中の暇つぶしなのです。

さて、「真似ぶ」という用語は当然私のオリジナルではなくて、以前からあります。「守破離」もそうですが、最初は型を真似ることから入ります。批判するにせよ中傷するにせよ議論するにせよ、対象を「自分なりに理解」するところから始まるわけです。「理解」という言葉を使っているのですが「理(ことわり)」を解するに至る前に、目の前にあるものをそのまま受け入れる、まさに「見たまま」という段階があるわけです。

このあたり、記号学のシニフィエ/シニフィアンが分かると話が早いのですが、あるいは物理学の「観察」という意味が分かると端折ってもよいのですが…簡単に言えば、見る対象のものと、自分が見ているものとは「異なる」ところも理解した上で、「見る」という現象を利用するわけです。

さて、スライドの話に移ると、

  • iPad のアプリケーションが何故そのスタイルに至っているのか?
  • Microsoft の metro style が何故その画面に至っているのか?

の解説になっています。なので、当日木沢さんがおっしゃられてた「MetroStyle の達人になるためには、iPad のヘビーユーザーにならないといけないのか?」…と訳で、YESです。これが「離」に至れる道です。あるいは「オリジナル」を作る前には「コピーから始める」というスタイルです。そもそも、ms-dos が unix のコピーですからね。

そういう企業文化なり、スタイルなりバックグラウンドなりを知った上で、iPad と Windows 8 MetroStyle を比べるという話だったりします。なので、実現できるのは2年ぐらい先だとは思うのですが、「高速道路」があるのならばしばらく乗っておいて、途中下車すればOKってことで。ただし、そのまま乗っかると、その高速道路の先は崖かもしれないのでご注意を、という現実があるかも。まぁ、少なくとも常に高速道路の先は「工事中」な訳ですが。

カテゴリー: 勉強会 | [資料] iPadに真似ぶ windows 8 タブレットプログラミング はコメントを受け付けていません

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=&quot;SampleDataItem&quot;/> and <see cref=&quot;SampleDataGroup&quot;/> 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=&quot;INotifyPropertyChanged&quot;/> 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=&quot;propertyName&quot;>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=&quot;SampleDataItem&quot;/> and <see cref=&quot;SampleDataGroup&quot;/> 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=&quot;MS Sans Serif&quot; fontsize=&quot;8pt&quot;>
	<LTEXT content=&quot;Group name:&quot; type=&quot;IDC_STATIC&quot; position=&quot;5,10,77,8&quot;/>
	<LTEXT content=&quot;Hatch cover no:&quot; type=&quot;IDC_STATIC&quot; position=&quot;5,26,77,8&quot;/>
	<LTEXT content=&quot;Length of hatch opening (m):&quot; type=&quot;IDC_STATIC&quot; position=&quot;5,42,101,8&quot;/>
	...
	<EDITTEXT name=&quot;IDC_E_HC_WAVE_LOADS_GROUP_NAME&quot; position=&quot;114,10,92,12&quot; ES_AUTOHSCROLL=&quot;true&quot; />
    <EDITTEXT name=&quot;IDC_E_HC_WAVE_LOADS_BREADTH&quot; position=&quot;114,57,92,12&quot; ES_AUTOHSCROLL=&quot;true&quot; />
	...
</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=&quot;FullScreenLandscape&quot;/>
                <VisualState x:Name=&quot;FullScreenPortrait&quot;>
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Width&quot; Storyboard.TargetName=&quot;imageBack&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;768&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Height&quot; Storyboard.TargetName=&quot;imageBack&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;1024&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Source&quot; Storyboard.TargetName=&quot;imageBack&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;Images/IMG_0049.PNG&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Visibility&quot; Storyboard.TargetName=&quot;listView1&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;Collapsed&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Visibility&quot; Storyboard.TargetName=&quot;imageMask&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;Collapsed&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Width&quot; Storyboard.TargetName=&quot;textBox1&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;658&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Height&quot; Storyboard.TargetName=&quot;textBox1&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;856&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Margin&quot; Storyboard.TargetName=&quot;textBox1&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;94,236,0,0&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Margin&quot; Storyboard.TargetName=&quot;buttonNew&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;738,152,0,0&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Margin&quot; Storyboard.TargetName=&quot;buttonPrev&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;254,1105,0,0&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Margin&quot; Storyboard.TargetName=&quot;buttonNext&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;500,1105,0,0&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Margin&quot; Storyboard.TargetName=&quot;buttonTrush&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;416,1105,0,0&quot;/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Margin&quot; Storyboard.TargetName=&quot;buttonContract&quot;>
                            <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;336,1105,0,0&quot;/>
                        </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件のコメント