C# Programming

Image簡単なモバイルアプリケーション開発6 

外気温計〜現在の場所、現在の時間の気温を表示

開発環境: Visual Studio 2005 

1.目次

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

2.目的

いままでの、簡単なモバイルアプリケーション開発の応用として、XML Web ServiceとRSSから現在場所の現在の気温を取得する という Web アクセスを2段階行う実験的アプリケーションを作成します。これにより、W-Zero3とWeb Serviceを組み合わせることにより、どの程度使えるのか試してみたいと思います。

Image

Windows Mobile アプリ本体と、データベース、Web Service、アメダスコードをスキャンする Windows Forms 2本も含めて、コンテストの締め切り直前の1/29, 30, 31 の平日の夜の3日間で作りました。シリアルポートと、SIMまわりの InterOpが予想外に難関だったため、予想工数を大幅に超過。会社から帰ってからの夜中しか時間が取れなかったので、さすがにつらかったです。

一応、XML Web Service のアメダスコードサービスは全国のデータをカバーしているので、国内で Willcom の電波が届けばどこでも動くはず・・・

3.参考書

  1. C#研究室.Live Space / W-ZERO3
  2. MSDN .NET Compact Framework 向けの表示方向切り替え対応および高dpi対応アプリケーションの開発
  3. 簡単なモバイルアプリケーション開発4〜現在の緯度、経度の取得
  4. 簡単なモバイルアプリケーション開発5〜XML Web Service で Amedas の観測所IDを取得する。 
  5. WZero3の通信を切断する方法

4.作り方

4.1 基本的な動作を決める

W-Zero3で現在の緯度、経度を取得できます。さらに、そこから最近傍のアメダス観測点を割り出すことができるようになりました。ここまでくれば、あとはアメダスの観測データを取得すれば、現在場所の気象データを取得できます。では、実際にどのようになっているのか 、気象庁とライブドアのRSSフィードを見てみましょう。

気象庁

http://www.jma.go.jp/jp/amedas_h/today-44131.html?groupCode=30&areaCode=000

Image

ライブドア

http://weather.livedoor.com/forecast/rss/amedas/point/44131.xml

Image

このページのソースを見てもらえば分かるように、気象庁のデータは HTML形式であり、どこのデータを取得すればよいの 単純には判断できません。一方、ライブドアのデータは、RSSフィードによるXML形式で提供されており、所望のデータの取り出しが非常に楽になっています。 そこで、今回はライブドアの RSSフィードを使用します。他にも、いろいろなデータソースがあると思いますので、処理しやすいサービスを選択するとよいでしょう。

なお、今回のアプリでは、実験的に”勝手に”データを使っていますので、RSSフィードのフォーマットが変更されたり、サービスが中止される可能性 があります。このアプリケーションは、あくまで”実験的アプリケーション”として取り扱ってください。

また、ここで作るアプリケーションは、アプリケーションを使っていただくことを目的にはしていませんので、単純に気温をデジタルで表示する だけの機能にします。

4.2 プロジェクトの作成

  1. プロジェクトの新規作成
    Visual Studioのメニューからプロジェクトの新規作成で、プロジェクトの種類で [スマートデバイス] → [デバイスアプリケーション] を選択します。
     
  2. ターゲットデバイスの設定
    エミュレータでも可能ですが、エミュレータではレスポンスが悪く、かったるいです。そこで、Windows Mobile 5.0 Pocket PC Deviceを指定して、実機上で開発します。
     
  3. Formの [FormFactor]プロパティ
    [Windows Mobile 5.0 Pocket PC VGA] を指定します。
     

4.3 表示部分のユーザーコントロールの作成

