C++Builder XE3でTCppWebBrowserを操作するアプリケーションをコマンドパターンで作成する

某掲示板でブラウザ(TCppWebBrowser)を操作するアプリケーションの作成に四苦八苦しているのを見て、自分ならどのように作成するかを考えてみました。

ブラウザを操作する処理をずらずら並べて書かずに、実行したい処理を次のように記述できるようにします。

//Googleのホームページにアクセスする
FCommands.push(new TNavigateCommand("http://www.google.co.jp/"));
//5秒待機する
FCommands.push(new TWaitCommand(5000));
//Yahooのホームページにアクセスする
FCommands.push(new TNavigateCommand("http://www.yahoo.co.jp/"));
//7秒待機する
FCommands.push(new TWaitCommand(7000));
//山本隆の開発日誌にアクセスする
FCommands.push(new TNavigateCommand("http://www.gesource.jp/weblog/"));

このようにすればアプリケーションが行う処理の見通しが良く、処理の追加や変更も簡単にできそうです。

実際にアプリケーションを作成してみます。

フォームにTCppWebBrowserとTTimerを配置します。

Form1

ヘッダーにTCommandクラスを前方宣言して、フォームのメンバ変数にコマンドを保持するコンテナを追加します。

#include <queue> //追加
class TCommand; //追加
class TForm1 : public TForm
{
__published:    // IDE で管理されるコンポーネント
  TCppWebBrowser *CppWebBrowser1;
  TTimer *Timer1;
  void __fastcall Timer1Timer(TObject *Sender);
private:    // ユーザー宣言
  std::queue<TCommand*> FCommands; //追加
public:     // ユーザー宣言
  __fastcall TForm1(TComponent* Owner);
};

Timer1のOnTimerイベントを追加します。
この処理では、メンバ変数FCommandsに実行するコマンドがあれば先頭のコマンドを実行します。
コマンドがなくなればタイマーを止めて終了します。

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
  //タイマーの状態を初期化する
  Timer1->Enabled = false;
  Timer1->Interval = 1;

  //コマンドがなければ終了
  if (FCommands.empty()) return;

  //先頭のコマンドを取り出す
  TCommand* command = FCommands.front();
  FCommands.pop();

  //コマンドを実行する
  command->Execute();

  //コマンドを破棄する
  delete command;

  //タイマーを再開する
  Timer1->Enabled = true;
}

タイマーを使ったのは一定時間待機するのが簡単だからです。
スレッドを使うなど、他の方法も考えられます。

フォームのコンストラクタに、実行するコマンドを登録する処理を追加します。

__fastcall TForm1::TForm1(TComponent* Owner)
  : TForm(Owner)
{
  //Googleのホームページにアクセスする
  FCommands.push(new TNavigateCommand("http://www.google.co.jp/"));
  //5秒待機する
  FCommands.push(new TWaitCommand(5000));
  //Yahooのホームページにアクセスする
  FCommands.push(new TNavigateCommand("http://www.yahoo.co.jp/"));
  //7秒待機する
  FCommands.push(new TWaitCommand(7000));
  //山本隆の開発日誌にアクセスする
  FCommands.push(new TNavigateCommand("http://www.gesource.jp/weblog/"));
}

コマンドはソースコードに記述するのでなく、ファイルから読み込むようにしたいところです。

コマンドの実装です。
サンプルプログラムなので手抜きです。(^_^;

まずは基底クラスから。

/**
 * コマンドの基底クラス
 */
struct TCommand {
  virtual ~TCommand() {}
  //コマンドを実行する
  virtual void Execute() = 0;
};

次に指定したURLを表示するコマンド。

/**
 * 指定したURLを表示するコマンド
 */
struct TNavigateCommand : public TCommand {
  TNavigateCommand(UnicodeString URL) : FURL(URL) {}
  void Execute() {
    Form1->CppWebBrowser1->Navigate(FURL.c_str());
  }
  const UnicodeString FURL;
};

指定した時間だけ待機するコマンド。

/**
 * 指定した時間だけ待機するコマンド
 */
struct TWaitCommand : public TCommand {
  TWaitCommand(int Interval) : FInterval(Interval) {}
  void Execute() {
    Form1->Timer1->Interval = FInterval;
  }
  const int FInterval;
};

以上で完成です。

あとは必要なコマンドを増やしたり、コマンドをファイルから読み込んで実行するようにすると使えるアプリケーションになるかもしれません。

ソースコードは次のようになりました。

Unit1.h

#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include "SHDocVw_OCX.h"
#include <Vcl.ExtCtrls.hpp>
#include <Vcl.OleCtrls.hpp>
#include <queue>
class TCommand;
class TForm1 : public TForm
{
__published:    // IDE で管理されるコンポーネント
  TCppWebBrowser *CppWebBrowser1;
  TTimer *Timer1;
  void __fastcall Timer1Timer(TObject *Sender);
private:    // ユーザー宣言
  std::queue<TCommand*> FCommands;
public:     // ユーザー宣言
  __fastcall TForm1(TComponent* Owner);
};

Unit1.cpp

//---------------------------------------------------------------------------
/**
 * コマンドの基底クラス
 */
struct TCommand {
  virtual ~TCommand() {}
  virtual void Execute() = 0;
};
/**
 * 指定したURLを表示するコマンド
 */
struct TNavigateCommand : public TCommand {
  TNavigateCommand(UnicodeString URL) : FURL(URL) {}
  void Execute() {
    Form1->CppWebBrowser1->Navigate(FURL.c_str());
  }
  const UnicodeString FURL;
};
/**
 * 指定した時間だけ待機するコマンド
 */
struct TWaitCommand : public TCommand {
  TWaitCommand(int Interval) : FInterval(Interval) {}
  void Execute() {
    Form1->Timer1->Interval = FInterval;
  }
  const int FInterval;
};
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
  : TForm(Owner)
{
  //Googleのホームページにアクセスする
  FCommands.push(new TNavigateCommand("http://www.google.co.jp/"));
  //5秒待機する
  FCommands.push(new TWaitCommand(5000));
  //Yahooのホームページにアクセスする
  FCommands.push(new TNavigateCommand("http://www.yahoo.co.jp/"));
  //7秒待機する
  FCommands.push(new TWaitCommand(7000));
  //山本隆の開発日誌にアクセスする
  FCommands.push(new TNavigateCommand("http://www.gesource.jp/weblog/"));
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
  //タイマーの状態を初期化する
  Timer1->Enabled = false;
  Timer1->Interval = 1;

  //コマンドがなければ終了
  if (FCommands.empty()) return;

  //先頭のコマンドを取り出す
  TCommand* command = FCommands.front();
  FCommands.pop();

  //コマンドを実行する
  command->Execute();

  //コマンドを破棄する
  delete command;

  //タイマーを再開する
  Timer1->Enabled = true;
}

コメント

  1. 某掲示板が今動かないので、こちらで失礼します。
    課題のご検討ありがとうございます。
    なかなか難しそうですが、適用を考えた場合、処理をwiatを必要とする単位に細分化してコマンドとしておくのだと思いました。今実装を検討していますが、まずFCommnadの資料は検索してもほとんどヒットしません。ヘルプでも出てきません。良い情報源はないでしょうか。

  2. 凄く参考になりました。
    成る程、queue はこうやって活用するんですね…。

    >fujioka sige さん

    横から失礼します。
    FCommand はこのソースで実装されたクラスです。
    queue の使い方にミソが有ります。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください