宇宙仮面のC# プログラミング

 
Image

簡単なモバイルアプリケーション開発9
Amedas 情報の表示 (WZero3DeAmedas V2.0)

開発環境: Visual Studio 2005/2008

1.目次

1.目次
2.目的
3.参考書
4.作り方
5.ダウンロード
6.ソースコード
7.まとめ

2.目的

簡単なモバイルアプリケーション開発7 Amedas 情報の表示を 作りましたが、予想外に反響が大きく、地域などのプリセット、画像の拡大縮小、画像の保存位置情報等いくつか要望があがってきました。もともと、そこまで作るつもりはなかったのですが、自分自身でもこの1週間使ってみたところ、結構使えるので、 設定の保存ぐらいしたくなり、V2.0 で拡張することにしました。と、予告してから後悔している今日この頃・・・

画像情報の拡大縮小、画像情報の保存を実施しようと思うと、WebBrowser コントロールでブラウズする方法では対応できません。このため、そもそもの設計を変更し、気象情報を画像としてダウンロードして、PictureBox コントロールに表示させる必要が出てきます。この場合、URL を組み立てて、ファイルを取得して、あとはその画像のビューアーを作るという形になります。しかし、ファイルに落とすには、わかりやすい名前でファイルをダウンロードする必要があります。そのためには、種類、地域、時間情報からファイル名を作成し、再度オープンするような仕組みが必要になります。 このような画像を表示するような実装に変更するとなると、画像ビューアーを作ることになるので、かなりの拡張が必要になります。

画像は次のように固定倍率ですが、拡大・縮小と縦横の移動も可能にします。ちなみに、次の画像は 気象衛星→全球→可視光 で、お気に入り。

Image Image Image
70% 100% 150%
Image Image Image
200% 300% 縦横移動も可能

おまけに、拡張しているうちに、海釣りに行くことになり、夏なので紫外線も知りたいとか、台風5号が発生しそうなので衛星写真も全球、北半球を見たい、 津波があっても怖いので地震も知りたい、見れるものは何でも見たいと、ほとんどお天気フェチに走ってしまいました。w

このため、結局当初の想定を大幅に超えて、以下の拡張を行いました。

  1. 種類、位置情報の保持
  2. 画像情報の拡大縮小
  3. 画像情報の保存
  4. 画像キャッシュの利用
  5. さらに、以下の情報を表示できるようにしました。
  • 天気予報
     天気予報
     天気予報(翌日)・・・追加
     天気予報(翌々日)・・・追加
  • レーダー・降水ナウキャスト
  • アメダス
     降水量、気温、日照時間、風向・風速・積雪深
  • 気象衛星
     全球(赤外線、可視光、水蒸気)・・・追加
     北半球(赤外線、可視光、水蒸気)・・・追加
     日本(赤外線、可視光、水蒸気)
  • 台風情報
  • 警報→海上警報・・・追加
  • 紫外線情報
     予測: 今日・明日の紫外線・・・追加
     予測: 今日・明日の晴天時紫外線・・・追加
     解析: 昨日の紫外線・・・追加
  • 天気図
     実況天気図・・・追加
     24時間予報図・・・追加
     48時間予報図・・・追加
     実況天気図(アジア)・・・追加
  • その他
     ウィンドプロファイラ
     黄砂観測実況図・・・追加
     黄砂予測図(地表付近の黄砂の濃度) ・・・追加
     黄砂予測図(大気中の黄砂の総量) ・・・追加
  • 地震情報
     震度速報・・・追加
     震源に関する情報・・・追加
     震源・震度に関する情報・・・追加
     各地の震度に関する情報・・・追加
     遠隔地震に関する情報・・・追加
  • 津波予報:津波警報・注意報・・・追加

注意:

  • このアプリケーションによる気象情報は、すべての気象庁の防災気象情報をカバーしていませんので、クリティカルな用途には使わないでください。
  • 気象庁の防災気象情報の気象情報URLが変更になってしまうと、動かなくなってしまいます。

3.参考書

(1) 気象庁の防災気象情報

