タブコントロールにドロップするには?

解決


 2004-11-25 07:05:20  No: 55331  IP: [192.*.*.*]

柚と申します。
今、VC++6の環境で、SDKのみでアプリケーションを作成しています。

ダイアログにタブコントロールを配置し、Aタブ,Bタブと作りました。
そして、Aタブにグループボックスを1つ作り、
そこにドラッグ・ドロップできるように、サブクラス化しました。

それで問題なのは、ファイルをドラッグドロップしようとしても
マウスカーソルすらドロップ可能な形に変更されないことです。

タブコントロールがなく、ダイアログに直接グループボックスを作成した場合は
正常な動作をしていましたし、
また、タブでのこのような形のアプリケーションは見たことがないので、
ひょっとしてタブコントロール上では無理なのかもと疑問も沸いているのですが、
どうでしょうか。

---
タブコントロール
http://home.a03.itscom.net/tsuzu/programing/tips28.htm  を参考にしました。

Aタブの子ダイアログ・プロシージャ
case WM_INITDIALOG:
  hGroupBox = GetDlgItem(hWnd, IDC_GroupBox);
  OriginalProc = (WNDPROC)GetWindowLong(hGroupBox, GWL_WNDPROC);
  SetWindowLong(hGroupBox, GWL_WNDPROC, (LONG)NewProc);
  SendMessage(hGroupBox, UserMessage, 0L, 0L);
  return TRUE;

NewProc()
case UserMessage:
  DragAcceptFiles(hWnd, TRUE);
case WM_DROPFILES:
  (ドラッグ・ドロップ処理)
---
処理上、何か足りないのでしょうか・・

どなたかアドバイスなどよろしくお願いいたします。

編集    削除
 2004-12-02 16:01:54  No: 55332  IP: [192.*.*.*]

柚です。
投稿から一週間が経ちまして、
未だ解決に至らず困っております。

それで、ご尽力いただいているこちらの皆様には失礼なのですが、
どうしても知りたいことですので、
本件についてマルチポストさせていただきたく思います。

大変申し訳ございませんが、ご了承下さるよう宜しくお願いします。

また、引き続き情報をお待ちしています。
ありがとうございました。

編集    削除
なーめ  2004-12-05 12:26:03  No: 55333  IP: [192.*.*.*]

[プロジェクト]->[プロジェクトへ追加]
->[コンポーネントおよびコントロールの追加]
-><Visual C++ Component>->プロパティシート

MFC ですが。いかが?

編集    削除
 2004-12-06 09:04:07  No: 55334  IP: [192.*.*.*]

お返事ありがとうございます。

ご提案の方法がMFCですので、
「このコンポーネントを適用するためにはプロジェクト内に最低1クラスのCCmdTarget派生クラスがなければなりません。」
といったエラーがやはり出てしまいましたが、
”プロパティシート”による方法は思いつきませんでしたので
SDK方式で試してみようと思います。

MFCとはいえ、感謝いたします。
また後日、結果報告させて頂きます。

編集    削除
なーめ  2004-12-07 03:54:48  No: 55335  IP: [192.*.*.*]

素朴な質問ですが。
Message がらみなので
Spy++ は使ってみました?

編集    削除
 2004-12-07 09:33:48  No: 55336  IP: [192.*.*.*]

Spy++は使用していませんが、

LRESULT CALLBACK NewProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  case UserMessage:
    MessageBox(hWnd, "UserMessage", "NewProc", MB_OK);
    DragAcceptFiles(hWnd, TRUE);
    break;
  case WM_DROPFILES:
    MessageBox(hWnd, "WM_DROPFILES", "NewProc", MB_OK);
    (ドラッグ・ドロップ処理)
  〜略〜
}

としてみたところ、
「UserMessage」は表示されるのに対し、「WM_DROPFILES」は表示されませんでした。
そのため、WM_DROPFILESメッセージが来ていないことがわかります。
考えられる理由として、
DragAcceptFiles(hWnd, TRUE);  が失敗している
ということなんだと思いますが・・
返値は void なのでそれが失敗・成功しているか確認できないんですよねぇ...。

ですので、そもそもタブコントロール上でサブクラス化したエリアに対してドラッグ・ドロップ可能?
という疑問になりました。
それで先のような機能をもつソフトがあるか検索してみたのですけど、発見できず・・。

編集    削除
なーめ  2004-12-07 10:37:19  No: 55337  IP: [192.*.*.*]

