たった1度しか開かないツリーを2度目以降も開くようにするには?


ボッケドリ  2003-11-17 07:41:39  No: 52529  IP: [192.*.*.*]

はじめまして、ボッケドリといいます。
Win2K(SP4)+VC6(SP3) でやってます。

今やってることは、
エクスプローラのように左側をツリービュー 右側をリストビュー にし、
データを管理するものを作ってます。
MFC AppWizard(exe) からステップを進め、Windows エクスプローラスタイルを
選択して始めました。

今作ってる所は、
リストビューの項目をダブルクリックして、ツリービューの閉じてるノードを
開く処理です。ダブルクリックされたら CMyListView::OnDblclk() で
ツリービュー の目的のノードを探しだし、見つかったノードに対して
開けメッセージを送ってます。処理はこんなん感じです。

  //
  // 目的のノードを探して開けメッセージを送る。
  //
  HTREEITEM trItem;

  // 表示されてる最初のノードを探す
  trItem = pDoc->m_leftView->GetFirstVisibleItem();
  while (trItem != NULL) {
    if (目的のノードだった) {
      // そしてメッセージを送る。
      ::SendMessage(pDoc->m_leftView->m_hWnd, TVM_EXPAND, TVE_EXPAND,
                    (DWORD)trItem);
      retrun;
    }
    // 次の表示されてるノードを探す
    trItem = pDoc->m_leftView->GetNextVisibleItem(trItem);
  }

そして期待通りにノードが開いてくれました。
これでOKと思ってたら問題がありました。


困った事というのは、
開いたノードをマウスで閉じた後、再びリストビューの項目をダブルクリック
しても期待した通りにノードが開かない事です。
そのノードの下に子がある事を示すために仮に付けといた子が現れるだけです。
マウスで操作すればメッセージがちゃんと届くのに上のような処理だと
CMyLeftView::OnItemexpanding() にはたった1度しかメッセージが届いてない
というのが分りました。

原因を調べてるうちに TVIS_EXPANDEDONCE というフラグが関係してるらしい
というのが分りましたが、どう対処したらいいのか分りませんでした。

再びツリーが開くようにメッセージが届くようにするにはどうしたら良いのか
誰か教えてください。お願いします。

編集 削除
なーめ  2003-11-29 17:13:53  No: 52530  IP: [192.*.*.*]

SendMessage() によるメッセージで開いていたら閉じる、閉じていたら開くという動作をする。
同時に、ダブルクリックでも開いていたら閉じる、閉じていたら開くという
動作をします。
つまり、ダブルクリックの時毎回、開いて閉じるわけですね。
OnExpanding() では親クラス呼びなどをしないので、勝手に
開いたり閉じたりはしません。OnExpanding() を呼び出す
デフォルト(親クラスの)の OnLButtonDblClk() ハンドラあたりで
開いたり閉じたりするのでしょう。

OnLButtonDblClk() を追加して、親クラス呼び
(//CTreeView::OnLButtonDblClk(nFlags, point);)
を停めると、OnExpanding()が全く呼ばれなくなります。

SendMessage() をせずに、直接 OnExpanding() を呼び出せばよいのでは。
というか、OnDblClk() と OnExpanding() から共通に呼び出される
ようにする関数を作るのがいいでしょう。
処理の流れと、メッセージの順序をを把握しなければなりません。
私も昔、似たようなことを経験しました。(^^;; 

ついでに、
次の部分について、

  trItem = pDoc->m_leftView->GetFirstVisibleItem();
  while (trItem != NULL) // "!= NULL" は無駄な表現です!!
  {
    if (目的のノードだった) {
      // そしてメッセージを送る。
      retrun;
    }
    // 次の表示されてるノードを探す
    trItem = pDoc->m_leftView->GetNextVisibleItem(trItem);
  }

ループせずに、
HitTest() を使ったらどうかな。

void CLeftView::OnDblclk(NMHDR* pNMHDR, LRESULT* pResult) 
{
  CPoint pt;
  GetCursorPos( &pt );  // スクリーン座標を得るので変換する
  GetTreeCtrl().ScreenToClient( &pt );
  HTREEITEM hT = GetTreeCtrl().HitTest( pt, NULL );
  if( hT )
  {
    CString cs = GetTreeCtrl().GetItemText( hT );
    TRACE("DBL CLICK = %s\n", cs );
    //SendMessage( TVM_EXPAND, TVE_EXPAND, (DWORD)hT);
    //  OnItemExpanding(NULL,NULL);
  }
  *pResult = 0;
}

TVIS_EXPANDONCE はアイテムをInsertして以来、一度でも開かれたこと
があることを意味するだけです。たとえば、このフラグがないときに
メモリをアロケートして作業域を確保したりしています。
おそらくエクスプローラでもこのフラグをもとにディレクトリの新規探索を
行っているのではないかな、と思われる。さもなくば、起動時に全ディレクトリ
を探索しなければなないだろう。それでは起動時の負荷が重過ぎる。

ところで、
pDoc->m_leftView->GetFirstVisibleItem();
pDoc->m_leftView->GetNextVisibleItem(trItem);
どうして、自分自身のメンバ関数を他のクラスの保持しているポインタを
経由して呼び出す必要があるのですか?
無駄に加えて、どこかのポインタがNULLになっていたら即、C0000005 ですよ。
それから、CTreeView と CTreeCtrl は別クラスです。

編集 削除