4.作り方

 

4.1 時間との戦い

さて、これだけ機能を追加しようと思うと、さすがに作り方をステップバイステップで解説するのは気が遠くなるので、わかりにくいクラスの概要を説明して、あとは ソースコード、インストーラを一式つけておきます。

何が、大変だったかというと、やはり画像を取り出すための URL に規則性がなく、それを個別に実装していくと、いたずらに同じようなコードが並ぶことになります。そこで、どこまで一般化できるか(するかどうか)が悩みました 。あまり凝った構成にすると全体が把握しづらくなるし、べたに書くとコードのメインテナンス性が落ちるし、ちょうどいい構成が難しいですね。

URLに関しては、たとえばアメダスの降水量であれば、

"http://www.jma.go.jp/jp/amedas/imgs/rain/{0}/{1}.png"

のように場所と時間がパラメータになります。時間は、過去から現在まで1時間おきです。

一方、警報は、次のように場所のみで、時間のパラメータは取りません。

"http://www.jma.go.jp/jp/warn/imgs/{0}/99.png"

また、予測: 紫外線はさらに複雑で、場所と時間のパラメータを取りますが、昼間と、18時〜翌日の6時の夜間では、与える時間のパラメータが違います。

"http://www.jma.go.jp/jp/uv/imgs/uv_color/forecast/{0}/{1}.png"

さらに、天気図では、24時間予報図、48時間予報図、実況天気図(アジア)では、過去3時間おき(3, 6, 9,・・・)であったり、過去6時間おき (3, 9, 15, 21)であったり、過去12時間おき (9, 21)であったり、と様々です。おまけに、これらの時間が、気象情報の表示種類が変わるごとに変更しなければなりません。

これらを単純に1つのメソッドで実装してしまうと、if...else if .... else if ... みたいになるか、switch が延々と並び、さらにそれらをメニューに登録しなければならず、とんでもないコードになります。

URLの時間のパラメータを見てみると、最低限 メニューに表示する文字列、url に時間パラメータを渡すための文字列(TimeCommand)があればよいことがわかります。しかし、MenuItem には、この時間パラメータを渡すための文字列(TimeCommand)を持たす場所がありません。そこで、MenuItemを継承して、MenuItemTime クラスを作成し、そこにTimeCommandを保持するようにします。

public class MenuItemTime : MenuItem
{
 
private string timeCommand;
 
public string TimeCommand
  {
   
get { return timeCommand; }
   
set { timeCommand = value; }
  }

 
public MenuItemTime(string menuText, string timeCmd)
  {
    Text = menuText;
    TimeCommand = timeCmd;
  }
}

Image

このMenuItemTimeを MenuItemCollection クラスに追加して、Menu.MenuItems に直接セットすることができるととても楽なのですが、このメソッドが用意されていません。しかたないので、MenuItemTimeの配列を用意し、それを順番にMenu.MenuItems に Add してく必要があります。

そこで、MenuItemCollection のかわりに、List<MenuItemTime> のジェネリックスを使用して、一度このリストに時間メニューの値を用意し、次にMenu.MenuItems にセットするという方法を使います。そのために、まず List<MenuItemTime> のラッパークラスとして、ListMenuItemTime クラスを次のように定義します。このメソッドとしては、時間経過に伴って、時間メニューを更新しなければならないので、そのためのUpdateメソッド、それからMenu に値をセットするための、SetTimeMenu(Menu menu, EventHandler eh) を定義します。ClearMenuCheck(Menu menuItem) は、メニューのチェックをまとめて外すための補助メソッドです。

