WPF で Google Map その 5
WPF で Google Maps API を利用するシリーズ、その 5。
今回は複数マーカーを作成してみる。マーカーの状態が変更される度に JavaScript からマネージ コード側へ通知している。このプログラムを応用すれば、ちょっとした Geo タグ編集アプリを実装できるかもしれない。
サンプル プログラム
前回のサンプルからの変更点。
- 複数マーカーの生成に対応
- 設計をより MVVM 的に変更
サンプル プログラムのプロジェクト一式は以下。ビルドには Visual Studio 2008 SP1、プログラムの実行には NET Framework 3.5 SP1 とネットワーク接続された環境が必要となる。
マーカー識別
Google Maps API は複数マーカーの作成をサポートしている。しかし個別のマーカー識別はは自前で管理しなければならない。今回は google.maps.Marker に id という整数値プロパティを追加し、生成ごとに一意の番号を割り当てることにした。
var markers = new Array(); //! マーカーのコレクション。
var nextID = 0; //! 次に割り当てられるマーカーの識別子。
// Marker に id プロパティを追加 ( 初期値は無効値 )
google.maps.Marker.prototype.id = -1;
/**
* マップ上にマーカーを追加します。
* マーカーの初期位置は、現在表示されているマップの中央座標となります。
*/
function addMarker()
{
var center = map.get_center();
var marker = new google.maps.Marker( { position: center, map: map, title: "マーカー", draggable: true } );
marker.id = nextID++;
markers[ markers.length ] = marker;
// ...以下、省略。
}
マーカーのインスタンスを生成する度に id プロパティに nextID を割り当て、その後に nextID をインクリメントさせている。
生成されたマーカーは markers という配列の末尾に追加している。id を添え字とした連想配列にする事も考えたが、今回のプログラムでマーカーの検索が必要なのは、マーカー位置への移動と削除ぐらいなので、通常の配列にしておいた。
マネージ コードへの更新通知
前回までは JavaScript からマネージ コードのクラスのプロパティを書き換えていたが、今回はプロパティの代りにメソッド呼び出しで更新を通知する。
マネージ コードと JavaScript の連携については、過去記事に解説したので今回は触れない。既に連携用クラスが作成済みであると仮定して、そこへ以下のメソッドを実装する。
/// <summary>
/// マップの移動が行われた時、スクリプト側から呼び出されます。
/// </summary>
/// <param name="latitude">緯度。</param>
/// <param name="longitude">経度。</param>
public void OnMapMoved( string latitude, string longitude )
{
}
/// <summary>
/// マーカーが追加された時、スクリプト側から呼び出されます。
/// </summary>
/// <param name="id">マーカーの識別子。</param>
/// <param name="latitude">緯度。</param>
/// <param name="longitude">経度。</param>
public void OnMarkerAdded( int id, string latitude, string longitude )
{
}
/// <summary>
/// マーカーの選択状態が変更された時、スクリプト側から呼び出されます。
/// マーカーの選択は常に単体のみとなります。
/// </summary>
/// <param name="id">選択されたマーカーの識別子。選択されているものがない場合は -1。</param>
public void OnMarkerSelectionChanged( int id )
{
}
/// <summary>
/// マーカーが移動された時、スクリプト側から呼び出されます。
/// </summary>
/// <param name="id">マーカーの識別子。</param>
/// <param name="latitude">緯度。</param>
/// <param name="longitude">経度。</param>
public void OnMarkerMoved( int id, string latitude, string longitude )
{
}
/// <summary>
/// マーカーが削除された時、スクリプト側から呼び出されます。
/// </summary>
/// <param name="id">マーカーの識別子。</param>
public void OnMarkerRemoved( int id )
{
}
各メソッドはマーカー操作時に JavaScript から呼び出すイベント ハンドラとなる。これらの内部でマネージ コードのデータ設定や更新通知を処理する。
プログラムの実行
プログラムを実行すると以下のようになる。
余談だが、今回は操作ボタンにアイコンを採用してみた。細かな操作説明はツールチップでおこなっている。
アイコンは Microsoft Expression Design を使用した Path データになる。Expression で作成した図形を XAML として出力して Path 部分をメイン ウィンドウの XAML にコピー & サイズ調整している。
反省点など
今回のプログラム開発で苦戦したところをまとめておく。
MVVM
マネージ コードと JavaScript を連携させる場合、WebBrowser コントロールと WebBrowse.ObjectForScripting に設定するクラスが必要となる。
MVVM 的には前者が View、後者が Model になるわけだが、これらの分離に苦労した。一応、WebBrowser から ViewModel と Model へ触れないようにしたが、まだ改善の余地があると思う。
JavaScript で定数が使えない
JavaScript では const を付けて変数を宣言する事で定数を定義できるのだが、IE は対応しておらず定数を含んだスクリプトを実行すると構文エラー 800A03EA
が発生する。WPF の WebBrowser は IE 相当なので不可避となる。そのためスクリプト内でマジック ナンバーが登場する事になり、可読性が低下してしまった。
この問題はマジック ナンバーの使用箇所を関数化する事で緩和できそうだが、今回は数ヶ所ほどので止めておく。書き換えは防げなくても定数的に扱う、とするだけで十分なのかもしれないが。
Google Map のスライダが簡略版になる
Google Map の伸縮スライダはマップの「初期」表示サイズの高さが少ないと簡略版になるようだ。そのため、前回の記事でも簡略版になっている。これを踏まえて今回のプログラムではウィンドウ全体のサイズを拡大し、ブラウザ部分を広げてみた。