>> DragAcceptFiles( hWnd, TRUE );

指定すべき hWnd が違っているかもしれませんね。

ダイアログもタブコントロールもその上に載っているコントロールも
全てウィンドウなので、それぞれ別の hWnd ハンドルを持っている
はずです。

全ての hWnd ハンドルについて(ひとつづつ)
DragAcceptFiles() してみるのはどうかな。

案その2.
ダイアログリソースのところで、ウィンドウスタイル
WS_EX_ACCEPTFILES
を加えてみるとか。
(本質的に DragAcceptFiles() するのと同じだが)
↓の例では DIALOG,TABCONTROL,EDITCONTROL に付いている。
---------------------------------------------------
IDD_ONLYDLG_DIALOG DIALOGEX 0, 0, 159, 108
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_ACCEPTFILES | WS_EX_APPWINDOW
CAPTION "OnlyDlg"
FONT 9, "MS Pゴシック"
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,102,7,50,14,0,WS_EX_ACCEPTFILES
    PUSHBUTTON      "キャンセル",IDCANCEL,102,23,50,14
    EDITTEXT        IDC_EDIT1,28,30,40,14,ES_AUTOHSCROLL,WS_EX_ACCEPTFILES
    CONTROL         "Tab1",IDC_TAB1,"SysTabControl32",0x0,28,62,50,30,
                    WS_EX_ACCEPTFILES
END
---------------------------------------------------

/////////////////////////////////////////////////
DragAcceptFiles() で登録していないウィンドウに
別アプリから WM_DROPFILES を送りつけるとどうなるか。
答えは、ちゃんと受け取ります。

WM_DROPFILESを送りつけるプログラムはこんなに簡単。

以下MFCダイアログアプリを生成、ボタン一つを配置して
その押下ハンドラを作成。

void CSendwmdropDlg::OnButton1() 
{
  CWnd * pWnd = GetDesktopWindow();
  if( pWnd == NULL ) return;
  BOOL bDone = FALSE;
  for( pWnd = pWnd->GetTopWindow(); pWnd; pWnd = pWnd->GetNextWindow( GW_HWNDNEXT ))
  {
    CString cs;
    pWnd->GetWindowText( cs );
    if( cs == "あなたのアプリのウィンドウタイトル")
    {
      pWnd->SendMessage( WM_DROPFILES, 0, 0 );
      return;
    }
  }
}

編集    削除
 2004-12-07 12:17:15  No: 55338  IP: [192.*.*.*]

>全ての hWnd ハンドルについて
  ひとつずつとするのがAcceptすべきものを探すだめだと推察いたしますので、
  先ずドラッグドロップが成功するか、
  3つの全てのウインドウ(メイン用, Aタブ用, サブクラス用)で
  DragAcceptFiles(hWnd, TRUE);  を最初に呼ばれるようにしたところ、
  ファイルをドラッグしてみると、全てのウインドウに於いて、
  確かにマウスカーソルはドロップできる形に変化はします。

  しかしながら、サブクラス化したエリアでドロップしても、
  「WM_DROPFILES」が表示されることはありませんでした。

  ただ、もしこの試みが成功したとしても、
  本来はサブクラス化エリアでのみマウスが反応しなければならないので
  それはそれで疑問が残りますが。

>案その2
  先と同様に、3つのウインドウのダイアログリソースの拡張スタイルで
  「ドラッグドロップを許可」にチェックを入れ、有効にしてみましたが、
  結果は先と同じでした。

>WM_DROPFILESを送りつけるプログラムはこんなに簡単。
  えっと・・
  ご提案は大変有り難いのですが、
  MFCは扱ったことがありませんので、
  プログラムとして一部なのでしょうけど、お恥ずかしながら作成することができません;;
  この点については精進するところで、
  本当に申し訳ございません。

  ただ、「DragAcceptFiles() で登録していない  〜略〜  ちゃんと受け取ります。」
  については、どうしてそうなるのか仕組みがよく分からないです。
  また、このお話に沿って考えますと、
  先の結果でマウスの変化からAcceptは成功しているので、結果は同じになるかと・・。

編集    削除
 2004-12-07 12:32:46  No: 55339  IP: [192.*.*.*]

済みません。追加/訂正いたします。

>どうしてそうなるのか仕組みがよく分からないです。
  ウインドウを指定して動作を操作するアプリケーションは見たことがあるので、
  たぶんそのようなことをするのだと思うのですが、
  原理がはっきりとは分からないということです。
  インスタンスをハンドルで管理できるから成せる業なのかもしれませんが。

