デバイス設定ダイアログの作成

ここではカスタムのデバイス設定ダイアログの作成の仕方について説明します。

このサンプルは IC Imaging Controlインストールディレクトリの%TOPLEVEL%\Samples\VCx\Making Device Settings内にございます。たとえばVC++2010 では %TOPLEVEL%\Samples\VC10となり、VC8では%TOPLEVEL%\Samples\VC8です。
プログラムを実行するためにプロジェクトファイル"MakingDeviceSettings.vcproj"を開きツールバーの"ビルド"を選択してください。これでプログラムを実行することができます。

IC Imaging Controlはデバイスの選択、設定が可能な標準のダイアログクラスライブラリリファレンス>クラス>Grabber>Grabber::showDevicePage Methodを備えています。しかしそれが必要な条件を満たさない場合、ここで紹介する方法に従ってカスタムのダイアログを作成することが可能です。

プロジェクトの新規作成

カスタムのデバイス設定ダイアログを使用するには、そのダイアログを呼び出すメインアプリケーションが必要となります。サンプルのプログラムはそのダイアログベースのメインアプリケーションを持っています。そのようなプロジェクトの作成は はじめに:Visual C++プロジェクトプログラマーズガイド>はじめに:Visual Studio C++で説明している手順に従っていただくことで簡単に行えます。

メインプログラムは"Device"というラベルのついたボタンがあり、これがクリックされると次のコードによってデバイス設定ダイアログが呼び出されます。

CDeviceDlg dlg( &m_Grabber, this );

// ダイアログを呼び出す
if( dlg.DoModal() == -1 )
{
  AfxMessageBox( TEXT("The dialog could not be created") );
  return -1;
}

CDeviceDlgはデバイス設定ダイアログを含むクラスです。m_Grabberはgrabberクラスのインスタンスでメインアプリケーションが使用します。デバイス設定ダイアログが設定を変更できるよう、プログラムのビルド時にダイアログクラスに渡されます。最後にdlg.DoModalが実際のダイアログを表示します。これがダイアログの呼び出しに必要なものです。

ダイアログの作成

ダイアログを作成するにはメニューから "挿入"、そして"リソース..."を選択します。 "ダイアログ" を選び"New" ボタンを押します。メニューから"View"を選び"ClassWizard..."を選択します。クラスウィザードダイアログが開き、ダイアログ用の新しいクラスを作成するように案内します。 "OK" をクリックしクラス名をCDeviceDlgとします。コンボボックスを5つ、チェックボックスを2つ、そしてシリアルナンバー表示用の静止テキストフィールドを1つ用意します。

ダイアログに機能を追加する

まず最初に、デバイスの設定状態を保持するデータ構造体が必要になります。これはユーザーが"Cancelボタンをクリックした際にその設定が元に戻る必要があるためです。ここの例では、データ構造体はダイアログクラスのヘッダファイル(ここでは"DeviceDlg.h")に含まれており、次のような中身になります。

struct Settings
{
  bool bDeviceAvail;
  std::string szDevice;

  bool bVideoNormAvail;
  std::string szVideoNorm;

  std::string szVideoFormat;

  bool bFrameRateAvail;
  double lfFrameRate;

  bool bInputChannelAvail;
  std::string szInputChannel;

  bool bFlipVAvail;
  bool bFlipV;

  bool bFlipHAvail;
  bool bFlipH;
};

private のメンバ変数を2つダイアログクラスに追加します。

Grabber*    m_pGrabber;
Settings    m_tDeviceState;

ダイアログを正しく初期化するために標準のコンストラクタは以下の通り実行される必要がございます。

CDeviceDlg::CDeviceDlg( Grabber* pGrabber, CWnd* pParent )
     :    CDialog(CDeviceDlg::IDD, pParent),
         m_pGrabber( pGrabber )
{
  //{{AFX_DATA_INIT(CDeviceDlg)
  //}}AFX_DATA_INIT
}

ダイアログが呼び出されると、コンボボックス内にはそれぞれ適切な値が入ることになります。また、デバイスの設定は保存する必要がございます。これはOnInitDialog関数の仕事です。この関数を追加するには、クラスウィザードを開き、CDeviceDlgをクラス名として選択します。"Object ID"はそのクラスを選び、"Message"は"WM_INITDIALOG"とします。では"Add Function"をクリックして次のコードを追加してください。

