選択ファイルのディレクトリの取得・表示の仕方

[戻る]
初心者@rumu 2002/07/22(月) 06:56:20
お世話になっています。今回もご教授お願いしたいのですが・・・。

CreateWindow("COMBOBOX"を指定)で作成したコンボボックスにGetCurrentDirectory と
SetWindowText を使って現在のカレントディレクトリの表示をするところまで出来てい
るのですが、
 リストビューのファイルを選択したときのそのディレクトリの表示の仕方がわかりま
せん。何となくイメージとしては、

   case WM_COMMAND:
     switch(LOWORD(wParam))
     { 
           case ID_COMBO:
               if (HIWORD(wParam) == CBN_DBLCLK) {
                    
     
                      (1)

               
               }

 のような処理で出来るとおもうのですが、(1)の部分をどうすればいいのかわかりま
せん。一応、SetCurrentDirectoryで処理してみたのですが、表示内容に変化はみられま
せんでした。このようなイメージで良いの?良いのだったら(1)の部分はどうしたら良い
の? CB_SETCURSELとか? 全くの的はずれ? よろしくお願いします。

環境 WinXP
      VC++.NET
      SDK

sugar 2002/07/22(月) 09:27:52
> リストビューのファイルを選択したときのそのディレクトリの表示
>
どこに表示したいのですか?
リストビューのアイテム選択を検出する必要があるはずですが、
掲載されているプログラムは、コンボボックスからのメッセージを処理しようとしてますよね。

それから、リストビューとコンボボックスは、同じアプリの同じ画面(例えばダイアログ)に
あるものと考えてよいのでしょうか。

初心者@rumu 2002/07/22(月) 17:38:22
> どこに表示したいのですか?

 コンボボックスに表示したいとおもっています。


> それから、リストビューとコンボボックスは、同じアプリの同じ画面(例えばダイアロ
グ)にあるものと考えてよいのでしょうか。

 はい。ツールバーの下にコンボボックスがあって、その下にリストビューがあります。

よろしくお願いします。

sugar 2002/07/22(月) 18:14:42
1) リストビュー内のアイテムがクリックされたことを検出できますか?
2) 選択された(クリックされた)アイテムからディレクトリを取得することはできますか?
3) 取得したディレクトリをコンボボックスに表示させる方法はご存知ですか?
4) 3)は、コンボボックスのタイプによって手法が異なるのですが、
   この辺りは大丈夫ですか?

一口に「リストビューのファイルを選択したときのそのディレクトリを
コンボボックスに表示する」と言っても、
実現のためにはいくつものステップを踏まないといけませんから、
もう少し問題を細分化しないと、手がつけられなくなってしまいますよ。

初心者@rumu 2002/07/23(火) 08:31:18
> 1) リストビュー内のアイテムがクリックされたことを検出できますか?

(switch文)
 case NM_CLICK:
   GetCursorPos関数;
   ScreenToClient関数;
     ListView_HitTest関数;
            
 これで、リストビュー内のアイテムがクリックされたことを検出できると思います。

> 2) 選択された(クリックされた)アイテムからディレクトリを取得することはできます
か?

 上記のListView_HitTest関数;以下のところで、GetCurrentDirectory関数で取得し
て、SetCurrentDirectory関数で設定するのでしょうか。すみません、よくわかっていま
せん。
    

> 3) 取得したディレクトリをコンボボックスに表示させる方法はご存知ですか?

 全くの勘違いでした。よくわかっていません。


> 4) 3)は、コンボボックスのタイプによって手法が異なるのですが、
   この辺りは大丈夫ですか?

 タイプとしては、レバーコントロールに 
 CreateWindow("COMBOBOX",    "", WS_CHILD | WS_VISIBLE | CBS_DROPDOWN,・・・・)
 で作成したコンボボックスをのせているのですが・・・。

 よろしくご教授のほどお願いします。

sugar 2002/07/23(火) 14:40:07
ひとつひとつ解決していきましょう、一度にすべてはムリです。

まず1番目ですが、「検出できると思う」のなら、本当にできるかどうか試してみましょう。
「百聞は一見に如かず」です。
NM_CLICKで間違いではありませんが、アイテムではないところをクリックしても
発生するので、その点は気をつけてください。
あと、GetCursorPos()以下3関数ですが、何のために使うのでしょう。
少なくともアイテムクリックの検出には必要ないと思われますが。

