Probleme mit einer Treeview



  • Hallo,
    ich habe mir eine Treeview Classe unter verwendung der WinApi gebastellt. Jetzt hab ich aber 2 kleinere probleme mit denen ich nicht mehr klarkomme. Die Treeview Classe hat eine Callback Funktion die mir das WM_CONTEXTMENU behandelt.

    ich will mir ein kontextmenu für die Treeview erstellen. dabei ist mir aufgefallen, das meine treeview das kontextmenu nur anzeigt, wenn ich einen doppelklick mit der rechten maustaste auf das jeweilige Element mache. Ferner kann ich auch nicht ein Element dauerhaft mit der rechten maustaste markieren. Wenn ich mit der rechten Maustaste auf ein element klicke, wird dieses ganz kurz markiert, und springt dann wieder auf das Element das ich irgendwann mit der linken maustaste markiert habe. weis jemand wieso diese seltsame ferhalten passiert?

    ich nutze auch die TVM_SETBKCOLOR & TVM_SETTEXTCOLOR Nachrichten. wenn ich diese nun zur Laufzeit des Programmes nutze, und die Textfarbe oder Hintergundfarbe der Treeview ändern will, stürzt das Prog ab wenn ich es beenden will! weis jemand wieso? kann man diese Funktionen nur bei der erstellung der Treeview nutzen?

    bin für jeglichen input dankbar....


  • Mod

    Auch wenn es hier für die MFC beschrieben steht so ist das Problem für die WinAPI identisch.
    WM_CONTEXTMENU wird vom Tree Ctrl nicht korrekt behandelt.
    http://support.microsoft.com/kb/222905/en-us

    Siehe auch:
    http://www.codeguru.com/Cpp/controls/treeview/misc-advanced/article.php/c691



  • Zu 1:
    Ich habe hier einfach WM_RBUTTONDOWN verwendet, als Ergänzung zu WM_CONTEXTMENU.
    Das habe ich auf die Schnelle zusammengeschnippelt, ich hoffe es ist so selbsterklärend? Wenn nicht, frag einfach nach...

    case WM_CONTEXTMENU:
      //hwnd = (HWND)wParam;      //handle to the window.
      //xPos = LOWORD( lParam );  //horizontal position of cursor.
      //yPos = HIWORD( lParam );  //vertical position of cursor.
      if ( lParam == 0xFFFFFFFF )
      {
        //Die Koordinaten sind ( -1, -1 ).
        //D.h. WM_KONTEXTMENU wurde durch Kontextmenü-Taste, oder durch "Shift"+F10" ausgelöst!
        htreeitem = TreeView_GetSelection( hwnd_tree );
        TreeView_GetItemRect( hwnd_tree, htreeitem, &rect, TRUE );
                                                                    //Koordinaten des ausgewählten TreeView-Items ermitteln.
        lParam = MAKELPARAM( rect.left, rect.top );                 //Und daraus die Mausposition ableiten, als ob mit der
                                                                    //rechten Maustaste gedrückt würde. So kann weiterverarbeitet
                                                                    //werden, als ob WM_RBUTTONDOWN angekommen wäre.
      }
      //Hier KEIN break!
    
      case WM_RBUTTONDOWN:
      //If the mouse is not captured, the message is posted to the window beneath the cursor.
      //Otherwise, the message is posted to the window that has captured the mouse.
      //fwKeys = wParam;          //key flags.
      //xPos = LOWORD( lParam );  //horizontal position of cursor.
      //yPos = HIWORD( lParam );  //vertical position of cursor.
      if ( ( b_treeview_mousedrag_abbruch_durch_rbuttondown == 0 )
           && ( b_treeview_mousedrag_aktiv == 0 ) )                 //Während TreeView Drag&Drop darf kein PopUp-Menü erscheinen.
      {
        //Hier Popup-Menü erzeugen.
        SetForegroundWindow( hwnd_tree );                           //Siehe KB135788 "Menus for notification icons do not work correctly".
        TrackPopupMenu( ... );  // bzw. TrackPopupMenuEx( ... );
        PostMessage( hwnd_tree, WM_NULL, 0, 0 );                    //Siehe KB135788 "Menus for notification icons do not work correctly".
        return( 0 );                                                //If an application processes this message, it should return zero.
      }
      else
      {
        //WM_RBUTTONDOWN hat bereits die TreeView Drag&Drop-Operation abgebrochen -> kein PopUp-Menü einblenden!
        b_treeview_mousedrag_abbruch_durch_rbuttondown = 0;
      }
      break;
    

    Zu 2:
    Da kann ich nur vermuten, daß in Deinem Source ein Bug enthalten ist. Zeig mal Dein Source.

    Martin

    [Nachtrag] Mist, da war Martin Richter schneller... Wenigstens hat meine Variante noch den Hinweis auf KB135788 http://support.microsoft.com/kb/KB135788/en-us 😉



  • hi,
    erstmal danke für die links & code. 👍
    der input hat mich schon ein stück weiter gebracht, ganz klappt es noch nicht, werd das aber noch hinkriegen.

    werd mich die tage diesbezüglich nochmal melden.



  • Hallo,
    nochmal danke an euch beide für den Input, die Infos haben mich schonmal auf den richtigen weg geleitet. das erste problem wäre schonmal gelöst.
    die immer zurückspringende Markierung, wenn man auf ein element mit der rechten maustaste drückt, hab ich unter NM_RCLICK und mit TVGN_DROPHILITE / TVM_SELECTITEM gelöst.

    case WM_NOTIFY:
        {
            switch(((LPNMHDR) lParam)->code)
            {
                case NM_RCLICK:
                {
    
                    HTREEITEM hItem = (HTREEITEM)SendMessage(hWndTreeCtrl,TVM_GETNEXTITEM,(WPARAM)TVGN_DROPHILITE,(LPARAM)NULL);
    
                    SendMessage(hWndTreeCtrl,TVM_SELECTITEM,(WPARAM)TVGN_CARET,(LPARAM)hItem);
                }
                break;
            }
        }
    break;
    

    durch rumstöbern in der MSDN hab ich gelesen, das Windows das Kontextmenu über WM_RBUTTONUP auslöst. dementsprechend hab ich meine lösung dort auch erstellt. Unter WM_CONTEXTMENU erstell ich dann ganz normal das Popupmenu.

    case WM_RBUTTONUP:
        {
            if(NULL != (HTREEITEM)SendMessage(hWndTreeCtrl,TVM_GETNEXTITEM,(WPARAM)TVGN_CARET,(LPARAM)NULL))
            {
                UINT uiHtFlag;
                TVHITTESTINFO tvht;
                DWORD dwPoints = ::GetMessagePos();
                POINT pt;
                pt.x	=   GET_X_LPARAM (dwPoints);
                pt.y	=   GET_Y_LPARAM (dwPoints);
    
                ::ScreenToClient(hDlg,&pt);
    
                tvht.pt.x	= pt.x;
                tvht.pt.y	= pt.y;
    
                HTREEITEM hActivItem = (HTREEITEM)SendMessage(hWndTreeCtrl,TVM_HITTEST,(WPARAM)0,(LPARAM)&tvht);
    
                if((NULL != hActivItem) && (tvht.flags & TVHT_ONITEM))
                {
                    SendMessage(hDlg,WM_CONTEXTMENU,(WPARAM)hWndTreeCtrl,(LPARAM)MAKELPARAM(tvht.pt.x,tvht.pt.y));
                }
            }
    
        }
    break;
    

    Was mir aber jetzt nicht so ganz schmeckt, ist das ich die lösung des Problems in meiner MainProcedur des Hauptdialoges löse. das Problem tritt ja in meiner
    TreeViewClasse auf, wo ich eine eigene Callback Procedur dafür habe. Das Problem dabei ist, die Procedur in der TreeViewClasse, funzt nicht richtig.
    Pack ich meine lösung für Problem 1 (NM_RCLICK / WM_RBUTTONUP / WM_CONTEXTMENU) in meine Procedur der TreeView, kann ich den wert
    ((LPNMHDR) lParam)->code nicht abfangen. WM_RBUTTONUP / WM_CONTEXTMENU wird wieder erst nach einem Doppelklick ausgelöst. 😕

    So erstell ich die CallBack für die TreeView.

    in der Header;
    class CCtrlTree
    {
    ....
    private:
    static LRESULT CALLBACK TreeCtrlProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
    };
    in der Quellcodedatei;

    BOOL CCtrlTree::Create(HWND hWndParent, DWORD dwStyle, DWORD dwStyleEx, CRect *rc, UINT uiCtrlID)
    {
        HINSTANCE hInstance = (HINSTANCE) GetWindowLong(hWndParent,GWL_HINSTANCE);
    
        INITCOMMONCONTROLSEX iccex; 
        iccex.dwICC = ICC_WIN95_CLASSES;
        iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
        InitCommonControlsEx(&iccex);
    
        HWND hWndTree = CreateWindowEx(dwStyleEx,WC_TREEVIEW ,_T(""),dwStyle, 
                                        rc->left,rc->top,rc->Width(),rc->Height(), 
                                        hWndParent,(HMENU)uiCtrlID,hInstance,NULL);
    
    ...
    
        SetWndProcedur	((WNDPROC)SetWindowLongPtr(hWndTree, GWLP_WNDPROC, (LONG_PTR) TreeCtrlProc));
    }
    

    die Procedur selbst:

    LRESULT CALLBACK  CCtrlTree::TreeCtrlProc (HWND hWndOwner, UINT message, WPARAM wParam, LPARAM lParam)
    {
        LRESULT lResult = CallWindowProc(GetWndProcedur(), hWndOwner, message, wParam, lParam );
    
        switch(message)
        {
            case WM_NOTIFY:
            {
                switch(((LPNMHDR) lParam)->code)
                {
                //Ab hier passiert gar nix mehr
    
                    case NM_RCLICK:
                    {
                        ...
                    }
                    break;
                }
            }
            break;
    
            case WM_RBUTTONUP:
            {
                    //WIRD ERST NACH DOPPELKLICK ANGESPROCHEN
            }
            break;
    
            case WM_CONTEXTMENU:
            {
                    //WIRD ERST NACH DOPPELKLICK ANGESPROCHEN
            }
            break;
    
        }
    return lResult;
    }
    

    Das nächste Problem was ich mit der TreeView habe ist, ich habe dort Icons an stelle der Button Angezeigt. Wenn jetzt der Dialog den Focus verliert,
    und durch ein Anderes Fenster verdeckt wird, und ich ihn später wieder in den vordergrund hole, ist von dem markierte Element das Icon verschwunden
    und auch die markierung (blau) ist nicht mehr da. 😮 Was ist das??

    Und mein Problem 2 die Colorierung des Hintergrundes & der Textfarbe zur laufzeit des Programmes hab ich halt auch noch. ich denke jetzt nicht das es der Code ist, den es ist einfach nur ein SendMessagebefehl.

    COLORREF CCtrlTree::SetBkColor( COLORREF clr )
    {	
        HWND hWndTreeCtrl = GetDlgItem();
    
        return (COLORREF)SendMessage(hWndTreeCtrl,TVM_SETBKCOLOR,(WPARAM)0,(LPARAM)clr);
    }
    
    COLORREF CCtrlTree::SetTextColor( COLORREF clr )
    {
        HWND hWndTreeCtrl = GetDlgItem();
    
        return (COLORREF) SendMessage(hWndTreeCtrl,TVM_SETTEXTCOLOR,(WPARAM)0,(LPARAM)clr);
    }
    

    An die COLORREF werte komm ich über eine Funktion im Hauptdialog mit

    CColorDialog cDlg;
    
    if(cDlg.DoModal() ==IDOK)
    {
        m_TreeCtrl.SetTextColor(cDlg.GetColor());//btw. SetBkColor
    
    }
    


  • Hi @rT!f@Ct,

    ich versuch in meiner knappen Zeit zu schreiben (muß gleich wieder weg) was mir auf Anhieb aufgefallen ist.

    In Deiner prozedur

    @rT!f@Ct schrieb:

    die Procedur selbst:

    LRESULT CALLBACK  CCtrlTree::TreeCtrlProc (HWND hWndOwner, UINT message, WPARAM wParam, LPARAM lParam)
    {
        LRESULT lResult = CallWindowProc(GetWndProcedur(), hWndOwner, message, wParam, lParam );
    
        switch(message)
        {
            case WM_NOTIFY:
            {
                switch(((LPNMHDR) lParam)->code)
                {
                //Ab hier passiert gar nix mehr
    
                    case NM_RCLICK:
                    {
                        ...
                    }
                    break;
                }
            }
            break;
    
            case WM_RBUTTONUP:
            {
                    //WIRD ERST NACH DOPPELKLICK ANGESPROCHEN
            }
            break;
    
            case WM_CONTEXTMENU:
            {
                    //WIRD ERST NACH DOPPELKLICK ANGESPROCHEN
            }
            break;
    
        }
    return lResult;
    }
    

    rufst Du zuerst CallWindowProc() auf, und dann erst kommen Deine eigentlichen Auswertungen.
    IMHO sollte CallWindowProc() eher am Ende Deiner Prozedur stehen, quasi als Abschluß:

    return( CallWindowProc( (WNDPROC)GetWndProcedur(), hWndOwner, message, wParam, lParam ) );
    

    BTW, was genau macht GetWndProcedur() ? Sie liefert hoffentlich genau diesen pointer zurück, den Dir schon SetWindowLong() beim Subclassen geliefert hat, oder nicht?

    Martin
    P.S.: Für die anderen probleme muß ich aus Zeitmangel erstmal passen...



  • GetWndProcedur() liefert mir einen eine zeiger auf die vorher erstellte Fensterprocedur.

    darin steht nur

    return m_wndProcedur;

    mit SetWndProcedur speicher ich halt den zeiger. die variable ist vom typ
    WNDPROC m_wndProcedur;

    Ich hab jetzt mal ein bischen rumprobiert und unter WM_NOTIFY lParam abgefangen.
    dabei ist mir aufgefallen das die NMHDR strucktur in der TreeView Callback procedur, nicht die werte hat, die WM_NOTIFY in der Haupt Callback Procedur vom hauptdialog hat. Dort stimmen beide werte überein, aber in der Treeview kommen hwndFrom & idfrom irgend wo her, aber nicht daher wo sie sollen.

    das gibts es doch nicht, windows bringt doch auch ihre trees zum laufen, das kann doch nicht sein das die treeview so versaut ist.

    case WM_NOTIFY:
    {
    	LPNMHDR nmhdr = (LPNMHDR)lParam;
    
    	Msg("0x%.08x == 0x%.08x ",nmhdr->hwndFrom,hWndOwner);
    
    }
    break;
    

Anmelden zum Antworten