C# Programming

Image

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

開発環境: Visual Studio 2005 

1.目次

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

2.目的

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

ということで、要望をまとめると、次のようになります。

  1. 種類、位置情報の保持
  2. 画像情報の拡大縮小
  3. 画像情報の保存

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

しかし、V1.0.0.0の実装は、Form.cs にべたに書いているので、今後拡張していこうと思うと行数が多くなり、また拡張性もよくありません。そこで、今後の拡張を考えて、種類、地域、時間を扱いやす形にリファクタリングし ます。

また、要望1の、「種類、位置情報の保持」ができるようにします。

Image

注意:

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

3.参考書

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

4.作り方

4.1 種類、位置情報のセーブとロード

.NET Compact Framework では、System.Configuration クラスがサポートされておらず、System.Configuration を使った設定ファイルの取り扱いができません。

このため、種類、地域情報のセーブとロードをするため、自分で設定項目をリード、ライトするコードを実装する必要があります。実現方法は、レジストリを使用するか、ファイルを使用するかになりますが、レジストリに書き込むのは好きではないので、ファイルにXML形式で書きだすことにします。ファイルへのXMLのリード、ライトは、System.Xml, System.IO がサポートされているため、簡単に実装できます。そこで、設定ファイルのリード、ライトを行うクラス ClassConfigFile.cs を追加し、次のように実装します。

  class ConfigFile
 
{

 
private string typeName;
 
public string TypeName
  {
   
get { return typeName; }
   
set { typeName = value; }
  }

  private string locName;
  public string LocationName
  {
    get { return locName; }
    set { locName = value; }
  }

  private string configFilePath = GetCurrentDirectory() + "\\config.xml";

  public void WriteConfigFile(string typeName, string locName)
  {
 
  using (XmlTextWriter xtw = new XmlTextWriter(configFilePath, Encoding.Unicode))
    {
      xtw.WriteStartElement("configuration");
      xtw.WriteElementString("種類", typeName);
      xtw.WriteElementString("地域", locName);
      xtw.WriteEndElement();
      xtw.Close();
    }
  }

  public void ReadConfigFile()
  {
    // 設定ファイルがない場合は、何もしない
    if (!File.Exists(configFilePath))
      return;
    using (XmlTextReader xtr = new XmlTextReader(configFilePath))
    {
      xtr.ReadStartElement("configuration");
      typeName = xtr.ReadElementString("種類");
      locName = xtr.ReadElementString("地域");
      xtr.ReadEndElement();
      xtr.Close();
    }
  }

ここで、Windows Mobile 固有の問題があります。それは、Windows Mobile では、ファイルのアクセスは相対パスで行うことができず、絶対パスで指定する必要があるということです。設定ファイルは、exeのおかれる場所に置きたいですよね。そのためには次の GetCurrentDirectory メソッドで、exeのアセンブリから絶対パスを取得します。

