求人の応募フォームを作ってよ、という依頼があって、そのときにごにょごにょと作ったのがあるので、それの簡易版を基にいくつかjQueryのプラグインを紹介する。
今回使用するJavascriptファイルは以下の5つ。
jquery-1.4.2.min.js
http://jquery.com/
言わずと知れたjQueryライブラリ。
jquery.validate.min.js
http://bassistance.de/jquery-plugins/jquery-plugin-validation/
検証(バリデーション、Validation)を行うプラグイン。
jquery.scrollTo-1.4.2-min.js
http://flesler.blogspot.com/2007/10/jqueryscrollto.html
滑らかなスクロールを行うプラグイン。今回のサンプルでは縦方向に短かいけれど、現物は項目数がもっと多かったため、入力エラーがあった場合はその項目までスクロールさせたかったので使用している。
hint.js
http://remysharp.com/2007/01/25/jquery-tutorial-text-box-hints/
Watermark(未入力時にヒントテキストがテキストボックス内に表示される)を行うプラグイン。
mailform.js
今回のサンプルを実装している外部ファイル。
上2つのライブラリはMSのCDN(Content Delivery Network)から参照可能なのでそれぞれ下記のURLを参照すると良い。
http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.min.js
http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js
フォームの外観は下図。ヒントとして入力例テキストボックスに表示されている。
何も入力しない状態で確認画面へをクリックすると下図になる。必須項目の検証が行われている。
年齢、メールアドレス、確認用メールアドレスはそれぞれ、数値の検証、メールアドレスの検証、メールアドレスと同じ値かの検証が行われている。
すべてを入力した状態が下図。
上図の状態で確認画面へをクリックすると下図になり、入力された内容の確認が行える。
同意するにチェックをいれると送信ボタンが有効になり、メール送信なりが行えるようになる。
ここから簡単にいくつかポイントを解説する。
mailform.htm
formタグのactionの値はでたらめなので適当な値に修正してもらいたい。formタグ内にsubmitボタンを配置するとクリック時に入力エラーがあっても勝手に飛んでいってしまうのでformの外側に配置している。入力項目はclassにinput_modeが設定されている。また確認時に表示する項目にはconfirm_modeが設定されている。
mailform.css
input.errorで検証時にエラーだった入力項目の表示を設定できる。今回のサンプルでは入力項目のテキストを赤に変え、周りを赤の線で囲っている。errorは検証エラーのメッセージの表示設定だ。
mailform.js
$form.validateで入力項目の検証を行っている。rulesでどの要素にどのようなルールを適用するかを指定している。messagesはルールに対応するメッセージを指定している。今回使用しているrequired, digits, emailなどのほかにも、range, url, dateなど色々と用意されているので、詳細はこちらを参照して欲しい。
Javascriptをオフにしている人をばっさりと斬って捨てる漢な決断が必要だが、こんな感じで入力内容の検証をすべてクライアントで行い、入力内容をポストすることも可能だ。
今回のサンプルは下記からダウンロードできる。
2010年11月17日水曜日
2010年10月24日日曜日
NuGet(以前はNuPack)で簡単インストール
※NuPackはNuGetという名称に変更されました。リンク修正済み
オープンソースライブラリを簡単に開発環境にインストールできるNuGetがリリースされた。いわゆるパッケージ管理システムだ。Visual Studioに統合されていて、どのバージョンでも使用できるようだ(ちなみに今回はVisual Studio 2010 Ultimateを使用している)。NuGetのインストール方法は簡単だ。下記のリンクからNuGet.Tools.vsixをダウンロードしてダブルクリックするだけだ。
NuGet
サイト右側のDownloadリンクから取得しよう。
※NuGetはいくつか問題が報告されていて私もはじめは使用できなかったので対処法を後述する。
Known Issues With NuGet CTP 1NuGetを使うと何が便利かというと、ライブラリのインストール(設定を含む)をすべて自動で行ってくれることだ。以前当ブログでも解説したElmahの設定なども自動で設定してくれて、すぐに使用できるようになる。また、複雑な依存関係があるようなNHibernate.Linqなどもすべて自動で設定してくれる。さらにNuGetの素晴らしいところはシステムレベルでは何もインストールしないので環境が汚染されることを心配しなくてよい。
では実際にNuGetを使用してみよう。NuGetの説明ではElmahが引っ張りだこなので同じ内容をやるのは気が引けるが、以前マニュアルでの導入方法を解説したこともあるのでここでもElmahの設定方法を解説する。
インストール後にVisual Studioを起動して適当にASP.NET MVCのプロジェクトを作成し、View → Other Windowを開くとPackage Manager Consoleがあるので選択しよう。ちなみに下図でPackage Manager Consoleの項目が二つあるのも既知のバグだ。ここではASP.NET MVCのプロジェクトを使用しているが、もちろんASP.NETのプロジェクトでも問題ないし、WPF、Silverlightなど、どのようなプロジェクトでもNuGetは使用可能だ。
Package Manager ConsoleでList-Packageと入力すると下図のリストが一覧できる。これはNuGetを使用して自動でインストールできるライブラリの一覧だ。
ついでAdd-Package elmahと入力する。これでインストール完了だ。いたって簡単。ReferencesにElmahが追加されているのが分かると思う。
またWeb.configにも必要な設定が追加されている。以前解説したのとは違い、ここではIn-Memoryにエラーをログするようになっている。
http://localhost:2430/elmah.axdにアクセスすると下図のElmahのエラーログページが表示されるはずだ。
以上コマンドラインでのインストール方法を解説したが、GUIで行う方法もある。Referencesを右クリックしてAdd Package Referenceを選択しよう。
Visual Studio 2010で馴染み深い下図のダイアログがポップアップするので、目当てのライブラリを検索する。
ライブラリが見つかったらInstallボタンをクリックしよう。
と、簡単にライブラリがインストールできるのが分かったと思うので、気になるライブラリがあったらガンガン導入して評価してみると良いだろう。
ここから既知のバグの対処法をいくつか紹介する。
・NuGetがアンインストールできない
管理者権限でVisual Studioを実行しないとTools → Extension Managerで表示されるダイアログのNuGetの項目にあるUninstallボタンが無効化されているので注意しよう。
・View → Other WindowsにPackage Manager Consoleがない
・Package Manager Consoleを実行するとSystem.Security.AccessControl.ObjectSecurityExceptionが表示される
どうやらReflectorのAddinをインストールしてあるとこれらの現象が発生するようだ。下記リンクを参考にReflectorのアドインを削除しよう。
方法: アドインを非アクティブにして削除する
・View → Other WindowsにPackage Manager Consoleが2つある
NuGetをインストールする以前にPower Shellをインストールしてあるとこうなってしまうようだ。ちなみにこれの解決方法はないそうだ。
2010年10月3日日曜日
MVVMパターンでSilverlightアプリを開発する その2
前回MVVMパターンと、それを実装するためのライブラリのMVVM Light Toolkitを紹介した。今回はソースコードを交えつつ解説したいと思う。
今回使用するソースコードは次の場所に公開している。
実行後の画面は下図の通りだ。
リストがあって、詳細ボタンをクリックすると詳細が表示されるといういたって単純な構成にしてある。
プロジェクトの構成は下図の通り。
MainView.xaml
MainView.xamlにFrameが定義してあり、状況によってViewsフォルダ配下のDetailsPage.xamlとListPage.xamlを表示する。MainPage.xamlで注目してほしい箇所は2箇所ある。まずUserControlタグのDataContext属性にキー名LocatorというオブジェクトのMainというプロパティがBindingされている。LocatorはViewModelのインスタンスを管理するViewModelLocatorクラスで、App.xamlでインスタンス化されている。
ViewModelLocator.csは下記の通り。
ViewModelごとに上記の記述が連なっていく。
ついで、MainPage.xamlで定義したFrameのSourceにCurrentPageがBindingされている。CurrentPageプロパティが定義されているのはMainViewModelクラスになる。MainViewModel.csのコードは下記になる。
MainViewModelはCurrentPageを変更することによりページの遷移の制御を行っている。MessengerはMVVM Light Toolkitが提供してくれる通信用のクラスだ。Messengerはデフォルトで1つのインスタンスを提供してくれるので、ViewModelのBroadCastのテスト用途などで指定のMessengerを使いたい場合など以外はMessenger.Defaultプロパティを使えば良いだろう。Register関数でライブラリ標準のNotificationMessageを受け取るように指定している。今回はNotificationMessageに遷移先を渡すようにしているので、Registerを呼び出した際のコールバック内でNotificationを参照してCurrentPageを変更する。
NotificationMessage.Notificationは文字列だが、NotificationMessageにはSenderとTargetも渡せるようになっているので、Registerハンドラーの中でNotificationの文字列比較だけでなく、Sender, Targetも使用してどこに対してのメッセージなのかを識別するようにしたほうが良いだろう。というのも、MessengerのSendとRegisterはGenericを使用してNotificationMessage以外にも任意の型を指定して簡単にメッセージを発信、受信することも可能だが、実際、データ型を指定してしまうとコードからどこの誰に対するメッセージなのかまったく識別できなくなってしまうし、また、メッセージ毎にメッセージ用のクラスを用意すると、それはそれで作りすぎるとかなりカオスな状態を引き起こしてしまう。そのため、Messengerの利用はプロジェクトの規模が大きくなればなるほど計画的に使用するべきだろう。
標準のメッセージクラスは以下の通り。
MessageBase
GenericMessage<T>
NotificationMessage
NotificationMessage<T>
NotificationMessageAction
NotificationMessageAction<T>
DialogMessage
PropertyChangedMessage<T>
ListPage.xaml
ListPage.xamlもMainPage.xamlと同様にDataContextへViewModelLocatorを介してListPageViewModelクラスのBindingを行っている。リストの表示に選択機能は必要ないのでItemsControlでListPageViewModelのBushiListプロパティを単純に表示している。
ListPage.xamlは下記の通り。
ListPage.xaml.csは下記の通り。
BushiListは名前の通りBushiクラスのリストだ。それをItemsControlのItemsSourceとしている。注目して欲しいのはDataTemplate内のButtonの定義だ。実はSilverlight4からButtonBaseクラスはCommandを実装するようになったので、ButtonBaseから派生しているButtonは本来ならばCommand="{Binding ShowDetailsCommand}"といったような記述が可能なのだが、今回はItensControlのDataTemplate内ということで、個々のアイテムのDataContextはBushiになってしまう。そのため単純に前述の定義を行ってもListPageViewModel内で定義されたShowDetailsCommandプロパティは見つからない。SL4ではRelativeSourceがWPFほど強力ではないので、それを補うため今回は下記のブログポストで見つけたBindingHelperクラスを使用している。
RelativeSource Binding with FindAncestor mode in Silverlight
CommandParameterにBindingのみを指定することで個々のアイテムのDataContextであるBushiを渡している。これを受けるのはPateListViewModelのShowDetailsCommandだ。ShowDetailsCommandはRelayCommandのGenericを使用してBushiクラスを受け取るようになっている。ShowDetailsCommand内で、BushiをMessengerでDetailsPageViewModelクラスへと発信している(実際にはTargetを設定していないのでBushiをRegisterしている対象へとばらまいている)。ついでNotificationMessageも発信して詳細ページへの遷移を促している。
一点注目して欲しいのがIsInDesignModeだ。このフラグを使用してデザインモード時に仮のデータを渡してやることにより、アプリケーションを実際に動作させてWCF経由などでデータを取得、画面に表示させなくともExpression BlendやVisual StudioのXamlビューで実際に動作させた場合と同等の画面確認が行えるようになっている。これを容易に可能にするのがMVVMパターンを使うことによってもたらされる恩恵の一つでもあり、Expression Blendとの親和性が高いことからBlendabilityが高いとも表現される。
DetailsPage.xaml
DetailsPage.xamlはGridタグでListPage.xamlで選択されたBushiの詳細を受け取り表示するだけである。また戻るボタン押下時にListPage.xamlへの遷移をNotificationMessageを発信することによりMainViewModelへと通知している。DetailsPage.xaml, DetailsPageViewModel.csの詳細はここまで説明してきたこととほぼ同じなので省略する。
まとめ
前回と今回で、MVVMパターン開発手法のMVVM Light Toolkit版を解説してきた。MVVM Light Toolkitは大きなライブラリではないけれど、MVVMパターンに必要な要素はすべて盛り込まれているので、MVVM Light Toolkitを使えばすぐにでもMVVMパターンで開発を行えるのが理解できたと思うし、容易に使える実感を持てたのではないだろうか。
この解説を通して、MVVMパターンを使用してUIとビジネスロジックの切り離しを明確にすることにより、ビジネスロジックの単体テストが容易になり、それにあわせてアプリケーション全体の保守性もあがり、またデザイナとの共同作業を行う上で重要なExpression Blendとの親和性も高まる、ということが伝わったら幸いだ。
今回使用するソースコードは次の場所に公開している。
実行後の画面は下図の通りだ。
リストがあって、詳細ボタンをクリックすると詳細が表示されるといういたって単純な構成にしてある。
プロジェクトの構成は下図の通り。
MainView.xaml
MainView.xamlにFrameが定義してあり、状況によってViewsフォルダ配下のDetailsPage.xamlとListPage.xamlを表示する。MainPage.xamlで注目してほしい箇所は2箇所ある。まずUserControlタグのDataContext属性にキー名LocatorというオブジェクトのMainというプロパティがBindingされている。LocatorはViewModelのインスタンスを管理するViewModelLocatorクラスで、App.xamlでインスタンス化されている。
ViewModelLocator.csは下記の通り。
public class ViewModelLocator{ public static MainViewModel MainStatic { get { if (_main == null) { CreateMain(); } return _main; } } public MainViewModel Main { get { return MainStatic; } } public static void ClearMain() { _main.Cleanup(); _main = null; } public static void CreateMain() { if (_main == null) { _main = new MainViewModel(); } } // 以下、他のViewModel用のPropertyが続く }
ViewModelごとに上記の記述が連なっていく。
ついで、MainPage.xamlで定義したFrameのSourceにCurrentPageがBindingされている。CurrentPageプロパティが定義されているのはMainViewModelクラスになる。MainViewModel.csのコードは下記になる。
public class MainViewModel : ViewModelBase { public MainViewModel() { CurrentPage = new Uri("/Views/ListPage.xaml", UriKind.Relative); Messenger.Default.Register(this, x => { switch(x.Notification) { case Notifications.ToList: CurrentPage = new Uri("/Views/ListPage.xaml", UriKind.Relative); break; case Notifications.ToDetails: CurrentPage = new Uri("/Views/DetailsPage.xaml", UriKind.Relative); break; } }); } Uri _currentPage; public Uri CurrentPage { get { return _currentPage; } set { _currentPage = value; base.RaisePropertyChanged("CurrentPage"); } } }
MainViewModelはCurrentPageを変更することによりページの遷移の制御を行っている。MessengerはMVVM Light Toolkitが提供してくれる通信用のクラスだ。Messengerはデフォルトで1つのインスタンスを提供してくれるので、ViewModelのBroadCastのテスト用途などで指定のMessengerを使いたい場合など以外はMessenger.Defaultプロパティを使えば良いだろう。Register関数でライブラリ標準のNotificationMessageを受け取るように指定している。今回はNotificationMessageに遷移先を渡すようにしているので、Registerを呼び出した際のコールバック内でNotificationを参照してCurrentPageを変更する。
NotificationMessage.Notificationは文字列だが、NotificationMessageにはSenderとTargetも渡せるようになっているので、Registerハンドラーの中でNotificationの文字列比較だけでなく、Sender, Targetも使用してどこに対してのメッセージなのかを識別するようにしたほうが良いだろう。というのも、MessengerのSendとRegisterはGenericを使用してNotificationMessage以外にも任意の型を指定して簡単にメッセージを発信、受信することも可能だが、実際、データ型を指定してしまうとコードからどこの誰に対するメッセージなのかまったく識別できなくなってしまうし、また、メッセージ毎にメッセージ用のクラスを用意すると、それはそれで作りすぎるとかなりカオスな状態を引き起こしてしまう。そのため、Messengerの利用はプロジェクトの規模が大きくなればなるほど計画的に使用するべきだろう。
標準のメッセージクラスは以下の通り。
MessageBase
GenericMessage<T>
NotificationMessage
NotificationMessage<T>
NotificationMessageAction
NotificationMessageAction<T>
DialogMessage
PropertyChangedMessage<T>
ListPage.xaml
ListPage.xamlもMainPage.xamlと同様にDataContextへViewModelLocatorを介してListPageViewModelクラスのBindingを行っている。リストの表示に選択機能は必要ないのでItemsControlでListPageViewModelのBushiListプロパティを単純に表示している。
ListPage.xamlは下記の通り。
<ItemsControl ItemsSource="{Binding BushiList}" > <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="10"> <Button Content="詳細" Margin="0 0 10 0" CommandParameter="{Binding}"> <local:BindingHelper.Binding> <local:RelativeSourceBinding Path="ShowDetailsCommand" TargetProperty="Command" /> </local:BindingHelper.Binding> </Button> <TextBlock Text="{Binding Name}" VerticalAlignment="Center"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
ListPage.xaml.csは下記の通り。
public class ListPageViewModel : ViewModelBase { public ListPageViewModel() { if (IsInDesignMode) { BushiList = new ObservableCollection<Bushi> { new Bushi{ Name = "Sample1", Age = 10, Address = "Tokyo" }, new Bushi{ Name = "Sample2", Age = 20, Address = "Kyoto" }, new Bushi{ Name = "Sample3", Age = 30, Address = "Osaka" } }; } else { BushiList = new ObservableCollection<Bushi> { new Bushi{ Name = "織田 信長", Age = 49, Address = "Kyo-" }, new Bushi{ Name = "羽柴 秀吉", Age = 46, Address = "Himeji" }, new Bushi{ Name = "徳川 家康", Age = 40, Address = "Mikawa" }, new Bushi{ Name = "明智 光秀", Age = 55, Address = "Tanpa" } }; } ShowDetailsCommand = new RelayCommand<Bushi>(x => { Messenger.Default.Send<Bushi>(x); Messenger.Default.Send<NotificationMessage>(new NotificationMessage(Notifications.ToDetails)); }); } public ObservableCollection<Bushi> BushiList { get; set; } public RelayCommand<Bushi> ShowDetailsCommand { get; private set; } }
BushiListは名前の通りBushiクラスのリストだ。それをItemsControlのItemsSourceとしている。注目して欲しいのはDataTemplate内のButtonの定義だ。実はSilverlight4からButtonBaseクラスはCommandを実装するようになったので、ButtonBaseから派生しているButtonは本来ならばCommand="{Binding ShowDetailsCommand}"といったような記述が可能なのだが、今回はItensControlのDataTemplate内ということで、個々のアイテムのDataContextはBushiになってしまう。そのため単純に前述の定義を行ってもListPageViewModel内で定義されたShowDetailsCommandプロパティは見つからない。SL4ではRelativeSourceがWPFほど強力ではないので、それを補うため今回は下記のブログポストで見つけたBindingHelperクラスを使用している。
RelativeSource Binding with FindAncestor mode in Silverlight
CommandParameterにBindingのみを指定することで個々のアイテムのDataContextであるBushiを渡している。これを受けるのはPateListViewModelのShowDetailsCommandだ。ShowDetailsCommandはRelayCommandのGenericを使用してBushiクラスを受け取るようになっている。ShowDetailsCommand内で、BushiをMessengerでDetailsPageViewModelクラスへと発信している(実際にはTargetを設定していないのでBushiをRegisterしている対象へとばらまいている)。ついでNotificationMessageも発信して詳細ページへの遷移を促している。
一点注目して欲しいのがIsInDesignModeだ。このフラグを使用してデザインモード時に仮のデータを渡してやることにより、アプリケーションを実際に動作させてWCF経由などでデータを取得、画面に表示させなくともExpression BlendやVisual StudioのXamlビューで実際に動作させた場合と同等の画面確認が行えるようになっている。これを容易に可能にするのがMVVMパターンを使うことによってもたらされる恩恵の一つでもあり、Expression Blendとの親和性が高いことからBlendabilityが高いとも表現される。
DetailsPage.xaml
DetailsPage.xamlはGridタグでListPage.xamlで選択されたBushiの詳細を受け取り表示するだけである。また戻るボタン押下時にListPage.xamlへの遷移をNotificationMessageを発信することによりMainViewModelへと通知している。DetailsPage.xaml, DetailsPageViewModel.csの詳細はここまで説明してきたこととほぼ同じなので省略する。
まとめ
前回と今回で、MVVMパターン開発手法のMVVM Light Toolkit版を解説してきた。MVVM Light Toolkitは大きなライブラリではないけれど、MVVMパターンに必要な要素はすべて盛り込まれているので、MVVM Light Toolkitを使えばすぐにでもMVVMパターンで開発を行えるのが理解できたと思うし、容易に使える実感を持てたのではないだろうか。
この解説を通して、MVVMパターンを使用してUIとビジネスロジックの切り離しを明確にすることにより、ビジネスロジックの単体テストが容易になり、それにあわせてアプリケーション全体の保守性もあがり、またデザイナとの共同作業を行う上で重要なExpression Blendとの親和性も高まる、ということが伝わったら幸いだ。
2010年9月26日日曜日
MVVMパターンでSilverlightアプリを開発する その1
WPFかSilverlightで開発する場合に非常に有用なデザインパターンがMVVMだ。MVVMが最初に提唱され始めたのが2006年ごろらしいので、かれこれ4年ほどたち、その間にMVVM Light ToolkitやPrismなどかなりこなれたライブラリ群が多数出たおかげで、現在はMVVMにのっとった開発が容易になっている。今回はPrismよりも簡易に使い始められるMVVM Light ToolkitでMVVMでの開発方法を紹介する。
MVVM Light Toolkitの導入
MVVM Light Toolkitの公式サイト
MVVM Light Toolkitのインストール方法
上記2つ目のリンクから「Binaries (WPF3.5SP1/SL3/WPF4/SL4/Windows Phone 7)」のリンクをクリックしてDLL群をダウンロードしよう。解凍するとProgram Files配下のフォルダができるのでそれをコピーしてProgram Files配下へと配置する。
あわせて
「Templates for Visual Studio 2010 (WPF3.5SP1/SL3/WPF4/SL4/Windows Phone 7)」
「Snippets for Visual Studio 2008, Visual Studio 2010 and Visual Studio 2010 Express (WPF3.5SP1/SL3/WPF4/SL4/Windows Phone 7)」
からテンプレートとコードスニペットをダウンロードしておくと良いだろう(私の環境はVS2010なのでそれようのファイルをダウンロードしている。自分の環境にあったものを選択してほしい)。配置方法はZipファイルを解凍後、それぞれ所定の位置にコピーすればよい。所定の位置は下図を参考にしてほしい。
例)C:\Users\Yoo\Documents\Visual Studio 2010\Templates
例)C:\Users\Yoo\Documents\Visual Studio 2010\Code Snippets\Visual C#\My Code Snippets
セットアップが完了したらVisual Studio 2010を起動し、新規プロジェクトの作成を選択し、Silverlightの項目を選択しよう。テンプレートをコピーしている場合は下図のようにMvvm Lightという項目があるはずなのでSL4のほうを選択しよう。
プロジェクトのスケルトンは下図のようになる。
MVVMについて
ここでMVVMパターンについて簡単に説明しておこう。
MVVMはModel View ViewModelの略称でMVC(Model View Controller), MVP(Model View Presenter)の流れから来ている。このMVVMを理解するうえで最も重要なのはVMの部分で、ViewModelがなんぞや、というのを理解すればおのずとMVVMがどういうものか理解できる。
※ここでViewModelのやりとりしている矢印がData Access Layerだけなのは、ViewとのやりとりはBindingを介して行うのでMVVMパターンの建前上ViewとViewModelの間にはやり取りが発生しない(Code Behindにイベントハンドラを記述する必要がなくなる)。
ここで上の微妙な図を使って図解する。図を見てもらうと分かると思うが、ViewModelはViewとModelの中間に位置する。ViewModelの存在意義はいくつがあるが、そのうちもっとも重要なのは、ModelとViewの間にある必要な情報の差を埋める部分にある。アプリケーションは多種多様な情報を扱うが、大体バックエンドのデータストアにはSQL ServerなりOracleなどのデータベースを使うのが一般的だろう。そのため、バックエンドにデータを流し込んだり、取得する単位はテーブルエンティティ毎になりやすい。しかし、RDBのテーブル構成はデータ保持の観点からは大変優秀だが、UIにそのまま表示する情報の形式としては足りなかったり、過剰であったりする。そこで、View(ここでいうUI)とModel(ここでいうテーブルエンティティ)の間を取り持つのにViewModelが存在する。つまりViewModelには、多様なデータを取得しViewに必要な情報を形成する役割とユーザのインプットをModelにしデータアクセスレイヤーに渡す役割という2つの大きな役割がある。
また、ViewModelにはそれ以外にもCommandとしてユーザのアクションハンドラーを公開することが求められる。ユーザのアクションハンドラー(要するにイベントハンドラー)をCommandとして公開する理由は下記の通り。
以下に簡単にまとめる。
MVVM Light Toolkitの説明
ここで簡単にMVVM Light Toolkitの説明をする。基本となる要素は下記の通りだ。
MessengerはViewModel間に限らず、ありとあらゆる場所への通知が可能だ。画面遷移やViewModel間の値変更の通知などはこのクラスが担うことになる。
RelayCommandはMVVM Light Toolkitが提供するCommandクラスだ。これの他にもEventToCommandというかなり強力なCommandクラスがあるので、SelectionChangedイベントなどCommandとしてハンドルできないUI要素のイベントがある場合はこちらを使うことになる。
と、ここまでで結構長くなってしまったのでコードを使っての解説は次回以降に持ち越すことにする。
MVVM Light Toolkitの導入
MVVM Light Toolkitの公式サイト
MVVM Light Toolkitのインストール方法
上記2つ目のリンクから「Binaries (WPF3.5SP1/SL3/WPF4/SL4/Windows Phone 7)」のリンクをクリックしてDLL群をダウンロードしよう。解凍するとProgram Files配下のフォルダができるのでそれをコピーしてProgram Files配下へと配置する。
あわせて
「Templates for Visual Studio 2010 (WPF3.5SP1/SL3/WPF4/SL4/Windows Phone 7)」
「Snippets for Visual Studio 2008, Visual Studio 2010 and Visual Studio 2010 Express (WPF3.5SP1/SL3/WPF4/SL4/Windows Phone 7)」
からテンプレートとコードスニペットをダウンロードしておくと良いだろう(私の環境はVS2010なのでそれようのファイルをダウンロードしている。自分の環境にあったものを選択してほしい)。配置方法はZipファイルを解凍後、それぞれ所定の位置にコピーすればよい。所定の位置は下図を参考にしてほしい。
例)C:\Users\Yoo\Documents\Visual Studio 2010\Templates
例)C:\Users\Yoo\Documents\Visual Studio 2010\Code Snippets\Visual C#\My Code Snippets
セットアップが完了したらVisual Studio 2010を起動し、新規プロジェクトの作成を選択し、Silverlightの項目を選択しよう。テンプレートをコピーしている場合は下図のようにMvvm Lightという項目があるはずなのでSL4のほうを選択しよう。
プロジェクトのスケルトンは下図のようになる。
MVVMについて
ここでMVVMパターンについて簡単に説明しておこう。
MVVMはModel View ViewModelの略称でMVC(Model View Controller), MVP(Model View Presenter)の流れから来ている。このMVVMを理解するうえで最も重要なのはVMの部分で、ViewModelがなんぞや、というのを理解すればおのずとMVVMがどういうものか理解できる。
※ここでViewModelのやりとりしている矢印がData Access Layerだけなのは、ViewとのやりとりはBindingを介して行うのでMVVMパターンの建前上ViewとViewModelの間にはやり取りが発生しない(Code Behindにイベントハンドラを記述する必要がなくなる)。
ここで上の微妙な図を使って図解する。図を見てもらうと分かると思うが、ViewModelはViewとModelの中間に位置する。ViewModelの存在意義はいくつがあるが、そのうちもっとも重要なのは、ModelとViewの間にある必要な情報の差を埋める部分にある。アプリケーションは多種多様な情報を扱うが、大体バックエンドのデータストアにはSQL ServerなりOracleなどのデータベースを使うのが一般的だろう。そのため、バックエンドにデータを流し込んだり、取得する単位はテーブルエンティティ毎になりやすい。しかし、RDBのテーブル構成はデータ保持の観点からは大変優秀だが、UIにそのまま表示する情報の形式としては足りなかったり、過剰であったりする。そこで、View(ここでいうUI)とModel(ここでいうテーブルエンティティ)の間を取り持つのにViewModelが存在する。つまりViewModelには、多様なデータを取得しViewに必要な情報を形成する役割とユーザのインプットをModelにしデータアクセスレイヤーに渡す役割という2つの大きな役割がある。
また、ViewModelにはそれ以外にもCommandとしてユーザのアクションハンドラーを公開することが求められる。ユーザのアクションハンドラー(要するにイベントハンドラー)をCommandとして公開する理由は下記の通り。
- ViewのUI要素のCommand(Button.Commandなど)とViewModel.CommandをBindingすることが可能となり、Code Behindにイベントハンドラを記述する必要がなくなる(保守性がアップ)
- イベントハンドラも含めてビジネスロジックはすべてViewModel上にあるので単体テストを行うことが容易になる(Testabilityがアップ)
以下に簡単にまとめる。
- View:UI要素の集合。プレゼンテーション層
- Model:テーブルエンティティだったり、DTOだったり、ビジネスエンティティだったり
- ViewModel:ViewとModelの中間層。ビジネスロジックはここ。Commandもここ
MVVM Light Toolkitの説明
ここで簡単にMVVM Light Toolkitの説明をする。基本となる要素は下記の通りだ。
- ViewModelBase
- Messenger
- RelayCommand
MessengerはViewModel間に限らず、ありとあらゆる場所への通知が可能だ。画面遷移やViewModel間の値変更の通知などはこのクラスが担うことになる。
RelayCommandはMVVM Light Toolkitが提供するCommandクラスだ。これの他にもEventToCommandというかなり強力なCommandクラスがあるので、SelectionChangedイベントなどCommandとしてハンドルできないUI要素のイベントがある場合はこちらを使うことになる。
と、ここまでで結構長くなってしまったのでコードを使っての解説は次回以降に持ち越すことにする。
2010年9月16日木曜日
Silverlightで複数ページを印刷する PrintDocument
Silverlightで単一ページを印刷するのは至極簡単でPrintDocumentを使用する。コード例は下記になる。
PrintPageイベント引数のPageVisualプロパティにVisual要素を渡すとその内容をそのまま印刷してくれる。ここで一点注意が必要なのだが、Print関数はユーザのアクション(ボタンクリックなど)をハンドルした関数内で呼び出さないとSecurityExceptionがThrowされる。
ついで複数ページを印刷する方法を解説する。元ネタは下記から。
Silverlight Business Apps: Module 6.2 - Multi Page Printing
PrintPageイベント引数にPrintableAreaに印刷可能範囲の縦横サイズが入っている。このサイズより外にあるものは印刷されないので、こちら側で制御してやる必要があり、二枚目以降の印刷が必要な場合は再度PrintPageイベントが発生しないといけないのでHasMorePagesにtrueを設定しフレームワークに印刷がすべて終わっていないことを伝えておく必要がある。
Measure関数で実際に描画されるコントロールのサイズを計測する。Measure関数を呼び出しておくとDesiredSizeにコントロールに必要なサイズが設定されるのでそれを参照して、印刷可能範囲との比較を行っていく。
一点はまった点としてinsideStackの存在理由を説明しておく。一見無駄なコーディングに見えるが、これがないと実際に印刷されるサイズとDesiredSizeで取得されるサイズに差異が生じる場合があり、予期せぬ印刷結果につながることがある。
というのも、ResultControlは内部的にGridを保持し、さらにRowDefinitionで3行保持している。ただ、ある特定の場合は最後の1行にデータが設定されないので上2行分のHeight40ピクセルほどで事足りる。しかしDesiredSizeでは毎回3行分のHeight60ピクセルが返却され、さらに始末の悪いことに描画は40ピクセル分で行われる。そのためプログラム上はHeight60ピクセルで計算している部分が実はHeight40ピクセルが正しく、その20ピクセル分の差異がitemsSourceの項目が多くなるほど積もり積もって大きくなり、正しい印刷制御が行えなくなってしまう。それを防ぐためにinsideStackを使用している。insideStackのHeightにDesiredHeightで取得したHeightを設定し、必ずその高さを保証することにより、実際に印刷されるものと、プログラム上で取得できるサイズの差異が発生しないようにしている。
var printDoc = new PrintDocument(); printDoc.PrintPage += (s, eArgs) => { eArgs.PageVisual = this; }; printDoc.Print("Print page");
PrintPageイベント引数のPageVisualプロパティにVisual要素を渡すとその内容をそのまま印刷してくれる。ここで一点注意が必要なのだが、Print関数はユーザのアクション(ボタンクリックなど)をハンドルした関数内で呼び出さないとSecurityExceptionがThrowされる。
ついで複数ページを印刷する方法を解説する。元ネタは下記から。
Silverlight Business Apps: Module 6.2 - Multi Page Printing
var printDoc = new PrintDocument(); bool firstPage = true; var index = 0; var itemsSource = new List<Item>{ 項目多数 }; printDoc.PrintPage += (s, eArgs) => { var itemHost = new StackPanel { Width = eArgs.PrintableArea.Width }; if (firstPage) { itemHost.Children.Add(new HeaderControl()); firstPage = false; } while (index < itemsSource.Count) { var item = itemsSource[index]; var control = new ResultControl { Text = item.Text }; control.Measure(new Size(eArgs.PrintableArea.Width, double.PositiveInfinity)); var desiredHeight = control.DesiredSize.Height; var insideStack = new StackPanel { Height = desiredHeight }; insideStack.Children.Add(control); itemHost.Children.Add(insideStack); itemHost.Measure(new Size(eArgs.PrintableArea.Width, double.PositiveInfinity)); if (itemHost.DesiredSize.Height > eArgs.PrintableArea.Height) { itemHost.Children.Remove(insideStack); eArgs.HasMorePages = true; break; } index++; } eArgs.PageVisual = itemHost; }; printDoc.Print("Print Page");
PrintPageイベント引数にPrintableAreaに印刷可能範囲の縦横サイズが入っている。このサイズより外にあるものは印刷されないので、こちら側で制御してやる必要があり、二枚目以降の印刷が必要な場合は再度PrintPageイベントが発生しないといけないのでHasMorePagesにtrueを設定しフレームワークに印刷がすべて終わっていないことを伝えておく必要がある。
Measure関数で実際に描画されるコントロールのサイズを計測する。Measure関数を呼び出しておくとDesiredSizeにコントロールに必要なサイズが設定されるのでそれを参照して、印刷可能範囲との比較を行っていく。
一点はまった点としてinsideStackの存在理由を説明しておく。一見無駄なコーディングに見えるが、これがないと実際に印刷されるサイズとDesiredSizeで取得されるサイズに差異が生じる場合があり、予期せぬ印刷結果につながることがある。
というのも、ResultControlは内部的にGridを保持し、さらにRowDefinitionで3行保持している。ただ、ある特定の場合は最後の1行にデータが設定されないので上2行分のHeight40ピクセルほどで事足りる。しかしDesiredSizeでは毎回3行分のHeight60ピクセルが返却され、さらに始末の悪いことに描画は40ピクセル分で行われる。そのためプログラム上はHeight60ピクセルで計算している部分が実はHeight40ピクセルが正しく、その20ピクセル分の差異がitemsSourceの項目が多くなるほど積もり積もって大きくなり、正しい印刷制御が行えなくなってしまう。それを防ぐためにinsideStackを使用している。insideStackのHeightにDesiredHeightで取得したHeightを設定し、必ずその高さを保証することにより、実際に印刷されるものと、プログラム上で取得できるサイズの差異が発生しないようにしている。
2010年9月15日水曜日
Dynamic LinqをLinq To Xmlで使うときの注意点 in Silverlight
Linq To SQLでDynamic LINQを良く使っていたのだが、先日Silverlight上でLinq To Xmlを同じように使ったところ結構はまったので紹介しておく。
問題のコードは下記。
上記は意味の無いDynamic Linqの使い方だがサンプル目的なので分かりやすく記述している。上記のコードを実行するとContainsの部分でMethodAccessExceptionがThrowされる。どうやらAnonymousクラスのプロパティに対してReflectionから実行しようとすると駄目なようだ。回避方法は下記の通り。
一時的にデータを保持するクラスを定義して、それをAnonymousクラスの代わりにインスタンス化すればよい。なんだかなぁ、という感じだがDynamic Linqが処理の重要な部分を占める場合は致し方ない。
問題のコードは下記。
var list = doc.Descendants("Row").Select(x => new { Original=x, Text=x.Element("FirstName").Value + x.Element("LastName").Value + x.Element("Address").Value }) .Where("Text.Contains(@0)", new string[]{ "何か" }).ToList();
上記は意味の無いDynamic Linqの使い方だがサンプル目的なので分かりやすく記述している。上記のコードを実行するとContainsの部分でMethodAccessExceptionがThrowされる。どうやらAnonymousクラスのプロパティに対してReflectionから実行しようとすると駄目なようだ。回避方法は下記の通り。
public class TemporaryDataHolder { public XElement Original { get; set; } public string Text { get; set; } } var list = doc.Descendants("Row").Select(x => new TemporaryDataHolder { Original=x, Text=x.Element("FirstName").Value + x.Element("LastName").Value + x.Element("Address").Value }) .Where("Text.Contains(@0)", new string[]{ "何か" }).ToList();
一時的にデータを保持するクラスを定義して、それをAnonymousクラスの代わりにインスタンス化すればよい。なんだかなぁ、という感じだがDynamic Linqが処理の重要な部分を占める場合は致し方ない。
Linq To XmlでXNamespaceを指定する
前回SqlMetalの使い方を説明したが、今回は前回出力したマッピングファイルの書き換えをLinq To XMLで行う方法を紹介する。
マッピングファイルを書き換える理由は次の通り。複数のDBをまたいだクエリをLinq To Sqlで行う場合、マッピングファイルのTable要素は下記のようにServerName、DBNameを含んだ記述にしなければならない。
dbo.users → ServerName.DBName.dbo.users
SQL Server 2008で複数のDBを単一クエリで扱うための設定は下記を参照してほしい。
Linked Serverの設定方法
sp_addlinkedserver (Transact-SQL)
で、Linq To Xmlを使用してのマッピングファイルの読み込みなのだが、下記のようにXNamespaceを指定しないと予期したようにTable要素は取得できない。
Linq To Xmlに限らずXmlDocumentやXPathでもXmlファイルを操作する場合は、名前空間の指定のある要素にはそれぞれXNamespaceを明示的に指定しなければ予期したように動作しないので注意しよう。
マッピングファイルを書き換える理由は次の通り。複数のDBをまたいだクエリをLinq To Sqlで行う場合、マッピングファイルのTable要素は下記のようにServerName、DBNameを含んだ記述にしなければならない。
dbo.users → ServerName.DBName.dbo.users
SQL Server 2008で複数のDBを単一クエリで扱うための設定は下記を参照してほしい。
Linked Serverの設定方法
sp_addlinkedserver (Transact-SQL)
で、Linq To Xmlを使用してのマッピングファイルの読み込みなのだが、下記のようにXNamespaceを指定しないと予期したようにTable要素は取得できない。
var doc = XDocument.Load(mappingFilePath); XNamespace ns = "http://schemas.microsoft.com/linqtosql/mapping/2007"; var table = doc.Descendants(ns+"Table").Where(x => 条件).FirstOrDefault(); // 省略
Linq To Xmlに限らずXmlDocumentやXPathでもXmlファイルを操作する場合は、名前空間の指定のある要素にはそれぞれXNamespaceを明示的に指定しなければ予期したように動作しないので注意しよう。
SqlMetalでLinq To SQLのマッピング情報を外部ファイル化する
Linq To SQLを使用する際にdbml必要なDBのテーブルをGUI上でD&Dして構築すると、DBとのマッピング情報やソースコードは自動で生成されるので改変できない。しかしSqlMetalを使うと、マッピング情報をマッピングファイルとして外部ファイル化したり、生成されるクラスに指定の名前空間や基底クラスを付与したりと色々細かい操作行えるようになる。
SqlMetalはVisual Studioをインストールするとついてくるコマンドラインツールで、初期では下記にある。
drive:\Program Files\Microsoft SDKs\Windows\vn.nn\bin
ここでいくつか使い方をみてみよう。
DBの環境は下記の通り:
SQL Server名: .\SQLSERVER2008R2
UID: sa
PWD: password
DB: TestDB
SqlMetal /server:.\SQLSERVER2008R2 /database:TestDB /user:sa /password:password /code:"C:\MatsuoSoftware\TestDB.cs" /map:"C:\MatsuoSoftware\TestDB.map" /serialization:Unidirectional /context:TestDBDataContext /namespace:MatsuoSoftware.Data /sprocs /functions
/server, /database, /user, /passwordはDBへの接続情報。/code, /mapで出力先と出力方法を指定する。ここで/dbmlを指定することも可能だが、その場合は/code, /mapオプションとは一緒に使えない。dbmlで出力するか、csファイルとmapファイルで出力するかの二択だ。/serializationでシリアル化の方法を指定する。Unidirectionalを指定するとDataContract, DataMember属性が生成されるクラス、プロパティに付与されるのでWCFサービスの戻り値や引数としてそのまま使用することが可能になる。/context, /namespaceは見ての通りだ。/sprocs, /functionsでストアドプロシージャと関数も出力に含めるよう指定している。
他にも基底クラスを指定する/entitybaseなどがあるので下記を参考に自分の目的にあったオプションを見つけて欲しい。
SqlMetal.exe (Code Generation Tool)
最後に、マッピングファイルの読み込み方法で苦労したので参考までに紹介しておく。
マッピングファイルのビルドアクションをResourceにしてからでないとなぜだか読み込めなかったのでGetManifestResourceStreamを使用している。
SqlMetalはVisual Studioをインストールするとついてくるコマンドラインツールで、初期では下記にある。
drive:\Program Files\Microsoft SDKs\Windows\vn.nn\bin
ここでいくつか使い方をみてみよう。
DBの環境は下記の通り:
SQL Server名: .\SQLSERVER2008R2
UID: sa
PWD: password
DB: TestDB
SqlMetal /server:.\SQLSERVER2008R2 /database:TestDB /user:sa /password:password /code:"C:\MatsuoSoftware\TestDB.cs" /map:"C:\MatsuoSoftware\TestDB.map" /serialization:Unidirectional /context:TestDBDataContext /namespace:MatsuoSoftware.Data /sprocs /functions
/server, /database, /user, /passwordはDBへの接続情報。/code, /mapで出力先と出力方法を指定する。ここで/dbmlを指定することも可能だが、その場合は/code, /mapオプションとは一緒に使えない。dbmlで出力するか、csファイルとmapファイルで出力するかの二択だ。/serializationでシリアル化の方法を指定する。Unidirectionalを指定するとDataContract, DataMember属性が生成されるクラス、プロパティに付与されるのでWCFサービスの戻り値や引数としてそのまま使用することが可能になる。/context, /namespaceは見ての通りだ。/sprocs, /functionsでストアドプロシージャと関数も出力に含めるよう指定している。
他にも基底クラスを指定する/entitybaseなどがあるので下記を参考に自分の目的にあったオプションを見つけて欲しい。
SqlMetal.exe (Code Generation Tool)
最後に、マッピングファイルの読み込み方法で苦労したので参考までに紹介しておく。
Assembly assembly = Assembly.GetExecutingAssembly(); var stream = assembly.GetManifestResourceStream("TestDB.map"); XmlMappingSource mappingSource = XmlMappingSource.FromStream(stream); return new TestDBDataContext(connectionString, mappingSource);
マッピングファイルのビルドアクションをResourceにしてからでないとなぜだか読み込めなかったのでGetManifestResourceStreamを使用している。
2010年8月21日土曜日
ListBoxのアイテムとして表示するUserControlの幅をひろげる
XAMLで期待したとおりにUIを表示するうちでいくつか大変な箇所があるが、今回紹介するのもそのうちの一つだ。
ケース1
ListBox内部にUserControlをアイテムとして表示する際に、内部に表示したUserControlが下図のようにListBoxの幅いっぱいまで広がってくれないことがある。
実際には下図のように表示したい。
ここからコードを交えて解説してく。
SampleUserControl - ListBox内部に表示するUserControl
SampleUserControl.xaml
SampleUserControl.xaml.cs
上記のコードはListBox内部に表示するUserControlの詳細になる。ListBoxからテキストをDataBindingできるようにTextをDependency Propertyにしている以外はいたって普通のコードだ。
MainPage.xaml
MainPage.xaml.cs
MainPage.xamlのポイントはListBoxのItemContainerStyleに渡しているResourcesで定義したStyleだ。このStyleでHorizontalContentAlignmentをStrechとしている。これでListBox内部のアイテムがListBoxの幅いっぱいまで広がって表示されるようになる。
ケース2
ListBox内部にUserControlをアイテムとして表示する際に、内部に表示したUserControlが下図のようにListBoxの幅を超えてどこまでも広がっていってしまうことがある。
ListBoxの幅で折り返したい。
この対処方はしごく簡単で、ListBoxの宣言部に下記を追加すればよい。
これでListBoxの幅で折り返されるはずだ。ちなみにSampleUserControl.xamlでTextBlockのTextWrapping="Wrap"を設定しているが、これを忘れると今回のサンプルではUserControlの表示そのものがListBoxの幅で切り取られてしまうので注意して欲しい。
ケース1
ListBox内部にUserControlをアイテムとして表示する際に、内部に表示したUserControlが下図のようにListBoxの幅いっぱいまで広がってくれないことがある。
実際には下図のように表示したい。
ここからコードを交えて解説してく。
SampleUserControl - ListBox内部に表示するUserControl
SampleUserControl.xaml
<Border BorderBrush="RoyalBlue" BorderThickness="2" CornerRadius="3" HorizontalAlignment="Stretch"> <TextBlock x:Name="_text" TextWrapping="Wrap" /> </Border>
SampleUserControl.xaml.cs
public SampleUserControl() { InitializeComponent(); _text.SetBinding(TextBlock.TextProperty, new Binding() { Source = this, Path = new PropertyPath("Text") }); } public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(SampleUserControl), new PropertyMetadata("Sample Text"));
上記のコードはListBox内部に表示するUserControlの詳細になる。ListBoxからテキストをDataBindingできるようにTextをDependency Propertyにしている以外はいたって普通のコードだ。
MainPage.xaml
<UserControl.Resources> <Style x:Key="ListBoxItemContainerStyle" TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> </Style> </UserControl.Resources> <Border BorderBrush="Tomato" CornerRadius="2" Padding="2" Margin="2" BorderThickness="2" Width="300" > <ListBox ItemsSource="{Binding TextList}" ItemContainerStyle="{StaticResource ListBoxItemContainerStyle}" Margin="5,5" > <ListBox.ItemTemplate> <DataTemplate> <application:SampleUserControl Text="{Binding}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Border>
MainPage.xaml.cs
public MainPage() { InitializeComponent(); TextList = new List<string> { "Silverlightサンプル", "マツオソフトウェア", "Yoo Matsuo", }; this.DataContext = this; } public List<string> TextList { get; set; }
MainPage.xamlのポイントはListBoxのItemContainerStyleに渡しているResourcesで定義したStyleだ。このStyleでHorizontalContentAlignmentをStrechとしている。これでListBox内部のアイテムがListBoxの幅いっぱいまで広がって表示されるようになる。
ケース2
ListBox内部にUserControlをアイテムとして表示する際に、内部に表示したUserControlが下図のようにListBoxの幅を超えてどこまでも広がっていってしまうことがある。
ListBoxの幅で折り返したい。
この対処方はしごく簡単で、ListBoxの宣言部に下記を追加すればよい。
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
これでListBoxの幅で折り返されるはずだ。ちなみにSampleUserControl.xamlでTextBlockのTextWrapping="Wrap"を設定しているが、これを忘れると今回のサンプルではUserControlの表示そのものがListBoxの幅で切り取られてしまうので注意して欲しい。
Silverlight起動時にError Code 2103で例外が発生したら
今まで動いていたSilverlightが突然起動しなくなるときがある。ブラウザが出力するエラーは「初期化エラー 2103」などというばかりでよく分からない。思い当たる節は名前空間をプロジェクト全体でいじったことぐらい・・・、とくるなら下記の方法を実践してもらいたい。
プロジェクトのプロパティ→Silverlightタブ
Startup Objectのドロップダウンリストを上下させて新しい名前空間のAppファイルを参照するようにする。
名前空間を一括置換などで変更している場合はここが変更されていないことがままある。その場合はSilverlightが起動に必要な情報を収集できなくなり、前述のエラーが出力されるようだ。
プロジェクトのプロパティ→Silverlightタブ
Startup Objectのドロップダウンリストを上下させて新しい名前空間のAppファイルを参照するようにする。
名前空間を一括置換などで変更している場合はここが変更されていないことがままある。その場合はSilverlightが起動に必要な情報を収集できなくなり、前述のエラーが出力されるようだ。
ExcelのAutomationのTipsあれこれ
業務でExcelで来たデータをSQL Serevrに流し込む必要があり、ExcelのAutomation用のツールを作ったのでそのときのTipsをいくつか紹介。
Excel Automationの概要自体は下記のリンクを参考にして欲しい。
How to automate Microsoft Excel from Microsoft Visual C#.NET
必ずCOMインスタンスを開放する
Excel Automationでもっともつまづきやすいのが、アプリが終了してもいつまでもExcelのプロセスが居座ることだ。これは色々と理由があるが下記のコードのような状況が最も陥りやすい。
問題はOpenを呼んでいるところだ。実はapplication.Workbooks.Openを呼び出した際に、内部的にapplication.WorkbooksのCOMインスタンスが生成されている。そのためCOMインスタンスを開放するためにWorkBooksもMarshal.FinalReleaseComObjectしなければならないのだが、ここではWokrBooksをハンドルしていないためにそれができない。正しくは下記のようになる。
これでExcelのプロセスが居座ることは解決できたが、例外処理が起きたときや、Cellから値を取得するためにCOMインスタンスを生成したときなど、開放処理を常に行うように気を使うのはなかなか骨が折れる。そのため、開放処理を簡便に行えるようなWrapperを作ることにした。
ちなみにこのアイディアは下記のStackoverflowのGary McGillから拝借している。
c# and excel automation - ending the running instance
Wrapperクラス
使っているところ
おまけ - Cellの値を取得
usingを使うことによりスコープ範囲外到達時に常にDisposeが呼ばれ、そのDispose内部でCOMインスタンスの開放を行っているため、COMの開放というわずらわしい処理を気にする必要がまったくなくなった。これでロジック部分に集中できるだろう。
Excel Automationの概要自体は下記のリンクを参考にして欲しい。
How to automate Microsoft Excel from Microsoft Visual C#.NET
必ずCOMインスタンスを開放する
Excel Automationでもっともつまづきやすいのが、アプリが終了してもいつまでもExcelのプロセスが居座ることだ。これは色々と理由があるが下記のコードのような状況が最も陥りやすい。
var application = new Excel.Application(); var workBook = application.Workbooks.Open("path", Type.Missing.... ); var sheet = workBook.Sheets[2]; Marshal.FinalReleaseComObject(sheet); workBook.Close(); Marshal.FinalReleaseComObject(workBook); application.Quit(); Marshal.FinalReleaseComObject(application);
問題はOpenを呼んでいるところだ。実はapplication.Workbooks.Openを呼び出した際に、内部的にapplication.WorkbooksのCOMインスタンスが生成されている。そのためCOMインスタンスを開放するためにWorkBooksもMarshal.FinalReleaseComObjectしなければならないのだが、ここではWokrBooksをハンドルしていないためにそれができない。正しくは下記のようになる。
var application = new Excel.Application(); var workBooks = application.Workbooks; var workBook = workBooks.Open("path", Type.Missing.... ); var sheet = workBook.Sheets[2]; Marshal.FinalReleaseComObject(sheet); workBook.Close(); Marshal.FinalReleaseComObject(workBook); Marshal.FinalReleaseComObject(workBooks); application.Quit(); Marshal.FinalReleaseComObject(application);
これでExcelのプロセスが居座ることは解決できたが、例外処理が起きたときや、Cellから値を取得するためにCOMインスタンスを生成したときなど、開放処理を常に行うように気を使うのはなかなか骨が折れる。そのため、開放処理を簡便に行えるようなWrapperを作ることにした。
ちなみにこのアイディアは下記のStackoverflowのGary McGillから拝借している。
c# and excel automation - ending the running instance
Wrapperクラス
class ComWrapper<T> : IDisposable { public ComWrapper(T t) { ComObject = t; } public void Dispose() { Marshal.FinalReleaseComObject(ComObject); } public T ComObject { get; set; } }
使っているところ
using (var application = new ComWrapper(new Application())) { try { using (var workbooks = new ComWrapper (application.ComObject.Workbooks)) { using (var workbook = new ComWrapper (workbooks.ComObject.Open(filePath, ...))) { try { using (var worksheet = new ComWrapper (workbook.ComObject.Sheets[1])) { // なにか処理 } } finally { workbook.ComObject.Close(); } } } } finally { application.ComObject.Quit(); } }
おまけ - Cellの値を取得
for( var rowIndex = 1; rowIndex < 10; rowIndex ++) { var colIndex = 0; using (var cell1 = new ComWrapper(worksheet.ComObject.Cells[rowIndex, ++colIndex])) using (var cell2 = new ComWrapper (worksheet.ComObject.Cells[rowIndex, ++colIndex])) { // 処理 var value = cell1.ComObject.Value } }
usingを使うことによりスコープ範囲外到達時に常にDisposeが呼ばれ、そのDispose内部でCOMインスタンスの開放を行っているため、COMの開放というわずらわしい処理を気にする必要がまったくなくなった。これでロジック部分に集中できるだろう。
2010年7月24日土曜日
COMコンポーネントをIISで実行する
サーバサイドでCOMを実行したいことはままあることで、それでは、と普通にローカルで動作しているCOMをIISのプロセス上で動かすとCoCreateInstanceでインスタンスが生成できなかったりする。考えられる理由は下記の通り。
・COMがシングルスレッド上でしか実行できない
・IISのアプリケーションプールが正しくない
一つ目は、いくつかのCOMはシングルスレッド上でしか実行できない。そのためマルチスレッド上でCoCreateInstanceを行ってもインスタンス生成ができない。回避方法はSTAのスレッドを開始して、そのスレッド上でCOMを呼び出せばよい。
Thread.SetApartmentState Method
ただ、STAなので処理はすべて順番に行われる。そのためリクエストが多重になればなるほどパフォーマンス上のボトルネックになるので注意が必要だ。
ついで、IISのアプリケーションプールのIDがNetWorkServiceのままだと動作しない場合がある。その場合は下図を参考にIDをLocalServiceに変更してもらいたい。
COMを実行したいアプリケーションプールの詳細設定を選択する。
プロセスモデルのIDをLocalServiceに変更する。
・COMがシングルスレッド上でしか実行できない
・IISのアプリケーションプールが正しくない
一つ目は、いくつかのCOMはシングルスレッド上でしか実行できない。そのためマルチスレッド上でCoCreateInstanceを行ってもインスタンス生成ができない。回避方法はSTAのスレッドを開始して、そのスレッド上でCOMを呼び出せばよい。
Thread.SetApartmentState Method
ただ、STAなので処理はすべて順番に行われる。そのためリクエストが多重になればなるほどパフォーマンス上のボトルネックになるので注意が必要だ。
ついで、IISのアプリケーションプールのIDがNetWorkServiceのままだと動作しない場合がある。その場合は下図を参考にIDをLocalServiceに変更してもらいたい。
COMを実行したいアプリケーションプールの詳細設定を選択する。
プロセスモデルのIDをLocalServiceに変更する。
WCFをSSLを介してSilverlightで使う
Silverlight4.0でSSLを介してWCFを使用したときに大変困った箇所を紹介する。1からの導入方法の紹介ではないので、それが必要な人は他のブログを参照して欲しい。
設定を行った環境は以下の通り。
Silverlight 4.0
WCF(このプロジェクトはIISにて仮想ディレクトリ化して動作させている)
IIS7
自己署名入り証明書
Windows 7 Pro 64bit
UAC
まず手始めとして、UAC(User Account Control)を無効にしたほうが良い。直接的には関係が無いのかもしれないが、UACを無効にせずに環境設定をおこなったらいつまでたっても動作しなかったものが、UAC無効化後に再度設定すると正常に動作しはじめた。特にWCFの仮想ディレクトリを作るところで違いがあるようだ。
How to Disable and Turn Off UAC in Windows 7
WCFの説明
Silverlightで開発をする場合サーバとの通信手段ではWCFを使用するのがスタンダードだ。しかし、このWCFが本当に優しくない。素人には大変厳しい仕様になっている。WCFは通信部分をすべて隠蔽してくれるので、サービスの参照の追加を行うとプロキシクラスを生成してくれて、クライアント側から簡単にサーバにアクセスできる手段を提供してくれる。しかし、この隠蔽が大変曲者だ。
プロキシクラスからサーバへリクエストを投げて何かしらの通信エラーが起こった場合、WCFはCommunicationExceptionを投げる。しかし、この例外、受け取るメッセージは常に一緒なのに要因は実に多岐に渡る。簡単に列挙するだけで下記の要因がある。
・Cross Domainエラーが発生している場合
・WCFのクライアント側の構成ファイルの記述に誤りがある場合
・証明書の設定が正しくない
ひとつひとつの要因が大きいのに、すべてCommunicationExceptionで包まれているので初見ではまったく意味が分からない。ここでは参考になったブログポストを紹介しておくので同じ現象にはまっている人は参考にして欲しい。
Network Security Access Restrictions in Silverlight
Making a Service Available Across Domain Boundaries
Cross Domainエラーが発生する場合は、inetpub/wwwrootの直下にclientaccesspolicy.xmlかcrossdomain.xmlを配置する必要がある。その両ファイルの背景と構成の説明。かなり長いので必要な箇所だけ拾い読みしてほしい。
Configuration Editor Tool (SvcConfigEditor.exe)
ServiceReferences.ClientConfigを編集するツール。記述に間違いがあるとエラーを出力するので、エレメントのチェックにも使える。
How to Set Up SSL on IIS 7
IISにSSLを設定する方法。真ん中から下以降を参考に。
IIS関連
Silverlight application running on FF gets CommunicationException during accessing a web using WCF through SSL, but not IE nor Chrome
IEとChromeでは動作するのにFireFoxだけCross Domainエラーを出力する場合に参考にしてほしい。
CommunicationExceptionでつまる原因は大体上記に当てはまると思うので、サーバに正常にアクセスできない場合は前述のブログポストを参考に対処してもらいたい。
設定を行った環境は以下の通り。
Silverlight 4.0
WCF(このプロジェクトはIISにて仮想ディレクトリ化して動作させている)
IIS7
自己署名入り証明書
Windows 7 Pro 64bit
UAC
まず手始めとして、UAC(User Account Control)を無効にしたほうが良い。直接的には関係が無いのかもしれないが、UACを無効にせずに環境設定をおこなったらいつまでたっても動作しなかったものが、UAC無効化後に再度設定すると正常に動作しはじめた。特にWCFの仮想ディレクトリを作るところで違いがあるようだ。
How to Disable and Turn Off UAC in Windows 7
WCFの説明
Silverlightで開発をする場合サーバとの通信手段ではWCFを使用するのがスタンダードだ。しかし、このWCFが本当に優しくない。素人には大変厳しい仕様になっている。WCFは通信部分をすべて隠蔽してくれるので、サービスの参照の追加を行うとプロキシクラスを生成してくれて、クライアント側から簡単にサーバにアクセスできる手段を提供してくれる。しかし、この隠蔽が大変曲者だ。
プロキシクラスからサーバへリクエストを投げて何かしらの通信エラーが起こった場合、WCFはCommunicationExceptionを投げる。しかし、この例外、受け取るメッセージは常に一緒なのに要因は実に多岐に渡る。簡単に列挙するだけで下記の要因がある。
・Cross Domainエラーが発生している場合
・WCFのクライアント側の構成ファイルの記述に誤りがある場合
・証明書の設定が正しくない
ひとつひとつの要因が大きいのに、すべてCommunicationExceptionで包まれているので初見ではまったく意味が分からない。ここでは参考になったブログポストを紹介しておくので同じ現象にはまっている人は参考にして欲しい。
Network Security Access Restrictions in Silverlight
Making a Service Available Across Domain Boundaries
Cross Domainエラーが発生する場合は、inetpub/wwwrootの直下にclientaccesspolicy.xmlかcrossdomain.xmlを配置する必要がある。その両ファイルの背景と構成の説明。かなり長いので必要な箇所だけ拾い読みしてほしい。
Configuration Editor Tool (SvcConfigEditor.exe)
ServiceReferences.ClientConfigを編集するツール。記述に間違いがあるとエラーを出力するので、エレメントのチェックにも使える。
How to Set Up SSL on IIS 7
IISにSSLを設定する方法。真ん中から下以降を参考に。
IIS関連
Silverlight application running on FF gets CommunicationException during accessing a web using WCF through SSL, but not IE nor Chrome
IEとChromeでは動作するのにFireFoxだけCross Domainエラーを出力する場合に参考にしてほしい。
CommunicationExceptionでつまる原因は大体上記に当てはまると思うので、サーバに正常にアクセスできない場合は前述のブログポストを参考に対処してもらいたい。
2010年7月4日日曜日
POCOとはなんぞや 自分的まとめ
以前、MIX 10のセッションビデオの中で「こういうのはPOCOで開発するんだよ」と解説してくれているのがあって(どのセッションかは失念)、そのときはたいして気にもしていなかったのだが、最近データアクセス層を作っているときに、おっ、ここはいっちょPOCOってみるか、と思いつき色々調べたらよく分からなかったので、今後のためにまとめておく。
(※間違っているかも知れないので話半分で聞いてください)
理解の元ネタは下記から収集。
POCO vs DTO at stackoverflow
POCO As A Lifestyle
What is the Difference Between a DTO and a POCO?
POCO vs DTOとなっているのは、一見POCOとDTOは似ているので、POCOのことを正確に理解していない人は「POCOってのはDTOと一緒だよ」とのたまうらしく、そちらの世界に導かれると「じゃぁなんでPOCOが必要なの?」って質問に答えられなくなるので、ホントのところはどうなってんだよ?という魂の叫びの表れである。
DTOはData Transfer Objectの略称で、DTOの唯一の目的は状態(データとかも含むのかな?)を他の層(とか他のアプリなど)へ渡すことにあって、OOP(オブジェクト指向プログラミング)的な振る舞い(特にビジネスロジック的なもの)を持つべきではない。必要な情報を渡すためだけの箱であって、それ以上でも以下でもない。
例:
POCOはPlain Old CLR Objectの略称で、これはPOJO(Plain Old Java Object)から来ている(POJOのWikiはこちら)。で、POCOはPOJOと同じく、クラスの設計に.net frameworkをかませない(命令させない、規制させない)で行おう、という考え方。
なので、ここまでをまとめると、POCOはシンプルなOOPへのアプローチ方法の説明で、DTOはデータをどっかに渡すためのパターン、となる
で、ここから自分の解釈になるのだけど、こんな風になるのかな、と図解してみた。
ビジネスエンティティやドメインモデルと呼ばれるているオブジェクトがPOCOである。各層をつなぐ矢印があるけれど、その際に渡すオブジェクト(があるならば)はDTOでもPOCOでも良い。ドメインモデルと合致しないようなデータを渡すだけならば、それようにDTOを作って渡す方が良い。なので、DTOはPOCOよりもシンプルな構造になる。
最後に、POCOはPersistence Ignorance(PI:永続化に無知。変な日本語で申し訳ないけれど訳し方が分からない)でなければならない。つまり、Save()や、GetDataById()のような、永続性を持つロジック(データの読み込み、保存など)を実装してはならない。そのようなロジックはデータアクセス層に実装しよう。
ということでPOCOを使うと各層を明確に分離できるので、より良い構造になる。
以下にDDD(Domain Driven Design)を理解するのに良いと勧められていた本を紹介しておく。Google BooksにPreviewがあるのでそちらもどうぞ。
Domain-Driven Design: Tackling Complexity in the Heart of Software
(※間違っているかも知れないので話半分で聞いてください)
理解の元ネタは下記から収集。
POCO vs DTO at stackoverflow
POCO As A Lifestyle
What is the Difference Between a DTO and a POCO?
POCO vs DTOとなっているのは、一見POCOとDTOは似ているので、POCOのことを正確に理解していない人は「POCOってのはDTOと一緒だよ」とのたまうらしく、そちらの世界に導かれると「じゃぁなんでPOCOが必要なの?」って質問に答えられなくなるので、ホントのところはどうなってんだよ?という魂の叫びの表れである。
DTOはData Transfer Objectの略称で、DTOの唯一の目的は状態(データとかも含むのかな?)を他の層(とか他のアプリなど)へ渡すことにあって、OOP(オブジェクト指向プログラミング)的な振る舞い(特にビジネスロジック的なもの)を持つべきではない。必要な情報を渡すためだけの箱であって、それ以上でも以下でもない。
例:
// 正しいDTO class PersonRightDTO { public string Name { get; set; } public DateTime Birthday { get; set; } } // 正しくないDTO class PersonWrongDTO { public string Name { get; set; } public DateTime Birthday { get; set; } public int GetAge() { return // Calculate here. } }
POCOはPlain Old CLR Objectの略称で、これはPOJO(Plain Old Java Object)から来ている(POJOのWikiはこちら)。で、POCOはPOJOと同じく、クラスの設計に.net frameworkをかませない(命令させない、規制させない)で行おう、という考え方。
なので、ここまでをまとめると、POCOはシンプルなOOPへのアプローチ方法の説明で、DTOはデータをどっかに渡すためのパターン、となる
で、ここから自分の解釈になるのだけど、こんな風になるのかな、と図解してみた。
ビジネスエンティティやドメインモデルと呼ばれるているオブジェクトがPOCOである。各層をつなぐ矢印があるけれど、その際に渡すオブジェクト(があるならば)はDTOでもPOCOでも良い。ドメインモデルと合致しないようなデータを渡すだけならば、それようにDTOを作って渡す方が良い。なので、DTOはPOCOよりもシンプルな構造になる。
最後に、POCOはPersistence Ignorance(PI:永続化に無知。変な日本語で申し訳ないけれど訳し方が分からない)でなければならない。つまり、Save()や、GetDataById()のような、永続性を持つロジック(データの読み込み、保存など)を実装してはならない。そのようなロジックはデータアクセス層に実装しよう。
ということでPOCOを使うと各層を明確に分離できるので、より良い構造になる。
以下にDDD(Domain Driven Design)を理解するのに良いと勧められていた本を紹介しておく。Google BooksにPreviewがあるのでそちらもどうぞ。
Domain-Driven Design: Tackling Complexity in the Heart of Software
2010年7月1日木曜日
行番号を取得するクエリ SQL Server
SQL Serverで行番号を取得するクエリを紹介する。
select row_number() over (order by column1) as row_number, * from table
このクエリはSQL Server 2005から導入されていたようで、以前SQL Server 2000で開発していた際に、同様の機能がないかとちまなこになって探していたのも今や昔。
サンプルクエリが下記ブログにたくさん載っているので参照されたし。
Multipurpose Row_Number() Function
select row_number() over (order by column1) as row_number, * from table
このクエリはSQL Server 2005から導入されていたようで、以前SQL Server 2000で開発していた際に、同様の機能がないかとちまなこになって探していたのも今や昔。
サンプルクエリが下記ブログにたくさん載っているので参照されたし。
Multipurpose Row_Number() Function
2010年6月30日水曜日
ZipファイルをUnzip(解凍)するクラス
Androidアプリを開発していて、Zipファイルを解凍するクラスを作ったので下記に公開しておく。
Stack overflowに投稿したのと同じものだけれど、こうした方が良い、などの意見があったらコメントお願いします。
How should I decompress a large data file in AsyncTask.doInBackground?
public class UnzipFiler { private final static int CHUNK_SIZE = 32 * 1024; byte[] _fileIOBuffer = new byte[CHUNK_SIZE]; public void unzipFile(File zipFile, String directory) throws IOException { ZipInputStream in = null; FileOutputStream os = null; try { in = new ZipInputStream (new FileInputStream(zipFile)); ZipEntry entry = null; while ((entry = in.getNextEntry ())!= null) { String entryName = entry.getName(); if (entry.isDirectory ()) { File file = new File (directory, entryName); file.mkdirs(); } else { File file = new File(directory, entryName); if (file.exists()){ file.delete(); } os = new FileOutputStream (file); int unzippedSize = 0; int reportedProgress = 100; int bytesRead = 0; while ((bytesRead = in.read (_fileIOBuffer))!= -1) { os.write(_fileIOBuffer, 0, bytesRead); unzippedSize += bytesRead; } os.close(); } } } catch (FileNotFoundException e) { Log.v("unzip", e.getMessage()); } catch (IOException e) { Log.v("unzip", e.getMessage()); } finally{ if (in != null ){ in.close(); } if (os != null ){ os.close(); } } } }
Stack overflowに投稿したのと同じものだけれど、こうした方が良い、などの意見があったらコメントお願いします。
How should I decompress a large data file in AsyncTask.doInBackground?
2010年6月18日金曜日
存在しないファイルを定期的にリクエストされたら
My-ClipではELMAHを使用しているので、何かしらエラーが起きるとメールが飛んでくる仕組みになっている。で、ELMAHを組み込んでからFileNotFoundExceptionがちょこちょこと報告されるようになり、それによると、いかにクラッカーさん達が日夜何かしらアカウント的なものにアクセスしようと試みているかがよく分かる。そういう場合は大抵、/account/hogehogeだったり/admin/hogehogeなどがリクエストされている。
が、本日受け取ったリクエストはいささか趣がことなり、下記のようなパスだった。
/SlurpConfirm404/booksontheweb.htm
このほかにも4つほど似たようなリクエストがあったのでGoogleしたら、下記のブログにたどりついた。
Inktomi Slurp Confirm 404
この記事によると、あのリクエストはYahooに登録されているWebサイトが正しく404を返却するかをYahooが確認するためのものらしい。ということは、いずれこのサイトも今は皆無であるYahooからの流入者がある、ということ、・・・なのかな?
が、本日受け取ったリクエストはいささか趣がことなり、下記のようなパスだった。
/SlurpConfirm404/booksontheweb.htm
このほかにも4つほど似たようなリクエストがあったのでGoogleしたら、下記のブログにたどりついた。
Inktomi Slurp Confirm 404
この記事によると、あのリクエストはYahooに登録されているWebサイトが正しく404を返却するかをYahooが確認するためのものらしい。ということは、いずれこのサイトも今は皆無であるYahooからの流入者がある、ということ、・・・なのかな?
2010年6月10日木曜日
Entity Frameworkを使ってSQLiteにAuto Incrementのデータを挿入する方法
SQLiteと.NETをつなぐ夢の架け橋であるSystem.Data.SQLiteを使い、VS2010でEntity Framework経由でSQLiteにデータを流し込もうと画策したのだがAuto Increment周りで大変四苦八苦したのでここに後続のために記しておく。
そもそもはじめから嫌な感じがした。SQLiteのことがよく分からなかったのでOfficial Siteをざっくりと読んでみたけど、公式サイトでは特にGUIのサポートなどはなく、UIがコマンドラインしかない。おいおいまじかよ、Foreign Keyとか多量にあるからいちいち記述すんのめんどいんですけど、とか気持ちがなえかけていたらSQLite Database Browserを見つけた。これで勝つると思ったのもつかの間、このGUIが思いのほかしょぼいし使いづらい。
で、くだんのSystem.Data.SQLite君を使うとVSのServer Explorerからテーブルを作ってー外部キー制約作ってー、というのがぽちぽちとできるっぽい(詳しくはここを観て欲しい)。SQL ServerのDBダイアグラムに比べるべくもないが、SQLite Database Browserさんよりは多分に使いやすいので、これでぽちぽちと作業に入った。
が、しかし、ここで問題が一点。主キーをAuto Incrementにしたいのにその設定がない。SQL Serverならば下図のように項目があるのだが、SQLiteのほうにはその項目がない。
(優秀なSQL Server君の図)
(ちょっと残念なSQLite氏の図)
おうおうおう?ということでまたぞろReferenceをざっくりと読んでみるに、Primary Keyと設定されていればId値がNullだったり0の場合は勝手に値を自動生成しますよ?とか書いてある。おぉ、すげー、ということでそのまま作業をすすめ、いざ準備が整ったのでささっとプログラミングして動かしたらうごかねー。
普通にPrimary KeyがUniqueではありませんと例外を投げ出されてしまった。なんでなんで、と色々ぐぐったけどあまり有効な情報がヒットしない。edmxファイル上でStoreGeneratedPatterをIdentityにしなさい、とかの指示はあったけど、それはedmxファイルを作ったときにやってあったのでまだ他の問題があるようだ。ふむ?と色々試行錯誤の結果、最終的にはSQLite Database Browserさんを使い、テーブルの編集→フィールドの編集→フィールドタイプの入力、で「INTEGER PRIMARY KEY AUTOINCREMENT」と入力し、edmx上の対応するテーブルを消去してから追加して実行したら正常に動作した。
なんだよ!AUTOINCREMENTって記述いるんじゃん!!
まぁEFでSQLiteにAuto Incrementなデータを流し込むためにはAUTOINCREMENTって記述が必要ってことなのかな、よく分からんけど。
追記:System.Data.SQLiteのAdd-in(なのか?)からもAuto Incrementの記述を追加できるの図
そもそもはじめから嫌な感じがした。SQLiteのことがよく分からなかったのでOfficial Siteをざっくりと読んでみたけど、公式サイトでは特にGUIのサポートなどはなく、UIがコマンドラインしかない。おいおいまじかよ、Foreign Keyとか多量にあるからいちいち記述すんのめんどいんですけど、とか気持ちがなえかけていたらSQLite Database Browserを見つけた。これで勝つると思ったのもつかの間、このGUIが思いのほかしょぼいし使いづらい。
で、くだんのSystem.Data.SQLite君を使うとVSのServer Explorerからテーブルを作ってー外部キー制約作ってー、というのがぽちぽちとできるっぽい(詳しくはここを観て欲しい)。SQL ServerのDBダイアグラムに比べるべくもないが、SQLite Database Browserさんよりは多分に使いやすいので、これでぽちぽちと作業に入った。
が、しかし、ここで問題が一点。主キーをAuto Incrementにしたいのにその設定がない。SQL Serverならば下図のように項目があるのだが、SQLiteのほうにはその項目がない。
(優秀なSQL Server君の図)
(ちょっと残念なSQLite氏の図)
おうおうおう?ということでまたぞろReferenceをざっくりと読んでみるに、Primary Keyと設定されていればId値がNullだったり0の場合は勝手に値を自動生成しますよ?とか書いてある。おぉ、すげー、ということでそのまま作業をすすめ、いざ準備が整ったのでささっとプログラミングして動かしたらうごかねー。
普通にPrimary KeyがUniqueではありませんと例外を投げ出されてしまった。なんでなんで、と色々ぐぐったけどあまり有効な情報がヒットしない。edmxファイル上でStoreGeneratedPatterをIdentityにしなさい、とかの指示はあったけど、それはedmxファイルを作ったときにやってあったのでまだ他の問題があるようだ。ふむ?と色々試行錯誤の結果、最終的にはSQLite Database Browserさんを使い、テーブルの編集→フィールドの編集→フィールドタイプの入力、で「INTEGER PRIMARY KEY AUTOINCREMENT」と入力し、edmx上の対応するテーブルを消去してから追加して実行したら正常に動作した。
なんだよ!AUTOINCREMENTって記述いるんじゃん!!
まぁEFでSQLiteにAuto Incrementなデータを流し込むためにはAUTOINCREMENTって記述が必要ってことなのかな、よく分からんけど。
追記:System.Data.SQLiteのAdd-in(なのか?)からもAuto Incrementの記述を追加できるの図
2010年6月6日日曜日
StreetViewのReferenceにないイベントを拾う ~ Google Maps API v3
Google Maps API v3に変わり、以前のようにStreetViewを使うために自前でゴリゴリとPegmanの挙動やら何やらを制御してやる必要がなくなったわけだが、それにあわせてイベントの種類も減ってしまったように見える(あくまでReference的な意味で)。
というのも、StreetViewPanoramのEvents項目を見ると、そこにはcloseclickしか載っていない。
これではいつMapがStreetViewモードに切り替わったかなどのイベントが拾えないじゃないか、と思いつつもデバッグ時にStreetViewPanoramのインスタンスを掘り下げてみたらReferenceに載っていないイベントがたくさん見つかった。なので、今回はそのうちの一例を紹介しようと思う。下記の例はgoogle.maps.StreetViewPanoramaの表示・非表示が切り替わったイベントをハンドルしている。これは、MapのほうでPegmanを地図上に落とした場合などにも呼ばれるイベントなので、Mapの動作に併せてStreetViewPanoramaの挙動を変えたいときに重宝するイベントだ。
というのも、StreetViewPanoramのEvents項目を見ると、そこにはcloseclickしか載っていない。
これではいつMapがStreetViewモードに切り替わったかなどのイベントが拾えないじゃないか、と思いつつもデバッグ時にStreetViewPanoramのインスタンスを掘り下げてみたらReferenceに載っていないイベントがたくさん見つかった。なので、今回はそのうちの一例を紹介しようと思う。下記の例はgoogle.maps.StreetViewPanoramaの表示・非表示が切り替わったイベントをハンドルしている。これは、MapのほうでPegmanを地図上に落とした場合などにも呼ばれるイベントなので、Mapの動作に併せてStreetViewPanoramaの挙動を変えたいときに重宝するイベントだ。
var map = new google.maps.Map("省略"); var point = new google.maps.LatLng("省略"); var panoramaOptions = { position: point, pov: { heading: 34, pitch: 10, zoom: 1 }}; var panorama = new google.maps.StreetViewPanorama(document.getElementById("streetview"), panoramaOptions); map.setStreetView(panorama); google.maps.event.addListener(panorama, "visible_changed", function () { alert(panorama.getVisible() ? "StreetView見えてる" : "StreetView見えてない"); });
IE8でStreetViewを表示すると描画がおかしくなる ~ Google Maps API v3
ASP.NETでホストしているサイトで、Google Maps API v3を使用し、IE8でStreetViewを表示すると下図のように描画が崩れてしまう。
一見分からないかもしれないが、画面中央に表示されるべき矢印が画面左側に寄ってしまっている。ちなみにFF、Chromeで表示すると下図のように正しく描画される。
この現象はgoogle.maps.Mapとgoogle.maps.StreetViewPanoramaに分けてStreetViewを実装しているのでまだこの程度で収まっているが、google.maps.MapにStreetViewの描画を任せている場合はその崩れ具合が悪化する。
ちなみに上記の問題を引き起こしているのは下記のタグだ。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" >
このタグがあるとStreetViewが正しく描画されないようだ。が、これがないとIE8ではCSSの描画が崩れるという問題があった。何か解決方法がないものか、と思いつつも面倒なのでとりあえず一旦休止。
一見分からないかもしれないが、画面中央に表示されるべき矢印が画面左側に寄ってしまっている。ちなみにFF、Chromeで表示すると下図のように正しく描画される。
この現象はgoogle.maps.Mapとgoogle.maps.StreetViewPanoramaに分けてStreetViewを実装しているのでまだこの程度で収まっているが、google.maps.MapにStreetViewの描画を任せている場合はその崩れ具合が悪化する。
ちなみに上記の問題を引き起こしているのは下記のタグだ。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" >
このタグがあるとStreetViewが正しく描画されないようだ。が、これがないとIE8ではCSSの描画が崩れるという問題があった。何か解決方法がないものか、と思いつつも面倒なのでとりあえず一旦休止。
Google Maps APIのV2をV3へ移行する
Google Maps APIのv2が先月正式に非推奨になったのでwww.my-clip.netのAPIをv3へと移行した。v3になったことで大幅に変わった箇所がいくつかあるので下記に列挙しておく。
・API Keyはいらない
v2までは必須だったAPI Keyはもう必要ない。下記のようにVersionを含まないUrlをScriptタグのソースに指定すれば常に最新のライブラリが参照できるようになっている。
http://maps.google.com/maps/api/js?sensor=true_or_false
・google.maps名前空間の導入
v2まではグローバル空間にGMap2、GMarker、GPolyLineというクラスが配置されていたが、v3からはそれぞれgoogle.maps.Map, google.maps.Marker, google.maps.Polylineとなっている。これにあわせていくつかメソッドやプロパティも変更されているので注意するように。
例1:GMarker.getLatLng() → Marker.getPosition()
例2:GeocoderClient.getLocations() → Geocoder.geocode()
(GeocoderはCallbackメソッドのシグネチャと返却される値の構造が変わっているので要注意)
・GBrowserIsCompatible()、GUnload()がない
Referenceで明示的に廃止されたとは記述されていないけれど、Referenceに記述がないのとForumなどの他のユーザーからの報告をもとに類推すると現状上記二つのメソッドに対応するものはない
・StreetViewのサポート
StreetViewの導入がしごく簡単になった。google.maps.MapクラスがStreetViewをサポートするので、Mapクラスの初期化時にオプションでstreetViewControl: trueを渡してやるだけでStreetViewが使用できるようになる。詳細はここに載っているサンプルコードを参照してもらいたい。
クラスの配置場所が変わっているので移行作業が大変そうに映るが思ったよりも大変ではなかった。
・API Keyはいらない
v2までは必須だったAPI Keyはもう必要ない。下記のようにVersionを含まないUrlをScriptタグのソースに指定すれば常に最新のライブラリが参照できるようになっている。
http://maps.google.com/maps/api/js?sensor=true_or_false
・google.maps名前空間の導入
v2まではグローバル空間にGMap2、GMarker、GPolyLineというクラスが配置されていたが、v3からはそれぞれgoogle.maps.Map, google.maps.Marker, google.maps.Polylineとなっている。これにあわせていくつかメソッドやプロパティも変更されているので注意するように。
例1:GMarker.getLatLng() → Marker.getPosition()
例2:GeocoderClient.getLocations() → Geocoder.geocode()
(GeocoderはCallbackメソッドのシグネチャと返却される値の構造が変わっているので要注意)
・GBrowserIsCompatible()、GUnload()がない
Referenceで明示的に廃止されたとは記述されていないけれど、Referenceに記述がないのとForumなどの他のユーザーからの報告をもとに類推すると現状上記二つのメソッドに対応するものはない
・StreetViewのサポート
StreetViewの導入がしごく簡単になった。google.maps.MapクラスがStreetViewをサポートするので、Mapクラスの初期化時にオプションでstreetViewControl: trueを渡してやるだけでStreetViewが使用できるようになる。詳細はここに載っているサンプルコードを参照してもらいたい。
クラスの配置場所が変わっているので移行作業が大変そうに映るが思ったよりも大変ではなかった。
2010年6月5日土曜日
AndroidのエミュレーターでASP.NET Dev Serverのlocalhostにアクセスする方法
Androidアプリをエミュレーター上で実行しWebアクセスするときに、テスト用にlocalhostを接続先に指定すると「Connection to http:localhost refused」とかなんとか言われて接続できない。
この理由はエミュレーター上でlocalhostとはエミュレーター自身を指すので、テスターが期待するようにはエミュレーターを実行しているマシンをルックアップしてくれない。なので解決方法は指定するURLを下記のようにプライベートIPアドレス(PvIP)へと変更しなければならない。
PvIPはコマンドプロンプトなどからipconfigを実行して取得すればよい。
で、このPvIPを指定する場合にASP.NET Development Serverを指定すると動かない。デブサバとはなんぞや、というのは下図のようなASP.NET開発者にはなじみの開発環境用のお手軽自動サーバーのことだ。
仮にデブサバが起動している状態でブラウザからhttp://192.168.0.2:52267/へとアクセスしても、サイトが見つからないと拒絶されてしまう。そのため、デブサバをPvIPで使うのは早々にあきらめて、IISでWebアプリケーションを仮想ディレクトリに登録する必要がある。
「デブサバのlocalhostにアクセスする方法」とこのポストのタイトルにあるが、それは色々と試したけれど無理だったので、もう普通にIISでテストしたいアプリを仮想ディレクトリにしたほうが早い。設定した仮想ディレクトリは下図のようになる。
一番下の項目のstripmeにアクセスするにはhttp://192.168.0.2:56526/となる。これでAndroidのエミュレーターからもHttpGetが正常に期待したサイトへとリクエストされるようになる。
この理由はエミュレーター上でlocalhostとはエミュレーター自身を指すので、テスターが期待するようにはエミュレーターを実行しているマシンをルックアップしてくれない。なので解決方法は指定するURLを下記のようにプライベートIPアドレス(PvIP)へと変更しなければならない。
HttpClient httpclient = new DefaultHttpClient(); HttpGet httpget = new HttpGet("http://192.168.0.2/foo/bar"); HttpResponse response; response = httpclient.execute(httpget); // 以下略(HttpGetのサンプルはこちらを参考にどうぞ:How-to: Android as a RESTful Client)
PvIPはコマンドプロンプトなどからipconfigを実行して取得すればよい。
で、このPvIPを指定する場合にASP.NET Development Serverを指定すると動かない。デブサバとはなんぞや、というのは下図のようなASP.NET開発者にはなじみの開発環境用のお手軽自動サーバーのことだ。
仮にデブサバが起動している状態でブラウザからhttp://192.168.0.2:52267/へとアクセスしても、サイトが見つからないと拒絶されてしまう。そのため、デブサバをPvIPで使うのは早々にあきらめて、IISでWebアプリケーションを仮想ディレクトリに登録する必要がある。
「デブサバのlocalhostにアクセスする方法」とこのポストのタイトルにあるが、それは色々と試したけれど無理だったので、もう普通にIISでテストしたいアプリを仮想ディレクトリにしたほうが早い。設定した仮想ディレクトリは下図のようになる。
一番下の項目のstripmeにアクセスするにはhttp://192.168.0.2:56526/となる。これでAndroidのエミュレーターからもHttpGetが正常に期待したサイトへとリクエストされるようになる。
2010年5月25日火曜日
Desktopをお洒落に管理 Fences
Fencesのフリー版をインストールしたという記事があったので、なんぞ?と思いつつリンク先のビデオを見たら、予想以上に使い勝手がよさげなのでインストールしてみた。
おぉ、デスクトップが綺麗に片付いた。ランチャーを使って起動している人には無用の長物だろうけれど、ハードデスクトップユーザの私にはありがたいツールだ。
おぉ、デスクトップが綺麗に片付いた。ランチャーを使って起動している人には無用の長物だろうけれど、ハードデスクトップユーザの私にはありがたいツールだ。
2010年5月24日月曜日
WPFを難読化しよう ~ Obfuscator
.NETのアセンブリはIL化されているので、普通にコンパイルしただけではアセンブリから逆アセンブルしてコードの中身をそっくりそのまま見れるようになっている。それはもうReflectorなどのツールを使ってしまえばコードの隅々まで手に取るように分かってしまう。
これではコードの知的財産権を意図せずに放棄しているようなもので、重要なビジネスロジックを実装している場合や、ビジネス的に価値のある高度なプログラミングをしている場合などは危なっかしくてアセンブリを頒布することができない。
そのため.NETでは商用のアセンブリなどをリリースする際には、アセンブリを逆アセンブルされても良いように、ILのコードを意味不明な変数名などに置き換えるコード難読化ツール(英語では大体「ほにゃらら obfuscator」という名称が用いられている。obfuscate=分かりにくくする)が利用されてきた。
今回はWPFアプリ用のObfuscatorで良いなと思ったツールを紹介したい。
商用のObfuscatorは大体$100~$400ぐらいと、個人で開発している開発者にとっては結構な値をはるものが多いが機能は異常に豊富だ。フリーのツールでも充分用を足せるものがあるので、ガチガチにかためる必要がない場合は「.net obfuscate free」などでGoogleしてもらいたい。
実はWPFアプリの難読化には一つ問題があり、他の.NETアセンブリと同様に難読化を行うとアプリが起動しなくなる。というのはCode Behindのコードは難読化されるのにXAMLは通常無視されるので起動時にプロパティなりクラスなりが見つからずに例外が発生して終了してしまう。それなのでWPFアプリにはそれに対応したObfuscatorが必要になる。
で、いくつか評価したフリーのツールで一番のお勧めObfuscatorはcodefort.orgだ。実際には完全にフリーなわけではなく、Free版とProfessional版に分かれている。XAMLまで含めた完璧な難読化を行いたい場合はProfessional版を買い求める必要があるが、簡易な難読化でよい場合はFree版でことたりる。使い方もいたって簡単で、難読化したいdllなりexeなりを選択すれば勝手に依存しているアセンブリを探してきてくれて、Buildタブをクリックすると任意のフォルダへ必要最小構成でアプリをパッキングしてくれる。Build後のアセンブリを前述のReflectorなどで参照してみれば変数名等が変更されているのが分かると思う。
これではコードの知的財産権を意図せずに放棄しているようなもので、重要なビジネスロジックを実装している場合や、ビジネス的に価値のある高度なプログラミングをしている場合などは危なっかしくてアセンブリを頒布することができない。
そのため.NETでは商用のアセンブリなどをリリースする際には、アセンブリを逆アセンブルされても良いように、ILのコードを意味不明な変数名などに置き換えるコード難読化ツール(英語では大体「ほにゃらら obfuscator」という名称が用いられている。obfuscate=分かりにくくする)が利用されてきた。
今回はWPFアプリ用のObfuscatorで良いなと思ったツールを紹介したい。
商用のObfuscatorは大体$100~$400ぐらいと、個人で開発している開発者にとっては結構な値をはるものが多いが機能は異常に豊富だ。フリーのツールでも充分用を足せるものがあるので、ガチガチにかためる必要がない場合は「.net obfuscate free」などでGoogleしてもらいたい。
実はWPFアプリの難読化には一つ問題があり、他の.NETアセンブリと同様に難読化を行うとアプリが起動しなくなる。というのはCode Behindのコードは難読化されるのにXAMLは通常無視されるので起動時にプロパティなりクラスなりが見つからずに例外が発生して終了してしまう。それなのでWPFアプリにはそれに対応したObfuscatorが必要になる。
で、いくつか評価したフリーのツールで一番のお勧めObfuscatorはcodefort.orgだ。実際には完全にフリーなわけではなく、Free版とProfessional版に分かれている。XAMLまで含めた完璧な難読化を行いたい場合はProfessional版を買い求める必要があるが、簡易な難読化でよい場合はFree版でことたりる。使い方もいたって簡単で、難読化したいdllなりexeなりを選択すれば勝手に依存しているアセンブリを探してきてくれて、Buildタブをクリックすると任意のフォルダへ必要最小構成でアプリをパッキングしてくれる。Build後のアセンブリを前述のReflectorなどで参照してみれば変数名等が変更されているのが分かると思う。
2010年5月19日水曜日
Bing API 2.0で独自のサーチアプリを作る
複数のキーワードを色々と組み合わせた検索結果が欲しい場合にいちいちその組み合わせごとに入力するのが面倒なのでBing API 2.0を使うアプリを作った。
初めはGoogle Search APIを使おうと思ったのだが、いつの間にだか仕様変更がされていて、Googleから提供されるライブラリを使いクライアントスクリプト上からのみしかアクセスできないようになっていた。それなので、クエリに対してほぼ制限のないBing APIを使うことにした。
Bing APIはかなり使いやすくBing Developer Centerで下図のフォームに必要事項を入力しAppIDを取得しさえすれば、後はBing APIの形式にのっとってHttp GETメソッドで取得すればよい。ちなみにAppIDの取得にはWindwows Live IDが必要なので、ない場合はWindows Live IDを取得してから作業をすすめよう。
(Company Nameなどは適当に入力したが特に問題はなかった)
Bing APIの実際を知るにはSDKをダウンロードしてコードを見るのが一番手っ取り早いが、いくつか解説しておこうと思う。
(Developer CenterでAPI Documentationなどが参照できるので詳細はそちらへ)
基本のクエリは下記のようになる(Bing APIではXMLかJsonで結果を受け取れる。Jsonで取得したい場合はxml.aspxではなくjson.aspxに対してリクエストすることになる)。
http://api.search.live.net/xml.aspx?Appid=&query=漫画&sources=web
一番後ろのパラメータにsourcesを指定しているがBing APIではSourceTypeを指定する必要がある。SourceTypeにはWeb, Image, News, InstantAnswer, Spell, Phonebook, RelatedSearch, Adとあるが下記のように複数組み合わせることも可能だ。
http://api.search.live.net/xml.aspx?Appid=&query=漫画&sources=web+image
いくつかの例を下記に示す。
複数のキーワードを組み合わせる場合
http://api.search.live.net/xml.aspx?Appid=&query=漫画 おすすめ&sources=web
http://api.search.live.net/xml.aspx?Appid=&query=漫画 おすすめ -Amazon&sources=web
検索結果を30件取得する場合
http://api.search.live.net/xml.aspx?Appid=&query=漫画&sources=web&web.count=30
30件の検索結果を、51件目から取得する場合
http://api.search.live.net/xml.aspx?Appid=&query=漫画&sources=web&web.count=30&web.offset=51
ちなみに、web.countは最大で50になる。そして検索結果は最初の1000件まで取得可能だが、web.offsetはweb.count + web.offset <= 1,000となる必要がある。 WebソースタイプのXMLフォーマットのレスポンスサンプルはここで参照できる。レスポンスとして取得したXMLやJsonの解析方法などはSDKで詳細にコーディングされているのでそちらを参照して欲しい。
最後に、Bing APIを使う上で次のようにいくつかの制約がある:検索結果をすべて表示すること、IPアドレスごとに秒間7つまでのリクエストしかできない、SEOのために使ってはならない、Bingを使っていると表示すること、などなど他にもいくつかあるのでAPI Basicsの一番最後の部分をしっかりと読んでから使って欲しい。
初めはGoogle Search APIを使おうと思ったのだが、いつの間にだか仕様変更がされていて、Googleから提供されるライブラリを使いクライアントスクリプト上からのみしかアクセスできないようになっていた。それなので、クエリに対してほぼ制限のないBing APIを使うことにした。
Bing APIはかなり使いやすくBing Developer Centerで下図のフォームに必要事項を入力しAppIDを取得しさえすれば、後はBing APIの形式にのっとってHttp GETメソッドで取得すればよい。ちなみにAppIDの取得にはWindwows Live IDが必要なので、ない場合はWindows Live IDを取得してから作業をすすめよう。
(Company Nameなどは適当に入力したが特に問題はなかった)
Bing APIの実際を知るにはSDKをダウンロードしてコードを見るのが一番手っ取り早いが、いくつか解説しておこうと思う。
(Developer CenterでAPI Documentationなどが参照できるので詳細はそちらへ)
基本のクエリは下記のようになる(Bing APIではXMLかJsonで結果を受け取れる。Jsonで取得したい場合はxml.aspxではなくjson.aspxに対してリクエストすることになる)。
http://api.search.live.net/xml.aspx?Appid=
一番後ろのパラメータにsourcesを指定しているがBing APIではSourceTypeを指定する必要がある。SourceTypeにはWeb, Image, News, InstantAnswer, Spell, Phonebook, RelatedSearch, Adとあるが下記のように複数組み合わせることも可能だ。
http://api.search.live.net/xml.aspx?Appid=
いくつかの例を下記に示す。
複数のキーワードを組み合わせる場合
http://api.search.live.net/xml.aspx?Appid=
http://api.search.live.net/xml.aspx?Appid=
検索結果を30件取得する場合
http://api.search.live.net/xml.aspx?Appid=
30件の検索結果を、51件目から取得する場合
http://api.search.live.net/xml.aspx?Appid=
ちなみに、web.countは最大で50になる。そして検索結果は最初の1000件まで取得可能だが、web.offsetはweb.count + web.offset <= 1,000となる必要がある。 WebソースタイプのXMLフォーマットのレスポンスサンプルはここで参照できる。レスポンスとして取得したXMLやJsonの解析方法などはSDKで詳細にコーディングされているのでそちらを参照して欲しい。
最後に、Bing APIを使う上で次のようにいくつかの制約がある:検索結果をすべて表示すること、IPアドレスごとに秒間7つまでのリクエストしかできない、SEOのために使ってはならない、Bingを使っていると表示すること、などなど他にもいくつかあるのでAPI Basicsの一番最後の部分をしっかりと読んでから使って欲しい。
Mr. Gengo 1.0 リリース
Mr. Gengoがとりあえず形になったのでリリースした。
Mr. Gengo 1.0 at FreewareFiles.com
Mr. Gengo 1.0 at Softpedia
日本人用の英語学習ツールはVectorなどにかなり高機能な素晴らしいものがあるので、日本単語を学びたい外国人用として上記のサイトへリリースした。
初めはFreewareのみに登録したのだが、数日してから突然Softpediaからメールが来て勝手にHostしてくれるようになった。
WPFの勉強用に組んだプログラムなので全然ブラッシュアップされていないが、何か思いついたら機能追加は随時していく予定だ。さしあたってはSilverlight版を作るつもりでいる。
Mr. Gengo 1.0 at FreewareFiles.com
Mr. Gengo 1.0 at Softpedia
日本人用の英語学習ツールはVectorなどにかなり高機能な素晴らしいものがあるので、日本単語を学びたい外国人用として上記のサイトへリリースした。
初めはFreewareのみに登録したのだが、数日してから突然Softpediaからメールが来て勝手にHostしてくれるようになった。
WPFの勉強用に組んだプログラムなので全然ブラッシュアップされていないが、何か思いついたら機能追加は随時していく予定だ。さしあたってはSilverlight版を作るつもりでいる。
2010年5月11日火曜日
UserControlのCode behindをDataContextに指定する方法
一つ前のポストでUserControlを独自の基本クラスから継承する方法を解説したが、そのクラスをDataContextに指定する方法を解説する。
と言ってもやることは至極簡単で下記のようにDataContextに対してRelativeSource Selfを指定するだけだ。
<localModel:BaseUserControl x:Class="MyWebCrawler.Controls.Keyword"
xmlns:localModel="clr-namespace:MyWebCrawler.Models"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
</localModel:BaseUserControl>
これでそのXAML自身をDataContextとすることができたので基本クラスなどに実装されているPropertyをBinding先として指定できる。ただ、DataContextに指定するクラスがINotifyPropertyChangedを継承するか、またはDependency Propertyを使用することを忘れないように注意しよう。
と言ってもやることは至極簡単で下記のようにDataContextに対してRelativeSource Selfを指定するだけだ。
<localModel:BaseUserControl x:Class="MyWebCrawler.Controls.Keyword"
xmlns:localModel="clr-namespace:MyWebCrawler.Models"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
</localModel:BaseUserControl>
これでそのXAML自身をDataContextとすることができたので基本クラスなどに実装されているPropertyをBinding先として指定できる。ただ、DataContextに指定するクラスがINotifyPropertyChangedを継承するか、またはDependency Propertyを使用することを忘れないように注意しよう。
UserControlを独自の基本クラスから継承する方法
下図のように似たような構造のUI要素が多い場合は、UserControlを作るにしてもまとめた機能を基本クラスにおきたくなる。
しかし、Code behindで継承元をUserControlから独自の基本クラス(BaseUserControl)へと下記のように変更しても「Partial Declaration may not have different base classes.」とコンパイルエラーが発生する。
この問題の解決方法はXAMLを下記のように変更する必要がある。
<localModel:BaseUserControl x:Class="MyWebCrawler.Controls.Keyword"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:localModel="clr-namespace:MyWebCrawler.Models">
</localModel:BaseUserControl>
しかし、Code behindで継承元をUserControlから独自の基本クラス(BaseUserControl)へと下記のように変更しても「Partial Declaration may not have different base classes.」とコンパイルエラーが発生する。
// これだけじゃ駄目 public partial class Keyword : BaseUserControl{ }
この問題の解決方法はXAMLを下記のように変更する必要がある。
<localModel:BaseUserControl x:Class="MyWebCrawler.Controls.Keyword"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:localModel="clr-namespace:MyWebCrawler.Models">
</localModel:BaseUserControl>
2010年5月6日木曜日
カスタムActionFilter その3
数ヶ月前にカスタムActionFilterの解説をしたが、今回はその補足を。
前回解説したものはAuthorizeAttributeから派生し、認証していないリクエストを他のURLへリダイレクトするというものだった。そのリダイレクトする箇所のコードは下記の通り。
が、ASP.NET MVC 2にアップグレードしたら上記のコードが期待通りに動作しなくなったのでGoogleした結果、下記のように書き換える必要があった。
これで期待通りに指定のURLへリダイレクトされるはずだ。
前回解説したものはAuthorizeAttributeから派生し、認証していないリクエストを他のURLへリダイレクトするというものだった。そのリダイレクトする箇所のコードは下記の通り。
string loginUrl = RedirectUrl(); filterContext.HttpContext .Response.Redirect(loginUrl, true);
が、ASP.NET MVC 2にアップグレードしたら上記のコードが期待通りに動作しなくなったのでGoogleした結果、下記のように書き換える必要があった。
string loginUrl = RedirectUrl(); filterContext.Result = new RedirectResult(loginUrl);
これで期待通りに指定のURLへリダイレクトされるはずだ。
2010年5月1日土曜日
Areaを使ってみる その2
今回はArea間をまたいだリンクの解説をする。
前回追加したAreaのリンク(http://www.my-clip.net/Sandbox/MrGengo/Index/)は下記のようになる。
4つ目の引数でAreaを渡していることに注目して欲しい。ついでSandboxからDefaultのAreaへリンクする場合は下記のようになる。
Area=""でDefaultのAreaへの参照となる。
正直他にも書こうとした内容があったので2回に分けたのだが、思いのほか役に立ちそうにない情報なので今回は予想以上に短いブログポストになってしまった。
前回追加したAreaのリンク(http://www.my-clip.net/Sandbox/MrGengo/Index/)は下記のようになる。
Httml.ActionLink("Link Text", "Index", "MrGengo", new { Area = "Sandbox" }, null });
4つ目の引数でAreaを渡していることに注目して欲しい。ついでSandboxからDefaultのAreaへリンクする場合は下記のようになる。
Httml.ActionLink("Link Text", "Index", "MrGengo", new { Area = "" }, null });
Area=""でDefaultのAreaへの参照となる。
正直他にも書こうとした内容があったので2回に分けたのだが、思いのほか役に立ちそうにない情報なので今回は予想以上に短いブログポストになってしまった。
2010年4月28日水曜日
Areaを使ってみる その1
ASP.NET MVC 2の新しい機能であるAreaを使って、新しいエイリアスを追加した。今回はその方法を解説しよう。
Area自体を追加するのは至極簡単。Areaを追加したいプロジェクトを右クリック→Add→Areaで下図のダイアログが表示されるので名前を適当に入力してOK。
入力した名前のフォルダがAreasフォルダの下に下図のようにできる。
初期状態でControllers, Models, Viewsとフォルダがあるが、Viewsを除いてすべて空っぽだ。注目したいのはSandboxAreaRegistration.csだ。このクラスで追加したArea用のAreaNameとRouteを設定する。
最後に、とても重要なのだが、Global.asaxに下記のコードを追加する必要がある。
このコードを忘れると追加したAreaのRouteが登録されないので注意しよう。AreaRegistration.RegisterAllAreas()が呼ばれるとフレームワークがAreaRegistrationを継承したクラスがないかを探し回ってRouteを登録していくという寸法だ。以上でAreaの追加は完了だ。適当なControllerを新しく追加したAreaに登録し下記のようにアクセスしてみよう。
従来のリンク
http://www.my-clip.net/Clip/Index/
新しいAreaへのリンク
http://www.my-clip.net/Sandbox/MrGengo/Index/
次回はArea間のActionLinkなどについて解説する。
Area自体を追加するのは至極簡単。Areaを追加したいプロジェクトを右クリック→Add→Areaで下図のダイアログが表示されるので名前を適当に入力してOK。
入力した名前のフォルダがAreasフォルダの下に下図のようにできる。
初期状態でControllers, Models, Viewsとフォルダがあるが、Viewsを除いてすべて空っぽだ。注目したいのはSandboxAreaRegistration.csだ。このクラスで追加したArea用のAreaNameとRouteを設定する。
最後に、とても重要なのだが、Global.asaxに下記のコードを追加する必要がある。
public static void RegisterRoutes(RouteCollection routes) { AreaRegistration.RegisterAllAreas(); }
このコードを忘れると追加したAreaのRouteが登録されないので注意しよう。AreaRegistration.RegisterAllAreas()が呼ばれるとフレームワークがAreaRegistrationを継承したクラスがないかを探し回ってRouteを登録していくという寸法だ。以上でAreaの追加は完了だ。適当なControllerを新しく追加したAreaに登録し下記のようにアクセスしてみよう。
従来のリンク
http://www.my-clip.net/Clip/Index/
新しいAreaへのリンク
http://www.my-clip.net/Sandbox/MrGengo/Index/
次回はArea間のActionLinkなどについて解説する。
2010年4月27日火曜日
Html.RenderPartialのIntelliTraceがExceptionで大変
Visual Studio 2010のIntelliTraceを使い始めてまず目につくのが下図のようなExceptionの情報だと思う。
Html.RenderPartial("hogehoge")でascxを描写している箇所でFileNotFoundExceptionがいくつも放り出されている。おいおいまじかよ、これどうなってんの?と焦るかもしれないが、これは決して深刻な問題ではない。この現象はデバッグ時にASP.NET MVCがViewのパス解決をキャッシュしないために毎度発生している。なのでweb.configのcompilation debugをfalseにしてやればこの問題は発生しない。
<compilation debug="false">
ちなみにWebアプリをリリースする際にはdebugモードは常にfalseにするように心がけよう。さもなければ、下記のようなデメリットにさいなまれることになる。
仮にひとつひとつのWebアプリケーションをチェックするのが面倒ならば、machine.config上に下記のセクションを追加してやれば、そのマシン上のWebアプリケーションはすべて強制的にリリースモードで実行される。
<configuration>
<system.web>
<deployment retail=”true”/>
</system.web>
</configuration>
Html.RenderPartial("hogehoge")でascxを描写している箇所でFileNotFoundExceptionがいくつも放り出されている。おいおいまじかよ、これどうなってんの?と焦るかもしれないが、これは決して深刻な問題ではない。この現象はデバッグ時にASP.NET MVCがViewのパス解決をキャッシュしないために毎度発生している。なのでweb.configのcompilation debugをfalseにしてやればこの問題は発生しない。
<compilation debug="false">
ちなみにWebアプリをリリースする際にはdebugモードは常にfalseにするように心がけよう。さもなければ、下記のようなデメリットにさいなまれることになる。
- 初期ページコンパイルにより時間がかかる(いくつかの最適化バッチが走らないので)
- 実行スピードの低下(デバッグ情報が追加されるので)
- 使用メモリの増大
- スクリプトと画像のキャッシュ無効化
仮にひとつひとつのWebアプリケーションをチェックするのが面倒ならば、machine.config上に下記のセクションを追加してやれば、そのマシン上のWebアプリケーションはすべて強制的にリリースモードで実行される。
<configuration>
<system.web>
<deployment retail=”true”/>
</system.web>
</configuration>
2010年4月15日木曜日
WPF:デザインビューでカスタムコントロールがロードされない
WPFでカスタムコントロールを作成し、それをPage(またはWindow)に貼り付けると、Pageのデザインビューでカスタムコントロールが正しくロードされずに下図のような状態になることがままある。
で、エラーも一緒に検出されていて「Could not create an instance of 'Your control'」とロードに失敗したカスタムコントロールはこれだ!と指摘してくれている。が、個々のカスタムコントロールのデザインビューでは正常にUIを確認できるため、なぜPageに貼り付けた途端に動作しなくなるのかは皆目検討がつかない。それなので長々とGoogleした結果、やっと正解にたどり着いた。
Troubleshooting WPF Designer load failures
理由は色々と考えられるようなので、かなりの確認事項があるけれど、Expression Blendなんかを使ってデザイナーと共同作業する人はデザインビューが使えないと死亡遊戯だと思うので一読をおすすめする。
ちなみに私の場合は、別ファイルに保存してApp.xamlで統合されているResourceDictionaryがデザインタイムにロードできないせいで読み込みエラーが発生していた。そのような場合は個々のカスタムコントロールのXAMLにResourceDictionaryへの参照を以下のように追加してやれば良い。
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/MainWindowRes.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
て、あああああああ、XAMLの構成を確認しようとしたらまたVS2008がハングった。むかつく。こいつはApp.xamlをいじるとすぐにハングしやがる。VS2010では解決済みらしいけれど、VS2010にファイルをアップデートするとExpression Blend 2が動かないというジレンマ。
で、エラーも一緒に検出されていて「Could not create an instance of 'Your control'」とロードに失敗したカスタムコントロールはこれだ!と指摘してくれている。が、個々のカスタムコントロールのデザインビューでは正常にUIを確認できるため、なぜPageに貼り付けた途端に動作しなくなるのかは皆目検討がつかない。それなので長々とGoogleした結果、やっと正解にたどり着いた。
Troubleshooting WPF Designer load failures
理由は色々と考えられるようなので、かなりの確認事項があるけれど、Expression Blendなんかを使ってデザイナーと共同作業する人はデザインビューが使えないと死亡遊戯だと思うので一読をおすすめする。
ちなみに私の場合は、別ファイルに保存してApp.xamlで統合されているResourceDictionaryがデザインタイムにロードできないせいで読み込みエラーが発生していた。そのような場合は個々のカスタムコントロールのXAMLにResourceDictionaryへの参照を以下のように追加してやれば良い。
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/MainWindowRes.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
て、あああああああ、XAMLの構成を確認しようとしたらまたVS2008がハングった。むかつく。こいつはApp.xamlをいじるとすぐにハングしやがる。VS2010では解決済みらしいけれど、VS2010にファイルをアップデートするとExpression Blend 2が動かないというジレンマ。
2010年4月13日火曜日
Visual Studio 2010がリリース
MSDNの購読者ではなくともフリーのVisual Studio 2010 ExpressはこちらからDownloadできる(Expressは機能が個々に分割されているので要件にあわせて取得する必要がある。例:C#でWindows Formsアプリを作るならVisual C# 2010 Expressが必要。Webアプリを作るならVisual Web Developer 2010 Expressが必要、という按配)。
新機能をざっと紹介しようかとも思ったのだが、あまりにもたくさん機能があるので断念。代わりに良いブログポストがあったので下記に紹介しておく。
Visual Studio 2010 Released
新機能をざっと紹介しようかとも思ったのだが、あまりにもたくさん機能があるので断念。代わりに良いブログポストがあったので下記に紹介しておく。
Visual Studio 2010 Released
2010年4月12日月曜日
Visual Studio 2010のJavaScriptインテリセンスがすごい
ScottGuのこのブログポスト(JavaScript Intellisense Improvements with VS 2010)がやばい。内容はVS 2010のJavaScriptに対するインテリセンスがすごいですよー、という話なのだが、確かにすごい。
ざっと説明すると、コーディングをしている間にVSが適時コード解析を行うので、変数を宣言して、何かしらの値を割り当てると、その値の型に基づいたインテリセンスをダイナミックに提供してくれる、という機能。
言葉で説明してもいまいち分からないと思うので、前述のブログポストからいくつかコードを抜粋させていただいた。
(前述のブログポストにインテリセンスの使用風景が画像入りで説明がされているので、そちらを見た方がもっとわかりやすいと思う)
他にも、VS2008で対応されていたDOMのインテリセンスだけではなく、windowオブジェクトに対する動的変数、動的関数までもコーディングしていく端からインテリセンスが有効になるからたまらん。すごすぎる。早く使いたい。
ざっと説明すると、コーディングをしている間にVSが適時コード解析を行うので、変数を宣言して、何かしらの値を割り当てると、その値の型に基づいたインテリセンスをダイナミックに提供してくれる、という機能。
言葉で説明してもいまいち分からないと思うので、前述のブログポストからいくつかコードを抜粋させていただいた。
var foo = "Some string value"; foo. // このときにstring用のインテリセンス(charAt, concatなど)が提供される foo = 35; // 数字を代入する foo. // ここではnumber用のインテリセンス(toPrecision, toStringなど)が提供される
(前述のブログポストにインテリセンスの使用風景が画像入りで説明がされているので、そちらを見た方がもっとわかりやすいと思う)
他にも、VS2008で対応されていたDOMのインテリセンスだけではなく、windowオブジェクトに対する動的変数、動的関数までもコーディングしていく端からインテリセンスが有効になるからたまらん。すごすぎる。早く使いたい。
2010年4月8日木曜日
EncodingとDecodingをクライアントアプリで行う .NET Framework 4.0
.NET Frameworkを使ったWindowsアプリで何かWeb用のデータなどを編集するようなものやURLをいじくるものの場合はEncoding、Decodingがしたくなるものだ。で、ASP.NETを使っている開発者ならばすぐにSystem.Web.dllにあるSystem.Web.HttpUtilityへと思いが飛び、HttpUtility.HtmlEncode(string), HttpUtility.HtmlDecode(string)を使おうとするのだが、Windowsアプリの初期状態のプロジェクトテンプレートにはSystem.Web.dllが含まれていないのでインテリセンスでは当然ながら見つからず、「あれ?あのユーティリティどこ(の名前空間)にあったっけ?」と思い出すまでやきもきするのだが、.NET Framework 4.0からはSystem.dllのSystem.Net名前空間にWebUtilityクラスが実装されたのでそのモヤモヤともついにおさらばだ。
WebUtilityのMSDN
以前からクライアントアプリなのにEndoding, DecodingするためだけにASP.NETのアセンブリを参照することに抵抗があったのでSystem.dllにWebUtilityが実装されて嬉しい限りだ。ちなみに今回の情報は下記のサイトから。
Encoding/Decoding URIs and HTML in the .NET 4 Client Profile
WebUtilityのMSDN
以前からクライアントアプリなのにEndoding, DecodingするためだけにASP.NETのアセンブリを参照することに抵抗があったのでSystem.dllにWebUtilityが実装されて嬉しい限りだ。ちなみに今回の情報は下記のサイトから。
Encoding/Decoding URIs and HTML in the .NET 4 Client Profile
2010年3月31日水曜日
jQueryでUIをブロックできるPlugin
jQueryでUIをブロックできるPluginを解説する。使ってみた感じは下図を参照して欲しい。
全体的に薄いグレーになっていて真ん中にメッセージが出ているのが見てとれると思うが、今回解説するPluginを使用するとそれが実現可能だ。使い方はいたって簡単。こちらからjquery.blockUI.jsをダウンロードして、下記のコードをペペッとscriptタグ内に貼り付ければ良いだけ。
他にも多様な使い方があるのでデモページを参照して欲しい。
全体的に薄いグレーになっていて真ん中にメッセージが出ているのが見てとれると思うが、今回解説するPluginを使用するとそれが実現可能だ。使い方はいたって簡単。こちらからjquery.blockUI.jsをダウンロードして、下記のコードをペペッとscriptタグ内に貼り付ければ良いだけ。
jQuery(document).ready(function() { $.blockUI({ title: 'Tips', message: 'メッセージだよー', css: { padding:'20px' } }); $('.blockOverlay').attr('title', 'クリックで解除').click($.unblockUI); });
他にも多様な使い方があるのでデモページを参照して欲しい。
登録:
投稿 (Atom)