物忘れの激しくなった自分宛ての開発メモ

            目次 

(これが正しいやり方かどうかは知りません。不具合が出ても責任は取れません)

下記について私になにか言いたいという方は掲示板にでも投稿してください。
ここに記述してほしい内容(質問)はメールでお願いします。暇があれば答える
かもしれません。

サンプルコードはエラー処理を行っていませんので、ご自分でインプリメント
する際はエラー処理を入れましょうね。エラー処理とは、エラーメッセージの
表示、継続できない場合はプログラムを終了させるという処理のことです。

000 MFCを使っている理由

001 PocketPC2002 MFC タップ&ホールドでくるくるが出るデフォルトの消し方

002 PocketPC2002 MFC 多重起動抑止を止める方法(多重起動させる方法)

003 PocketPC2002 MFC フォーカスを取り返す方法

004 PocketPC2002 MFC モードレスダイアログを開く

005 PocketPC2002 MFC オーナー描画について

006 PocketPC2002 MFC タップ&ホールド後の通知を受け取る。

007 旧機種 MFC タップ&ホールドを自分で組み込む

008 MFC ウィンドウ間(自分宛てでも可能)ユーザメッセージを投げる、受け取る

009 MFC タイマーの使い方(WM_TIMER)

010 MFC ワーカースレッドの起こし方

011 PocketPC2002 英語版SDKのエミュレータを日本語表示可能にする
   MSサイトで日本語イメージのダウンロードができます
   http://www.microsoft.com/mobile/developer/downloads/ppc2002emulator/default.asp

=================== ラジェンダ関連

BE5-001 CSOButton(ラジェンダのボタン)をMFCで使う。

 

 

 

 

 

 


000 MFCを使っている理由

良い点

悪い点

もう少しMFCでのプログラミング用HELPが充実していれば判りやすいと思います。
結構つまらないことで悩んでしまいますので、、、、

MFCのヘルプに望むこと

と思ってこのページを作ることにしたんです。MSDNにもそれなりに書いてあるけど、、、

MFCの速さはWin32APIで記述している場合とほとんど変わりません。MFCを使った
場合のオーバーヘッドはMFCのDLLをロードするためにメモリを消費するということ
が一番おおきな事だと思います。近頃のCE機ならこれも気にならないほどメモリに
余裕がありますけど、、、、

 


001 PocketPC2002 MFC タップ&ホールドでくるくるが出るデフォルトの消し方

stdafx.cppに以下の記述を入れる。

リビルドする。

void CWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
	Default();
}

これ以外でも応用が利きそうです。(なんでもオーバーライドできるのか?)


002 PocketPC2002 MFC 多重起動抑止を止める方法

stdafx.cppに以下の記述を入れる。

MFCソースでAfxWinMainを検索する。
(ここにCOPYして載せると著作権とか問題になるかもしれないのでやめた)

このモジュールをCOPYして、stdafx.cppに入れる。

#ifdef 内にMutexをいじっている個所があるので、#ifdef から#endifまでを削除する。

リビルドする。


003 PocketPC2002 MFC フォーカスを取り返す方法

モードレスダイアログを開いているときに、CViewにフォーカスを取り返すためには

CViewで記述するのなら

	::SetFocus(NULL); //一旦フォーカスを持っているWndからフォーカスを取り上げる。
	SetFocus(); //自分にフォーカスする。
一旦フォーカスを取り上げれば自分にフォーカスしなおすことができます。

 


004 PocketPC2002 MFC モードレスダイアログを開く

 作成したダイアログクラスのOnOK、OnCancelをオーバーライドして、何も起きないようにします。
void CDColor::OnOK() 
{
}

void CDColor::OnCancel() 
{
}
 Createをpublicで作成します。DoModalの代わりにこれを呼び出してダイアログを開きます。

void CDColor::Create(CWnd *pWnd)
{
	m_bFullScreen = FALSE; //これはPocketPC特有のフルスクリーンになるのを防ぐためです。
	CDialog::Create(IDD, pWnd);
	m_pView = (CPPaintView *)pWnd;
}
  作成側では通常newで作成しますので、ダイアログが閉じられた段階で自分を削除します。
  これで作成側は削除する必要はありません。但し、作成側は消えているかどうか判らなく
  なる可能性がありますので、作成側はCreateした直後にこのダイアログのm_hWndを覚えて
  おいて、消えているかどうかを ::IsWindow(hWnd)でチェックします。消えていて、再度
  開きたい場合はnewして構いませんし、消えていなければ作成したダイアログへのポインタ
  が有効です。消えている場合は作成したダイアログのポインタが無効ですのでそのポインタ
  を使って操作するとメモリフォルトが起きます。