  private static string GetCurrentDirectory()
  {
    string fqn = Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName;
    FileInfo finfo = new FileInfo(fqn);
    return finfo.DirectoryName;
  }

以上より、次のような簡単な XML ファイルのリード・ライトをするためのクラスの出来上がりです。

<configuration > 
    <種類>ウィンドプロファイラ</種類>
    <地域>関東地方</地域> 
</configuration>

4.2 種類、位置情報のセーブとロードを行うダイアログ

種類、位置情報のセーブとロードを行うダイアログを実装します。そこで、次のようなFormConfig.cs を追加し、ラベル、コンボボックス、ボタンを次のように配置します。

Image

次に、メインフォームのメニューに「設定」を追加し、次のように呼び出します。

private void menuItemConfig_Click(object sender, EventArgs e)
{
 
FormConfig formConfig = new FormConfig(menuItemLocation, menuItemType);
  formConfig.ShowDialog();
}

ここで、コンボボックスに表示する種類と、地域ですが、メインフォームのメインメニューにほとんど同じ項目をセットする必要があります。 単純に実装すると、メインメニューに全国、北海道、・・・と実装し、この設定用のコンボボックスでも、同様に全国、北海道、・・・と実装と実装することになってしまいます。しかし、複数の箇所で、同じ 項目があると、将来的な拡張やメインテナンスが面倒になります。そこで、コンボボックスに表示する項目は、メインフォームのメインメニューからデータを読み取り、このコンボボックスに追加するようにします。 つまり、メインフォームのメインメニューがマスターということになります。もう少し拡張性を持たせるのであれば、ファイルにメニューに必要な情報を格納しておき、それを元にメニューを構築するという手もありますが、この程度のアプリならそこまでは必要ないでしょう w。

種類メニューの一部は、警報→洪水 というように階層構造になっているので、末端(リーフ)のメニュー項目だけを追加します。末端(リーフ)のメニュー項目かどうかは、MenuItems.Count == 0で判断します。複数階層の場合には、再帰呼び出しをすればいいのですが、ここでは2階層までの前提でサーチすることにします。

/// <summary>
///
種類メニューに値をセットする
/// </summary>
///
<param name="menuItemType">メインフォームの種類メニュー</param>

private void SetAmedasType(MenuItem menuItemType)
{
 
foreach (MenuItem mi in menuItemType.MenuItems)
  {
   
if (mi.MenuItems.Count == 0)
      comboBoxType.Items.Add(mi.Text);
   
else
      foreach (MenuItem miSecondLevel in mi.MenuItems)
        comboBoxType.Items.Add(miSecondLevel.Text);
  }
  comboBoxType.SelectedIndex = 0;
}

同様に、地域メニューをメインメニューから設定するようにします。

/// <summary>
///
地域メニューに場所をセットする
/// </summary>
///
<param name="amedasLocation">メインフォームの地域メニュー</param>

private void SetAmedasLocation(MenuItem amedasLocation)
{
 
foreach (MenuItem al in amedasLocation.MenuItems)
  {
    comboBoxLocation.Items.Add(al.Text);
  }
  comboBoxLocation.SelectedIndex = 0;
}

次に、セーブボタンが押された時のイベントハンドラを追加し、XMLファイルへ書き込みます。書き込めなかった場合などの例外を処理したい場合は、try-catch のエラー処理を追加してください。

private void buttonSave_Click(object sender, EventArgs e)
{
  configFile.WriteConfigFile(
    (string)comboBoxType.SelectedItem,
    (string)comboBoxLocation.SelectedItem);

  Close();
}

設定ファイルを読み込みは、次のようにXmlTextReaderで読み込み、種類、地域を読み込みます。その値をコンボボックスのSelectedItemにセットしてあげます。

/// <summary>
///
設定ファイルをロードして、表示する。
/// </summary>
private void LoadConfig()
{
  configFile.ReadConfigFile();

 
// 種類メニューを設定
  foreach(object o in comboBoxType.Items)
 
if (((string)o) == configFile.TypeName)
    comboBoxType.SelectedItem = o;

 
// 場所メニューを設定
  foreach (object o in comboBoxLocation.Items)
 
if (((string)o) == configFile.LocationName)
    comboBoxLocation.SelectedItem = o;

  buttonSave.Focus();
}

以上により、次のような設定ダイアログが表示できます。

Image

4.3 種類メニューのリファクタリング

V1.0.0.0の種類メニューの処理がいまいちだったので、次のようにリファクタリングしました。考え方は、class MenuItem から継承したクラスAmedasTypeMenuItem を作成し、MenuItem で必要な情報をすべて持たせる方法です。

各MenuItemは、URL 生成のための文字列フォーマット(urlFormat)と、その文字列フォーマットに位置情報が必要かどうか(bool locationMenu)、場所情報が必要かどうか(bool timeMenu)を持たせます。

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

namespace Uchukamen.WZero3.WZero3DeAmedas
{
 
public class AmedasTypeMenuItem : MenuItem
  {
   
internal bool timeMenu;
   
internal bool locationMenu;
 
  internal string urlFormat;

 
  public AmedasTypeMenuItem(string name, bool loc, bool time, EventHandler eh, string sformat)
    {
      Text = name;
      locationMenu = loc;
      timeMenu = time;
      urlFormat = sformat;
      Click +=
new System.EventHandler(eh);
    }
  }

  public class AmedasTypeMenu
 
{
   
public void Set(MenuItem mi, EventHandler eh)
    {
      mi.MenuItems.Add(
new AmedasTypeMenuItem("天気予報", true, false, eh,
     
"http://www.jma.go.jp/jp/yoho/images/g1/{0}_telop_today.png"));
      mi.MenuItems.Add(
new AmedasTypeMenuItem("レーダー・降水ナウキャスト", true, true, eh,
      
"http://www.jma.go.jp/jp/radnowc/imgs/{2}/{0}/{1}.png"));

     
int isat = mi.MenuItems.Add(new AmedasTypeMenuItem("気象衛星", false, false, eh, null));
      mi.MenuItems[isat].MenuItems.Add(
new AmedasTypeMenuItem("赤外線", false, true, eh,
      
"http://www.jma.go.jp/jp/gms/imgs_c/0/infrared/1/{0}.png"));
      mi.MenuItems[isat].MenuItems.Add(
new AmedasTypeMenuItem("可視光", false, true, eh,
     
"http://www.jma.go.jp/jp/gms/imgs_c/0/visible/1/{0}.png"));

      中略
    }
  }
}

これらの初期設定は、メインメニューから次のように行います。

private void CreateTypeMenu()
{
 
AmedasTypeMenu atm = new AmedasTypeMenu();
  atm.Set(menuItemType, menuItemType_Click);
}

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

4.4 時間メニューのリファクタリング

同様に、V1.0.0.0 では、時間メニューも1つのファイルに作っていましたので、これも別クラスとして分離します。基本的な考え方は、種類メニューと同様に、MenuItem から継承した AmedasTimeMenuItem クラスを作成し、そこにメニューに表示する時間(Text)と URL を作成する際に必要となる時間のパラメータ(TimeCommand) を格納します。

  public class AmedasTimeMenuItem : MenuItem
 
{
 
  public DateTime Time;
   
public string TimeCommand;
   
public AmedasTimeMenuItem(){ }
   
public AmedasTimeMenuItem(string menuText, DateTime time, string timeCmd)
    {
      Text = menuText;
      Time = time;
      TimeCommand = timeCmd;
    }
  }
 

また、時間メニューには、次の2つの種類があります。

  • 通常の過去12時間分(1時間おき)の List<AmedasTimeMenuItem> normalTime
  • 過去6時間分(1時間おき)と1時間後まで(10分おき)の List<AmedasTimeMenuItem> normalTime

これらの2つの時間メニューを AmedasTimeSet クラスが管理します。


 
public class AmedasTimeSet
 
{
   
public List<AmedasTimeMenuItem> normalTime = new List<AmedasTimeMenuItem>();
   
public List<AmedasTimeMenuItem> nowCastTime = new List<AmedasTimeMenuItem>();

   
/// <summary>
    /// レーダー・降水ナウキャスト以外の場合の時間メニューを作成
    /// 過去12時間まで1時間刻み
    /// </summary>
    public void CreateNormalTime()
    {
       中略
    }

   
/// <summary>
    /// レーダー・降水ナウキャストの場合の時間メニューを作成
    /// 1時間後まで10分刻みの予想と
    /// 過去6時間まで1時間刻み
    /// </summary>
    public void CreateNowCastTime()
    {
       中略
    }
  }

時間メニューは、時間が経過すると後にアップデートする必要があります。そこで、時間メニューを定期的に更新するための UpdateTimeMenu() メソッドで、時間メニューの更新を行います。時間メニューには、通常の normalTime と予想時の nowCastTime の2つがありますので、それぞれ必要に応じて更新します。

private void UpdateTimeMenu()
{
 
if (selectedTypeMenu.Text.EndsWith("ナウキャスト"))
  {
    atm.CreateNowCastTime();
    currentTimeCommand = atm.SetNowCastTimeMenu(menuItemTimeMain, menuItemNowCastTimeEvent);
  }
 
else
  {
    atm.CreateNormalTime();
    currentTimeCommand = atm.SetNormalTimeMenu(menuItemTimeMain, menuItemNormalTimeEvent);
  }
}

4.5 地域メニューのリファクタリング

同様に地域メニューも独立したファイルにリファクタリングします。基本的な考え方は同じで、MenuItemから継承した AmedasLocationMenuItem クラスを作成し、地域名表示のための(Text)と、地域コード(locationCode) を合わせて管理します。

 
public class AmedasLocatoinMenuItem : MenuItem
 
{
   
internal string locationCode;
   
public AmedasLocatoinMenuItem(string locCode, string name, EventHandler eh)
    {
      locationCode = locCode;
      Text = name;
      Click +=
new System.EventHandler(eh);
    }
  }

また、このMenuItem をまとめて処理するためのクラス AmedasLocationMenu を追加します。

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

これにより、メインフォームからは、地域メニューの作成は、次のような呼び出しに集約することができます。イベントハンドラは、menuItemLocationEventにまとめてしまいます。

/// <summary>
///
地域メニューを作成
/// </summary>
private void CreateLocationMenu()
{
 
AmedasLocationMenu alm = new AmedasLocationMenu();
  alm.Set(menuItemLocation, menuItemLocationEvent);
}

4.6 メインルーチンのリファクタリング

V1.0.0.0では、種類のメニューごとに、次のようなコードがつらつらと10以上も並んでいて、拡張性が悪いです。そこで、もう少しこのあたりを整理します。

    ///


    /// レーダー・予測
    ///

    private void menuItemRadar_Click(object sender, EventArgs e)
    {
      ClearAllMenuItemTypeChecked();
      menuItemRadar.Checked = true;

      menuItemLocation.Enabled = true;
      menuItemTimeMain.Enabled = true;

      SetNowCastTimeMenu();

      ShowAmedas();
    }

 

基本的な考え方は、地域、時間をメニューよりセットした時に、次のパラメータに格納します。