>結果は同じになるかと・・。
  「WM_DROPFILES」が仮に送られても、サブクラスのエリアでなければ意味がないので、
  そこまでプログラムで指定しなければ(出来るのかわかりませんが)、成功しないかと。

編集    削除
なーめ  2004-12-07 14:15:13  No: 55340  IP: [192.*.*.*]

>> しかしながら、サブクラス化したエリアでドロップしても、
>>「WM_DROPFILES」が表示されることはありませんでした。

WNDCLASS wc;
wc.lpfnWndProc = NewProc;

こういった部分も複数あると思いますが、
間違いないですよね。

各ウィンドウプロシジャにWM_LBUTTONDOWN など、入れてみて
関数名とウィンドウ名を表示 (OutputDebugString():下記)してみては
どうでしょう。
クリックした場所に対応するメッセージが出ているかどうか。
WM_LBUTTONDOWN も WM_DROPFILES と同じ運命になっていたら、
ウィンドウプロシジャの差し違いかもしれない。

//////////////////////////////////////////////////
http://www.kumei.ne.jp/c_lang/sdk/sdk_67.htm
こんなのありましたが。(^^;;
//////////////////////////////////////////////////

>> MFCは扱ったことがありませんので、
そうだったのですか。
MFC は遅いとか、XXXXだとかいろいろうわさがあるため、
あえて、避けられているのかと思いましたが。
MFC を知っているなら、VB並(言い過ぎ?)に目的のものがさっと作れる
非常に開発効率のよいものと思っています。
大きなプログラムの一部を切り出してテストするにはもってこいの方法です。
MSも日々精進を重ねていますしね。
悪い噂はバグをコンパイラやライブラリのせいにする人から始まると
思っています。

>> プログラムとして一部なのでしょうけど
ここで避けたらこんなおいしいツールをみすみす逃すことになります。
他の部分は全て AppWizardが書いてくれたのです。「生成」とは
そういう意味で「作成」とは違います。
だから簡単なのです。
手順を説明しますね。
まず、VC++ を起動します。

[ファイル][新規作成][MFC AppWizard(exe)]
[プロジェクト名]:名前を入れる
[位置]: いつもの場所。
[OK]
[ダイアログベース] (ラジオボタン)を押す
[次へ]
[次へ]
[次へ]
[終了]
[OK]
ここでダイアログエディタになるので、
ボタンを一個配置します。
[Button1]
配置したこのボタンをダブルクリックします。
[OnButton1] <- 名前が気に入らなければ変えてください。
[OK] を押す。
とすると
void CExDlg::OnButton1() 
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
}
が表示されます。
ここに私のコードを貼り付けます。
"あなたのアプリのウィンドウタイトル"
を書き換えます。
そしてコンパイル[F7]
そして実行[F5]
以上です。
何秒でできましたか。

さらに上記でダイアログエディタが表示されたところで、
[ClassView]選択。
CXXXXDlg を右クリック。
[Windows メッセージハンドラの追加]
[〜メッセージ用フィルタ] を"ウィンドウ"に
WM_DROPFILES をダブルクリック。
[既存の編集]

これであなたの当初の目的に大分近づいていませんか?
ちなみに当方で MFC で追認しても WM_DROPFILES ハンドラ、
OnDropFiles() に来ないという現象には遭遇しなかったのです。

MFC では間違いやすい部分、決り文句な部分、といったことをWizard等の
開発環境がある程度サポートしてくれるので、本質的なところに集中できます。

昔は決まり文句部分を雛型ソースとして管理していたものですが、
その必要もなくなりました。いまでは、OnButton1の中身のような貼り付け
コードチップのライブラリをIISで管理している形で...。

>> マウスの変化からAcceptは成功しているので

ちょっと説明が足りなかったのですが、
Accept していないウィンドウにもメッセージ WM_DROPFILES が届くという
ということです。
Accept の可否に関わらず 自前のウィンドウプロシジャが WM_DROPFILES を
受け取るかどうかを確かめられますよね。

で OnButton1() をちょっと改造して、
アプリの子ウィンドウに SendMessage() するようにしてやれば
TABコントロールなどもテストできます。
---------
if 文の中でさらに
for( pWnd = pWnd->GetTopWindow(); pWnd; pWnd = pWnd->GetNextWindow( GW_HWNDNEXT ))
再帰すればいいですか。
---------