void CDColor::PostNcDestroy() 
{

	CDialog::PostNcDestroy();
	delete this;
}
 
PocketPCのMFCではメニュー部にダミーの画面を表示して使えないようにされてしまいますが、
モードレスではメニューを消されては困ります。よって、
BOOL CDColor::OnInitDialog() 
{
	CDialog::OnInitDialog();
	m_pWndEmptyCB->DestroyWindow(); //これもPocketPC特有です。この画面がメニューを隠しています。
	return TRUE;
}
と、このようにすればモードレスダイアログを出すことができます。フォーカスの移動は
前述003を参照して ください。

005 PocketPC2002 MFC オーナー描画について

MFCのHelpにも載っていますけど

 
void CDColor::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
	switch(nIDCtl)
	{
	case IDC_COL1: //ここでオーナー描画コントロールかどうかを判定して
		DrawColBtn(0, lpDrawItemStruct); //描画します。
		break;
	default:
		CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
		break;
	}
}
以下の様にLPDRAWITEMSTRUCT内のhDCをCDCにAttachすれば通常のCDCとして使えます。
最後にDetachを忘れずに!
void CDColor::DrawColBtn(int nId, LPDRAWITEMSTRUCT lpDraw)
{
	CDC dc;
	dc.Attach(lpDraw->hDC);
	CRect rectCli;
	::GetClientRect(lpDraw->hwndItem, rectCli);
	dc.DrawFrameControl(rectCli, DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_PUSHED);
	CBrush brs(m_aryCol[nId]);
	CRect rect1;
	rect1 = rectCli;
	rect1.top += 2;
	rect1.bottom -= 2;
	rect1.left += 2;
	rect1.right -= 2;
	dc.FillRect(rect1, &brs);
	dc.Detach();
}

006 PocketPC2002 MFC タップ&ホールド後の通知を受け取る。

各親WindowのOnNotifyハンドラを作って、以下の例であればIDC_EDIT1のタップ&ホールド
の通知でメニューを表示しているところです。

タップ&ホールドはCWnd::SHRecognizeGestureのヘルプを見てください。CWnd::OnLButtonDownで
呼び出しているよって書いてあります。PocketPC2002から赤いくるくるが出るようになったので
判りやすくなったと言えるかも? (プログラムによっては要らないっていう人もいるでしょうけど)

 

 
BOOL CDData::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) 
{
	NMHDR *pNMH = (NMHDR *) lParam;
	if (pNMH->code == GN_CONTEXTMENU)
	{
		PNMRGINFO pnmrgi = (PNMRGINFO) lParam;
		switch(pNMH->idFrom)
		{
		case IDC_EDIT1:
			{
 				CMenu *pMenu = new CMenu;
 				pMenu->LoadMenu(IDR_POPUP_EDT);
				CMenu *pSub = pMenu->GetSubMenu(0);
 				pSub->TrackPopupMenu(TPM_LEFTALIGN, pnmrgi->ptAction.x, pnmrgi->ptAction.y, this);
 				delete pMenu;
			}
			break;
		}
	}
	return CDialog::OnNotify(wParam, lParam, pResult);
}

007 タップ&ホールドを自分で組み込む

タップ&ホールドを自分で組み込むこともできます。(赤いくるくるは出ませんけど)
古い機種でもタップ&ホールドで動かしたいという場合はこれを使って、全ての機種で
同じような操作感で操作できるようにします。(これを使えば上記のSHRecognizeGestureは不要です)

但し、各コントロールに入れる必要があるので、ちょっと面倒です。使っているコントロールの
各メソッドをオーバーライドしなきゃいけいないから、、、、(コントロールを使っていない画面
(CViewなど)ではこれで大丈夫です)

