こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

C++のクラス内で、ウインドウプロシージャなどの・・・

Visual C++ 2008で、Windowsフォームアプリケーションを作っています。
.NET Frameworkでは実現できないウインドウを作るために、WindowsAPIに頼り始めたのですが

これが分からないのでとても不便な状態なのですが

WindowsAPIで使うための、ウインドウプロシージャや、ウインドウハンドルや、デバイスコンテキストハンドル・・・等を
C++のクラスの中で、staticを付けずに普通のメンバとして組み込む事は、出来るのでしょうか?

- - - - - -
今までは分からなかったので、とりあえずAPIに触れてみようと思いstaticをつけてどうにかやっていたのですが、それだとクラス内のインスタンスメンバにアクセスできないので、クラス内に作ってる意味がない感じになってしまいます。

投稿日時 - 2009-01-26 02:00:08

QNo.4661604

困ってます

質問者が選んだベストアンサー

 こんにちは。補足頂きました。

 WndProc等の事について、説明不足だったかもしれません。

>>仮想関数WndProcと共用体の別ウインドウハンドルをここからどう使っていくかが把握できないので

 union
 {
  HWND hWnd;
  HWND hDlg;
  HWND hCtl;
 };

 の事でしょうか。此れに関しては、何れも同じなので、深い意味は無いです・・・。
 ネイティブのwin32プログラムでウィンドウハンドルの命名の仕方に、通常のウィンドウやキャプションの無いクライアント領域子ウィンドウにhWnd、ダイアログウィンドウにはhDlg等と名づけ、エディットボックスやコンボボックス等のコントロールのウィンドウハンドルにhCtlと名づけたりする事が良くあるので、共用体にして名前を複数用意しただけの事です。
 何れもウィンドウである為、操作に使用するwin32APIはほぼ同一ですが、プロシージャ内のコーディング等に幾分の違いがあります。

>>そして、つまり…いずれにしてもSetProp・GetPropは使って、一段階は変換する必要がある…って事ですよね?
 はい。プロシージャはコールバック関数なので、呼び出される度クラスのインスタンス外に放り出される為、「何らかの手段において」このウィンドウを開いた張本人「クラスのポインタ」を探さなければなりません。
 よく有るやり方は、ウィンドウハンドルに対し、自由データを設定できるwin32API利用して、クラスのポインタを無理矢理キャストしてhWndとクラスのポインタの関連付けを行い、プロシージャコールバック時に第一パラメータのhWndから、win32APIを使ってつまみ出すと言うやり方です。
 ウィンドウハンドルに対して自由データを設定できる関数は、以下のものがありますが、何れもlong型やHANDLE型(素性はvoid*)なので、強引なキャストは避けられなくなります。

 「SetProp/GetProp」
 http://msdn.microsoft.com/ja-jp/library/cc411066.aspx
 http://msdn.microsoft.com/ja-jp/library/cc364724.aspx

 「SetWindowLongPtr/GetWindowLongPtr」
 http://msdn.microsoft.com/ja-jp/library/cc411204.aspx
 http://msdn.microsoft.com/ja-jp/library/cc364762.aspx

 例えば、
 ::SetWindowLongPtr(hWnd, GWL_USERDATA, (long)this);
 CWnd* pWnd = (CWnd*)::GetWindowLongPtr(hWnd, GWL_USERDATA);
 の様にして使う。

 別の手法としては、hWndをキーにしてCWnd*を値にするハッシュテーブルの様なデータ構造を作成、又は利用すればこの様な強引なキャストを避ける事も可能です。

 また、プロシージャのコールバックについては、メッセージの種類に応じて、wParamとlParamを、規定された型へキャストして使用する事になっています。
 例えば、WM_NCCREATEの場合は
 http://msdn.microsoft.com/en-us/library/ms632635(VS.85).aspx

 wParam 未使用
 lParam CREATESTRUCT*型へキャスト

 http://msdn.microsoft.com/en-us/library/ms632603(VS.85).aspx
 CREATESTRUCT型のlpCreateParamsメンバ変数に、CreateWindowExの一番最後のパラメータに渡した数値が入っている
 と言う事です。

 //クラス内でウィンドウを作成
 ::CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("test c++cli win32"), TEXT("hello win32"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, w, h, hWndParent, NULL, m_hInst, this/*クラスのポインタを入れて送り届けている*/);

 //プロシージャ内
 if(uMsg == WM_NCCREATE)
 {
  LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);//規定通りキャストする
  pWnd = static_cast<CWnd*>(lpcs->lpCreateParams);//ココにクラスのポインタが入っている
  ::SetProp(hWnd, TEXT("window ptr"), pWnd);//論点のSetProp()APIを使用して、ウィンドウハンドルに設定している
  pWnd->m_hWnd = hWnd;//クラスのメンバにウィンドウハンドルを代入している
  lResult = pWnd->WndProc(msg);//クラスの仮想関数WndProcを呼び出してあげる(以後、この仮想関数内に色々実装される)
 }

 また、WM_NCCREATEはウィンドウの非クライアント領域(キャプションバーなど)が作成された時に呼び出されるメッセージです(ウィンドウが作成されてから2番目に呼ばれる)。