表示部分は、温度を表示するだけの非常にシンプルな機能なので、ユーザーコントロールでひとまとめにします。Form にぺたぺた直接張ることも可能ですが、ユーザーコントロールにしておくと、コードの独立性が高まり、可読性がよくなります。また、コントロールを他でも使いまわせるので、再利用する可能性がある場合に便利です。

  1. コントロールライブラリ
    新しいプロジェクトの追加より、コントロールライブラリを TemperatureControlという名前で追加します。
    Image
     
  2. フォームファクターの変更
    次のようにユーザーコントロールが作成されますので、フォームファクターを [Windows Mobile 5.0 Pocket PC Square VGA] に変更します。
     
  3. サイズの変更
    サイズは 480x480 として、縦でも横でも表示レイアウトが容易なサイズにします。
     
  4. pictureBoxの貼り付け
    次のように pictureBoxを貼り付けます。名前はわかりやすいように、左から
    pictureBox100 ・・・100の位
    pictureBox10 ・・・100の位
    pictureBox1 ・・・100の位
    pictureBoxPeriod ・・・ピリオド
    pictureBox01 ・・・0.1の位
    pictureBoxC ・・・℃
    とします。
    また、SizeMode プロパティを "StretchImage" とします。
    さらに、pictureBoxPeriod、pictureBoxC の "Image" プロパティ、それぞれ"."、"℃"の画像を指定します。
    あわせて、pictureBoxの位置を調整します。
     
  5. サイズの変更
    次に、ユーザーコントロールのサイズを調整して、次のようにします。

    Image

     
  6. ImageListの追加
    ツールボックスより ImageList を追加し、0-9 の画像を追加します。 さらに、マイナス "-" を11番目の画像として追加します。

    Image

     
  7. 背景色の設定
    背景色を黒にします。
    プロパティを実装して、プロパティで変更可能にすることもできますが、ここでは画像を使っているので、黒で固定にします。
     
  8. 表示ロジックの追加
    コントロールのプロパティに温度を追加し、そのプロパティを変更したらばその温度を表示するようにします。そのために、まずプロパティを追加し、表示ロジック ShowTemperature(float temp)を実装します。 ソースコードの内容は、6章を見てもらえればわかると思います。
     
  9. TemeratureControl をビルドして、エラーがないことを確認します。


以上で、温度を表示するためのユーザーコントロールの作成は終わりです。
 

4.4 画像パーツの作成

基本的なデザインに従って、画面パーツを作成します。私の場合、高価なデザインツールなど持っていないので、PowerPointで代用しました。お手軽に作る分にはそれなりに使えます。次のように、1枚目がメインページのデザイン、2枚目がグラフがどのように表示するかのデザイン、3枚目がAbout画面のデザインです。

Image

このようなUIのアプリケーションを作る際に気をつけなければいけないのが、UIのデザインに意外と時間がかかるということです。変なところに凝ったり、色が気に入らなかったり、サイズが合わなかったり、コントロールやフォントのサイズの微調整が必要だったりして、非常に時間がかかります。

今回使用する画像のパーツは次のような、0-9、マイナス、小数点、℃だけです。

Image

画像のパーツのサイズも大事です。大きすぎると、アプリケーションのサイズが大きくなります。小さすぎると、ぎざぎざしてしまいます。また、場合によっては解像度の違いによりモアレが発生してしまう場合もあるでしょう。できれば、表示サイズと実際のデザインのサイズが一致しているのが望ましいです。 サイズは、4.3章のユーザーコントロールの作成で画像のサイズが決まりますので、それに合わせるようにします。

これらの画像をプロジェクトに Images というフォルダーを作成して、コピーします。

4.5 コントロールの配置

4.4で作成した温度を表示するためのユーザーコントロールや、メッセージを表示するための TextBox、エラーメッセージを表示するための Notification コントロールを配置します。

  1. ユーザーコントロールの配置
    コンパイルが終わると、次のようにツールボックス上に [UserControl1] というコントロールが表示されます。
    Image
     
  2. フォームへコントロールを配置
    Formへツールボックスから、[UserControl1]をドラッグ&ドロップします。Dock プロパティを Topにします。
    Image
     
  3. Form の背景を黒にします。
     
  4. メッセージ表示用に TextBox の追加
    メッセージ表示のために、textBox を1つ貼り付けます。名前は "textBoxMsg" とします。Dock プロパティを Fill にします。Text プロパティは無し。MultiLine プロパティは、"True"とします。ForeColor プロパティは、黒以外の適当な色にします。ReadOnlyプロパティは True にします。
     
  5. Notification の追加
    エラーメッセージ表示用に Notification コントロールをツールボックスより追加します。

4.5 メインメニューの追加

  1. メインメニュー: 左ボタン
    左ボタンを押したときのイベントハンドらで、Application.Exit()を実行するようにします。

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

     
  2. メインメニュー: 右ボタン
    Refresh ボタン をおしたら、温度を取得し、表示するようにします。
    メインメニュー右ボタンのイベントハンドラを追加し、ShowTemperature() を呼び出します。

    private void menuItemGetTemp_Click(object sender, EventArgs e)
    {
        ShowTemperature()
    }

    private void ShowTemperature()
    {

    }

ShowTemperature() メソッドで、温度を表示するロジックを実装します。

4.6 経度、緯度情報の取得部分の実装

