MFC で Numeric Edit コントロール

Posted Mon Jul 07 2014

今更 MFC かって感じですが,うちのソフトとかはまだ MFC で書かれてます.将来が無さそうなのであまり勉強したくないのですが...GUI は未だにガチでやったことないので苦手です.先月,エディットコントロールの入力制限などを行った時の覚書です.

入力制限を行う

CEdit を継承したクラスを作成し,OnChar 関数をオーバーライドします.

#ifndef NUMERIC_EDIT_H_
#define NUMERIC_EDIT_H_

class CNumericEdit : public CEdit
{
  DECLARE_DYNAMIC(CNumericEdit)

public:
  CNumericEdit();
  virtual ~CNumericEdit();

  void SetRoundPlaceValue(const int RoundPlaceValue)
  {
    m_iRoundPlaceValue = RoundPlaceValue;
  }

  int GetRoundPlaceValue() const
  {
    return m_iRoundPlaceValue;
  }

protected:
  afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);

  CString m_strDelim;
  int m_iRoundPlaceValue;

  DECLARE_MESSAGE_MAP()
};

#endif // NUMERIC_EDIT_H_
#include "stdafx.h"
#include "NumericEdit.h"

IMPLEMENT_DYNAMIC(CNumericEdit, CEdit)

CNumericEdit::CNumericEdit()
{
  // determine the decimal delimiter buffer size
  const int nBuffLen = ::GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, NULL, 0 );
  _ASSERT( nBuffLen > 0 );

  // get the decimal number delimiter
  const int nResult = ::GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, 
      m_strDelim.GetBuffer(nBuffLen), nBuffLen );
  _ASSERT(nResult != 0);
  m_strDelim.ReleaseBuffer();
}

CNumericEdit::~CNumericEdit()
{
}

BEGIN_MESSAGE_MAP(CNumericEdit, CEdit)
  ON_WM_CHAR()
END_MESSAGE_MAP()

void CNumericEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
  CString strText;
  GetWindowText(strText);

  int iStart, iEnd;
  GetSel(iStart, iEnd);

  // 小数点が入力された場合
  if (nChar == m_strDelim)
  {
    // 空欄の場合は小数点の入力を許可しない
    if (strText.IsEmpty())
    {
      return;
    }
    else
    {
      // 小数点が既に入力されている場合は入力を許可しない
      if (strText.Find(m_strDelim) >= 0)
        return;
    }
  }

  // マイナスを入力できるのは空欄の時だけ
  if (nChar == '-')
  {
    if (!strText.IsEmpty())
      return;
  }

  // 小数点以下の入力文字数を制限
  if (nChar >= '0' && nChar <= '9' &&
      strText.Find(m_strDelim) != -1)
  {
    int iLength = strText.GetLength();
    int iRoundPlaceValue = strText.Find(m_strDelim);

    if (iStart == iEnd)
    {
      if (iStart > iRoundPlaceValue && 
          (iLength - iRoundPlaceValue) > m_iRoundPlaceValue)
        return;
    }
  }

  if ((nChar >= '0' && nChar <= '9') ||
      (nChar == m_strDelim) ||
      (nChar == '-') ||
      (nChar == '\b'))
  {
    CEdit::OnChar(nChar, nRepCnt, nFlags);
  }
}

ダイアログから値を取得するとき

下記のようにダイアログを起動して入力された値を取得するときは注意が必要です.DoModal() 関数が IDOK を返した時点でダイアログ内のコントロールが破棄されてしまうため値を取得する事ができません.この場合はメンバに CString 型の変数を定義し,DDX_Text で値を更新するようにします.

CMyDialog dialog;
if (dialog.DoModal() == IDOK) {
  // この時点で CMyDialog 内のコントロールは消滅する
  CString strText;
  dialog.m_editValue.GetWindowText(strText); // assertion error
}

入力値の制限と値取得を同時に行う

CEdit 型と CString 型の変数を定義し DoDataExchange 内に,DDXControl と DDXTEXT を追加します.

void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
  CDialog::DoDataExchange(pDX);

  // 入力値の制限,OnChar をオーバーライドしたクラス
  DDX_Control(pDX, IDC_EDIT_VALUE, m_editValue);

  // ダイアログ消滅後の値取得用
  DDX_Text(pDX, IDC_EDIT_VALUE, m_strValue);
}

入力値の範囲をチェックする

ダイアログデータバリデーションを使用します.DoDataExchange 内に下記のように関数を追加します.

void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
  CDialog::DoDataExchange(pDX);

  // 入力値の制限,OnChar をオーバーライドしたクラス
  DDX_Control(pDX, IDC_EDIT_VALUE, m_editValue);

  // ダイアログ消滅後の値取得用
  DDX_Text(pDX, IDC_EDIT_VALUE, m_strValue);

  // 入力値の範囲確認
  // 他の型用の DDV も標準であります
  DDV_MinMaxDouble(pDX, atof(m_strValue), 0.0, 1.0);
}

複数のエディットコントロール間で有効性を確認する

2つのエディットコントロール間の関係性から,データの有効性を決めたいことがあるかと思います.例えばユーザに上限値と下限値を入力してもらう場合,上限値は下限値より大きくなければいけません.このような場合にはデータが有効か確認するための独自関数を作成します.

void DDV_CheckLowerUpper(CDataExchange* pDX, double lower, double upper)
{
  if (lower > upper) {
    AfxMessageBox(_T("下限値には上限値より小さい数値を入力してください."), MB_ICONWARNING);
    pDX->Fail();
  }
}

上記の関数を DoDataExchange 内に追記します.

void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
  CDialog::DoDataExchange(pDX);

  // 入力値の制限,OnChar をオーバーライドしたクラス
  DDX_Control(pDX, IDC_EDIT_LOWER_VALUE, m_editLower);
  DDX_Control(pDX, IDC_EDIT_UPPER_VALUE, m_editUpper);

  // ダイアログ消滅後の値取得用
  DDX_Text(pDX, IDC_EDIT_LOWER_VALUE, m_strLower);
  DDX_Text(pDX, IDC_EDIT_UPPER_VALUE, m_strUpper);

  // 入力値の範囲確認
  // 他の型用の DDV も標準であります
  DDV_MinMaxDouble(pDX, atof(m_strLower), 0.0, 1.0);
  DDV_MinMaxDouble(pDX, atof(m_strUpper), 0.0, 1.0);

  // 下限値と上限値が入力されている場合は 下限値 < 上限値 であるかチェックする
  if (!m_strLower.IsEmpty() && !m_strUpper.IsEmpty()) {
    DDV_CheckLowerUpper(pDX, atof(m_strLower), atof(m_strUpper));
  }
}

Reference