ラベル MFC の投稿を表示しています。 すべての投稿を表示
ラベル MFC の投稿を表示しています。 すべての投稿を表示

2016年5月3日火曜日

MFC オートコンプリートというより選択リストから選択できるように。

エディットコントロールにコンボボックスのようにリストボックスをつけて、エディットの入力内容に基づいて選択候補を表示するというものが欲しかった。コンボボックスではどうしても操作がおかしくなるので実現できなかった。IAutoCompleteは訳がわからない。フィルターを切っても、内部で訳の解らないフィルターをされるので、自由に表示できず、全く使えなかった。
単純にこちらで指定した文字列群を表示して選択ができるだけのインタフェースが見つからなかった。
で、作成してみた。


クラス名:AcEdit   これ自体にリストボックスを持たせてある。
AcEdit.h
-------------------------------
#pragma once
#include "afxcoll.h"
#include "PopupListBox.h"


// AcEdit

class AcEdit : public CEdit
{
 DECLARE_DYNAMIC(AcEdit)

public:
 AcEdit();
 virtual ~AcEdit();

protected:
 DECLARE_MESSAGE_MAP()
public:
private:
 // リストボックスITEM
 CStringArray m_straryListItem;
public:
 // ボップアップ
 CPopupListBox *m_pPopListBox; // これがポップアップリストボックスダイアログ
 void ResetContent();
 void AddString(LPCTSTR lpszString);
 void ShowDropDown(bool bShow);

 afx_msg LRESULT OnUserSelChange(WPARAM wParam, LPARAM lParam);
 afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
 void Moved();
};
-----------------------------------------

AcEdit.cpp
-----------------------------------------
// AcEdit.cpp : 実装ファイル
//

#include "stdafx.h"
#include "APPLICATION.h"  ダイアログのため。
#include "AcEdit.h"


// AcEdit

IMPLEMENT_DYNAMIC(AcEdit, CEdit)

AcEdit::AcEdit()
{
 this->m_straryListItem.SetSize(0, 1500);
 this->m_pPopListBox = new CPopupListBox();
}

AcEdit::~AcEdit()
{
 delete m_pPopListBox;
 m_pPopListBox = NULL;
}


BEGIN_MESSAGE_MAP(AcEdit, CEdit)
 ON_MESSAGE(WM_USER_SEL_CHANGE, OnUserSelChange)
 ON_WM_KEYDOWN()
END_MESSAGE_MAP()



// AcEdit メッセージ ハンドラー

void AcEdit::ResetContent()
{
 if (!::IsWindow(m_pPopListBox->m_hWnd))
 {
  m_pPopListBox->Create(this);
 }
 m_pPopListBox->RemoveString();
 ShowDropDown(false);
}


void AcEdit::AddString(LPCTSTR lpszString)
{
 if (!::IsWindow(m_pPopListBox->m_hWnd))
 {
  m_pPopListBox->Create(this);
 }
 m_pPopListBox->AddString(lpszString);

}


void AcEdit::ShowDropDown(bool bShow)
{
 if ((m_pPopListBox) && (::IsWindow(m_pPopListBox->m_hWnd)))
 {
  if (bShow)
  {
   m_pPopListBox->ShowWindow(SW_SHOWNA);
  }
  else
  {
   m_pPopListBox->ShowWindow(SW_HIDE);
  }
 }
}
LRESULT AcEdit::OnUserSelChange(WPARAM wParam, LPARAM lParam)
{
 /// list選択した通知
 CString strA;
 m_pPopListBox->GetCurSelString(strA);
 this->SetWindowTextA(strA);
 this->SetFocus();
 return 0L;
}


void AcEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
 if ((nChar == VK_UP) || (nChar == VK_DOWN) || (nChar == VK_TAB))
 {
  if (m_pPopListBox)
  {
   m_pPopListBox->StartSel();
  }
 }

 CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
}


void AcEdit::Moved()
{
 if (m_pPopListBox)
  m_pPopListBox->Reshow();
}
-----------------------------------------------------
popuplistbox.h
-----------------------------------------------------
#pragma once
#include "afxwin.h"

// CPopupListBox ダイアログ
#define WM_USER_SEL_CHANGE  (WM_USER + 0x601)

class CPopupListBox : public CDialogEx
{
 DECLARE_DYNAMIC(CPopupListBox)

public:
 CPopupListBox(CWnd* pParent = NULL);   // 標準コンストラクター
 virtual ~CPopupListBox();

 // ダイアログ データ
 enum { IDD = IDD_LISTBOX };

protected:
 virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV サポート