OnLButtonDownで押された位置を覚えてホールド開始フラグをONしてホールドタイマー500msを掛けます。
OnLButtonUpでホールド開始フラグをOFFしホールドタイマーをキャンセルします。
OnMouseMoveでホールド開始フラグをOFFします。ホールドタイマーをキャンセルします。
OnTimerでホールドタイマーのタイムアップで 且つ、ホールド開始フラグがONなら、押された位置を元に処理を行います。(ポップアップメニューを出すとか)

 


008 MFC ウィンドウ間(自分宛てでも可能)ユーザメッセージを投げる、受け取る

これは基本ですね。(ここに書いておくほどのことでもないかも)

Stdafx.hに使いたいメッセージを定義します。

#define WM_USER_MY_MSG1   (WM_USER + 0x1001)

0X1001から使うのはこれ以下(WM_USERから WM_USER + ???)まではMFCが
使っているらしいとのことで、これを避けるためにこの辺りから使います。

たぶん CViewからの派生クラスで使うと思いますが、、、

投げる方法は

	pView->PostMessage(WM_USER_MY_MSG1, wParam, lParam);
または
  ::PostMessage(hWnd, WM_USER_MY_MSG1, wParam, lParam); 

 

受け取り側の定義

CMyView.hに

    //この位置に書きます。(DECLARE_MESSAGE_MAPより前に書く)
	afx_msg LRESULT OnUserMyMsg1(WPARAM wParam, LPARAM lParam);
	//{{AFX_MSG(CTestConfigView)
		// メモ -  ClassWizard はこの位置にメンバ関数を追加または削除します。
		//           この位置に生成されるコードを編集しないでください。
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()

CMyView.cppの

BEGIN_MESSAGE_MAP(CMyView, CView)
	//この位置に書きます,行の最後に;を入れないこと!
	ON_MESSAGE(WM_USER_MY_MSG1, OnUserMyMsg1)
	//{{AFX_MSG_MAP(CTestConfigView)
		// メモ - ClassWizard はこの位置にマッピング用のマクロを追加または削除します。
		//    この位置に生成されるコードを編集しないでください。
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
	・・・
LRESULT CMyView::OnUserMyMsg1(WPARAM wParam, LPARAM lParam)
{
  // wParam, lParamの内容により処理します。
	// なるべくクラスのポインタは投げない方が良いです。
	// lParamにメモリ領域のポインタを使ったりした場合は、何処でnew deleteするかを
	// よく考えて使ってください。投げる方がローカル変数領域(自動変数領域)を投げる
	// とこの受け取り時点で領域がなくなっています。できるだけポインタでないものを使う
	// 方が良いでしょう。配列のインデックスとか、wParam、lParamに収まる内容を使いましょう。
	return 0; // これが正常終了です。0を返すのが正しいです(メッセージの処理を終了したという意味)
}

たまに、Windowsの画面の切り替えが遅かったり、ListCtrlなんかを使った場合に
応答が遅れて画面描画が間に合わなかったり、そのメッセージ応答中にそのコントロール
を動かしてはいけない場合があったり(詳しく説明されている資料が少ないですけど)します。
そのような場合にこの自分宛てメッセージを使って、タイミングをずらしてあげるということで
解決する場合がありますので、、、、、タイマーを使わないとダメな場合も多いですけど。

 


009 MFC タイマーの使い方(WM_TIMER)

基礎中の基礎ですが、、、

CWndから派生されているクラスで使うことができます。使えないMFCクラスの代表は

CWinApp ==>CMainFrame

CDocument ==>CView

m_hWndが有効なら(画面表示している状態なら)有効です。画面が無い状態で
はタイマーを設定できませんので注意してください。画面がなくなるとタイマーも
自動的に破棄されます。

タイマーは止めない限り何度でもそのセットしたインターバルで通知されます。

タイマーの精度は低いので気をつけてください。

セットの仕方

 
	SetTimer(1015, 5000, NULL);
	//  ID=1015で 5秒タイマーを掛けています。