投稿日時 - 2009-01-27 12:08:20

お礼

こんばんは。ありがとうございます!

ああ、質問が止まりません

SetProp/GetProp

SetWindowLongPtr/GetWindowLongPtr

のいずれかで行う場合は、どちらがより優れていますか?


--------------

>ハッシュテーブルの様なデータ構造

というのは、これもやはりあらかじめプロシージャとかなにから全部別途用意しておいて、そっちに飛ばす

その際、キャストして、か
評価して、か

の違いになるという事でしょうか?

--------------

なるほど
LPCREATESTRUCTの部分も良く分からなかったのですが
そっちにthisがあって、そういう仕掛けになっていたのですね。
納得です。

と、ここで

>CREATESTRUCT型のlpCreateParamsメンバ変数に、CreateWindowExの一番最後のパラメータに渡した数値が~

このCREATESTRUCTを使って出来るのかな・・??
という疑問は湧いたのですが、ウインドウの表示非表示切り替えを行いたいのですが、可能なのでしょうか?また、可能だとしてもどのように書けばいいのかピンとこないので、実例を教えていただく事は出来ますでしょうか?
http://oshiete1.goo.ne.jp/qa4666555.html

投稿日時 - 2009-01-27 21:41:37

このQ&Aは役に立ちましたか?

2人が「このQ&Aが役に立った」と投票しています

回答(5)

ANo.5

 こんばんは。

>>SetProp/GetProp
>>SetWindowLongPtr/GetWindowLongPtr
>>のいずれかで行う場合は、どちらがより優れていますか?
 SetProp/GetPropの方が良いと思います。

>>これもやはりあらかじめプロシージャとかなにから全部別途用意しておいて、そっちに飛ばす
>>その際、キャストして、か
>>評価して、か
>>の違いになるという事でしょうか?
 extern等で唯一のハッシュテーブル等を用意して置き、多数のプロシージャ内で其れを使用し、ウィンドウハンドルをキーに当てて、検索、評価、あれば呼び出し、無ければ登録する、と言った具合でしょう。
 WM_NCCREATE内で登録し、WM_NCDESTROY内でウィンドウが無くなる為、登録解除する事になります。
 WM_NCCREATEを通過すれば、WM_NCDESTROYが来るまで、その他のメッセージに関しては、必ず検索が成功する筈です。

>>ウインドウの表示非表示切り替えを行いたいのですが、可能なのでしょうか?また、可能だとしてもどのように書けばいいのかピンとこないので、実例を教えていただく事は出来ますでしょうか?
 リンク先質問の方で回答します。

投稿日時 - 2009-01-27 22:18:31

お礼

全く驚くべき丁寧さで解説していただき、本当にありがとうございました!!
感謝の限りも御座いません。

概念的な部分は質問前と比べて色々把握できましたが
細かい実践的で具体的な部分については
今後また疑問が出たら新しく質問を立てさせていただきますので、もしご都合がよろしければその時はまたお願いいたします。

しかし本当にありがとうございました。

投稿日時 - 2009-01-27 22:36:16

ANo.3

 こんばんは。補足頂きました。