では、次に経度、緯度情報の取得部分を実装します。

  1. WZero3Locator.csの追加
    簡単なモバイルアプリケーション開発4〜現在の緯度、経度の取得 で作成した WZero3Locator.cs をプロジェクトに追加します。WZero3Temperature プロジェクトを選択し、[プロジェクト]→[新しい項目の追加]→[クラス]で、WZero3Locator.cs というファイルを追加し、現在の緯度、経度の取得で作成したコードを追加します。
     
  2. WZero3Locator.cs の namespace は、"Uchukamen.WZero3" になっているので、Form の namespace をリファクターにより、"Uchukamen.WZero3"に変更します。パーシャルクラスでほかの場所でも namespace が記述されていますので、リファクターを使用しないと、修正忘れで次のようなエラーになる場合があります。

    エラー 1 'WZero3Temperature.Form1.Dispose(bool)': オーバーライドする適切なメソッドが見つかりませんでした。

    というエラーが発生している場合は、namespace の修正忘れをチェックしてみてください。


    Image

    Image
     
  3. 前回はWZero3Locator.cs でエラーが発生した場合に、手を抜いて単に Exception をあげていましたが、それだとエラーの判定ができません。今回は、独自の例外を上げるように WZero3Locator.cs 改としました。このために、WZero3LocatorException クラスを追加しています。public class WZero3LocatorException : Exception
    {
      
    public WZero3LocatorException(string message): base(message)
      {
      }
    }

4.7 Amedas 観測所コードの取得

次に、簡単なモバイルアプリケーション開発5〜XML Web Service で Amedas の観測所IDを取得する。 で作成した XML Web Service を呼び出し、緯度、経度情報から、アメダスの観測所コードを取得するコードを実装します。

  1. XML Web Service の追加
    ソリューションエクスプローラの参照設定を右クリックして、"Web 参照の追加"を選択します。するとW Web 参照の追加 ダイアログが表示されるので、次のように http://uchukamen.com/Amedas.asmx を追加します。
    Image
     
  2. W-SIM通信状態の判定
    緯度、経度は、W-SIMが通信中だと取得することができません。そこで、GetWsimStateInfo()メソッドにより、W-SIMの状態を取得して、W-SIMが正常であり、通信中では無い状態(0)の時だけ、緯度、経度を取得するようにします。このため、次のInterOpを追加します。

    using
    System.Runtime.InteropServices;
    [
    DllImport("shphonelib.dll", EntryPoint = "?GetWsimStateInfo@CshphoneClientlib@@SAHXZ")]
    public static extern int GetWsimStateInfo();
    enum WSIMState { Normal = 0, NoSim = -2, Com = -3, Off = -7 };
    // 0:Normal : 通常
    //-2:NoSim : W-SIMなし
    //-3:Com : 通信中
    //-7:Off
  3. アメダス観測所コードの取得
    このときのコードは、次のように GetWsimStateInfo の返り値を判断して、GetW3Location() を呼び出します。このとき、SIMが通信中の場合は、緯度、経度情報を取得することができません。このために、通信中の場合にはRasConn.CloseAllConnections() を呼び出して、通信を強制的に切断します。RasConn.CloseAllConnections() については、次の章で説明します。

    ///
    <summary>
    ///
    アメダスの観測所コードを取得して、データを表示する。
    /// 結果はWZero3LocationReceived() がコールバックされるので、
    /// データの表示はWZero3LocationReceived() からキックされる。
    /// </summary>
    private void ShowTemperature()
    {
      ShowTextMessage(
    "現在位置計測中・・・");
      
    // SIMの状態を調べる
      int result = GetWsimStateInfo();
      
    if (result == (int)WSIMState.NoSim)
      {
        ShowErrorNotification(
    "Error", "SIMがありません。");
        
    return;
      }
      
    if (result == (int)WSIMState.Off)
      {
        ShowErrorNotification(
    "Error", "W-SIMをオンにしてください");
        
    return;
      }
      
    if (result == (int)WSIMState.Com)
      {
        
    // 通信中
        // 通信中の場合は、緯度、経度情報を取得できない。
        // このため、強制的に通信を切断する。
        RasConn.CloseAllConnections();
        
    // MSDN によると、切断後約3秒待つ必要がある。
        for (int i = 0; i < 3; i++)
        {
          
    Thread.Sleep(1000);
          
    Application.DoEvents();
        }
      }

      
    // 緯度、経度情報を取得する。
      GetW3Location();
    } 
  4. 緯度、経度の読み取り
    WZero3Locatorは、緯度、経度情報を取得完了した際に、Received デリゲートを呼び出します。そこで、Receivedイベントハンドラとして、WZero3LocationReceivedデリゲートを追加します。緯度、経度情報の読み取りが成功すると、この Received イベントが呼び出されれます。

    private
    WZero3Locator w3Locator = new WZero3Locator();

    public
    FormTemperarure()
    {
      InitializeComponent();

      w3Locator.Received += WZero3LocationReceived;
    }
  5. 緯度、経度の読み取り
    緯度、経度の読み取りは、なぜかかなりの頻度で失敗し、正しくデータ読み取れない場合があります。そこで、つぎのように複数回読み取りをリトライする必要があります。

    private void GetW3Location()
    {
     
    // エラーとなる場合が多いので、5回成功するまで繰り返す。
      for (int i = 0; i < 5; i++)
      {
       
    try
       
    {
         
    // アメダスのコードを取得する。
         
    w3Locator.GetLocation();
        
     // 例外が上がらなければ、成功したのでループから抜ける。
       
      break;
        }
       
    catch (Uchukamen.WZero3.WZero3Locator.WZero3LocatorException exc)
        {
          ShowErrorNotification(
    "Error", exc.Message);
        }
       
    catch (Exception exc)
        {
          ShowErrorNotification(
    "Error", exc.Message);
         
    break;
        }
      }
    }
  6. 経度、緯度が正常に読み出せると、WZero3LocationReceived デリゲートが呼び出されます。そこで、WZero3LocationReceivedの中で、GetTemperature(code) でアメダス観測所コードを元に、アメダスの温度を取得メソッドを呼び出します。

     /// <summary>
    ///
    GetLocation() でSerialPortから読み込みが成功すると
    /// このデリゲートが呼び出される。
    /// そこで、GetTemperature(code) で温度を取得する。
    /// </summary>
    private void WZero3LocationReceived(object sender, EventArgs e)
    {
      com.uchukamen.Amedas amedas = new com.uchukamen.Amedas();
      code = amedas.GetNearestCode(w3Locator.FLatitude, w3Locator.FLongitude);
      ...
      GetTemperature(code);
    }