 DECLARE_MESSAGE_MAP()
public:
 afx_msg void OnNcDestroy();
 virtual void OnOK();
 virtual void OnCancel();
 // データ表示用
 CListBox m_listBox;
 afx_msg void OnLbnSelchangeListAc();
 int AddString(LPCTSTR lpszString);
 // 登録文字列を削除
 void RemoveString();
 // 選択中の文字を取得する。
 int GetCurSelString(CString & strSelString);
 void Create(CWnd *pWnd);
 CWnd *m_pWnd;
 void StartSel();
 void Reshow();

 afx_msg void OnLbnDblclkListAc();
};
----------------------------------------------
popuplistbox.cpp
----------------------------------------------
// PopupListBox.cpp : 実装ファイル
//

#include "stdafx.h"
#include "APPLICATION.h" // リソースのため
#include "PopupListBox.h"
#include "afxdialogex.h"

// CPopupListBox ダイアログ

IMPLEMENT_DYNAMIC(CPopupListBox, CDialogEx)

CPopupListBox::CPopupListBox(CWnd* pParent /*=NULL*/)
 : CDialogEx(CPopupListBox::IDD, pParent)
{
 m_pWnd = NULL;

}

CPopupListBox::~CPopupListBox()
{
}

void CPopupListBox::DoDataExchange(CDataExchange* pDX)
{
 CDialogEx::DoDataExchange(pDX);
 DDX_Control(pDX, IDC_LIST_AC, m_listBox);
}


BEGIN_MESSAGE_MAP(CPopupListBox, CDialogEx)
 ON_WM_NCDESTROY()
 ON_LBN_SELCHANGE(IDC_LIST_AC, &CPopupListBox::OnLbnSelchangeListAc)
 ON_LBN_DBLCLK(IDC_LIST_AC, &CPopupListBox::OnLbnDblclkListAc)
END_MESSAGE_MAP()


// CPopupListBox メッセージ ハンドラー




void CPopupListBox::OnOK()
{
 // TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。

 // CDialogEx::OnOK();
 if (m_pWnd)
  m_pWnd->PostMessageA(WM_USER_SEL_CHANGE);
}


void CPopupListBox::OnCancel()
{
 // TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。

 // CDialogEx::OnCancel();
}



int CPopupListBox::AddString(LPCTSTR lpszString)
{
 if (IsWindowEnabled())
 {
  int ret = m_listBox.AddString(lpszString);
  CRect rect1;
  m_listBox.GetWindowRect(rect1);
  int nW = rect1.Width();
  CDC *pDC = m_listBox.GetDC();
  CSize size1 = pDC->GetTextExtent(lpszString);
  ReleaseDC(pDC);
  if (size1.cx > nW)
   nW = size1.cx; // 最大の値とする。

  int nH = 0;
  if (m_listBox.GetCount() > 20)
  {

   nH = m_listBox.GetItemHeight(0) * 20 + 6;
   nW += 24; // ScrollBar
  }
  else
  {
   nH = m_listBox.GetItemHeight(0) * m_listBox.GetCount() + 6;
  }
  rect1.bottom = rect1.top + nH;
  rect1.right = rect1.left + nW;
  this->MoveWindow(rect1);
  ScreenToClient(rect1);
  m_listBox.MoveWindow(rect1);
  return ret;
 }
 return -1;
}


// 登録文字列を削除
void CPopupListBox::RemoveString()
{
 if (IsWindowEnabled())
 {
  CRect rect1;
  m_listBox.GetWindowRect(rect1);
  ScreenToClient(rect1);
  rect1.right = rect1.left + 60;
  m_listBox.MoveWindow(rect1);
  this->m_listBox.ResetContent();
 }
}


// 選択中の文字を取得する。
int CPopupListBox::GetCurSelString(CString & strSelString)
{
 int nIdx = 0;
 if (IsWindowEnabled())
 {
  nIdx = m_listBox.GetCurSel();
  if (nIdx >= 0)
  {
   m_listBox.GetText(nIdx, strSelString);
  }
 }
 return nIdx;
}


void CPopupListBox::Create(CWnd *pWnd)
{
 CDialog::Create(IDD, pWnd);
 m_pWnd = pWnd; // 呼び出し元
 Reshow();
 m_pWnd->SetFocus();
}
void CPopupListBox::Reshow()
{
 CRect rect1;
 if (m_pWnd && ::IsWindow(m_pWnd->m_hWnd))
 {
  m_pWnd->GetWindowRect(rect1);
  this->SetWindowPos(NULL, rect1.left, rect1.bottom, 0, 0, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE);
 }
}