受け取り方はクラスウィザードで WM_TIMERのハンドラを作ります。

 
void CMyView::OnTimer(UINT nIDEvent) 
{
	if (nIDEvent == 1015)
	{
		KillTimer(1015); //メッセージボックスやダイアログを出す前には必ず停止します。
		AfxMessageBox(_T("タイムアップ!"));
		SetTimer(1015, 5000, NULL); //必要なら再度タイマーを設定します。
		//メッセージボックスやダイアログ、メニューなどを出さない場合で、インターバル
		//ごとにカウントアップするだけなどの処理なら、KillTimer、SetTimerを呼び出す
		//必要はありません。
		// ただし、WM_TIMERは必ず通知されますが、時間は正確ではありません。
		// また、インターバルが短すぎると途中数回分が抜け落ちる場合があります。
		// 正確な時刻は ::GetTickCount()を使って経過ミリ秒を取り出してください。
		// CE機の場合、::GetTickCount()は電源断で停止しますので気をつけてくださいね。
		// COleDateTime::GetCurrentTime()ではミリ秒は取得できません。
	}
	CView::OnTimer(nIDEvent);
}

010 MFC ワーカースレッドの起こし方

バックグラウンドで何かをやりたいという場合、ワーカースレッド(画面描画を行わないスレッド)を
起こします。

以下のクラスはスレッドをラップしたクラスPThreadCtrlです。PThreadCtrlはこのままでは使えません。派生して使います。

 
class PThreadCtl  
{
public:
	
	PThreadCtl();
	void Kill();
	virtual ~PThreadCtl();
public:
	virtual void Run() = 0;
public:
	char m_szThreadName[10];
	void Active();
	bool m_bStop;
	bool m_bWaiting;
	bool Create();
	HANDLE m_hThread;
	DWORD  m_dwThreadID;
	HANDLE m_hTerminateEvent;
	HANDLE m_hStartEvent;
};
DWORD WINAPI ThreadProc( LPVOID pParam )
{
	// バックのThread 
	PThreadCtl * pThis = (PThreadCtl *)pParam;
	pThis->m_hStartEvent = ::CreateEvent( NULL, true, false, NULL);
	pThis->m_hTerminateEvent = ::CreateEvent( NULL, true, false, NULL);
	HANDLE pHandles[2];
	bool m_bEnd = false;
	while(!m_bEnd)
	{
		pHandles[0] = pThis->m_hTerminateEvent;
		pHandles[1] = pThis->m_hStartEvent;
		pThis->m_bWaiting = true;
		DWORD dwRet = ::WaitForMultipleObjects(2, pHandles, false, INFINITE);
		pThis->m_bWaiting = false;
		switch(dwRet)
		{
		case WAIT_OBJECT_0: // Prio End
			m_bEnd = true;
			break;
		case (WAIT_OBJECT_0 + 1):
			::ResetEvent(pThis->m_hStartEvent);
			pThis->Run();
			break;
		}
	}
	::SetThreadPriority(pThis->m_hThread, THREAD_PRIORITY_TIME_CRITICAL);
	CloseHandle(pThis->m_hTerminateEvent);
	CloseHandle(pThis->m_hStartEvent);
	pThis->m_bStop = true;
	return 0;
}



PThreadCtl::PThreadCtl()
{
	m_hThread = NULL;
	m_bStop = false;
	m_bWaiting = false;
}

PThreadCtl::~PThreadCtl()
{
	if (m_bWaiting)
		if (!m_bStop)
		{
			::SetEvent(m_hTerminateEvent);
			while(!m_bStop)
				Sleep(3);
		}
}

bool PThreadCtl::Create()
{
	// CreateThread
	if (m_hThread)
		return false; // already exist
	m_bWaiting = false;
	m_hThread = ::CreateThread(NULL, 0, ThreadProc, (void *)this, CREATE_SUSPENDED, &m_dwThreadID);
	if (m_hThread == NULL)
		return false;
	::ResumeThread(m_hThread);
	while (!m_bWaiting)
		Sleep(5);
	return true;
}

void PThreadCtl::Kill()
{
	if (m_bStop)
		return;
	::ResetEvent(m_hStartEvent);  // Cancel restart !
	::SetEvent(m_hTerminateEvent);
	while(!m_bStop)
		Sleep(3);
	Sleep(3);
}

void PThreadCtl::Active()
{
	::SetEvent(m_hStartEvent);
}
以下の様に派生して使います。派生して使う理由は特にありませんが、上記
スレッド制御のソースコードは共通で使えるということだけです。
#include "ThreadCtl.h"
class PDrawThread : public PThreadCtl  
{
public:
	void Run();
	PDrawThread();
	virtual ~PDrawThread();
	//作業領域をここに定義します。
	bool	m_bProcessed;
  CWnd * m_pWnd;	
};
// 
PDrawThread::PDrawThread() //メンバー変数の初期化
{
}

