ListView - Multiline-Header --> Wie?



  • Hallo!

    Ich habe eine Tabelle (ListView) im Report-Modus. Funktioniert auch alles wunderbar mit dieser, nur beiße ich mir die Zähne daran aus mehrzeilige Spaltenüberschriften zu erzeugen. Kennt sich vielleicht jemand von euch darin aus und könnte mir gegebenenfalls einen kleinen Denkanstoß (vielleicht in Form von Code-Snippets) geben?

    THX im Voraus 😉



  • dann wirst du \r\n sicher schon ausprobiert haben 🙄



  • @flenders: Ja, hab ich. Da kommen nur wieder die üblichen "Kästchen", wie wenn man versucht in ein einzeliges Textfeld eine Zeilenschaltung einzufügen 😞

    Ich habe es auch schon mit LV_COLUMN versucht, klappt aber auch nicht.
    In MFC habe ich schon ein Beispiel für ein mehrzeiliges Header gesehen. Da wurde der Header durch ableiten der vordefinierten CHeaderCtrl-Klasse selbst gezeichnet. Aber wie mache ich das mit der WinAPI???



  • moin meister ...

    1.) HWND ListView_GetHeader(HWND hwndLV);
    2.) Subclassing
    3.) WM_PAINT bearbeiten

    mfg
    RB



  • @RED-BARON: Aha, gut, jetzt habe ich den Handle vom Header. Danke 😉
    Aber was meinst du mit "Subclassing" und mit was muss ich das WM_PAINT-Ereigniss überschreiben?
    Sorry, aber ich bin kein WinAPI-Programmierer. Bis jetzt habe ich nur mit der MFC und wxWindows visuell geproggt...



  • Bill hat leider nicht daran gedacht, dass einige sowas gerne hätten. Sowas gibt es also nicht standardmäßig. Du musst es selber malen. Dazu musst du zuerstmal das Control subclassen - das heißt, du musst dem Control eine neue WindowProc geben. Das geht so:

    // g_OldCtrlProc ist global
    WNDPROC g_OldCtrlProc;
    
    // Die alte WindowProc herausbekommen
    g_OldCtrlProc = GetWindowLong(hwnd, GWL_WNDPROC);
    
    // Dem Control eine neue geben
    SetWindowLong(hwnd, GWL_WNDPROC, NewCtrlProc);
    

    Dies kannst du in deinem Programm schreiben. Dazu brauchst du natürlich ne neue WindowProc:

    LRESULT CALLBACK NewCtrlProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
    {
       static PAINTSTRUCT ps;
       static HDC         hdc;
    
       switch(uiMsg)
       {
          case WM_PAINT:
             hdc = BeginPaint(hwnd, &ps);
             // Hier die Zeichenoperationen
             EndPaint(hwnd, &ps);
             return 0;
       }
    
       // Die alte WindowProc aufrufen
       return CallWindowProc(g_OldCtrlProc, hwnd, uiMsg, wParam, lParam);
    }
    


  • @WebFritzi: Wow, super! Jetzt kann ich wenigstens den Header manipulieren wie ich möchte (zeichnerisch). Aber das mit dem Multiline klappt irgendwie immer noch nicht 😞 Immer wenn ich versuche auf die DRAWITEMSTRUCT-Struktur zu kommen kriege ich eine Speicherzugriffsverletzung!
    Hier habe ich mal ein Code-Schnippsel wie man es in MFC macht:

    void CHeaderCtrlEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
    {
       ASSERT(lpDrawItemStruct->CtlType == ODT_HEADER);
       HDITEM hdi;
       TCHAR  lpBuffer[256];
    
       hdi.mask = HDI_TEXT;
       hdi.pszText = lpBuffer;
       hdi.cchTextMax = 256;
    
       GetItem(lpDrawItemStruct->itemID, &hdi);
    
    	CDC* pDC;
    	pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
    
    	//THIS FONT IS ONLY FOR DRAWING AS LONG AS WE DON'T DO A SetFont(...)
    	pDC->SelectObject(GetStockObject(DEFAULT_GUI_FONT));
       // Draw the button frame.
       ::DrawFrameControl(lpDrawItemStruct->hDC, 
          &lpDrawItemStruct->rcItem, DFC_BUTTON, DFCS_BUTTONPUSH);
    
    	UINT uFormat = DT_CENTER;
    	//DRAW THE TEXT
       ::DrawText(lpDrawItemStruct->hDC, lpBuffer, strlen(lpBuffer), 
          &lpDrawItemStruct->rcItem, uFormat);
    
       pDC->SelectStockObject(SYSTEM_FONT);
    }
    

    Nun, wie gesagt: Ich habe solche Sachen noch nie mit der WinAPI erledigt. Jetzt kann ich aber kein MFC verwenden. Könnte mir jemand diesen MFC-Code in WinAPI-Code übersetzen?

    Ach übrigens, die Nachricht über die Zeichnung des Headers fange ich ab in WM_NOTIFY.



  • Wie sieht denn dein WinAPI-Code aus bzw. wo genau bekommst du die Speicherzugriffsverletzung?



  • @flenders: Nun, die Sache ist die: Ich programmier es nicht in C++. Ich entwickle die Anwendung in PureBasic, was alle WinAPI-Funktionen direkt unterstützt. Aber der Sourcecode ist für einen C-Coder leicht lesbar:

    Procedure.l WinProc(...)
    ...
      Select uMsg
        Case #WM_NOTIFY 
          *pnmh.NMHDR = lParam 
          Select *pnmh\code 
            Case #NM_CUSTOMDRAW 
              *LVCDHeader.NMLVCUSTOMDRAW = lParam 
              If *LVCDHeader\nmcd\hdr\hWndFrom=ListGadget 
                  ; Hier zeichne ich die Zellen
              ElseIf *LVCDHeader\nmcd\hdr\hWndFrom = SendMessage_(ListGadget, #LVM_GETHEADER, 0, 0)
                *header.DRAWITEMSTRUCT = *LVCDHeader\nmcd\lItemlParam
                If DrawFrameControl_(*header\hDC, @*header\rcItem, #DFC_BUTTON, #DFCS_BUTTONPUSH ) = 0
                  ; Bis zu dieser MessageBox komme ich nicht mal!
                  MessageRequester("Fehler", "DrawFrameControl")
                EndIf
                DrawText_(*header\hDC, "test", Len("test"), @*header\rcItem, #DT_CENTER)
             EndIf 
          EndSelect
    

    Die Zeichnung der Zellen klappt, und die Übergabe der Pointer ist auch in Ordnung. Den Fehler kriege ich bei DrawFrameControl_(...) und dann stürzt das Programm komplett ab.

    Im PB-Forum habe ich auch schon nachgefragt, aber da weiß keiner eine Antwort. Ich hoffe halt, dass mir wenigstens die C++-Kollegen weiterhelfen können 😉

    Ich hoffe man schmeißt mich jetzt aus diesem Forum nicht raus deswegen 🙄



  • Klappt denn dein Code, wenn du das DrawFrameControl_ auskommentierst, oder stürzt er dann bei DrawText_ ab 😉
    Hast du schonmal den Debugger mitlaufen lassen 🙄



  • moin meisters ...

    habe mich nochmal dem Thema angenommen:

    Ich habe eine ListView auf nen Dialog gezogen. Der LV eine Spalte und
    3 Items hinzugefügt, das ganze bei WM_INITDIALOG ...

    Folgender Code schließt sich dem an:

    WM_INITDIALOG:
    ...
    
    hList = GetDlgItem(hDlg, IDC_LIST1);
    hHeader = ListView_GetHeader(hList);
    
    for( int i=0; i < Header_GetItemCount(hHeader); i++)
    {
        HDITEM hdi;
        hdi.mask = HDI_FORMAT|HDI_HEIGHT;
        if( Header_GetItem(hHeader, i, &hdi) == false )
            MessageBox(hDlg, "error", "1", MB_OK);
    
        hdi.fmt |= HDF_OWNERDRAW;
        hdi.mask = HDI_FORMAT|HDI_HEIGHT;
        hdi.cxy *=2;
    
        if( Header_SetItem(hHeader, i, &hdi) == false )
            MessageBox(hDlg, "error", "2", MB_OK);
    }
    
    ListProc = (WNDPROC)GetWindowLong(hList, GWL_WNDPROC);
    SetWindowLong(hList, GWL_WNDPROC, (LONG)SubListProc);
    
    HeaderProc = (WNDPROC)GetWindowLong(hHeader, GWL_WNDPROC);
    SetWindowLong(hHeader, GWL_WNDPROC, (LONG)SubHeaderProc);
    ...
    

    dann folgen die SubProcs ( stehen eigentlich weiter oben, egal )

    WNDPROC ListProc;
    WNDPROC HeaderProc;
    
    LRESULT CALLBACK SubListProc(HWND hwnd, INT uMsg, WPARAM wParam, LPARAM lParam)
    {
    	switch( uMsg )
    	{
    		case WM_DRAWITEM:
    		{
    			LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT) lParam;
    			if( lpdis->CtlType == ODT_HEADER )
    			{
    				HDITEM hdi;
    				TCHAR  lpBuffer[256];
    				hdi.mask = HDI_TEXT;
    				hdi.pszText = lpBuffer;
    				hdi.cchTextMax = 256;
    				Header_GetItem(lpdis->hwndItem, lpdis->itemID, &hdi);
       				DrawFrameControl(lpdis->hDC, &lpdis->rcItem, DFC_BUTTON, DFCS_BUTTONPUSH);
    				UINT uFormat = DT_CENTER;
    				DrawText(lpdis->hDC, lpBuffer, strlen(lpBuffer), &lpdis->rcItem, uFormat);				
    			}
    		}
    		break;
    
    		default:
    			return CallWindowProc(ListProc, hwnd, uMsg, wParam, lParam);
    	}
    
    	return 0;
    }
    
    LRESULT CALLBACK SubHeaderProc(HWND hwnd, INT uMsg, WPARAM wParam, LPARAM lParam)
    {
    	switch( uMsg )
    	{
    	case HDM_GETITEMRECT:
    		{ 
    			((LPRECT)lParam)->top = 0;
    			((LPRECT)lParam)->left = 0;
    			((LPRECT)lParam)->right = 100;
    			((LPRECT)lParam)->bottom = 34;
    		}
    	default:
    		return CallWindowProc(HeaderProc, hwnd, uMsg, wParam, lParam);
    	}
    	return 0;
    }
    

    Ich habe das Beispiel von CodeGuru hier abgewandelt verwendet (ListViewSub).

    Wie man sehen sind ein paar experimentellle Zeile enthalten, mit welchen
    versucht wird, die Höhe des Header "gewaltsam" zu vergrößern, was aber nicht gelingt.

    Bevor Tipps angebracht werden möchte ich aus der MSDN zitieren.

    HDI_HEIGHT The cxy member is valid and specifies the item's height.
    HDI_WIDTH The cxy member is valid and specifies the item's width.

    ... und jetzt dürfen mal alle herzlich Lachen 🤡

    COMMCTRL.H

    #define HDI_WIDTH 0x0001
    #define HDI_HEIGHT HDI_WIDTH

    🤡 🤡 🤡 🤡 🤡 🤡 🤡 🤡 🤡

    Ergebnis es ist wohl nicht möglich die Höhe zu ändern, es sei denn man macht
    es doch so wie bei http://www.codeguru.com/listview/HeaderCtrlEx.shtml
    was ich nicht glauben wollte, daß es nur so geht.

    Wobei ich nichts mit dem hier anfangen kann

    m_NewHeaderFont.CreatePointFont(190,"MS Serif");
    //A BIGGER FONT MAKES THE CONTROL BIGGER
    m_HeaderCtrl.SetFont(&m_NewHeaderFont);

    Was ich noch nicht probiert habe, aber mir denke, in der SubListProc
    WM_NOTIFY == > NM_CUSTOMDRAW abfängt und bei

    if(lpcd->dwDrawStage == CDDS_ITEMPREPAINT )

    einen neuen (größeren) FONT in den HDC einsetzt und CDRF_NEWFONT zurück gibt.

    Bei WM_DRAWITEM wird dann wieder ein andere (kleinerer FONT) genommen ...

    mfg
    RB


Anmelden zum Antworten