public class ListMenuItemTime
{
 
protected List<MenuItemTime> timeList = new List<MenuItemTime>();
 
public void Set(List<MenuItemTime> tList)
  {
    timeList = tList;
  }

 
/// <summary>
  /// 時間のリストList<MenuItemTime>を更新する。
 
/// </summary>
  virtual public void Update()
  {
    timeList.Clear();
  }

 
/// <summary>
  /// メニューに時間をセットする
  /// </summary>
  /// <param name="menu">セットするメニュー</param>
  /// <param name="eh">メニューのイベントハンドラ</param>
  /// <returns></returns>
  virtual public string SetTimeMenu(Menu menu, EventHandler eh)
  {
    menu.MenuItems.Clear();

   
foreach (MenuItemTime at in timeList)
    {
     
int i = menu.MenuItems.Add(at);
      menu.MenuItems[i].Click +=
new System.EventHandler(eh);
    }
    ClearMenuCheck(menu);

   
return null;
  }

 
virtual public string GetDefaultTimeCommand() { return null; }

 
protected void ClearMenuCheck(Menu menuItem)
  {
   
// メニューのチェックをクリアする
    foreach (MenuItem mi in menuItem.MenuItems)
    {
     
if (mi.Checked) mi.Checked = false;
     
if
(mi.MenuItems.Count != 0)
        ClearMenuCheck(mi);
    }
  }
}

あとは、それぞれの気象情報に合わせて、ListMenuItemTime というリストを作成します。まとめると次のような構成になります。

public class TimeListNormal : ListMenuItemTime
{
 
/// <summary>
  /// レーダー・降水ナウキャスト以外の場合の時間メニューを作成
  /// 過去12時間まで1時間刻み
  /// </summary>
  public override void Update()
  {
   
base.Update();

   // 中略
  
for (int i = 0; i < 12; i++)
   {
     // 過去12時間まで1時間刻みで追加する。
   
DateTime t = baseHour.Subtract(new TimeSpan(i, 0, 0));
   
string timeCmd = t.ToString("yyyyMMddHHmm") + "-00";
    timeList.Add(
new MenuItemTime(t.ToString("HH:mm"),timeCmd));
  }
}

  /// <summary>
  ///
メニューに時間をセットする
  /// </summary>
  public override void SetTimeMenu(Menu menu, EventHandler eh)
  {
    base.SetTimeMenu(menu, eh);

    timeList[0].Checked =
true;
  }

 
public override string GetDefaultTimeCommand()
  {
   
return
timeList[0].TimeCommand;
  }
}

このように各時間メニューごとに ListMenuItemTimeから継承したクラスを作成します。(下図)

Image

このクラスダイアグラムは一部で、全体としてはこのぐらい(下図)になります。

Image

ファイルは、ClassMenuTime.cs にまとめてあり(下図)、500行程度です。

Image

このままだと、これらのインスタンスがバラバラなので、MenuItemHanderクラスでインスタンスをまとめて管理します。(下図)

Image

4.2 地域メニュー

幸い、地域メニューは地方だけに絞り込んだので、シンプルにすみました。時間メニューと同様に、MenuItemを継承して、locationCode を持たせた MenuItemLocation クラスを作成します(下図)。これをMenuLocation クラスで、場所メニューに追加するだけです。

Image

using System;
using System.Windows.Forms;
namespace Uchukamen.WZero3.WZero3DeAmedas
{
 
public class MenuItemLocation : MenuItem
  {
   
private string locationCode;
   
public string LocationCode
    {
     
get { return locationCode; }
     
set { locationCode = value; }
    }

   
public MenuItemLocation(string locCode, string name, EventHandler eh)
    {
      locationCode = locCode;
      Text = name;
      Click +=
new System.EventHandler(eh);
    }
  }

 
public class MenuLocation
 
{
   
public void Set(MenuItem mi, EventHandler eh)
    {
      mi.MenuItems.Add(
new MenuItemLocation("000", "全国", eh));
      mi.MenuItems.Add(
new MenuItemLocation("201", "北海道地方(北西部)", eh));
      中略
      mi.MenuItems.Add(
new MenuItemLocation("219", "宮古・八重山地方"
, eh));
    }
  }
}

4.3 種類メニュー

種類メニューも基本的に地域メニューと同じ考え方で、V1.2.0.0から大きな変更はありません。

各MenuItemは、画像をアクセスするための Url の文字列フォーマット UrlString と、その文字列フォーマットに位置情報が必要かどうか(bool LocationMenu)、場所情報が必要かどうか(bool TimeMenu)を持たせます。