PDrawThread::~PDrawThread() //メンバー変数の後始末
{

}
PDrawThread::Create(CWnd *pWnd)
{
	m_pWnd = pWnd;
	PThreadCtrl::Create();
}
void PDrawThread::Run() // これがスレッドとして実際に呼び出される関数です。
{
	//ここにやりたいことを記述します。
	m_pProcessed = true;
	m_pWnd->PosetMessage(WM_USER_MY_MSG1, 0, 0);
}
void PDrawThread::Active()
{
	m_bProcessed = false;
	PThreadCtl::Active();
}

//MFCのクラスはスレッドを意識して作られていませんので、親スレッドが
//変更する変数を子スレッドが直接変更してはいけません。CStringなどを
//両方で扱うとクラッシュします。作業領域として定義したMFCのクラスを
//このRun内で扱うのは構いません。処理結果は何かしらのフラグ(bool変数)
//で通知できるように工夫します。処理終了はなるべくPostMessageを使います。
生成方法は CMyViewかCMyDocument のメンバ変数に

PDrawThread m_Thread; 

のように定義します。ポインタにしてnew、deleteしても構いませんが、
特に意味があるかどうかは判りません。必須のスレッドならnew,delete
はしない方が良いでしょう。newした時にメモリ不足になるようなら、
プログラムが動作しないということですから。

 どこかの初期化で一度だけ
   m_Thread.Create(this);
を呼び出しておきます。これでスレッドは待機状態です。 Runは未だ呼び出されません。

処理を開始したい場合は  PDrawThreadの作業領域に値を設定してあげて 
    m_Thread.Active()
を呼び出します。これで1度だけRun()が呼び出されます。  
m_Threadが処理を終えたかどうかを知るには  
m_bProcessed == true  
です。 

上記のRunの例では WM_USER_MY_MSG1を通知しています。これを
親Windowが受け取れば、その時点で処理が終了しているわけです。 

ワーカースレッドが動作中、画面の動きがギクシャクするようなら、
::SetThreadPriorityでワーカースレッドのプライオリティを下げます。
下げたり上げたりするのであれば、なるべくPDrawThread内で行います。
Runの最初で下げておいて最後に戻しておくとか、、、


 

011 PocketPC2002 英語版SDKのエミュレータを日本語表示可能にする

必要なファイルはPocketPC2002本体からCOPYします。実機が無い方は無理?

PocketPC2002の\Windowsにある

cour.ttf
msgothic.ttc

これらをデスクトップの適当な場所(一時場所)に写します。

eVCのツール「リモートファイルビューワー」でこれらをエミュレータの\WindowsにCOPYします。

次に、以下のレジストリをエミュレータに作成します。(以下はpieRegのテキスト形式です)

 
\\HKEY_LOCAL_MACHINE
[SOFTWARE\Microsoft\FontLink]
[SOFTWARE\Microsoft\FontLink\SystemLink]
	"Tahoma"=SZ:\Windows\msgothic.ttc,MS PGothic
	"Courier New"=SZ:\Windows\msgothic.ttc,MS PGothic
	"MS PGothic"=SZ:\Windows\tahoma.ttf,Tahoma
	"MS Gothic"=SZ:\Windows\tahoma.ttf,Tahoma
[SOFTWARE\Microsoft\FontLink\SkipTable]
	"Tahoma"=SZ:005c,00a5,007e,0391-03c9,2026,2116,221a,25a0-25ff
	"Courier New"=SZ:005c

 この後、エミュレータのSoftResetを行うと日本語表示できるようになります。

エミュレータを終了させる際、現状維持を選択してください。Offすると上記設定
とCOPYしたファイルがなくなり、初期化されてしまいます。

US版エミュレータはMultiByteToWideCharが正常に動作しません。自分で作って
ください。(笑えるようなものを私は作成しました。このソースコードが欲しい方は
メールを下さい。500円で差し上げます。既に弊社のプログラムのいずれかの
ライセンスを持っている方には無料で差し上げます。)

この方法が著作権を侵害しているかどうかは知りませんので、個人で試したい
方は試してみてください。また、ここでの記述に問題があるという方はメール
ください。このTipsを削除します。