2番目ですが、このアプリケーション固有の仕様になる部分ですよね、
初めに「リストビューのファイルを選択したときのそのディレクトリの表示」と言ってますが、
ここでいうディレクトリって、何のディレクトリなんですか?
GetCurrentDirectory()は、アプリケーションの現在のディレクトリを返すだけですが、
それは仕様としてあっているのですか?

3番目ですが、最初の発言で「コンボボックスにカレントディレクトリの表示をするところ
まで出来ている」とありますが。
何か矛盾してませんか?

4番目ですが、コンボボックスがリバーコントロール上にあるということは、大事なことです。
これは初めに言っておかなければなりません。
ただ、コンボボックスのウィンドウハンドルさえ保持していれば、何でもできますよ。

初心者@rumu 2002/07/23(火) 20:51:34
> まず1番目ですが、「検出できると思う」のなら、本当にできるかどうか試してみま
しょう。

  case NM_CLICK:
     MessageBox(hWnd,"ディレクトリを表示したいよ","がんばれ!",MB_OK);
     break;

これでリストビュー内のアイテムをクリックするとメッセージボックスが出現され、ア
イテム外だとメッセージボックスは出現されませんでした。これで検出の確認は出来た
と思うのですが・・・。

> 2番目ですが、このアプリケーション固有の仕様になる部分ですよね、
初めに「リストビューのファイルを選択したときのそのディレクトリの表示」と言って
ますが、ここでいうディレクトリって、何のディレクトリなんですか?

 クリックして選択されたアイテムのディレクトリ(絶対パス)であって、
C:\マイドッキュメント\マイソフトウエア\のような感じのものです。(エクスプローラ
のアドレス欄に表示される文字列と同じものです。)

> GetCurrentDirectory()は、アプリケーションの現在のディレクトリを返すだけです
が、それは仕様としてあっているのですか?

あっ、これも間違っていました。アプリケーションの現在のディレクトリは必要ないか
も・・・。

> 3番目ですが、最初の発言で「コンボボックスにカレントディレクトリの表示をする
ところまで出来ている」とありますが。何か矛盾してませんか?

 WM_CREATE のところでコンボボックスを作成すると同時にGetCurrentDirectory() と
 SetWindowText()を実行してしまったのですが、これもおかしいですね。間違ってまし
た。

> コンボボックスのウィンドウハンドルさえ保持していれば、何でもできますよ。

コンボボックスのウィンドウハンドルをグローバル変数に保持すればOKですね。

よろしくお願いします。

sugar 2002/07/24(水) 09:40:01
アイテムのクリックは大丈夫そうですね。

ところで、ディレクトリはアイテム毎に異なるものを持つことを想定しているようですが。
どこのディレクトリのなんていう名前のファイルかは、
どこかに保持しておかなければなりませんよ。
フルパス名をアイテムやサブアイテムの名称に設定しているなら話は別ですが、
そうでないなら、パス情報を保持する工夫を別途施す必要があります。

コンボボックスを生成する時にCBS_DROPDOWNスタイルを設定しているので、
ドロップダウンコンボというタイプであると推測されます。
(コンボボックスの種類についてはMSDNで調べてください。)
このタイプのコンボボックスは、SetWindowText()で「表示」は可能です。
しかし、ダウンリストの方へは追加されません。
追加する場合はCB_ADDSTRINGメッセージを使ってください。
この辺りはコンボボックスの基本ですので、しっかりおさえておくようにしましょう。

初心者@rumu 2002/07/24(水) 10:24:15
ありがとうございます。

> ところで、ディレクトリはアイテム毎に異なるものを持つことを想定しているようで
すが。どこのディレクトリのなんていう名前のファイルかは、どこかに保持しておかな
ければなりませんよ。

 自信ありませんが、char szfname[MAX_PATH]; で保持できるような気がするのです
が・・・。


それと、前出(2002/07/22(月) 18:14:42)の

> 2) 選択された(クリックされた)アイテムからディレクトリを取得することはできま
すか?

なのですが、この辺が特に理解できていないのですが。どの関数でディレクトリを取得
すればいいのでしょうか。ご指導お願いできないでしょうか。