void CPopupListBox::StartSel()
{
 m_listBox.SetFocus();
 this->m_listBox.SetCurSel(0);
}


void CPopupListBox::OnLbnDblclkListAc()
{
 if (m_pWnd)
  m_pWnd->PostMessageA(WM_USER_SEL_CHANGE);
}

----------------------------------------------------
Dialog定義
----------------------------------------------------
IDD_LISTBOX DIALOGEX 0, 0, 139, 9
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_SYSMENU
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    LISTBOX         IDC_LIST_AC,0,0,138,9,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
END


MFC経験者ならそれなりに理解できると思います。リストボックスの横幅の処理がまずいですが、なんとかなるでしょ。 勝手に修正してください。 ダイアログ以外でなんとかしようと思ったのですが、、、使えるものが見当たらないので、自作です。 上位のダイアログエディタで、EditBoxを配置した後、変数追加でAcEditクラスの変数 m_edtFind として追加します。 AcEditの通知関数は、EN_CHANGEを追加して、下記のように。

.....
 ON_EN_CHANGE(IDC_EDIT_FIND_R, &CDFindNext::OnEnChangeEditFindR)
.....
void CDFindNext::OnEnChangeEditFindR()
{
 CString strA;
 this->m_edtFind.GetWindowTextA(strA);
 if (strA.GetLength())
 {
  char cC = strA.GetAt(0);
  if ((cC >= '0') && (cC <= '9'))
   this->m_btnOK.SetWindowTextA("Go");
  else
   this->m_btnOK.SetWindowTextA("検索");
  m_bFirst = true;
  SetStrings();
  SetTimer(TM_IDX_DROPDOWN, 500, NULL);
 }
 else
 {
  m_edtFind.ShowDropDown(false); // 消す
 }
}
void CDFindNext::SetStrings(void)
{
 CStringArray straryF;
 straryF.SetSize(0, 1500);
 CString strA;
 m_edtFind.GetWindowTextA(strA);
 if (strA.GetLength())
 {
  if (((CMainFrame *)AfxGetMainWnd())->FindComments(strA, straryF))
  {

   m_edtFind.ResetContent(); ///リストボックス文字列を消します。
   for (int i = 0; i < straryF.GetCount(); i++)
   {
    CString strAA = straryF.GetAt(i);
    strAA.Trim();
    m_edtFind.AddString(strAA);/// 文字列セット
   }
   m_edtFind.ShowDropDown(true); //// 表示指令
  }
 }

}


ここまで書けばなんとかなるでしょ。 作ったばかりですが、大きな問題はなさそうですのでUPしました。 細かいところは自分で修正してください。 余計な定義が残っているかもしれません。よろしく対処してください。
FindCommnetsは自作の選択項目の検索モジュールです。ここのstraryFがリストに表示されるという塩梅です。

2016年3月22日火曜日

MFC、opencv、USBカメラ

机の上でテストした基板の状態などの写真を簡単に撮影して、結果報告や資料として纏めておきたいということで、USBカメラをPCに接続して簡単に写真が撮れないかと。opencvを使うと簡単にできるらしいということで。
既知の人には今さら何を言ってるんだろうと思われるかもしれないけど。とりあえず。

まず、USBカメラは、、、

ELECOM WEBカメラ 200万画素 1/4インチCMOSセンサ ガラスレンズ搭載 筒型 イヤホンマイク付 ブラック UCAM-DLA200HBK

 

を選択。amazonで3千円、少し古い型のようだけど、ガラスレンズだということで決めた。

VisualStudio2013、MFCにて簡単なシャッターを押すだけのモニタを作る。

まず、適当なダイアログプロジェクトを作る。

そのプロジェクトにopencvを参照させるには、ツールメニューのNugetパッケージマネージャー、ソリューションのNugetパッケージの管理を開く。

右上の オンラインの検索 に opencvと入れて検索させる。
たぶん先頭に出てくる OpenCV OpenCV is a library of programming for realtime computer vision.のインストール釦を押す。
これでopencvが使えるようになる。
-------------------
stdafx.h
-------------------
#include <opencv2/core/core.hpp>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
を追加する。
 
 大体下記のソースを参照して貰えば、概要は分かると思います。実際はスレッドを起こして実行しています。