4.8 通信の強制的な切断

緯度、経度は、W-SIMが通信中だと取得することができません。そこで、SIM の通信を強制的に切断するために、RasConn.cs というクラスを追加し、通信 を強制的に切断のためのコードを実装します。コードは、WZero3の通信を切断する方法を参照してください。class RasConn で、CloseAllConnectionsメソッドで、すべてのRAS接続を切断します。WZero3 なのでRAS接続は1つと仮定して、すべて切断という乱暴な方法をとっていますが、正しくは接続内容を判断して、適切な接続を切断するという処理にしたほうがよいと思います。

なお、この処理を調べるのが、一番厄介でした。何が大変なのかというと、.NET Compact Framework では.NET Framework とはマーシャリングが若干異なっていて、複合型の構造体の取扱いで注意が必要です。このあたりは、C# 研究室.Live Space に少しまとめておきました。

4.9 アメダス情報の取得

次に、アメダス観測所コードを元に、RSSフィードから気象情報を取得する処理を実装します。処理自体は、HttpWebRequestで、RSSフィードを読み込み、そこから気象情報を切り出すという処理になります。データ自体は、"℃です。"というように余分な文字列も入っているので、正規表現で 必要なデータを切り出します。 切り出した温度をユーザーコントロールにセットしてあげれば、温度が表示されます。

大枠は次のような処理ですが、詳細は6章のソースコードを見てください。

private void GetTemperature(int code)
{
   
const string webServiceUrl = "http://weather.livedoor.com/forecast/rss/amedas/point/";
   
string encodedUrl = webServiceUrl + code.ToString() + ".xml";
   
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(encodedUrl);
    req.Method =
"GET";

   
WebResponse res = req.GetResponse();

   
using (Stream resStream = res.GetResponseStream())
   
using (XmlTextReader reader = new XmlTextReader(resStream))
    {
      
while (!reader.EOF)
      {
        ・・・readerから読み込み、データを切り出す。
         温度を float temp に入れる。
          ユーザーコントロールに温度をセットすると温度が表示される。
          userControl11.Temperature = temp;
      }
    }
}

4.10 残りの作業

次に、お決まりの次の作業を行えば、完成です。
縦型、横型レイアウトの調整
メインメニューの実装
アイコンの追加
バージョン情報の表示
インストーラの追加

5. ダウンロード

Version 1.0.0.0  2007/4/15 WZero3Temperature.CAB 

動作環境

.NET Compact Framework 2.0 が必要です。

検証

WS004SH + SIM 灰耳で動作確認をしました。