>>まずは、typedefなどをしなくても、普通に宣言してるだけで
>>「struct 構造体名 構造体変数」
>>でなく
>>「構造体名 構造体変数」
>>で出来るのは、C++の機能でしょうか?
 はい。
 そして、c++に置いては、classとstructは同一です。唯一の違いは、内部に置かれたメンバ「関数(当然コンストラクタ、デストラクタも)・変数」が、
 class デフォルトでprivate
 struct デフォルトでpublic
 と言う事です。

 テンプレートに関しては、コンパイルの時点で方が決まるという事です。
 クラスの外側に置く方法と、関数のパラメータに置く事が出来ます。

 //例えばクラスの外側に置く
 struct DATA
 {
  int i;
  char ch;
  short sh;
 };

 template<class T>
 struct bogus
 {
  T* p;
 };

 bogus<int> ptr1;
 bogus<long> ptr2;
 bogus<DATA> ptr3;
 ptr1.p;//int*
 ptr2.p;//long*
 ptr3.p;//DATA*
 になる。

 //例えば関数に置く
 template<class _WPARAM, class _LPARAM>
 INT CustSendMessage(HWND hWnd, UINT uMsg, _WPARAM wParam, _LPARAM lParam)
 {
   return ::SendMessage(hWnd, uMsg, (WPARAM)wParam, (LPARAM)lParam);
 }

 SendMessage()APIは、ウィンドウに操作する度必要ですが、uMsgに指定したフラグに応じて、WPARAMとLPARAMに様々なデータを入れなければいけません。
 場合によっては、ポインタを無理矢理キャストして入れることもあります。

 例えば、ウィンドウからテキストを取り出したい場合、

 ですが、其のまま描くと

 TCHAR ch[128];
 ::SendMessage(hWnd, WM_GETTEXT, sizeof(ch)/sizeof(TCHAR), (LPARAM)ch);

 と言う事に成ります。テンプレートを使えば、

 ::CustSendMessage(hWnd, WM_GETTEXT, sizeof(ch)/sizeof(TCHAR)/*unsigned intにコンパイルされる*/, ch/*TCHAR*にコンパイルされる*/);

 自動的に型が決定される為、むやみなキャストを減らす事が出来ます。
 実は、このテンプレートはとても奥が深くて、こういった技法の事を言及するだけで辞典一冊書ける位です。
 C++書籍の中には、テンプレートのみを一点に絞って説明する専門書もあります。
 また、Cではマクロでしていた事をC++ではテンプレートでする事も多いです。
 STLも、テンプレート技法の一環と言えるかも知れません。興味があれば、此方も調査されて見ると良いでしょう。

投稿日時 - 2009-01-26 19:03:24

補足

どうにか大よその構造は分かってきました。

仮想関数WndProcと共用体の別ウインドウハンドルをここからどう使っていくかが把握できないので、うまく説明できないような感覚に襲われていますが
少なくとも「仮想関数」があるとかそもそも「関数がある」とかいう事で、実際問題全くその…構造体・クラスにはpublicかprivate以外差がないのか、周辺情報とともに、以下の質問で確認させてください。
http://oshiete1.goo.ne.jp/qa4664223.html

そして、つまり…いずれにしても
SetProp・GetProp
は使って、一段階は変換する必要がある…って事ですよね?

投稿日時 - 2009-01-27 00:25:04

お礼

こんばんはmachongolaさん、またもありがとうございます!
詳細や状況がより明確になりました。

やはりですか。私もそんな予感がしました。
でも、その分これうまく使いこなせたら面白そうですよね…?(♪)

templateの使用感含め、徐々にチェックしている感じです。

とりあえず今回は「一つの質問」として、内容的には締めにして十分だと思いますが、把握すべき事が多いですので
もう少し確認等に時間をください。

本当にちょっとした付加質問でしたら、また浮上する可能性もありますが、難しそうな質問でしたら再度別途立てますので、その時はまた可能でしたらよろしくお願いします。
(私は、丁寧に答えてくださる方に、ありがとうポイントをたっぷり用意したい気分ですから。(微笑))

そして、全体がすっきり解決しましたら、その時この質問を締めとさせていただきます。


しかし、結構突っ込んだところまで本当にありがとうございます。
じっくりと挑んでみます。

投稿日時 - 2009-01-26 21:51:58

ANo.2

 こんにちは。

 staticのWndProc側でチョッとした処理をした後、メンバ関数版のWndProcに転送します。
 WM_NCCREATEでthisポインタを受け取ったら、ウィンドウに関連付けて使い、WM_NCDESTROY辺りでthisポインタを切り離して削除します。
 その他、メンバ関数のWndProc内で処理した上、DefWindowProcに「渡す・渡さない」を決めさせたりする必要があるかもしれません。結構大変です。
 大分あるアラがあると思いますが、参考程度に。

struct CMessage
{
template<class _WPARAM, class _LPARAM>
CMessage(HWND h, UINT u, _WPARAM wp, _LPARAM lp)
: hWnd(h), uMsg(u), wParam((WPARAM)wp), lParam((LPARAM)(lp)), bHandled(FALSE)
{}

union
{
HWND hWnd;
HWND hDlg;
HWND hCtl;
};
UINT uMsg;
WPARAM wParam;
LPARAM lParam;
BOOL bHandled;//WndProc内でココにTRUEを入れるとDefWindowProcを呼ばない。
};