あと MessageBox のかわりに
OutputDebugString() 
を使ってみませんか。
これは DevStudio のデバッグ窓に出力するものです。
いちいち OK を押さずにすみますよ。

P.S 
余談です。
WM_DROPFILES はファイルしかドロップできませんよね。
IE のリンクをドラッグ&ドロップしたり、
VC++ のエディタのように選択したテキストをドラッグ&ドロップしたり。
これには IDropTarget なるクラスを使用します。

編集    削除
 2004-12-08 04:26:06  No: 55341  IP: [192.*.*.*]

>WNDCLASS wc;
  メインウインドウはCreateDialog()で作成しておりまして、処理の追加等以外は
  http://home.a03.itscom.net/tsuzu/programing/tips28.htm
  とほぼ同じものと考えて頂ければよろしいのですけど・・
  ですので、ウインドウクラス構造体は定義していなかったりします。

>WM_LBUTTONDOWN
  LRESULT CALLBACK NewProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  {
    case UserMessage:
      略
    case WM_LBUTTONDOWN:
      MessageBox(hWnd, "WM_LBUTTONDOWN", "NewProc", MB_OK);
      break;
    case WM_DROPFILES:
      略
  }
  としてみたのですが、
  サブクラスエリアをいくら左クリックしても「WM_LBUTTONDOWN」は表示されませんでした。
  プロシージャの差し違い・・  それがどのような意味なのかはわかりませんが、
  逆に「UserMessage」が表示されるのは何故か謎に。

>おいしいツール
  確かに私からすれば勿体なく思います。
  お時間を割いて頂いて、詳しい手順で、感謝いたします。
  早速ご指示の通り作成し、
  「何秒でできましたか。」迄の内容で一度実行させて頂きました。
  勿論、問題となっているWM_DROPFILESを表示しないアプリケーションを起動した状態で、
  Button1をクリックしたわけですが、
  結果何も起きませんでした。。

  そこで、一旦ツールを終了し、「[既存の編集]」までの追加変更を加えてみたのですけど、
  何も起きませんでした。。
  実は、この追加変更の内容の意味が分からなかったりしますが。

  それで試しに、
 「タブコントロールがなく、ダイアログに直接グループボックスを作成した」アプリケーション
  に対し、ツールを実行してみたのですけど、何も起きませんでした。。。

  それで、よくわからないままなのですけど、
    if( cs == "TestApp")
    {
      for( pWnd = pWnd->GetTopWindow(); pWnd; pWnd = pWnd->GetNextWindow( GW_HWNDNEXT ))
        pWnd->SendMessage( WM_DROPFILES, 0, 0 );
      return;
    }
  と変更してみますと、問題のアプリケーションはページ違反で、強制終了してしまい、
  ツールは  Debug Assertion Failed!  で終了してしまいました。
  処理の意味が分からないので、たぶんかな〜り妖しいことを書いたのだと思うのですけど。
  申し訳ございません。

  私にとって、このツールは今は宝の持ち腐れのようです...。

  >OutputDebugString()
    ゆくゆくは使い分けなどして考えてみたいのですが、
    今は開発環境とは切り離して実行させてみたいので、特に今利益がなければ
    課題とさせていただければ幸いです。
    素材は自由に使えるまで温存し、自分が必要になったとき手を出せるものだと考えてますので。
    MFCについても、先ずSDKからということで。
    ありがとうございます。

編集    削除
なーめ  2004-12-08 15:43:03  No: 55342  IP: [192.*.*.*]

>> メインウインドウはCreateDialog()で作成しておりまして、処理の追加等以外は
>> http://home.a03.itscom.net/tsuzu/programing/tips28.htm
>> とほぼ同じものと考えて頂ければよろしいのですけど・・

そのままトレースしてみますね。
IDD_DIALOG を "IDD_DIALOG" に...

ダイアログのドラッグ&ドロップを許可すると
DlgProc()
には WM_DROPFILESは来る。
しかし、ダイアログ全体では受け取りたくない。

IDD_DIALOG_SHEET_1/2 だけで WM_DROPFILES を受け取りたい。
これら2つだけにドラッグ&ドロップを許可。

マウスがドロップ許可状態にならない。
TAB コントロールにも許可を与えると、
ドロップ可能にはなる。

ところが、
TabSheetProc() 
に WM_DROPFILES は来ない。

という状況まで確認。