  • 地域コード private string locationCode;
  • 時間パラメータ  private string currentTimeCommand;

次に、その時の種類、場所、時間の3つのパラメータの組み合わせにより、BuildUrl() メソッドにより URL を作成します。

private Uri BuildUrl()
{
  Uri amedasUrl = null;
  bool
loc = selectedTypeMenu.locationMenu;
  bool time = selectedTypeMenu.timeMenu;
  string strFormat = selectedTypeMenu.urlFormat;

  if
(strFormat.IndexOf("radnowc") >= 0)
    amedasUrl = new Uri(string.Format(strFormat, locationCode, currentTimeCommand, nowCastCommand));
  else if (loc == true && time == true)
    amedasUrl = new Uri(string.Format(strFormat, locationCode, currentTimeCommand));
   
  中略
 
  else
if (loc == false && time == false)
    amedasUrl = new Uri(string.Format(strFormat));
  return amedasUrl;
}

この URL を WebBrowserで Navigate することにより、画像を表示します。

/// <summary>
///
アメダス情報を表示する
/// </summary>
private void ShowAmedas()
{
  if (selectedTypeMenu == null)
    return;

  Uri
amedasUrl = BuildUrl();
  webBrowser1.Navigate(amedasUrl);
}
 

4.7 スタートメニューに追加

「スタート->プログラムで本ソフトが起動できるようになるとよいのですが。」というコメントをいただいたのですが、普段ローンチャーを使用しているので、スタート->プログラムでソフト起動というのは思いもよりませんでした。

スタート メニューにアイコンを追加するには、Cab プロジェクトの中で、次のように対象コンピュータ上のファイルシステムを右クリックし、特別なフォルダの追加→スタートアップフォルダを指定します。

Image

すると、次のようにスタート メニューが追加されます。そこで、プライマリー出力を右クリックして、ショートカットを作成します。

Image

そのショートカットをスタート メニュー フォルダに移動します。このとき、名前を適切に変更しないとビルドに失敗しますので、注意してください。

Image

以上により、Cab ファイルからインストールすると、スタート→プログラムにショートカットが追加されます。

4.8 今後の予定

機能的には種類、地域の設定ができるようにしたことと、時間メニューの更新が適切に行えていなかったバグを修正しただけですが、大幅に作り変えてしまいました。 クラスダイアグラムは、ざっくりと次のような構成です。

Image

Image

これで、WebBrowser から PictureBox に切り替えるための基本的な準備はできたと思うので、次期バージョンV2.0 で画像の拡大・縮小を実装したいと考えています。

5. ダウンロード

WZero3DeAmedas.CAB Version 1.1.0.0  2007/7/22

V1.0.0.0 からの修正点は次の通り。

  • 種類、地域情報をプリセットできるようにした。
  • 時間メニューの更新がタイムリーに行えないバグを修正。


WZero3DeAmedas.CAB  Version 1.2.0.0  2007/7/23

今日、通勤途上で V1.1.0.0 を使っていたら、バグを見つけてしまいました。
ごめんなさい。m_ _m 修正版です。 
V1.1.0.0 からの修正点は次の通りです。

  • バグ修正: 他の表示種類から、レーダー・降水ナウキャストを指定した時のURLのバグを修正。
  • バグ修正: 時間メニューの更新がタイムリーに行えないバグ(その2)を修正。
  • その他: インストール時に、スタート→プログラムにショートカットを追加。
  • その他: アイコンを修正。

WZero3DeAmedas Version 2.0.0.0 Beta1 2007/8/3 を公開しました。

動作環境

.NET Compact Framework 2.0 が必要です。

検証

WS004SH で動作確認をしました。

注意 

  • 動作は保証しません。クリティカルな用途には使わないでください。
  • 気象庁の防災気象情報の一部しか対応ていません。
  • 気象庁の防災気象情報の気象情報URLが変更になってしまうと、動かなくなってしまいます。
  • 1つの画像に対して、数十KB〜数百KBの通信が必要です。通信コストにご注意ください。
  • データが無い場合、"Not Found" エラーになる場合があります。原因としては、パラメータを渡す際のバグ、データ作成が間に合っていないこととが考えられます。
  • 種類を変更した場合、時間は最も近い過去の時間に再設定します。

6. ソースコード

主なソースコードです。

FormMain.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Reflection;
using System.Xml;
using System.IO;


namespace Uchukamen.WZero3.WZero3DeAmedas
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
      // 種類のメニューを作成する
      CreateTypeMenu();

      // 場所のメニューを作成する
      CreateLocationMenu();

      // 設定ファイルを読み込む
      LoadConfig();

      // 時間のメニューを作成する
      UpdateTimeMenu();