cv::VideoCapture(int カメラ番号)この番号はPCに接続しているカメラの番号なので、0から始まる。私の場合はノートPCにカメラが既に付いているので1が外付けのカメラだった。OSが認識する順なので、立ち上げ時に繋いでいると順序が入れ替わる可能性がある。結構不便。
cv::Mat これにイメージが取り込まれる。cam >> cam1image
cv::imwrite()、ファイル名の拡張子でJPGだったりPNGなどを指定できる。
CvvImageはDCとの仲介用で、これはネットからのコピペです。なかなか見つからなかった。権利上の問題があるかもしれないので、ここに貼り付けるのは憚れるので、以下のURLを見てください。
http://blog.csdn.net/vsooda/article/details/7547431
中国語ですが、ソース表示の上にViewPlain釦があります。

CvvImageを探す以外はすごく簡単でした。今時は簡単にできるのだなと感慨に耽ってしまった。

で、カメラをスタンドにグルーで付けて、上から撮影できるようにした。





 簡単だったというメモでした。

 
void PCamClass::Run()
{
 // Override
 cv::VideoCapture cam(m_nNum);
 cam.set(CAP_PROP_FRAME_WIDTH, 1600);
 cam.set(CAP_PROP_FRAME_HEIGHT, 1200);
 DWORD dwLastTick = ::GetTickCount();
 while (1)
 {
  if ((::GetTickCount() - dwLastTick) < 334) // 3fps
  {
   Sleep(50);
   continue;
  }
  dwLastTick = ::GetTickCount();
  cv::Mat cam1image;
  cam >> cam1image; 
  
  if (m_bGetJPEG && !m_bGotJPEG)
  {
   /// Save to the FileName
   cv::imwrite(m_strFileName, cam1image);
   m_bGotJPEG = true;
  }

  CvvImage cimg;
  IplImage img = cam1image;

  cimg.CopyOf(&img);

  if (IsWindow(m_hWnd))
  {
   HDC hDC = ::GetDC(m_hWnd);
   cimg.DrawToHDC(hDC, &m_rectDraw);
   ReleaseDC(m_hWnd, hDC);
  }
  DWORD dwRet = ::WaitForSingleObject( this->m_hTerminateEvent, 0);
  switch (dwRet)
  {
  case WAIT_OBJECT_0: // Prio End
   return;
  }
 }
}

2016年3月12日土曜日

MFC MDI ドキュメントタイプ選択の画面を出したくない。

MFCのMDIでは拡張子に応じて、ドキュメントタイプを選択して、ドキュメントテンプレートでCDocumentとCViewを開くようになっている。
しかし、空のドキュメントを開かせようとすると、プログラム起動時にドキュメントタイプを選択するダイアログが出てしまう。

これは知らないユーザにとっては何を選択するべきか、判りにくい。

で、空のドキュメントは常にデフォルトのドキュメントタイプを開かせておき、その後、実ファイルを開いた時だけ該当のドキュメントタイプが利用できる様にしたい場合。

拡張子だけで、中身のないファイルを扱えるようにしてあるので、ドキュメントタイプを指定して新規ファイルを作成できなくても問題がないようになっている前提である。ちょっと特殊かもしれないが、とにかく選択ダイアログは判りにくいので表示しない。


CWinApp::InitInstace()
{

   ................

    CMultiDocTemplate* pDocTemplate;
    pDocTemplate = new CMultiDocTemplate(IDR_XXXXXXXXTYPE,
        RUNTIME_CLASS(CXXXXXXXXDoc),
        RUNTIME_CLASS(CChildFrame), // カスタム MDI 子フレーム
        RUNTIME_CLASS(CXXXXXXXXView));
    if (!pDocTemplate)
        return FALSE;
    AddDocTemplate(pDocTemplate);

    通常はここに追加のテンプレートを定義するのだが、コマンドラインに指定がなければ追加しない。
    if (strlen(m_lpCmdLine))
    {
        pDocTemplate = new CMultiDocTemplate(IDR_YYYYYYYYTYPE,
            RUNTIME_CLASS(CYYYYYYYYDoc),
            RUNTIME_CLASS(CChildFrame), // カスタム MDI 子フレーム
            RUNTIME_CLASS(CYYYYYYYYView2));
        if (!pDocTemplate)
            return FALSE;
        AddDocTemplate(pDocTemplate);
    }
   ................

  pMainFrame->UpdateWindow();
  // このInitInstance()の最後で、追加しなかったドキュメントタイプを追加しておく
    if (!strlen(m_lpCmdLine))
    {
        pDocTemplate = new CMultiDocTemplate(IDR_YYYYYYYYTYPE,
            RUNTIME_CLASS(CYYYYYYYYDoc),
            RUNTIME_CLASS(CChildFrame), // カスタム MDI 子フレーム
            RUNTIME_CLASS(CYYYYYYYYView2));
        if (!pDocTemplate)
            return FALSE;
        AddDocTemplate(pDocTemplate);
    }
}