Image

種類に関するメニューは、MenuForType クラスが管理します(下図)。ここで、SetAmedasMenu で気象関連の種類のメニューを追加し、SetEarthQuakeMenu メソッドで地震関連の種類のメニューを追加します。

Image

using System;
using
System.Collections.Generic;
using System.Windows.Forms;

namespace Uchukamen.WZero3.WZero3DeAmedas
{
 
public class MenuItemType : MenuItem
 
{
   
private bool timeMenu;
   
public bool TimeMenu
    {
     
get { return timeMenu; }
     
set { timeMenu = value
; }
    }
    中略

    public MenuItemType(string name, bool loc, bool time,
     
ListMenuItemTime tList, EventHandler eh, string urlStr)
    {
      Text = name;
      locationMenu = loc;
      timeMenu = time;
      urlString = urlStr;
      timeList = tList;
      Click +=
new System.EventHandler(eh);
    }
  }

 
public class MenuForType
  {
   
private MenuTimeHander menuTimeHander;

   
public MenuForType(MenuTimeHander mth)
    {
      menuTimeHander = mth;
    }

  //
 
public void SetAmedasMenu(MenuItem mi, EventHandler eh)
  {
    // 天気予報のトップメニュー
   
int iobs = mi.MenuItems.Add(new MenuItemType(
     
"天気予報", false, false, null, eh, null));
    // 天気予報のサブメニュー
    mi.MenuItems[iobs].MenuItems.Add(
new MenuItemType(
       
"天気予報", true, false, null, eh,
       
"http://www.jma.go.jp/jp/yoho/images/g1/{0}_telop_today.png"));
    中略
    mi.MenuItems[ietc].MenuItems.Add(
new MenuItemType(
       
"黄砂観測実況図", false, true, menuTimeHander.timeListKosa, eh,
       
"http://www.jma.go.jp/jp/kosa/imgs/kosaobs/000/{0}.png"));
    }
  }

  // 地震関連のメニューを追加
 
public void SetEarthQuakeMenu(MenuItem mi, EventHandler eh)
  {
    //地震のトップメニュー
   
int ieq = mi.MenuItems.Add(new MenuItemType(
     
  "地震情報", false, false, null, eh, ""));
    // 地震のサブメニュー
    mi.MenuItems[ieq].MenuItems.Add(
new MenuItemType(
   
    "地震情報(震度速報)", false, false, null, eh,
       
"http://www.jma.go.jp/jp/quake/quake_sindo_index.html"));
    中略
    mi.MenuItems[ieq].MenuItems.Add(
new MenuItemType(
     
  "地震情報(遠地地震に関する情報)", false, false, null, eh,
       
"http://www.jma.go.jp/jp/quake/quake_foreign_index.html"));
  }
}

種類のメニューの初期設定は、次のように呼び出します。

private void CreateMenuForType()
{
  MenuForType
menuForType = new MenuForType(menuTypeHander);

  // 天気関連の種類メニューを追加する
 
menuForType.SetAmedasMenu(menuItemType, menuItemType_Click);

  // 地震関連の種類メニューを追加する
 
menuForType.SetEarthQuakeMenu(menuItemType, menuItemEarthQuake_Click);

  // デフォルトでは、トップメニューを選択する。
 
selectedTypeMenu = (MenuItem)(menuItemType.MenuItems[0]);
}

これで、種類メニュー関連をこの1つのクラスに集中させます。

4.4 画像を読み込む

さて、ここまででメニュー関連の実装はほぼ終わり、メニューを選択することにより、種類、地域、時間に応じたパラメータを扱うことができるようになりました。次に、これらのパラメータから、画像のURLを計算し、画像を読み込みます。画像を読み込む際には、メインフォームと同じスレッドで読み込むと、メインフォームが画像の読み込み中にフリーズしてしまいます。そこで、画像を読み込む処理はメインスレッドとは別のスレッドで実行する必要があります。