// タブコントロールのシート上に貼り付ける子ダイアログを生成
// 親ハンドルにはhDlg(hTabCtrlでない)を指定するのがミソ

と書いてあるが、あえて、g_hTabCtrl を指定すると、
WM_DROPFILES 

TabSheetProc() 
に来ますね。

でも子ダイアログがタブコントロールをはみだしてしまっています(私の場合)。
このへん RECT の計算を修正するとよろしいか。

では、今まで WM_DROPFILES はどこへいっていたのか。
上の変更を元に戻します。
g_hTabCtrl = GetDlgItem(hDlg, IDC_TAB);
があるので g_hTabCtrl をサブクラス化して TabProc(...)を追加
してみましょう。

WNDPROC g_prgTabOrgProc;

DlgProc()
{
switch()
case WM_INITDIALOG:
g_hTabCtrl = GetDlgItem(hDlg, IDC_TAB);
g_prgTabOrgProc = (WNDPROC)GetWindowLong( g_hTabCtrl, GWL_WNDPROC );
SetWindowLong( g_hTabCtrl, GWL_WNDPROC, (LONG)TabProc );
...
}

LRESULT CALLBACK TabProc(HWND hTab, UINT msg, WPARAM wp, LPARAM lp)
{
  char s[272];
  switch( msg )
  {
    case WM_DROPFILES:
      sprintf(s,"Tab::OnDropFiles().\n");
      OutputDebugString(s);
      break;
  }
  return( CallWindowProc( g_prgTabOrgProc,hTab,msg,wp,lp ));
}

子ダイアログのドロップは許可のままにします。
これで Tab コントロールのドロップ許可を禁止にすると
マウスカーソルが反応しないところは同じです。

ゆえにドロップできません。
許可にした状態でファイルをドロップすると
Tab コントロールが WM_DROPFILES を受け取ってます。

つまり、見かけは子ダイアログがTABコントロールの上に乗っているが、
メッセージに関してはTABコントロールの方が上を覆っているような動作をする。

TABコントロールが上を覆わないようにするには
CreateDialog() で親を TABコントロールにしなければならない。
そうすると、RECTの計算のところを考えなければならない。

そういうことですね。

>>たぶんかな〜り妖しいことを書いたのだと
いえ、コードはそれで正しいはずです。

>> Debug Assertion Failed!
詳しい内容がわからないので、問題の深刻さは不明ですが、
相手アプリによってはたまにあるんですよ。
こういう場合は
SendMessage() を PostMessage() にします。
やけどをするまえに手をひっこめる、といったところです。
最初から Post.. を示せばよかったですね。
Post...でも落ちる場合は見たことがありません。(^^;;
(Win98/Win2K/WinXp|VC++6.0)

ちなみにおいしいツールとは
VC++の AppWizard(DialogBase) のことです。
私のは単なる使い捨てのゴミです。
何といっても相手ウィンドウ名が決め打ちですからね。
wparam/lparam も 0 ですし。

編集    削除
 2004-12-10 03:34:21  No: 55343  IP: [192.*.*.*]

> 見かけは子ダイアログがTABコントロールの上に乗っているが、
> メッセージに関してはTABコントロールの方が上を覆っているような動作をする。
  なるほど・・
  親ハンドルがタブコントロールでなく親ダイアログならば、
  メッセージはタブコントロールに行くというわけですね。
  見た目に騙されていたと...(苦笑

  詳しいご解説を交えていただき、大変感謝いたします。

>RECTの計算
  子ダイアログのサイズをいくら小さくしても、高さだけはタブシートからはみ出しますので、
  -6 で対応させました。
  恐らくタブシートの3D描画で必要な領域なのかもしれません。

>PostMessage()
  変更したところ、確かに落ちることはなくなりました。
  でも相変わらず、アプリは無反応だったり、落ちたり・・(苦笑

>私のは
  私にとっては十分魅力的なツールですよ。
  応用を生み出せる機能がありますから。
  AppWizardはMFCほどでなくともSDKにもありますから。

実はマルチポスト先
(http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200412/04120005.txt)
でも、同様な症状で、
プロパティシートによる方法でご解決なされた方がおられまして、
それで同時進行で作成しかけていたのですけど、
OK と キャンセル のボタンは PSH_NOAPPLYNOW のように削除できないようで、、
返信に遅れをとってしまい申し訳ございませんでした。

お時間頂き、ありがとうございました。

編集    削除