注意 

  • このバージョンでは、コンテスト応募版の問題点のいくつかを修正しています。
  • アメダス観測拠点の気象情報になるため、現在位置の最近傍観測拠点のデータになります。たとえば、相模原市で測定しても、相模原市には観測拠点がないため、最も近い観測拠点の八王子あるいは海老名などと表示されます。
  • このアプリケーションは、LiveDoor の気象情報RSS、および自分が作成した緯度、経度情報からアメダス観測所のコードを提供するXML Web Service に依存しています。これらのサービスは、いつ停止するか、変更になるかわかりません。このために、常に動作を保証するものではなく、あくまで実験的なソフトです。
  • ActiveSync 接続中、ワイアレス接続中は、正しく動作しません。
  • 赤耳?青耳?と呼ばれるSIMで、ATコマンドで返される緯度、経度情報のフォーマット が異なっていると、位置情報取得に失敗する可能性があります。テストしてないですが、たぶん赤耳はOK。
  • アメダス観測所コードを取得する部分などが非同期処理になっていないので、改善する余地が残っていますが、これ以上手を入れてるつもりはないので、このへんでw。
  • SerialPort アクセスのためのデリゲートや、位置情報がエラーになった場合の処理など、コードが気持ち悪いです。(T T)。だれか、すっきりリファクターしたら、コードを送ってください。w

6. ソースコード

主なソースコードです。

Form1.cs

using System;
using System.Windows.Forms;
using System.Net;
using System.IO;
using System.Xml;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Threading;

namespace Uchukamen.WZero3
{
  public partial class FormTemperature : Form
  {
    public FormTemperature()
    {
      InitializeComponent();

      w3Locator.Received += WZero3LocationReceived;
    }

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

    private void menuItemGetTemp_Click(object sender, EventArgs e)
    {
      ShowTemperature();
    }

    private int code;

    enum WSIMState { Normal = 0, NoSim = -2, Com = -3, Off = -7 };
    // 0:Normal : 通常 
    //-2:NoSim  : W-SIMなし 
    //-3:Com    : 通信中 
    //-7:Off
    [DllImport("shphonelib.dll", EntryPoint = 
       "?GetWsimStateInfo@CshphoneClientlib@@SAHXZ")]
    public static extern int GetWsimStateInfo();

    private WZero3Locator w3Locator = new WZero3Locator();

    /// 
/// アメダスの観測所コードを取得して、データを表示する。
    /// 結果は WZero3LocationReceived() がコールバックされるので、
    /// データの表示は WZero3LocationReceived() からキックされる。
    /// 
private void ShowTemperature()
    {
      ShowTextMessage("SIM状態確認中");

      // SIMの状態を調べる
      int result = GetWsimStateInfo();
      if (result == (int)WSIMState.NoSim)
      {
        ShowErrorNotification("Error", "SIMがありません。");
        return;
      }
      if (result == (int)WSIMState.Off)
      {
        ShowErrorNotification("Error", "W-SIMをオンにしてください。");
        return;
      }
      if (result == (int)WSIMState.Com)
      {
        // 通信中
        // 通信中の場合は、緯度、経度情報を取得できない。
        // このため、強制的に通信を切断する。
        ShowTextMessage("SIM切断中");
        RasConn.CloseAllConnections();

        // MSDN によると、切断後約3秒待つ必要がある。
        for (int i = 0; i < 3; i++)
        {
          textBoxMsg.Text += ".";
          Thread.Sleep(1000);
          Application.DoEvents();
          if (GetWsimStateInfo() == (int)WSIMState.Normal)
            break;
        }
      }

      // 緯度、経度情報を取得する。
      GetW3Location();
    }

    private void GetW3Location()
    {
      ShowTextMessage("現在位置計測中");

      string errMsg = "";
      // エラーとなる場合が多いので、5回成功するまで繰り返す。
      for (int i = 0; i < 5; i++)
      {
        try
        {
          textBoxMsg.Text += ".";
          textBoxMsg.Refresh();
          // アメダスのコードを取得する。
          w3Locator.GetLocation();
          // 例外が上がらなければ、成功したのでループから抜ける。
          return;
        }
        catch (WZero3Locator.WZero3LocatorException exc)
        {
          errMsg = exc.Message;
        }
        catch (Exception exc)
        {
          ShowErrorNotification("Error", exc.Message);
          break;
        }
      }
      ShowErrorNotification("Error", 
          "現在位置の取得に失敗しました。" + errMsg);
    }

    /// 
/// GetLocation() でSerialPortから読み込みが成功すると
    /// このデリゲートが呼び出される。
    /// そこで、GetTemperature(code) で温度を取得する。
    /// 
private void WZero3LocationReceived(object sender, EventArgs e)
    {
      ShowTextMessage("アメダス観測所コード取得中");
      com.uchukamen.Amedas amedas = new com.uchukamen.Amedas();
      code = amedas.GetNearestCode
         (w3Locator.FLatitude, w3Locator.FLongitude);

      GetTemperature(code);
    }