.NET Framework 2.0ではスレッド関連の機能も強化されていて、パラメータを渡してスレッドを実行することができます。詳細は、http://www.microsoft.com/japan/msdn/netframework/skillup/core/article6.aspx を見てください。

しかし、.NET Compact Framework 2.0では、この機能がサポートされていません。したがって、次のような ThreadImageLoader クラスを作成する必要がありました。実装している内容は簡単で、ThreadImageLoader クラスにパラメータを渡しておき、その中でパラメータを使用しながらスレッドを実行して画像を読み込むという処理になります。

class ThreadImageLoader
{
 
private Thread thread = null;
 
public ThreadImageLoader(FormMain fMain, PictureBoxWeb pBox, ProgressBar pBar, Uri param, bool save)
  {
    // パラメータをセット
  }

  // スレッドを実行
 
public void Start()
  {
    thread =
new Thread(AsyncLoadImage);
    thread.Start();
  }

 
public void Abort()
  {
    thread.Abort();
  }

 
public void Join()
  {
    thread.Join();
  }

  // 画像を読み込む

  public
void AsyncLoadImage()
  {
    ・・・
  }
}

ここで注意しなければならないのが、メインフォームは別のスレッドで画像の読み込みを行い、その画像をメインスレッドの PictureBox に書き込んでやる必要があります。しかし、メインスレッドと同一のスレッドでなければ、メインスレッドのPictureBoxに書き込みできません。このような別スレッドから、コントロールと同じスレッドに処理を渡す方法として、ほとんどのコントロールに Invoke メソッドが用意されています。別スレッドに、コントロールのインスタンスを渡しておいて、そのコントロールから Invoke メソッドを呼び出してあげると、コントロールと同一のスレッドで動作してくれます。同様に、非同期の BeginInvoke, EndInvoke もサポートされています。ここでは、Invoke メソッドでコントロールと同じスレッドをキックしてあげます。.NET Framework 2.0からは、コントロールに対して別のスレッドから書き込みが行われると例外が上がるようになっています。

具体的な AsyncLoadImage() の骨格は、次のような流れになっています。

public void AsyncLoadImage()
{
 
try
  {
   
WebRequest request = HttpWebRequest.Create(urlAmedas.ToString());
    中略
    using
(HttpWebResponse response = (HttpWebResponse)request.GetResponse())
   
using (BinaryReader dataStream = new BinaryReader(response.GetResponseStream()))
   
using (MemoryStream memStream = new MemoryStream((int)(response.ContentLength)))
    {
      byte[] buff = new byte[response.ContentLength + 10000];
      中略

      while (true)
      {
        画像をメモリーストリームに読み込む
      }
      // Bitmap をメモリストリームから作成する。
      bmp =
new Bitmap(memStream);
      // 必要に応じて、メモリストリームからファイルに書き出す。
    }

    picBox.Invoke(
new DelegSetImage(picBox.SetImage), new object[] { bmp });
    picBox.Invoke(
new DelegScale(picBox.scale), new object[] { 1.0f });
  }
 
catch (WebException exc)
  {

 
  中略
  }
 
finally
  {
 
  中略
  }
}

これに、あとはプログレスバーを表示したり、ファイルに書き出したりの処理が入るだけですので、1つ1つ追っていけば理解できると思います。

4.5 画像の表示

さて、画像読み込み用のスレッドで画像の読み込みが終わると、picBox.Invoke(new DelegVoid(picBox.Invalidate)) が最後に呼び出されて、picBox の再描画が必要になったことをメインフォームに教えます。メインフォームでは、Invalideate()が呼び出されると、OnPaint イベントハンドらが呼び出されて、PictureBox の再描画処理が始まります。この OnPaint で実際の描画処理を実装してあげればよいことになります。

ところで、今回は拡大・縮小、移動を行わせようとすると、メインフォームでいろいろ実装する必要が出てきてしまいます。しかし、このような拡大・縮小、移動であれば PictureBox 内で閉じて実行することができるので、PictureBox から継承したクラスを作成し、そこにそのような面倒な処理を隠ぺいすることにします。