struct CWnd
{
//こっちがコールバック
static LRESULT CALLBACK StaticProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CWnd* pWnd = static_cast<CWnd*>(::GetProp(hWnd, TEXT("window ptr")));
CMessage msg(hWnd, uMsg, wParam, lParam);
LRESULT lResult = FALSE;
//初回登録
if(uMsg == WM_NCCREATE)
{
LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
pWnd = static_cast<CWnd*>(lpcs->lpCreateParams);
::SetProp(hWnd, TEXT("window ptr"), pWnd);
pWnd->m_hWnd = hWnd;
lResult = pWnd->WndProc(msg);
}
//ウィンドウのポインタがあったら
if(pWnd)
{
switch(uMsg)
{
case WM_CLOSE:
lResult = pWnd->WndProc(msg);
if(msg.bHandled)return lResult;

::DestroyWindow(hWnd);
return lResult;

case WM_NCDESTROY:
lResult = pWnd->WndProc(msg);
::RemoveProp(hWnd, TEXT("window ptr"));
pWnd->Destruct();

}
}
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}

explicit CWnd(bool bExit = false) : m_hWnd(NULL), m_hInst(::GetModuleHandle(NULL)), m_bExit(bExit){}
~CWnd(){if(m_bExit)::PostQuitMessage(0);}
ATOM Regist()
{
WNDCLASSEX wc = {sizeof(wc)};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = &CWnd::StaticProc;
wc.hInstance = m_hInst;
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
wc.lpszClassName = TEXT("test c++cli win32");
return ::RegisterClassEx(&wc);

}
HWND Create(int x, int y, int w, int h, HWND hWndParent)
{
return ::CreateWindowEx(WS_EX_CLIENTEDGE,
TEXT("test c++cli win32"),
TEXT("hello win32"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
0, 0, w, h, hWndParent, NULL, m_hInst, this);

}
HWND Create(int x, int y, int w, int h, System::Windows::Forms::Form^ form)
{return this->Create(x, y, w, h, static_cast<HWND>(form->Handle.ToPointer()));}
void Destruct(){delete this;}
protected:
//こっちに転送する
virtual LRESULT WndProc(CMessage& msg){return FALSE;}
HWND m_hWnd;
HINSTANCE m_hInst;
bool m_bExit;//ココがtrueの場合、閉じたら全てを終了させる
};

//こんな感じで呼び出す
public:Form1(void)
{
InitializeComponent();
//
//TODO: ここにコンストラクタ コードを追加します
//
CWnd* pWnd = new CWnd();
pWnd->Regist();
pWnd->Create(0, 0, this->Width, this->Height, this);
}

投稿日時 - 2009-01-26 12:29:42

お礼

こんにちは。
具体的なコードをありがとうございます。

今頑張って読み解いている最中なのですが
私はプログラミングに触れ初めて実質的には4か月くらいで、CやC++/CLIはそこそこいじっていたのですが、ネイティブのC++は
丁度、あまりやっていませんでした。

でも色々な事が起きてて、この際全部理解し尽くしたいですので
いくらか適宜質問させてください。

マネージ内だとstructが使いづらいか、エラーが出て作り方が良く分からなかったので、今までは全部クラスでやってて、構造体は使った事がなかったのですが
(先ほどネイティブクラス内でstructを色々とチェックしてみました)

まずは、typedefなどをしなくても、普通に宣言してるだけで
「struct 構造体名 構造体変数」
でなく
「構造体名 構造体変数」
で出来るのは、C++の機能でしょうか?

あと、templeteについては込み入った内容になるかもしれない・・?と感じたので、別質問を立てさせていただきました。
http://oshiete1.goo.ne.jp/qa4662948.html
もしよろしければ、ご教授のほどよろしくお願いします。

投稿日時 - 2009-01-26 17:43:15

ANo.1

SetPropあたりを使って、ウィンドウハンドルにクラスのポインタを設定してみてはどうでしょうか?

検索ワード:WndProc SetProp

投稿日時 - 2009-01-26 09:02:58

お礼

ありがとうございます。
調べて考えてみて、それで動作確認は出来たっぽい感じなのですが
調べてみたら色々不安になるような事を見たので
これであっていて、問題はないか
問題がない場合でも、より良い方法があったら教えてください。



WNDCLASSEX a;
に対し
a.lpszClassName = "任意";
a.lpfnWndProc = Proc;


とし
HWND b=CreateWindowExA(~, a.lpszClassName
~~~~~);

に対し
::SetProp(b, "なにか", (HANDLE)this);


とし
例えばこれが
「Draw関数を持つ、Treeクラス」内でやるなら

プロシージャはスタティックにするよりないのでそうしておいて
その中で
LRESULT CALLBACK Tree::Proc(HWND hw, UINT uaMsg, WPARAM wp, LPARAM lp){
Tree* tWnd = (Tree*)GetProp(hw, "なにか");
……

とすれば

このプロシージャ内で

tWnd->Draw();

等を行える、という事で正しいでしょうか?

投稿日時 - 2009-01-26 11:13:55

あなたにオススメの質問