sugar 2002/07/24(水) 11:27:53
> char szfname[MAX_PATH]; で保持できるような気がするのですが・・・。
>
方向としてはあってます。
ただ、各アイテム毎に異なるディレクトリ情報があるわけですから、
リストビューに追加したアイテムの数だけ、バッファを用意する必要があります。

このバッファをどこに配置するかが考えどころです。
アイテム数が固定であるならグローバルで宣言しておくのもよいですが、
不定である場合、事前にいくつ分のバッファを用意すればよいかが定まりません。

そのような場合は、アイテムを追加する度にmalloc()等で動的にメモリを確保する手段を
お勧めします。
アイテムをリストに追加する際に使用するLVITEM構造体には、
lParamというメンバがあります。
ここに任意の数値データを格納することができるので、確保したメモリのアドレスは
ここにセットしておけばよいでしょう。

「選択されたアイテムからディレクトリを取得することができるか」というのは、
選択したアイテムと、上記で説明したバッファの対応付けができているか、ということです。
lParamにアドレスを格納しておけば、対応付けは特に考える必要もありません。

この辺りは、リストコントロール(人によってはリストビューコントロール)の基本です。
おさえておきましょう。

初心者@rumu 2002/07/25(木) 06:50:42
ありがとうございます。

アイテム数は不定であるので、"lParam" にmalloc()等で動的に確保したメモリのアドレ
スをセットする仕方でやろうと思います。でも、具体的にはどのような感じで・・・。
よろしくご指導お願いします。

sugar 2002/07/25(木) 09:21:52
まずは自分でコードを組んでみましょう。
ダメだったら、ダメなコードをたたき台として提示してください。
そうすれば、ここがダメ、あそこがダメと具体的な話ができます。

(1)動的に確保したメモリに任意のディレクトリをセットし、
   そのアドレスをlParamにセットしてリストコントロールにアイテム追加を行うところ

(2)NM_CLICKを受けた時に選択状態にあるアイテムを認識し、
   そのアイテムのlParamを取得して、そこにセットされてる文字列(具体的には
   ディレクトリ)を得るところ

上記2点について考えられれば、かなりゴールに近づくのではないでしょうか。

メモリを動的に確保する方法がわからないなら、リストコントロール云々の前に
そちらを勉強する必要があります。

尚、話の流れの中で説明を省略してしまいましたが、
メモリを動的に確保した場合、その方法論によらず、
不要になった時は必ずそのメモリを解放してあげなければなりませんから、
忘れないようにして下さい。

初心者@rumu 2002/07/26(金) 08:54:48
ありがとうございます。

あれから頭で色々と考えてコードの組み立てをしょうとしているのですが、すみませ
ん、もう、最初から分からないです。

 > 動的に確保したメモリに任意のディレクトリをセットし・・・ 

ですが、

 動的にメモリを確保→ sgm=SHGetMalloc(&lpMalloc);
        開放→ lpMalloc->・・・->Release(lpMalloc);

はこれでいいように思うのですが・・・、

 > "任意のディレクトリ" 

がよく分かりません。ディレクトリは取得するものと思っているのですが、ここで言う

"任意のディレクトリ"とは何なのか教えてください。

どこからかGET!するもの?ひょっとして自前でこしらえるもの?

ご指導お願いします。

sugar 2002/07/26(金) 09:47:47
>  動的にメモリを確保→ sgm=SHGetMalloc(&lpMalloc);
>         開放→ lpMalloc->・・・->Release(lpMalloc);
>
すみません、その関数、私使ったことがないもんで、ちょっとよくわかんないです。
シンプルに、
 確保→ char *p = (char*)malloc(sizeof(char) * MAX_PATH);
 解放→ free(p);
でよろしいかと。

>  > "任意のディレクトリ" 
> がよく分かりません。ディレクトリは取得するものと思っているのですが、ここで言う
> "任意のディレクトリ"とは何なのか教えてください。
> 
!ちょっと待ってください、どうやら認識にズレがあるようです。
リストビューにあるアイテムは、Aディレクトリのファイル、Bディレクトリのファイル...
の各アイテムがあるのではないのですか?
だからこそ、アイテムをクリックした時に、パス名をコンボボックスに表示するのだと
認識していたのですが。
「リストビューのファイルを選択したときのそのディレクトリの表示」という発言は、
そういう意味ではなかったのでしょうか?