そこで、次のような PictureBoxから継承した PictureBoxWeb クラスを作成し、その中で拡大・縮小 ScaleImage メソッド、移動のための MoveUp/MoveDown/MoveLeft/MoveRight メソッドを実装します。

Image

水平・垂直への移動は、極めて簡単で描画位置を変更し、Invalidate() により、再描画を指示するだけです。

/// <summary>
///
水平・垂直への移動
/// </summary>
public void MoveRight()
{ sx += (
int)(20 / sz); Invalidate(); }

public
void MoveLeft()
{ sx -= (
int)(20 / sz); Invalidate(); }

public
void MoveUp()
{ sy -= (
int)(20 / sz); Invalidate(); }

public void MoveDown()
{ sy += (
int
)(20 / sz); Invalidate(); }

拡大縮小も同様に、スケールのパラメータからオリジナルの画像の座標と描画先の座標を計算して、再描画をかけるだけですが、ちょっとめんどうなのでPowerPointに拡大・縮小の説明をまとめておきました。

/// <summary>
///
拡大縮小時のパラメータを計算する
/// </summary>
///
<param name="s">拡大縮小率</param>
public void
ScaleImage(float s)
{
  // オリジナルの画像の座標と描画先の座標を計算する。
  この計算はわかりにくいけれど、図を書いて考えてね。

  Invalidate();  // 再描画を指示する。
}

そして、最後にInvalidate を呼び出すことにより、次の OnPaint イベントハンドらが呼び出されます。そこでは、基本的に、計算したオリジナルの画像の座標と描画先の座標をもとに、originalの画像をもとにDrawImage で描画を行います。

protected override void OnPaint(PaintEventArgs pe)
{
  if (original == null)
    return
;

  Rectangle
destinationRect = new Rectangle(
    (Parent.Width - normalizedWidth) / 2,  (Parent.Height - normalizedHeight) / 2,
    normalizedWidth, normalizedHeight);

  pe.Graphics.DrawImage(original, destinationRect,
    sx, sy, sWidth, sHeight,
    GraphicsUnit.Pixel, new System.Drawing.Imaging.ImageAttributes());

  // 基本クラス OnPaint を呼び出しています
 
base.OnPaint(pe);
}

4.6 地震情報の取扱い

V1.x では、地震情報は扱えませんでした。これは、Amedas などの画像アクセス方法と同じメカニズムが取れないためです。具体的に言うと、
http://www.jma.go.jp/jp/quake/quake_local_index.html
のページにあるように表形式で、最新の情報が表示されます。

Image

このリストをクリックすると、次のようなHTMLにアクセスに行きます。
http://www.jma.go.jp/jp/quake/03110600391.html

Image

このページで表示されている画像のURL は、次のようになっています。
http://www.jma.go.jp/jp/quake/images/japan/03110600391.png
要するにこれと同じことをプログラムで実行しないと、画像を取得することができません。そこで、EarthQuake というクラスを追加し、そこに地震関連の実装を集中させます。処理は、地震の表を読み込む→表を分解して、行ごとのデータにする→行ごとのデータを分解して、1つの行の情報のかたまり class EarthQuake (発表日時、発生日時、震央地、マグニチュード、最大震度、地図へのURL)で管理するという流れになります。

class EarthQuake は次のような構成です。

Image

これをジェネリクス List <EarthQuake> で管理しています。

Image

メニューで地震関連が選択された場合、次のように呼び出します。

private void ShowEarthQuakeListView(MenuItemType mItem)
{
  中略
 
ListEarthQuake leq = new ListEarthQuake();
  leq.GetInfo(mItem.UrlString.ToString());

  listViewEarthQuake.Items.Clear();
 
foreach (EarthQuake eqinfo in leq)
  {
    listViewEarthQuake.Items.Add(
new ListViewItem(eqinfo.Get()));
  }
  リストビューを表示する。
}

これで、次のようにリストビューが表示されます。

Image

次に、ユーザーがリストを選択すると、次のように画像を表示するメソッドを呼びます。