    /// 
/// アメダスの観測所コードから気象情報を取得し、結果を表示する。
    /// 
/// 
private void GetTemperature(int code)
    {
      ShowTextMessage("気象情報取得中");
      try
      {
        const string webServiceUrl = 
           "http://weather.livedoor.com/forecast/rss/amedas/point/";
        string encodedUrl = 
              webServiceUrl + code.ToString() + ".xml";
        HttpWebRequest req = (HttpWebRequest)
            WebRequest.Create(encodedUrl);
        req.Method = "GET";

        string category = "";
        string description = "";
        string chimei = "";

        string tempString = null;
        string windString = null;
        string rainString = null;

        WebResponse res = req.GetResponse();
        Regex regexChimei = new Regex("^.*(?=の)");
        using (Stream resStream = res.GetResponseStream())
        using (XmlTextReader reader = new XmlTextReader(resStream))
        {
          while (!reader.EOF)
          {
            if (reader.IsStartElement("category"))
              category = reader.ReadElementString();
            else if (reader.IsStartElement("description"))
            {
              description = reader.ReadElementString();
              if (description.IndexOf("気温") > 0)
              {
                tempString = description;
                Match m = regexChimei.Match(description);
                if (m.Success)
                  chimei = m.Value;
              }
              if (description.IndexOf("降水量") > 0)
                rainString = description;
              if (description.IndexOf("風速") > 0)
                windString = description;
            }
            else
              reader.Read();

            if (description.EndsWith("℃です。"))
            {
              Regex regexTemp = 
                  new Regex("-?[0-9]{1,2}(.[0-9])?(?=℃)");
              Match m = regexTemp.Match(description);
              if (!m.Success)
                return;
              float temp = float.Parse(m.Value);
              userControl11.Temperature = temp;
            }
          }

          if (tempString != null) 
             textBoxMsg.Text = tempString + "\r\n";
          if (tempString != null) 
             textBoxMsg.Text += rainString + "\r\n";
          if (tempString != null) 
             textBoxMsg.Text += windString + "\r\n";
          textBoxMsg.Refresh();
        }
      }
      catch (Exception exc)
      {
        ShowErrorNotification("Error", exc.Message);
      }
    }

    #region メッセージ表示関連メソッド
    private void ShowTextMessage(string msg)
    {
      textBoxMsg.Text = msg;
      textBoxMsg.Refresh();
    }

    private void ShowErrorNotification(string caption, string text)
    {
      // TextBoxをクリア
      ShowTextMessage("");
      // エラーノティフィケーションを表示
      notification1.Caption = caption;
      notification1.Text = text;
      notification1.Visible = true;
    }
    #endregion
  }
}

6.1 WZero3Locator.cs 改

2007/4/8  WZero3LocatorException を返すように変更しました。

using System;
using System.IO.Ports;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Collections;
using System.Threading;


namespace Uchukamen.WZero3
{
    class WZero3Locator
    {
        public class WZero3LocatorException : Exception
        {
            public WZero3LocatorException(string message)
                : base(message)
            {
            }
        }

        private SerialPort serialPort = 
           new SerialPort("COM1", 9600, System.IO.Ports.Parity.None, 8);

        public WZero3Locator()
        {
            serialPort.NewLine = "\r\n";
            serialPort.DtrEnable = true;
            serialPort.Handshake = Handshake.RequestToSend;
            serialPort.RtsEnable = true;
            serialPort.DataReceived += 
              new SerialDataReceivedEventHandler(serialPort_DataReceived);
            serialPort.PinChanged += 
              new SerialPinChangedEventHandler(serialPort_PinChanged);
            serialPort.ErrorReceived += 
              new SerialErrorReceivedEventHandler(serialPort_ErrorReceived);
            serialPort.Disposed += new EventHandler(serialPort_Disposed);
            serialPort.ReadTimeout = 1000;
            serialPort.WriteTimeout = 1000;
        }

        public void Dispose()
        {
            serialPort.Dispose();
        }

        private void Send(string str)
        {
            while (serialPort.CtsHolding == true)
                Thread.Sleep(100);
            serialPort.WriteLine(str);

            Debug.WriteLine(">>>>>>>>>>>>:" + str);
        }

        public void GetLocation()
        {
            resultString = "";
            if (serialPort.IsOpen == false)
                serialPort.Open();

            Send("AT@LBC1");
            Send("AT@LBC?");
            Send("AT@LBC2");
            Send("AT");

            serialPort.Close();
        }

        private void serialPort_Disposed(object sender, EventArgs e)
        {
            Debug.WriteLine(resultString);
            ParseData(resultString);
            Received(this, null);
        }

        public EventHandler Received = null;

        private string resultString = "";

        private void serialPort_DataReceived
            (object sender, SerialDataReceivedEventArgs e)
        {
            string retStr = serialPort.ReadExisting();

            Debug.WriteLine("<<<<<<<<<<<<\n" + retStr);

            resultString += retStr;
        }