ちょっと話がそれてしまいましたが、少なくとも私はそういう認識だったため、
各アイテム(=各ファイル)の所在するパス名を、
別途確保したメモリに保持しておく必要があると判断しました。
ここが違うと、前提が変わってしまいますね...(汗

PATIO 2002/07/26(金) 09:52:05
横槍になりますけれど、

普通にランタイム関数のmallocしても良いような気がします。
あとは、GlobalAllocを使う手もあるかと。
IMallocまで行かなくてもいいような。
メモリの確保方法は色々あるので確認しておいたほうがいいと思いますよ。

あと、リストボックスのアイテムと対応するディレクトリに関する情報は
どこから提供されるのかがわからないと返答のしようが無いと思いますよ。
その情報はプログラマ側で予めわかる物なのですか?
それとも外側から与えられる物なのでしょうか?
そもそも、リストボックス内のアイテムとディレクトリの関係がどんな物なのか
あなた自身は把握しているのでしょうか?

初心者@rumu 2002/07/26(金) 11:55:24
ありがとうございます。

なんか最初の説明が簡略化されてマズカッタような気がします...(汗 すみません。

エクスプローラのように、左ツリービューと右リストビューがあって、最終的には、
左ツリービュー内のアイテム(フォルダー)をクリックするたびにパス名の切替表示を
したいのです。同様に、右リストビューのアイテム(左ツリービューのアイテムを階層
展開してきたときの下層ファイル)もクリックするたびにパス名の切替表示をしたいの
です。

左ツリービューの最上親はデスクトップとなっています。

このような説明で分かるでしょうか・・・。

メモリを動的に確保する方法については、もう少し勉強してみます。

よろしくご指導お願いします。

sugar 2002/07/26(金) 13:41:33
エクスプローラとアドレスバーのイメージだったんですか!?
「どういうものを作りたいのか」は質問者しかわからないのですから、
最初に説明して欲しかったですね...

コンボボックスの役割を整理しましょう。
ツリーから選択されているアイテム(フォルダ)のパスを表示するわけですね?
つまり、「今、何フォルダが開かれているか」を示すわけです。
次にリストビューですが、
まさにエクスプローラの動きと同等ということでよろしいですか?
リストビュー内のファイルやフォルダを「クリックしただけ」では、
「今、開かれているフォルダ」は何ら変更がないように思われるのですが、いかがですか?
この時、コンボボックスには何を表示したいと考えていますか?

作りたいものをイメージし、動作をより具体的にしましょう。
そうしないと、やり取りばかりが増えていって、なかなか解決には至れませんよ。

初心者@rumu 2002/07/27(土) 06:50:07
ありがとうございます。お騒がせしてすいません。

 > エクスプローラとアドレスバーのイメージだったんですか!?

 はい。そうなんですぅ。
 
 >コンボボックスの役割を整理しましょう。
 >ツリーから選択されているアイテム(フォルダ)のパスを表示するわけですね?
 >つまり、「今、何フォルダが開かれているか」を示すわけです。

 はい。全くそのとおりですぅ。

 >次にリストビューですが、
 >まさにエクスプローラの動きと同等ということでよろしいですか?
 >リストビュー内のファイルやフォルダを「クリックしただけ」では、
 >「今、開かれているフォルダ」は何ら変更がないように思われるのですが、いかがで 
 >すか?
 >この時、コンボボックスには何を表示したいと考えていますか?

 あっ、ここはちょっと工夫したいです。リストビュー内のファイルやフォルダを「クリ
ック」したときは、ツリービューと同様にパス名を表示したいです。実行はダブルクリ
ックにしますから。

 このようなイメージなのですが、明確さを欠いで申し訳なかったです。
 よろしくご指導お願いします。

sugar 2002/07/27(土) 16:00:17
ツリービューのアイテムをクリックした時(=選択状態にあるアイテムが変更になった時)に、
コンボボックスにディレクトリを表示するというのは、いいでしょう。

リストビューの動作ですが、
ツリービューで任意のアイテムをクリック(=ディレクトリを特定)して初めて、
リストビューにアイテムが追加される、ということを理解してください。

これをふまえ、
リストビューのアイテムを「クリック」した時というのは、前述の理由により
既にディレクトリが特定されている訳ですから、
改めでコンボボックスにディレクトリ名を表示するという行為をする必要はありませんよね?

次に、リストビューのアイテムを「ダブルクリック」した時ですが、
ダブルクリックしたアイテムが「フォルダ」だった場合、
そのフォルダを開く(=そのディレクトリに移動する)んですよね?
ということは即ち、それに連動してツリービューの表示が更新されるんですよね?
更新されるとは即ち、開いたフォルダのツリーアイテムが選択状態になるわけですよね?
選択状態にあるツリーアイテムが変更になったのなら、冒頭に述べた通り
コンボボックスに新しいパス名が表示されるわけですから、
リストビューでどうこうする必要はなくなりますよね?

これらのことから、ツリービューの選択状態が変わった事をトリガに
コンボボックスの表示を切り替えるようにすれば、事は丸く収まりそうじゃないですか?

初心者@rumu 2002/07/27(土) 22:33:59
ありがとうございます。

全くその通りです。エクスプローラーもそのようになっています。

>ツリービューの選択状態が変わった事をトリガに
>コンボボックスの表示を切り替えるようにすれば、事は丸く収まりそうじゃないです 
>か?

 はい、そう思います。結局はリストビューのアイテム選択からパス名を表示するので
はなく、ツリービューのアイテム選択からパス名を表示するようにすれば事足ります
ね。
 とすると、ツリービュー側のNM_CLICK以下で処理すればいいと思うのですが・・・。

よろしくご指導願います。

sugar 2002/07/29(月) 14:36:02
ツリービューに代わって、何が問題になりますか?
ディレクトリ構成はツリーがそのものを構成していると思われますので、
今までのリストビューよりかは問題の解決方法は明確かと思うのですが。
(ツリーの親アイテムを辿って行けば...)

> とすると、ツリービュー側のNM_CLICK以下で処理すればいいと思うのですが・・・。
>
キーボード操作を考慮するなら、TVN_SELCHANGEDの方が妥当じゃないでしょうか。

タルタル 2002/07/30(火) 09:06:49
ありがとうございます。

>ツリービューに代わって、何が問題になりますか?
>ディレクトリ構成はツリーがそのものを構成していると思われますので、
>今までのリストビューよりかは問題の解決方法は明確かと思うのですが。
>(ツリーの親アイテムを辿って行けば...)

とくに問題はありません。道すじがはっきりしました。

>キーボード操作を考慮するなら、TVN_SELCHANGEDの方が妥当じゃないでしょうか。

はい。TVN_SELCHANGEDで試行錯誤しています。

只今、奮闘中・・・です。

初心者@rumu 2002/07/30(火) 09:19:58
あっ〜、すいません。直前回答↑のハンドルネーム「タルタル」は、「初心者@rumu」
の次に考えた第2ハンドルネームです。まだ初心者ではあるけど・・・。すいません、
なんか独り言みたいで・・・。

初心者@rumu 2002/07/31(水) 12:23:09
中間報告です(半分解決)。

MSDNライブラリで「SHGetPathFromIDList」関数なるものを見つけたので、

    int* p;
    TCHAR szPath[MAX_PATH];

  case TVN_SELCHANGED:

    (省略)
 
    //動的メモリの確保    (すいません、GlobalAllocにしました。)    
    
    p = (int *)GlobalAlloc(GMEM_FIXED, sizeof(int) * MAX_PATH);
                            
    if(SUCCEEDED(SHGetPathFromIDList(lpItemData->lpItemID, szPath)));
    {
       SetWindowText(hCombo,szPath);
    }
                        
    //動的メモリの開放
    GlobalFree(p);
       return 0;

としたところ、OK!でした。また、case NM_CLICK でも、少し手間は要りますがOK!でし
た。

ただ、完璧ではなく不満が残ってます。 8T_T8

「デスクトップ」をクリックしたときは、正常に"C:\Documents and Settings\・・・
\デスクトップ"と表示され、「マイドキュメント」では正常に"C:\Documents and 
Settings\・・・\My Documents" と表示されます。
 でも、「マイドキュメント」に格納された「Becky ! ver.2」をクリックする
と、"C:\Documents and Settings\・・・\デスクトップ\Becky ! ver.2" と表示さ
れ、・・・\My Documents\Becky ! ver.2とは表示されていません・・・?Windows標準
のエクスプローラでは正確に表示されているのに・・・。

 何が原因なのかはわかりません。ご教授お願いできないでしょうか。m(__)M

sugar 2002/07/31(水) 20:01:41
SHGetPathFromIDList()は使ったことがないのでわかりませんが、
このAPIがバグってることがありえないと考えた場合、
実は単純なミスだったりすることもあるので、もう一度確認してみることです。

例えば引数に与えているlpItemData->lpItemIDは意図したものなのか。
「Becky ! ver.2」は本当にデスクトップにないのか。
フォルダ情報を取得する方法に誤りがないか。
ツリーの構成は本当に間違っていないか。
等々...

ところでGlobalAlloc()で領域確保をしているようですが、
載せられたソースを見る限りでは、何のために確保したのかがわかりません。

初心者@rumu 2002/08/01(木) 00:37:46
sugarさん、ありがとうございます。

>例えば引数に与えているlpItemData->lpItemIDは意図したものなのか。
>Becky ! ver.2」は本当にデスクトップにないのか。
>フォルダ情報を取得する方法に誤りがないか。
>ツリーの構成は本当に間違っていないか。
>等々...

  いま、確認しています。ちょっと時間かかりそうです・・・。

>ところでGlobalAlloc()で領域確保をしているようですが、
>載せられたソースを見る限りでは、何のために確保したのかがわかりません。

 次のように修正しました。でも、自信のほうは・・・。

/*---------------------------------------------------------------------------*/

TCHAR szPath[MAX_PATH];       //バイト数が最大
int *p;                       //メモリオブジェクトのハンドルをいれる
LPTSTR lpOut[1024];           //パスを格納するバッファー

  case TVN_SELCHANGED:

    (省略)
 
      //動的メモリの確保     
    p = (int *)GlobalAlloc(GMEM_FIXED, sizeof(int) * MAX_PATH);
                            
    if(SUCCEEDED(SHGetPathFromIDList(lpFTItemData->lpItemIDL, szPath)));
    {
      wsprintf(lpOut,szPath, *p);
       SetWindowText(hCombo,lpOut);
      }
                        
        //動的メモリの開放
        GlobalFree(p);
      return 0;

 以上です。よろしくお願いします。

sugar 2002/08/01(木) 10:28:28
それでもやはり意図が伝わってきません。
GlobalAlloc()で確保したメモリの使い道はなんですか?

wsprintf()の使い方、おかしくないですか?
各引数の意味をMSDNで再チェックするべきです。
実行後、lpOutには何が格納されることを期待してますか?

初心者@rumu 2002/08/02(金) 15:15:01
やったっぁー、出来ましたっ!!   |_(^0^)/

sugarさん、色々とご相談にのって頂いてありがとうございました。
PATIOさんも、ありがとうございました。

SHGetPathFromIDList()の第1引数を変えることで正常にフルパス名を表示することが出
来ました。

ただ、この場合に、動的メモリを確保する必要性に疑問があって、単に、

char szPath[MAX_PATH]; 
 
/*------------------------------------------------------------------------*/

 case TVN_SELCHANGED:

    (省略)
 
    if(SUCCEEDED(SHGetPathFromIDList(lpItemData->lpItemIDL2, szPath)));
    {
            SetWindowText(hCombo,szPath);
      }
        return 0;

としただけなのですが、やっぱりマズイでしょうか。メモリ関係もよく理解できていま
せん。何か参考になるものがあれば教えてほしいのですが・・・。
よろしくお願いします。

sugar 2002/08/02(金) 18:09:53
自分自身で何だかよくわからず、利用目的も不明確のままのメモリ確保なら、
ムリにする必要はないんじゃないでしょうか。
確かに私はメモリの確保についてふれる発言をしましたが、
途中で話の筋がガラッと変わってしまいましたからね。
(リストビューの話の時は、必要だと思いましたよ、ホントに。)

まずは、おつかれさまでした。
長きに渡ってがんばった甲斐がありましたね。

初心者@rumu 2002/08/02(金) 23:01:12
[[解決]]
sugarさん、ありがとうございました。
一応、解決チェックを付けておきます。

古いスレッドには追加発言できません。