BOOL CDeviceDlg::OnInitDialog()
{
  CDialog::OnInitDialog();

  if( m_pGrabber->isDevValid() )
  {
    if( m_pGrabber->isLive() )
    {
      // ライブモード時はダイアログの表示ができないというメッセージ
      AfxMessageBox( TEXT("The device settings dialog is not available while the live video is running.
                                       \n\nStop the live video first.") );

       // スーパークラスのOKボタンのクリックイベントの呼び出し
      CDialog::OnOK();

      return TRUE;

    }
  }

  // 現在有効なデバイスの設定の保存
  saveDeviceSettings();

  // コンボボックスに適切な値を入力
  updateDevices();
  return TRUE;  // コントロールにフォーカスをセットしない場合にはTRUEを返す
           // 例外: OCX プロパティページはFALSEを返す
}

OnInitDialog関数にはgrabberがライブモードにあるかどうかをチェックする機能もあることを覚えておいてください。ライブモード中にはダイアログは表示できないというメッセージが表示されます。この振る舞いはデバイスの変更によって引き起こされる影響について警告するために実装されているものです。MemBufferCollectionクラスライブラリリファレンス>クラス>MemBufferCollectionはデバイスが変更されると無効になってしまします。それによってMemBufferCollectionクラスライブラリリファレンス>クラス>MemBufferCollection 内の画像データが失われてしまいます。
さらにMemBufferCollectionクラスライブラリリファレンス>クラス>MemBufferCollectionが無効であればframeReadyクラスライブラリリファレンス>クラス>GrabberListener>GrabberListener::frameReady Method のようなイベントも呼び出すことができなくなります。

ユーザーがダイアログの"Cancel" ボタンを押した際にはそのデバイスの設定は元に戻る必要があります。変更を全て無効にするためには以下のコードを挿入して"Cancel"ボタンのクリックイベントを追加します。

void CDeviceDlg::OnCancel()
{
  restoreDeviceSettings();

  CDialog::OnCancel();
}

関数restoreDeviceSettingsは以前に保存されたデバイスの設定を取り戻します。
saveDeviceSettingsrestoreDeviceSettingsは以下の通り実装されます。

void CDeviceDlg::saveDeviceSettings()
{
  if( m_pGrabber->isDevValid() )
  {
    m_tDeviceState.bDeviceAvail = true;
    m_tDeviceState.szDevice = m_pGrabber->getDev().c_str();

    if( m_pGrabber->isVideoNormAvailableWithCurDev() )
    {
      m_tDeviceState.bVideoNormAvail = true;
      m_tDeviceState.szVideoNorm = m_pGrabber->getVideoNorm().c_str();
    }
    else
    {
      m_tDeviceState.bVideoNormAvail = false;
    }

    m_tDeviceState.szVideoFormat = m_pGrabber->getVideoFormat().c_str();

    if( m_pGrabber->isFrameRateListAvailable() )
    {
      m_tDeviceState.bFrameRateAvail = true;
      m_tDeviceState.lfFrameRate = m_pGrabber->getFPS();
    }
    else
    {
      m_tDeviceState.bFrameRateAvail = false;
    }

    if( m_pGrabber->isInputChannelAvailableWithCurDev() )
    {
      m_tDeviceState.bInputChannelAvail = true;
      m_tDeviceState.szInputChannel = m_pGrabber->getInputChannel().c_str();
    }
    else
    {
      m_tDeviceState.bInputChannelAvail = false;
    }

    if( m_pGrabber->isFlipVAvailable() )
    {
      m_tDeviceState.bFlipVAvail = true;
      m_tDeviceState.bFlipV = m_pGrabber->getFlipV();
    }
    else
    {
      m_tDeviceState.bFlipVAvail = false;
    }

    if( m_pGrabber->isFlipHAvailable() )
    {
      m_tDeviceState.bFlipHAvail = true;
      m_tDeviceState.bFlipH = m_pGrabber->getFlipH();
    }
    else
    {
      m_tDeviceState.bFlipHAvail = false;
    }
  }
  else
  {
    m_tDeviceState.bDeviceAvail = false;
  }
}
void CDeviceDlg::restoreDeviceSettings()
{
  if( m_tDeviceState.bDeviceAvail )
  {
    m_pGrabber->openDev( m_tDeviceState.szDevice );

    if( m_tDeviceState.bVideoNormAvail )
    {
      m_pGrabber->setVideoNorm( m_tDeviceState.szVideoNorm );
    }

    m_pGrabber->setVideoFormat( m_tDeviceState.szVideoFormat );

    if( _tDeviceState.bFrameRateAvail )
    {
      m_pGrabber->setFPS( m_tDeviceState.lfFrameRate );
    }

    if( m_tDeviceState.bInputChannelAvail )
    {
      m_pGrabber->setInputChannel( m_tDeviceState.szInputChannel );
    }

    if( m_tDeviceState.bFlipVAvail )
    {
      m_pGrabber->setFlipV( m_tDeviceState.bFlipV );
    }

    if( m_tDeviceState.bFlipHAvail )
    {
      m_pGrabber->setFlipH( m_tDeviceState.bFlipH );
    }
  }
  else
  {
    m_pGrabber->closeDev();
  }
}

ダイアログのコントロールを扱う際、クラスウィザードを使って5つのコンボボックスに "CBN_SELCHANGE" イベントを追加します。また"BN_CLICKED" イベントを2つのチェックボックスに追加します。コントロールにアクセスするために、最後にメンバ変数を各コンボボックス、チェックボックス、静止テキストフィールドに追加します。

コンボボックス用の"CBN_SELCHANGE" イベントのコードは以下の通りとなります。

void CDeviceDlg::OnSelchangeComboDevice()
{
  // デバイスをオープンにする
  if( m_cboDevice.IsWindowEnabled() )
  {
    CString device;

    m_cboDevice.GetWindowText( device );

    if( m_pGrabber->openDev( (LPCTSTR)device ) )
    {
      __int64 iSerial;
      CString serial;

       // シリアルナンバーを読み取る
      if( m_pGrabber->getDev().getSerialNumber( iSerial ) )

       {
        // シリアルナンバーを表示
        serial.Format( TEXT("%I64X"), iSerial );
        m_staticSerialOut.SetWindowText( serial );
      }
      else
      {
        // "n\a"を表示
        m_staticSerialOut.SetWindowText( NOT_AVAILABLE );
      }
      // デバイスによって変化する他のコントロールのアップデート
      updateVideoNorms();
      updateInputChannels();
      updateFlip();
    }
  }
}

上記のコードはコンボボックスで選択されたテキストを読み取るものです。そして選択されたデバイスがオープンな状態になります。その後シリアルナンバーが読み取られ表示されます。シリアルを取得できない場合、もしくはそのデバイスがシリアルを持っていない場合には"n\a"(NOT_AVAILABLE)が表示されます。そして他のコントロールはそれぞれの"update"関数がコールされることで更新されます。

"CBN_SELCHANGE" イベント用のコードはさらに簡単です。下記は入力チャンネル用コンボボックスのコードです。

void CDeviceDlg::OnSelchangeComboInputchannel() 
{
  if( m_cboInputChannel.IsWindowEnabled() )
  {
    CString inputchannel;

    m_cboInputChannel.GetWindowText( inputchannel );

    if( !m_pGrabber->setInputChannel( (LPCTSTR)inputchannel ) )
    {
      AfxMessageBox( TEXT("Input Channel Error") );
    }
  }
}

関数はコンボボックスで選択されたテキストを取得し、それに従って入力チャンネルを設定します。他のコンボボックス用のコードも同様ですが、例外としてビデオ規格の"CBN_SELCHANGE" イベントは"updateVideoFormats"を呼び出します。これはビデオフォーマットがビデオ規格に依存するためで、同様の理由でビデオフォーマットの"CBN_SELCHANGE"イベントは "updateFrameRates" を呼び出します。フレームレート用のコードは修正が必要です。これはsetFPSクラスライブラリリファレンス>クラス>Grabber>Grabber::setFPS Methodメソッドはdouble の変数をパラメータとして受け付けますが m_cboFrameRate.GetWindowText は文字列を返すからです。

ではチェックボックス用のコードに移ります。

void CDeviceDlg::::OnCheckFlipv() 
{
  if( m_pGrabber->isFlipVAvailable () )
  {
    m_pGrabber->setFlipV( m_chkFlipV.GetCheck() == 1 );
  }
}

水平反転用のコードも同様です。

update 関数を見てください。各コンボボックス用のupdate関数があります。これらは各ボックスに適切な値を入力するために使用されます。以下がupdateDevice関数のコードとなります。

void CDeviceDlg::updateDevices()
{
  // コンボボックス内をクリア
  m_cboDevice.ResetContent();

  // 利用可能なデバイスの一覧の取得
  Grabber::tVidCapDevListPtr pVidCapDevList = m_pGrabber->getAvailableVideoCaptureDevices();
  if( pVidCapDevList == 0 || pVidCapDevList->empty() )
  {
    m_cboDevice.AddString( NOT_AVAILABLE );
    m_cboDevice.EnableWindow( false );
    m_cboDevice.SetCurSel( 0 );
    return;
  }

  m_cboDevice.EnableWindow( true );

  // コンボボックスに入力
  for ( Grabber::tVidCapDevList::iterator it =
     pVidCapDevList->begin();
     it != pVidCapDevList->end();
     ++it )
  {
    m_cboDevice.AddString( CString( it->c_str() ) );
  }

  // 有効なデバイスの選択
  if( m_pGrabber->isDevValid() )
  {
    m_cboDevice.SelectString( -1, CString( m_pGrabber->getDev().c_str() ) );
   }

  // 他のコントロールをデバイスに合わせて更新
  updateVideoNorms();
  updateInputChannels();
  updateFlip();
}

他のコンボボックス用のコードも基本的に同じです。

最後にupdateFlip関数のコードです。

void CDeviceDlg::updateFlip() 
{
  // 水平反転が可能かをチェック
  if( m_pGrabber->isFlipHAvailable() )
  {
    m_chkFlipH.EnableWindow( true );
    if( m_pGrabber->getFlipH() )
    {
      m_chkFlipH.SetCheck( 1 );
    }
    else
    {
      m_chkFlipH.SetCheck( 0 );
    }
  }
  else
  {
    m_chkFlipH.EnableWindow( false );
    m_chkFlipH.SetCheck( 0 );
  }

  // 垂直反転が可能かをチェック
  if( m_pGrabber->isFlipVAvailable() )
  {
    m_chkFlipV.EnableWindow( true );
    if( m_pGrabber->getFlipV() )
    {
      m_chkFlipV.SetCheck( 1 );
    }
    else
    {
      m_chkFlipV.SetCheck( 0 );
    }
  }
  else
  {
    m_chkFlipV.EnableWindow( false );
    m_chkFlipV.SetCheck( 0 );
  }
}