        private void serialPort_ErrorReceived
             (object sender, SerialErrorReceivedEventArgs e)
        {
            Debug.WriteLine("serialPort_ErrorReceived");
        }

        private void serialPort_PinChanged
            (object sender, SerialPinChangedEventArgs e)
        {
            Debug.WriteLine("serialPort_PinChanged: CTS=" 
              + serialPort.CtsHolding.ToString());
        }

        #region 郵便番号、緯度、経度のプロパティ
        private string errString = "";
        public string ErrString
        {
            get { return errString; }
        }

        private string zipCode;
        public string ZipCode
        {
            get { return zipCode; }
        }

        private string latitude;
        public string Latitude
        {
            get { return latitude; }
        }

        private string longitude;
        public string Longitude
        {
            get { return longitude; }
        }

        private float fLatitude;
        public float FLatitude
        {
            get { return fLatitude; }
        }

        private float fLongitude;
        public float FLongitude
        {
            get { return fLongitude; }
        }
        #endregion

        #region 郵便番号、緯度、経度の文字列処理
        public void ParseData(string str)
        {
            zipCode = GetZipCode(str);
            latitude = GetLatitude(str);
            longitude = GetLongitude(str);

            fLatitude = GetFloatLatitude(latitude);
            fLongitude = GetFloatLongitude(longitude);
        }

        private float GetFloatLatitude(string latitude)
        {
            Regex regVal = new Regex("[0-9]+");
            MatchCollection mcLatitude = regVal.Matches(latitude);
            return float.Parse(mcLatitude[0].Value)
                + (float.Parse(mcLatitude[1].Value)) / 60.0f
                + (float.Parse(mcLatitude[2].Value)) / 3600.0f;
        }

        private float GetFloatLongitude(string longitude)
        {
            Regex regVal = new Regex("[0-9]+");
            MatchCollection mcLongitude = regVal.Matches(longitude);
            return float.Parse(mcLongitude[0].Value)
                + float.Parse(mcLongitude[1].Value) / 60.0f
                + float.Parse(mcLongitude[2].Value) / 3600.0f;
        }

        private string GetZipCode(string str)
        {
            Regex regZip = new Regex("[0-9][0-9][0-9][0-9][0-9][0-9][0-9]");
            Match match = regZip.Match(str);
            if (!match.Success)
                throw (new WZero3LocatorException("郵便番号の取得に失敗"));
            return match.Value;
        }

        private string GetLatitude(string str)
        {
            Regex regLat = new Regex("[NS][0-9]+:[0-9]+:[0-9]+");
            Match match = regLat.Match(str);
            if (!match.Success)
                throw (new WZero3LocatorException("緯度の取得に失敗"));
            return match.Value;
        }

        private string GetLongitude(string str)
        {
            Regex regLong = new Regex("[EW][0-9]+:[0-9]+:[0-9]+");
            Match match = regLong.Match(str);
            if (!match.Success)
                throw (new WZero3LocatorException("緯度の取得に失敗"));
            return match.Value;
        }

        #endregion
    }
}

6.2 RasConn.cs

SIMの接続を強制的に切断するためのクラス

using System;
using System.Runtime.InteropServices;