      // データを表示する
      ShowAmedas();
    }

    #region 設定ファイルを読み込む
    private void LoadConfig()
    {
      ConfigFile configFile = new ConfigFile();
      configFile.ReadConfigFile();

      // 種類メニューを設定
      MenuItem miType = FindMenu(menuItemType, configFile.TypeName);
      if (miType != null)
      {
        miType.Checked = true;
        selectedTypeMenu = (AmedasTypeMenuItem)miType;
        UpdateTimeMenu();
      }

      // 種類によっては、場所メニュー、時間メニューの有効、無効が違う
        menuItemLocation.Enabled = selectedTypeMenu.locationMenu;
        menuItemTimeMain.Enabled = selectedTypeMenu.timeMenu;
      

      // 場所メニューを設定
      MenuItem miLoc = FindMenu(menuItemLocation, 
        configFile.LocationName);
      if (miLoc != null)
      {
        miLoc.Checked = true;
        locationCode = ((AmedasLocatoinMenuItem)miLoc).locationCode;
      }
    }

    private void UpdateTimeMenu()
    {
      if (selectedTypeMenu.Text.EndsWith("ナウキャスト"))
      {
        atm.CreateNowCastTime();
        currentTimeCommand = atm.SetNowCastTimeMenu(menuItemTimeMain, 
          menuItemNowCastTimeEvent);
      }
      else
      {
        atm.CreateNormalTime();
        currentTimeCommand = atm.SetNormalTimeMenu(menuItemTimeMain, 
           menuItemNormalTimeEvent);
      }
    }

    /// 
    /// メニューの文字列が一致するメニューを返す
    /// 
    /// 探すMenuItem
    /// 探す文字列
    /// 成功: MenuItem、失敗:null
    private MenuItem FindMenu(MenuItem menuItem, string text)
    {
      foreach (MenuItem mi in menuItem.MenuItems)
      {
        if (mi.MenuItems.Count == 0 && text == mi.Text)
          return mi;

        if (mi.MenuItems.Count != 0)
        {
          MenuItem res = FindMenu(mi, text);
          if (res != null)
            return res;
        }
      }
      return null;
    }
    #endregion

    # region メニューのイベントハンドラー

    /// 
    /// 終了
    /// 
    private void menuItemTerminate_Click(object sender, EventArgs e)
    {
      Application.Exit();
    }

    /// 
    /// About、バージョン情報
    /// 
    private void menuItemAbout_Click(object sender, EventArgs e)
    {
      FormAbout fb = new FormAbout();
      fb.ShowDialog();
    }

    # endregion

    #region 表示メソッド
    /// 
    /// アメダス情報を表示する
    /// 
    private void ShowAmedas()
    {
      if (selectedTypeMenu == null)
        return;

      Uri amedasUrl = BuildUrl();

      webBrowser1.Navigate(amedasUrl);
    }

    private Uri BuildUrl()
    {
      Uri amedasUrl = null;
      bool loc = selectedTypeMenu.locationMenu;
      bool time = selectedTypeMenu.timeMenu;
      string strFormat = selectedTypeMenu.urlFormat;


      if (strFormat.IndexOf("radnowc") >= 0)
        amedasUrl = new Uri(string.Format(strFormat, 
          locationCode, currentTimeCommand, nowCastCommand));
      else if (loc == true && time == true)
        amedasUrl = new Uri(string.Format(strFormat,
          locationCode, currentTimeCommand));
      else if (loc == true && time == false)
        amedasUrl = new Uri(string.Format(strFormat,
          locationCode));
      else if (loc == false && time == true)
        amedasUrl = new Uri(string.Format(strFormat,
          currentTimeCommand));
      else if (loc == false && time == false)
        amedasUrl = new Uri(string.Format(strFormat));

      return amedasUrl;
    }
    #endregion

    #region 地域のメニュー

    private string locationCode;

    /// 
    /// 地域メニューを作成
    /// 
    private void CreateLocationMenu()
    {
      AmedasLocationMenu alm = new AmedasLocationMenu();
      alm.Set(menuItemLocation, menuItemLocationEvent);
    }

    /// 
    /// 地域を選択した
    /// 
    private void menuItemLocationEvent(object sender, EventArgs e)
    {
      // Location メニューのチェックマークを更新
      ClearMenuCheck(menuItemLocation);
      ((MenuItem)sender).Checked = true;

      locationCode = ((AmedasLocatoinMenuItem)sender).locationCode;

      ShowAmedas();
    }
    #endregion

    #region 時間メニュー
    private string currentTimeCommand = "";

    AmedasTimeSet atm = new AmedasTimeSet();

    private void menuItemNormalTimeEvent(object sender, EventArgs e)
    {
      // Location メニューのチェックマークを更新
      ClearMenuCheck(menuItemTimeMain);
      ((MenuItem)sender).Checked = true;

      currentTimeCommand = ((AmedasTimeMenuItem)sender).TimeCommand;
      ShowAmedas();
    }

    string nowCastCommand = "radar";

    private void menuItemNowCastTimeEvent(object sender, EventArgs e)
    {
      // Location メニューのチェックマークを更新
      ClearMenuCheck(menuItemTimeMain);
      ((MenuItem)sender).Checked = true;

      currentTimeCommand = ((AmedasTimeMenuItem)sender).TimeCommand;

      if (((MenuItem)sender).Text.EndsWith("(予想)"))
        nowCastCommand = "nowcast";
      else
        nowCastCommand = "radar";

      ShowAmedas();
    }

    private void menuItemTimeMain_Popup(object sender, EventArgs e)
    {
      UpdateTimeMenu();
    }

    #endregion

    #region 設定メニュー
    private void menuItemConfig_Click(object sender, EventArgs e)
    {
      FormConfig formConfig = new FormConfig(
           menuItemLocation, menuItemType);
      formConfig.ShowDialog();
    }
    #endregion

    #region アメダスの種類を選択するメニュー
    /// 
    /// 
    /// 
    public void menuItemType_Click(object sender, EventArgs e)
    {
      // Location メニューのチェックマークを更新
      ClearMenuCheck(menuItemType);
      ((MenuItem)sender).Checked = true;

      MenuTypeChanged((AmedasTypeMenuItem)sender);
    }

    // 現在のメニューの種類
    AmedasTypeMenuItem selectedTypeMenu = null;

    /// 
    /// メニュー:種類が変更になった
    /// 
    /// 
    private void MenuTypeChanged(AmedasTypeMenuItem mi)
    {
      mi.Checked = true;

      selectedTypeMenu = mi;


      menuItemLocation.Enabled = mi.locationMenu;
      menuItemTimeMain.Enabled = mi.timeMenu;

      nowCastCommand = "radar";

      if (mi.timeMenu)
      {
        UpdateTimeMenu();
      }
      ShowAmedas();
    }

    private void CreateTypeMenu()
    {
      AmedasTypeMenu atm = new AmedasTypeMenu();
      atm.Set(menuItemType, menuItemType_Click);
      selectedTypeMenu = (AmedasTypeMenuItem)(
         menuItemType.MenuItems[0]);
    }
    #endregion

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

        if (mi.MenuItems.Count != 0)
          foreach (MenuItem miSecond in mi.MenuItems)
            if (miSecond.Checked) miSecond.Checked = false;
      }
    }

    private void menuItemMenuMain_Popup(object sender, EventArgs e)
    {
      UpdateTimeMenu();
    }
  }
}