private void listViewEarthQuake_SelectedIndexChanged(object sender, EventArgs e)
{
  中略

  // 画像のURLを取得する
 
string imageUrl = ((ListView)sender).Items[selectedIndex].SubItems[5].Text;
  EnablePicBox();
  // 画像を表示する
  ShowEarthQuake(
new Uri(imageUrl));
}

画像表示のためのメソッド ShowEarthQuake() は、基本的にAmedas の画像表示と同じロジックをURLを変えて次のように呼び出しているだけです。

private void ShowEarthQuake(Uri url)
{
  中略
  loadImageThread =
new ThreadImageLoader(this, picBox, progressBar1, url, configFile.Save);
  loadImageThread.Start();
}

なお、どのようにHTML から情報を切り出しているかというと、正規表現(Regex) を使用しています。幸い、.NET Compact Framework でも正規表現がサポートされているので助かりました。

一行分抜き出す正規表現
    Regex
reg = new Regex("(?<=<tr><td nowrap><a href=).*(?=</td></tr>)");

その1行から各データを抜き出すための正規表現は次の通り(ちょっといい加減だけど)
private
Regex regAnnounce = new Regex("(?<=.html>).*分(?=</a>)");
private Regex regUrl = new Regex("[0-9]+.html");
private Regex regTime = new Regex("[ 0-9]+日[0-9]+時[0-9]+分頃");
private Regex regShindo = new Regex("震度[0-9]");
private Regex regMagnitude = new Regex("M[0-9][.]?[0-9]?");
private Regex regLocation = new Regex("(?<=頃</td><td nowrap>).*(?=</td><td nowrap>M)");

以上、数行で切り出すことができます。正規表現は、便利便利w。

おまけに、津波も同じように実装しました。地震と津波で ListView を共通化することもできるのですが、かえってコードの見通しが悪くなりそうだったので、別々に持たせることにしました。

4.7 FormMain の処理

以上で、主要なクラスの説明は終わりです。これらのクラスを適切に呼び出すのが FormMain の役割になります。おもにメニュー関連のイベントハンドラーの処理になりますので、説明は不要と思います。構成は次のようになっています。

Image

4.8 作ってみての感想

.NET Compact Framework では、WebClient がないなど、一部制約がありますが WebRequest/WebResponse などの代替手段を使用できます。また、正規表現はひょっとしたらサポートされていないかもと思ったのですが、これも標準でサポートされており、実装上困ることはほとんどありませんでした。

それから、V1.0, V1.1 のときに、ネーミング規則もあまり考えずに進めてしまいましたが、例えばMenuItem を継承した場合、AbcMenuItem としますか? それとも MenuItemAbc としますか?

最初 AbcMenuItem としていましたが、複数の同じようなクラスを作る必要があり、すべてMenuItemAbc というネーミング規則にしました。今後もそうするでしょう。ちょっとした点ですけれど、意外とコードを読むうえで、重要です。皆さんどうしているのでしょうか・・・気になる。

それにしても、こんな名前の変更が Visual Studio のリファクタリングで簡単にできます。Visual Studio のリファクリタイングは、機能としてはパッとしないですが、これがなかったら、死んでました。

処理的にも実際に実装した部分はコメント込みで1700行ちょっとかかりましたが、基本的な動作はシンプルですので、その他便利ツールなどを作ってみてはいかがでしょうか。

開発環境は、Visual Studio 2008 Express Edtion が無償でダウンロードできます。

5. ダウンロード

 こちらのページより、最新版をダウンロードしてください。

6. ソースコード

お見せできるような内容ではありませんが、ソースコードを貼りつけるレベルではなくなってしまったので、まとめて置いておきます。こんな作りではだめよとか、ご意見お待ちしています。

WZero3DeAmedas2050.zip  Version 2.0.5.0 2008/9/21

 

7. まとめ

とにかく、ちゃんとしたインターフェースを使ってアクセスしているわけではないので、NowCast などの時間の取り扱いが変則で、だいぶ醜いコードになってしまいました。orz。

Web Service でデータを無償提供してほしい。>気象庁