namespace Uchukamen.WZero3
{
    /// 
/// This class is based on code from "mikinder".
    /// http://www.developersdex.com/vb/message.asp?p=2916&r=5643969
    /// 
class RasConn
    {
        const int MAX_PATH = 260;
        const int RAS_MaxDeviceType = 16;
        const int RAS_MaxPhoneNumber = 128;
        const int RAS_MaxEntryName = 20;
        const int RAS_MaxDeviceName = 128;
        const int SUCCESS = 0;
        const int ERROR_NOT_ENOUGH_MEMORY = 8;
        const int RASBASE = 600;
        const int ERROR_BUFFER_TOO_SMALL = RASBASE + 3;
        const int ERROR_INVALID_SIZE = RASBASE + 32;
        #region DllImport
        //// --- RASCONN data structure definition (refer to ras.h) --
        //private const int RAS_MaxEntryName = 20;
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public struct RASCONN
        {
            public int dwSize;
            public IntPtr hrasconn;
            [MarshalAs(UnmanagedType.ByValTStr, 
                SizeConst = RAS_MaxEntryName + 1)]
            public string szEntryName;
        }
        // --------------------------------------------
        [DllImport("coredll.dll", SetLastError = 
             true, CharSet = CharSet.Auto)]
        private static extern uint RasEnumConnections(
            [In, Out] RASCONN[] rasconn,
            [In, Out] ref int cb,
            [Out] out int connections);
        [DllImport("coredll.dll")]
        private static extern uint RasHangUp(IntPtr pRasConn);
        #endregion
        /// 
/// Returns all active RAS connections as 
        /// an array of data structure RASCONN
/// 
public static RASCONN[] GetAllConnections()
        {
            RASCONN[] tempConn = new RASCONN[1];
            RASCONN[] allConnections = tempConn;
            tempConn[0].dwSize = Marshal.SizeOf(typeof(RASCONN));
            int lpcb = tempConn[0].dwSize;
            int lpcConnections = 0;
            uint ret = RasEnumConnections
                (tempConn, ref lpcb, out lpcConnections);
            if (ret == ERROR_INVALID_SIZE)
            {
                throw new Exception
                    ("RAS: RASCONN data structure has invalid format");
            }
            else if (ret == ERROR_BUFFER_TOO_SMALL && lpcb != 0)
            {
                // first call returned that 
                // there are more than one connections
                // and more memory is required
                allConnections = new RASCONN[lpcb 
                  / Marshal.SizeOf(typeof(RASCONN))];
                allConnections[0] = tempConn[0];
                ret = RasEnumConnections(allConnections,
                  ref lpcb, out lpcConnections);
            }
            // Check errors
            if (ret != SUCCESS)
            {
                throw new Exception("RAS returns error: " + ret);
            }
            if (lpcConnections > allConnections.Length)
            {
                throw new Exception
                   ("RAS: error retrieving correct connection count");
            }
            else if (lpcConnections == 0)
            {
                // if there are no connections 
                // resize the data structure
                allConnections = new RASCONN[0];
            }
            return allConnections;
        }
        /// 
/// Closes all active RAS connections
        /// 
/// 
public static void CloseAllConnections()
        {
            RASCONN[] connections = GetAllConnections();
            for (int i = 0; i < connections.Length; ++i)
            {
                RasHangUp(connections[i].hrasconn);
            }
        }
    }
}

6.3 温度表示のためのユーザーコントロール

 

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

namespace Uchukamen.WZero3
{
  public partial class UserControl1 : UserControl
  {
    public UserControl1()
    {
      InitializeComponent();
    }

    private float temperature;
    public float Temperature
    {
      set
      {
        temperature = value;
        ShowTemperature(temperature);
      }
    }

    /// 
/// 気温を PictureBox に表示する
    /// 
/// 気温
public void ShowTemperature(float temp)
    {
      bool minus = false;
      if (temp < 0)
        minus = true;
      float absTemp = Math.Abs(temp);
      if (absTemp >= 100.0f)
        throw new ArgumentException("±100℃以上は表示できません。");
      int d10 = ((int)absTemp) / 10;
      int d1 = ((int)absTemp) % 10;
      int d01 = (int)(Math.Round((absTemp - d10 * 10 - d1) * 10));

      if (!minus && d10 > 0)
      {
        // 99〜10 ℃
        pictureBox100.Visible = false;
        pictureBox10.Visible = true;
        pictureBox10.Image = imageList1.Images[d10];
        pictureBox1.Image = imageList1.Images[d1];
        pictureBox01.Image = imageList1.Images[d01];
      }
      else if (!minus && d10 == 0)
      {
        // 9.9〜0 ℃
        pictureBox100.Visible = false;
        pictureBox10.Visible = false;
        pictureBox1.Image = imageList1.Images[d1];
        pictureBox01.Image = imageList1.Images[d01];
      }
      else if (minus && d10 == 0 && d1 == 0)
      {
        // -0.1〜-0.9 ℃
        pictureBox100.Visible = false;
        pictureBox10.Visible = true;
        pictureBox10.Image = imageList1.Images[10];  // Minus
        pictureBox1.Image = imageList1.Images[d1];
        pictureBox01.Image = imageList1.Images[d01];
      }
      else if (minus && d10 == 0 && d1 > 0)
      {
        // -1.0〜-9.9 ℃
        pictureBox100.Visible = false;
        pictureBox10.Visible = true;
        pictureBox10.Image = imageList1.Images[10];  // Minus
        pictureBox1.Image = imageList1.Images[d1];
        pictureBox01.Image = imageList1.Images[d01];
      }
      else if (minus && d10 > 0)
      {
        // -10.0〜-99.9 ℃
        pictureBox100.Visible = true;
        pictureBox100.Image = imageList1.Images[10];  // Minus
        pictureBox10.Visible = true;
        pictureBox10.Image = imageList1.Images[d10];
        pictureBox1.Image = imageList1.Images[d1];
        pictureBox01.Image = imageList1.Images[d01];
      }
      Refresh();
    }
  }
}

7. まとめ

Web Service + RSSフィードの処理を活用することによって、非力なモバイルデバイスでもそこそこのことができるようになるのではないかと思います。