FormConfig.cs

using System;
using System.Windows.Forms;

namespace Uchukamen.WZero3.WZero3DeAmedas
{
  public partial class FormConfig : Form
  {
    public FormConfig(MenuItem amedasLocation, MenuItem menuItemType)
    {
      InitializeComponent();

      SetAmedasType(menuItemType);

      SetAmedasLocation(amedasLocation);

      LoadConfig();
    }

    ConfigFile configFile = new ConfigFile();

    /// 
    /// 設定ファイルをロードして、表示する。
    /// 
    private void LoadConfig()
    {
      configFile.ReadConfigFile();

      // 種類メニューを設定
      foreach(object o in comboBoxType.Items)
        if (((string)o) == configFile.TypeName)
          comboBoxType.SelectedItem = o;

      // 場所メニューを設定
      foreach (object o in comboBoxLocation.Items)
        if (((string)o) == configFile.LocationName)
          comboBoxLocation.SelectedItem = o;

      buttonSave.Focus();
    }

    /// 
    /// 種類メニューに値をセットする
    /// 
    /// メインフォームの種類メニュー
    private void SetAmedasType(MenuItem menuItemType)
    {
      foreach (MenuItem mi in menuItemType.MenuItems)
      {
        if (mi.MenuItems.Count == 0)
          comboBoxType.Items.Add(mi.Text);
        else
          foreach (MenuItem miSecondLevel in mi.MenuItems)
            comboBoxType.Items.Add(miSecondLevel.Text);
      }
      comboBoxType.SelectedIndex = 0;
    }

    /// 
    /// 地域メニューに場所をセットする
    /// 
    /// メインフォームの地域メニュー
    private void SetAmedasLocation(MenuItem amedasLocation)
    {
      foreach (MenuItem al in amedasLocation.MenuItems)
        comboBoxLocation.Items.Add(al.Text);
      comboBoxLocation.SelectedIndex = 0;
    }

    private void buttonSave_Click(object sender, EventArgs e)
    {
      configFile.WriteConfigFile(
        (string)comboBoxType.SelectedItem, 
        (string)comboBoxLocation.SelectedItem);
      Close();
    }

    private void buttonCancel_Click(object sender, EventArgs e)
    {
      Close();
    }
  }
}

ClassConfigFile.cs

using System.Text;
using System.Xml;
using System.Reflection;
using System.IO;

namespace Uchukamen.WZero3.WZero3DeAmedas
{
  class ConfigFile
  {
    private string typeName = "天気予報";

    public string TypeName
    {
      get { return typeName; }
      set { typeName = value; }
    }

    private string locName = "全国";

    public string LocationName
    {
      get { return locName; }
      set { locName = value; }
    }

    private string configFilePath = GetCurrentDirectory() + "\\config.xml";

    public void WriteConfigFile(string typeName, string locName)
    {
      using (XmlTextWriter xtw = new XmlTextWriter(configFilePath, Encoding.Unicode))
      {
        xtw.WriteStartElement("configuration");
        xtw.WriteElementString("種類", typeName);
        xtw.WriteElementString("地域", locName);
        xtw.WriteEndElement();
        xtw.Close();
      }
    }

    public void ReadConfigFile()
    {
      // 設定ファイルがない場合は、何もしない
      if (!File.Exists(configFilePath))
        return;

      using (XmlTextReader xtr = new XmlTextReader(configFilePath))
      {
        xtr.ReadStartElement("configuration");
        typeName = xtr.ReadElementString("種類");
        locName = xtr.ReadElementString("地域");
        xtr.ReadEndElement();
        xtr.Close();
      }
    }

    private static string GetCurrentDirectory()
    {
      string fqn = Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName;
      FileInfo finfo = new FileInfo(fqn);
      return finfo.DirectoryName;
    }
  }
}

ClassMenuLocation.cs

using System;
using System.Windows.Forms;

namespace Uchukamen.WZero3.WZero3DeAmedas
{
  public class AmedasLocatoinMenuItem : MenuItem
  {
    internal string locationCode;

    public AmedasLocatoinMenuItem(string locCode, string name, EventHandler eh)
    {
      locationCode = locCode;
      Text = name;

      Click += new System.EventHandler(eh);
    }
  }

  public class AmedasLocationMenu
  {
    public void Set(MenuItem mi, EventHandler eh)
    {
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("000", "全国", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("201", "北海道地方(北西部)", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("202", "北海道地方(東部)", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("203", "北海道地方(南西部)", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("204", "東北地方(北部)", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("205", "東北地方(南部)", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("206", "関東地方", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("207", "甲信地方", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("208", "北陸地方(東部)", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("209", "北陸地方(西部)", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("210", "東海地方", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("211", "近畿地方", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("212", "中国地方", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("213", "四国地方", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("214", "九州地方(北部)", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("215", "九州地方(南部)", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("216", "奄美地方", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("217", "沖縄・大東島地方", eh));
      mi.MenuItems.Add(new AmedasLocatoinMenuItem("219", "宮古・八重山地方", eh));
    }
  }
}

ClassMenuTime.cs

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

namespace Uchukamen.WZero3.WZero3DeAmedas
{
  public class AmedasTimeMenuItem : MenuItem
  {
    public DateTime Time;
    public string TimeCommand;
    public AmedasTimeMenuItem()
    { }
    public AmedasTimeMenuItem(string menuText, 
       DateTime time, string timeCmd)
    {
      Text = menuText;
      Time = time;
      TimeCommand = timeCmd;
    }
  }

  public class AmedasTimeSet
  {
    public List normalTime = new List();
    public List nowCastTime = new List();

    /// 
    /// レーダー・降水ナウキャスト以外の場合の時間メニューを作成
    /// 過去12時間まで1時間刻み
    /// 
    public void CreateNormalTime()
    {
      // リアルタイムでAmedasのデータは取得できない。
      // 大体20分ぐらい遅れるので、現在時刻ー20分する。
      DateTime nowMinus20Min = DateTime.Now.Subtract(
        new TimeSpan(0, 20, 0));
      // その時の時間(H)をベースの時間とする。
      DateTime baseHour = new DateTime(
        nowMinus20Min.Year, nowMinus20Min.Month, nowMinus20Min.Day,
        nowMinus20Min.Hour, 0, 0);

      normalTime.Clear();

      for (int i = 0; i < 12; i++)
      {
        DateTime t = baseHour.Subtract(new TimeSpan(i, 0, 0));
        string timeCmd = t.ToString("yyyyMMddHHmm") + "-00";
        normalTime.Add(new AmedasTimeMenuItem(t.ToString("HH:mm"), 
          t, timeCmd));
      }
    }

    /// 
    /// レーダー・降水ナウキャストの場合の時間メニューを作成
    /// 1時間後まで10分刻みの予想と
    /// 過去6時間まで1時間刻み
    /// 
    public void CreateNowCastTime()
    {
      // リアルタイムでAmedasのデータは取得できない。
      // 大体20分ぐらい遅れるので、現在時刻ー20分する。
      DateTime nowMinus20Min = DateTime.Now.Subtract(
        new TimeSpan(0, 20, 0));
      // その時の時間(10分単位)をベースの時間(baseHourMin)とする。
      DateTime baseHourMin = nowMinus20Min.Subtract
          (new TimeSpan(0, nowMinus20Min.Minute % 10, 0));
      // その時の時間(1時間単位)をベースの時間(baseHour)とする。
      DateTime baseHour = new DateTime(
        nowMinus20Min.Year, nowMinus20Min.Month, nowMinus20Min.Day,
        nowMinus20Min.Hour, 0, 0);

      nowCastTime.Clear();

      // 1時間後まで10分刻みの予想
      for (int i = 6; i > 0; i--)
      {
        DateTime t = baseHourMin.Add(new TimeSpan(0, 10 * i, 0));
        string timeCmd = baseHourMin.ToString("yyyyMMddHHmm") + 
           "-0" + i.ToString();
        nowCastTime.Add(new AmedasTimeMenuItem
           (t.ToString("HH:mm(予想)"), t, timeCmd));
      }

      // 過去6時間まで、1時間刻み
      for (int i = 0; i < 6; i++)
      {
        DateTime t = baseHour.Subtract(new TimeSpan(i, 0, 0));
        string timeCmd = t.ToString("yyyyMMddHHmm") + "-00";
        nowCastTime.Add(new AmedasTimeMenuItem(t.ToString("HH:mm"),
           t, timeCmd));
      }
    }


    public string SetNormalTimeMenu(Menu menu, EventHandler eh)
    {
      menu.MenuItems.Clear();

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

      ClearMenuCheck(menu);
      normalTime[0].Checked = true;
      return normalTime[0].TimeCommand;
    }

    public string SetNowCastTimeMenu(Menu menu, EventHandler eh)
    {
      menu.MenuItems.Clear();

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

      ClearMenuCheck(menu);
      menu.MenuItems[6].Checked = true;
      return nowCastTime[6].TimeCommand;
    }


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

        if (mi.MenuItems.Count != 0)
          foreach (MenuItem miSecond in mi.MenuItems)
            if (miSecond.Checked) miSecond.Checked = false;
      }
    }
  }
}

ClassMenuType.cs

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

namespace Uchukamen.WZero3.WZero3DeAmedas
{
  public class AmedasTypeMenuItem : MenuItem
  {
    internal bool timeMenu;
    internal bool locationMenu;
    internal string urlFormat;

    public AmedasTypeMenuItem(string name, bool loc, 
        bool time, EventHandler eh, string sformat)
    {
      Text = name;
      locationMenu = loc;
      timeMenu = time;
      urlFormat = sformat;

      Click += new System.EventHandler(eh);
    }
  }


  public class AmedasTypeMenu
  {
    public void Set(MenuItem mi, EventHandler eh)
    {
      mi.MenuItems.Add(new AmedasTypeMenuItem("天気予報", true, false, eh,
        "http://www.jma.go.jp/jp/yoho/images/g1/{0}_telop_today.png"));
      mi.MenuItems.Add(new AmedasTypeMenuItem("レーダー・降水ナウキャスト", true, true, eh,
        "http://www.jma.go.jp/jp/radnowc/imgs/{2}/{0}/{1}.png"));

      int isat = mi.MenuItems.Add(new AmedasTypeMenuItem("気象衛星", false, false, eh,
        null));
      mi.MenuItems[isat].MenuItems.Add(new AmedasTypeMenuItem("赤外線", false, true, eh,
       "http://www.jma.go.jp/jp/gms/imgs_c/0/infrared/1/{0}.png"));
      mi.MenuItems[isat].MenuItems.Add(new AmedasTypeMenuItem("可視光", false, true, eh,
       "http://www.jma.go.jp/jp/gms/imgs_c/0/visible/1/{0}.png"));
      mi.MenuItems[isat].MenuItems.Add(new AmedasTypeMenuItem("水蒸気", false, true, eh,
       "http://www.jma.go.jp/jp/gms/imgs_c/0/watervapor/1/{0}.png"));

      mi.MenuItems.Add(new AmedasTypeMenuItem("降水量", true, true, eh,
        "http://www.jma.go.jp/jp/amedas/imgs/rain/{0}/{1}.png"));
      mi.MenuItems.Add(new AmedasTypeMenuItem("気温", true, true, eh,
        "http://www.jma.go.jp/jp/amedas/imgs/temp/{0}/{1}.png"));
      mi.MenuItems.Add(new AmedasTypeMenuItem("日照時間", true, true, eh,
        "http://www.jma.go.jp/jp/amedas/imgs/sun/{0}/{1}.png"));
      mi.MenuItems.Add(new AmedasTypeMenuItem("風向・風速", true, true, eh,
        "http://www.jma.go.jp/jp/amedas/imgs/wind/{0}/{1}.png"));
      mi.MenuItems.Add(new AmedasTypeMenuItem("積雪深", true, true, eh,
        "http://www.jma.go.jp/jp/amedas/imgs/snow/{0}/{1}.png"));
      mi.MenuItems.Add(new AmedasTypeMenuItem("台風", false, false, eh,
        "http://www.jma.go.jp/jp/typh/images/wide/all-00.png"));

      mi.MenuItems.Add(new AmedasTypeMenuItem("ウィンドプロファイラ", false, true, eh,
        "http://www.jma.go.jp/jp/windpro/imgs/000/{0}.png"));

      int iwarn = mi.MenuItems.Add(new AmedasTypeMenuItem("警報", false, false, eh,
        null));
      mi.MenuItems[iwarn].MenuItems.Add(new AmedasTypeMenuItem("すべて", true, false, eh,
        "http://www.jma.go.jp/jp/warn/imgs/{0}/99.png"));
      mi.MenuItems[iwarn].MenuItems.Add(new AmedasTypeMenuItem("大雨", true, false, eh,
        "http://www.jma.go.jp/jp/warn/imgs/{0}/02.png"));
      mi.MenuItems[iwarn].MenuItems.Add(new AmedasTypeMenuItem("洪水", true, false, eh,
        "http://www.jma.go.jp/jp/warn/imgs/{0}/03.png"));
      mi.MenuItems[iwarn].MenuItems.Add(new AmedasTypeMenuItem("暴風(強風)", true, false, eh,
        "http://www.jma.go.jp/jp/warn/imgs/{0}/04.png"));
      mi.MenuItems[iwarn].MenuItems.Add(new AmedasTypeMenuItem("波浪", true, false, eh,
        "http://www.jma.go.jp/jp/warn/imgs/{0}/06.png"));
      mi.MenuItems[iwarn].MenuItems.Add(new AmedasTypeMenuItem("雷", true, false, eh,
        "http://www.jma.go.jp/jp/warn/imgs/{0}/08.png"));
      mi.MenuItems[iwarn].MenuItems.Add(new AmedasTypeMenuItem("濃霧", true, false, eh,
        "http://www.jma.go.jp/jp/warn/imgs/{0}/10.png"));
      mi.MenuItems[iwarn].MenuItems.Add(new AmedasTypeMenuItem("低温", true, false, eh,
        "http://www.jma.go.jp/jp/warn/imgs/{0}/13.png"));
    }
  }
}

 

7. まとめ

V1.0.0.0 と V1.1.0.0 では、ほとんど機能的には変わらないのに、リファクタリングで大幅に変わりました。Visual Studio 2005 のリファクタリング機能がないと、とてもやってられなかったですね。

あと、NowCast の時間の取り扱いが変則で、だいぶ醜いコードになってしまいました。orz。Web Service を提供してほしい。強くお願い。>>気象庁

さて、準備は整ったし、たぶん今の枠組みを大きく変えなくても、V2.0はできると思うけれど・・・・・あとは、時間が取れたらということで・・・。