Editierbares ListIconGadget

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Editierbares ListIconGadget

Beitrag von NicTheQuick »

Hallo,

bin gerade dabei mir ein editierbares ListIconGadget zu basteln. Die
Codes, die es schon gibt, haben mir nicht sonderlich weitergeholfen, weil
man da immer das Handle des Hauptfensters brauchte.

Und jetzt gilt es noch ein paar Problemchen zu lösen.
Zum einen hätte ich gerne in dem Edit Control die selbe Schriftart wie im
ListIconGadget, weil ich nicht weiß, wie man das anstellt.
Zum anderen weiß ich selbst nicht genau, ob ich das mit den Callbacks
alles so richtig gemacht hab. Vielleicht kann einfach mal jemand drüber
schauen und meinen Code korrigieren. Welche Message muss ich denn
z.B. abfangen, wenn ich wissen will, wann das Gadget komplett
freigegeben wurde? #WM_DESTROY ist scheinbar nicht die letzte.
Zum dritten wird das Edit Control nicht an die selbe Position gesetzt wie
der zu editierende Text steht.

Hier also der Code:

Code: Alles auswählen

EnableExplicit

Structure ListIconGadget_S
  id.l
  *hnd
  *oldCB
  *hedit
  item.l
  subitem.l
  *edit_oldCb
EndStructure

Procedure ListIconGadget_EditCB(hwnd.l, Msg.l, wParam.l, lParam.l)
  Protected *li.ListIconGadget_S = GetWindowLong_(hwnd, #GWL_USERDATA)
  Protected result.l, itemdata.LVITEM
  
  Select Msg
    Case #WM_KEYDOWN, #WM_KILLFOCUS
      If wParam = #VK_RETURN Or Msg = #WM_KILLFOCUS
        ;Text aus dem Stringgadget nehmen
        itemdata\pszText = AllocateMemory(1025 * SizeOf(Character))
        PokeW(itemdata\pszText, 1024)
        SendMessage_(hwnd, #EM_GETLINE, 0, itemdata\pszText)
        
        ;Text in ListIconGadget schreiben und Speicher wieder freigeben
        itemdata\iSubItem = *li\subitem
        SendMessage_(*li\hnd, #LVM_SETITEMTEXT, *li\item, itemdata)
        FreeMemory(itemdata\pszText)
        
        ;Alten Callback wieder aktivieren
        SetWindowLong_(hwnd, #GWL_WNDPROC, *li\edit_oldCB)
        
        ;
        InvalidateRect_(*li\hnd, 0, 0)
        
        ;StringGadget schließen
        DestroyWindow_(hwnd)
        
        *li\hedit = 0
        ProcedureReturn 0
      
      ElseIf wParam = #VK_ESCAPE
        ;Alten Callback wieder aktivieren
        SetWindowLong_(hwnd, #GWL_WNDPROC, *li\edit_oldCB)
        
        ;
        InvalidateRect_(*li\hnd, 0, 0)
        
        ;StringGadget schließen
        DestroyWindow_(hwnd)
        
        *li\hedit = 0
        ProcedureReturn 0
      EndIf
  EndSelect
  
  ProcedureReturn CallWindowProc_(*li\edit_oldCB, hwnd, Msg, wParam, lParam)
EndProcedure

Procedure ListIconGadget_CB(hwnd.l, Msg.l, wParam.l, lParam.l)
  Protected *li.ListIconGadget_S = GetWindowLong_(hwnd, #GWL_USERDATA)
  Protected hit.LVHITTESTINFO, r.Rect, itemdata.LVITEM
  
  Select Msg
    Case #WM_LBUTTONDBLCLK
      ;Mauskoordinaten relativ zum Gadget
      hit\pt\x = lparam & $FFFF
      hit\pt\y = (lparam >> 16) & $FFFF
      ;Zeile und Spalte herausfinden
      SendMessage_(hwnd, #LVM_SUBITEMHITTEST, 0, hit)
      ;Debug "Row: " + Str(hit\iItem) + ", Column: " + Str(hit\iSubItem)
      
      ;Koordinaten des Eintrags herausfinden
      r\left = #LVIR_LABEL
      r\top = hit\iSubItem
      SendMessage_(hwnd, #LVM_GETSUBITEMRECT, hit\iItem, r)
      ;Debug "Rect: " + Str(r\left) + ", " + Str(r\top) + ", " + Str(r\right) + ", " + Str(r\bottom)
      
      If hit\iItem >= 0
        ;Text des Eintrags auslesen
        itemdata\iSubItem = hit\iSubItem
        itemdata\pszText = AllocateMemory(1025 * SizeOf(Character))
        itemdata\cchTextMax = 1024
        SendMessage_(hwnd, #LVM_GETITEMTEXT, hit\iItem, itemdata)
        
        ;Falls schon ein StringGadget besteht, dann schließen und Änderungen übernehmen
        If *li\hedit
          SendMessage_(*li\hedit, #WM_KEYDOWN, #VK_RETURN, 0)
        EndIf
        
        ;neues StringGadget erstellen
        *li\hedit = CreateWindow_("edit", 0, #WS_CHILD | #WS_VISIBLE | #ES_LEFT, r\left, r\top, r\right - r\left, r\bottom - r\top, *li\hnd, 0, GetWindowLong_(*li\hnd, #GWL_HINSTANCE), 0)
        If *li\hedit
          ;Text zuweisen
          SendMessage_(*li\hedit, #EM_REPLACESEL, #False, itemdata\pszText)
          *li\edit_oldCB = SetWindowLong_(*li\hedit, #GWL_WNDPROC, @ListIconGadget_EditCB())
          *li\item = hit\iItem
          *li\subitem = hit\iSubItem
          SetWindowLong_(*li\hedit, #GWL_USERDATA, *li)
          SetFocus_(*li\hedit)
        EndIf
        
        If itemdata\pszText : FreeMemory(itemdata\pszText) : EndIf
        ProcedureReturn 0
      EndIf
  EndSelect
  
  ProcedureReturn CallWindowProc_(*li\oldCB, hwnd, Msg, wParam, lParam)
EndProcedure

Procedure ListIconGadget_SetEditable(*li.ListIconGadget_S)
  *li\hnd = GadgetID(*li\id)
  SetWindowLong_(*li\hnd, #GWL_USERDATA, *li)
  *li\oldCB = SetWindowLong_(*li\hnd, #GWL_WNDPROC, @ListIconGadget_CB())
EndProcedure

Define li.ListIconGadget_S

If OpenWindow(0, 0, 0, 400, 300, "´bla", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
  If CreateGadgetList(WindowID(0))
    
    li\id = ListIconGadget(#PB_Any, 0, 0, 400, 300, "Column0", 100, #PB_ListIcon_FullRowSelect)
      ListIconGadget_SetEditable(li)
      AddGadgetColumn(li\id, 1, "Column1", 100)
      AddGadgetColumn(li\id, 1, "Column2", 100)
    
    AddGadgetItem(li\id, 0, "Row0" + Chr(10) + "XXX" + Chr(10) + "XXX")
    AddGadgetItem(li\id, 1, "Row1" + Chr(10) + "XXX" + Chr(10) + "XXX")
    AddGadgetItem(li\id, 2, "Row2" + Chr(10) + "XXX" + Chr(10) + "XXX")
    AddGadgetItem(li\id, 3, "Row3" + Chr(10) + "XXX" + Chr(10) + "XXX")
    
    Repeat
    Until WaitWindowEvent() = #PB_Event_CloseWindow
  EndIf
EndIf
End
Benutzeravatar
Kiffi
Beiträge: 10714
Registriert: 08.09.2004 08:21
Wohnort: Amphibios 9

Re: Editierbares ListIconGadget

Beitrag von Kiffi »

NicTheQuick hat geschrieben:Die Codes, die es schon gibt, haben mir nicht sonderlich weitergeholfen,
weil man da immer das Handle des Hauptfensters brauchte.
das hier auch schon gesehen?

http://www.purebasic.fr/english/viewtop ... 992#199992

Grüße ... Kiffi
a²+b²=mc²
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

Naja, da muss man auch die Window-ID angeben. Siehe 'StartEditing()'.
Und genau das will ich ja nicht.
Ich bin ja eigentlich auch schon soweit, dass es funktioniert. Bloß die
Positionierung des StringGadgets und die Schriftart funktioniert noch nicht so
wie es soll. Aber vielleicht kann ich die relevanten Stellen ja dem Code
entnehmen. Wenn es denn irgendwann klappt, braucht man nur noch eine
einzige Procedure, die man aufruft und das ListIconGadget ist editierbar.
Sowas hab ich eben noch nirgendwo gefunden.
Außerdem mag ich es nicht, wenn Unmengen von globalen Variablen dafür
verschwendet werden. In diesem Fall könnte man damit auch nur ein
ListIconGadget editierbar machen und nicht mehrere.

Meins soll im Endeffekt auch noch andere Funktionen mit sich bringen.
Z.B. durch einen einfachen Klick Häkchen in den Spalten setzen und wieder
entfernen.

Trotzdem Danke für den Link. Damit kann ich bestimmt was anfangen.

///Edit:
Ich sehe auch gerade, dass netmaestro da ziemlich viel rumtrickst. Er fügt
unter anderem vor die erste Spalte noch eine ein, deren Breite er auf Null
setzt. Das finde ich auch nicht schön. Da ist mein Trick besser. :wink:
Benutzeravatar
edel
Beiträge: 3667
Registriert: 28.07.2005 12:39
Computerausstattung: GameBoy
Kontaktdaten:

Beitrag von edel »

Dir ist aber schon bewusst das Windows selber nen Edit-Control,
genau fuer diesen Zweck, erstellt ?
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

Ja, hab das in der MSDN gelesen, hab's aber nicht zum Laufen gekriegt. :wink:

Egal, jetzt bin ich gerade so weit fertig geworden wie ich es brauche:

Code: Alles auswählen

EnableExplicit

Prototype ListIconGadget_Request(GadgetID.l, row.l, column.l, *userdata)

Structure ListIconGadget_S
  id.l
  *hnd
  *oldCB
  *hedit
  item.l
  subitem.l
  *edit_oldCb
  *requestCB.ListIconGadget_Request
  *userdata
EndStructure

;Der Callback für das Edit Control
Procedure ListIconGadget_EditCB(hwnd.l, Msg.l, wParam.l, lParam.l)
  Protected *li.ListIconGadget_S = GetWindowLong_(hwnd, #GWL_USERDATA)
  Protected result.l, itemdata.LVITEM
  
  Select Msg
    Case #WM_KEYDOWN, #WM_KILLFOCUS
      If wParam = #VK_RETURN Or Msg = #WM_KILLFOCUS ;Wenn Return gedrückt oder der Fokus verloren wurde
        ;Text aus dem Stringgadget nehmen
        itemdata\pszText = AllocateMemory(1025 * SizeOf(Character))
        PokeW(itemdata\pszText, 1024)
        SendMessage_(hwnd, #EM_GETLINE, 0, itemdata\pszText)
        
        ;Text in ListIconGadget schreiben und Speicher wieder freigeben
        itemdata\iSubItem = *li\subitem
        SendMessage_(*li\hnd, #LVM_SETITEMTEXT, *li\item, itemdata)
        FreeMemory(itemdata\pszText)
        
        ;Alten Callback wieder aktivieren
        SetWindowLong_(hwnd, #GWL_WNDPROC, *li\edit_oldCB)
        
        ;ListIconGadget auffrischen
        InvalidateRect_(*li\hnd, 0, 0)
        
        ;StringGadget schließen
        DestroyWindow_(hwnd)
        
        *li\hedit = 0
        ProcedureReturn 0
      
      ElseIf wParam = #VK_ESCAPE ;Wenn Escape gedrückt wurde
        ;Alten Callback wieder aktivieren
        SetWindowLong_(hwnd, #GWL_WNDPROC, *li\edit_oldCB)
        
        ;ListIconGadget auffrischen
        InvalidateRect_(*li\hnd, 0, 0)
        
        ;StringGadget schließen
        DestroyWindow_(hwnd)
        
        *li\hedit = 0
        ProcedureReturn 0
      EndIf
  EndSelect
  
  ProcedureReturn CallWindowProc_(*li\edit_oldCB, hwnd, Msg, wParam, lParam)
EndProcedure

;Der Callback für das ListIconGadget
Procedure ListIconGadget_CB(hwnd.l, Msg.l, wParam.l, lParam.l)
  Protected *li.ListIconGadget_S = GetWindowLong_(hwnd, #GWL_USERDATA)
  Protected hit.LVHITTESTINFO, r.Rect, itemdata.LVITEM, hfont.l, minpos.l, maxpos.l, cpp.l, doedit.l
  
  Select Msg
    Case #WM_LBUTTONDOWN
      ;Mauskoordinaten relativ zum Gadget
      hit\pt\x = lparam & $FFFF
      hit\pt\y = (lparam >> 16) & $FFFF
      
      ;Zeile und Spalte ermitteln
      SendMessage_(hwnd, #LVM_SUBITEMHITTEST, 0, hit)
      
      If hit\iItem >= 0
        If *li\requestCB
          If *li\requestCB(*li\id, hit\iItem, hit\iSubitem, *li\userdata)
            doedit = #True
          EndIf
        Else
          doedit = #True
        EndIf
        
        If doedit
          ;Falls schon ein StringGadget besteht, dann schließen und Änderungen übernehmen
          If *li\hedit
            SendMessage_(*li\hedit, #WM_KEYDOWN, #VK_RETURN, 0)
          EndIf
          
          ;Koordinaten des Eintrags errechnen
          r\left = #LVIR_BOUNDS
          r\top = hit\iSubItem
          SendMessage_(hwnd, #LVM_GETSUBITEMRECT, hit\iItem, r)
          If hit\iSubItem = 0 ;Bei erster Spalte muss die Breite korrigiert werden
            r\left - 2
            r\right = r\left + SendMessage_(hwnd, #LVM_GETCOLUMNWIDTH, 0, 0) + 2
          EndIf
          
          ;Text des Eintrags auslesen
          itemdata\iSubItem = hit\iSubItem
          itemdata\pszText = AllocateMemory(1025 * SizeOf(Character))
          itemdata\cchTextMax = 1024
          SendMessage_(hwnd, #LVM_GETITEMTEXT, hit\iItem, itemdata)
          
          ;neues StringGadget erstellen
          *li\hedit = CreateWindow_("edit", 0, #ES_AUTOHSCROLL | #WS_CHILD | #ES_LEFT, r\left + 6, r\top, r\right - r\left - 6, r\bottom - r\top - 1, *li\hnd, 0, GetWindowLong_(*li\hnd, #GWL_HINSTANCE), 0)
          If *li\hedit
            ;Text zuweisen
            SendMessage_(*li\hedit, #WM_SETFONT, SendMessage_(hwnd, #WM_GETFONT, 0, 0), 0)
            SendMessage_(*li\hedit, #EM_REPLACESEL, #False, itemdata\pszText)
            *li\edit_oldCB = SetWindowLong_(*li\hedit, #GWL_WNDPROC, @ListIconGadget_EditCB())
            *li\item = hit\iItem
            *li\subitem = hit\iSubItem
            SetWindowLong_(*li\hedit, #GWL_USERDATA, *li)
            ShowWindow_(*li\hedit, #SW_SHOW)
            SetFocus_(*li\hedit)
          EndIf
          
          If itemdata\pszText : FreeMemory(itemdata\pszText) : EndIf
          ProcedureReturn 0
        EndIf
      EndIf
    
    Case #WM_PAINT
      If *li\hedit
        ;Koordinaten des Eintrags errechnen
        r\left = #LVIR_BOUNDS
        r\top = *li\subitem
        SendMessage_(hwnd, #LVM_GETSUBITEMRECT, *li\item, r)
        If *li\subitem = 0 ;Bei erster Spalte muss die Breite korrigiert werden
          r\left - 2
          r\right = r\left + SendMessage_(hwnd, #LVM_GETCOLUMNWIDTH, 0, 0) + 2
        EndIf
        
        MoveWindow_(*li\hedit, r\left + 6, r\top, r\right - r\left - 6, r\bottom - r\top - 1, 0)
        
        InvalidateRect_(*li\hedit, 0, 0)
      EndIf
    
    ;Aufpassen, dass das Edit Control nicht nach oben oder unten aus dem Fenster rutscht, links und rechts ist egal (vorerst)
    Case #WM_VSCROLL
      If *li\hedit
        cpp = SendMessage_(hwnd, #LVM_GETCOUNTPERPAGE, 0, 0)
        minpos = SendMessage_(hwnd, #LVM_GETTOPINDEX, 0, 0)
        maxpos = minpos + cpp
        
        Select wParam & $FFFF
          Case #SB_THUMBPOSITION, #SB_THUMBTRACK
            
            minpos = (wParam >> 16) & $FFFF
            maxpos = minpos + cpp
            
            If *li\item < minpos
              ProcedureReturn 0
            ElseIf  *li\item >= maxpos
              ProcedureReturn 0
            EndIf
          
          Case #SB_LINEDOWN
            minpos + 1
            If *li\item < minpos : ProcedureReturn 0 : EndIf
          
          Case #SB_LINEUP
            maxpos - 1
            If *li\item >= maxpos : ProcedureReturn 0 : EndIf
            
          Case #SB_PAGEDOWN
            minpos + cpp
            If *li\item < minpos : ProcedureReturn 0 : EndIf
          
          Case #SB_PAGEUP
            maxpos - cpp
            If *li\item >= maxpos : ProcedureReturn 0 : EndIf
        EndSelect
      EndIf
  EndSelect
  
  ProcedureReturn CallWindowProc_(*li\oldCB, hwnd, Msg, wParam, lParam)
EndProcedure

;Die Procedure zum Initialisieren von allem oben drüber
Procedure ListIconGadget_SetEditable(*li.ListIconGadget_S, *requestCB = 0, *userdata = 0)
  *li\hnd = GadgetID(*li\id)
  SetWindowLong_(*li\hnd, #GWL_USERDATA, *li)
  *li\oldCB = SetWindowLong_(*li\hnd, #GWL_WNDPROC, @ListIconGadget_CB())
  *li\requestCB = *requestCB
  *li\userdata = *userdata
EndProcedure

;=============================================================================================
;====================================== E X A M P L E ========================================
;=============================================================================================

;Ein simpler Callback, der für die 0. und 3. Spalte und die in *userdata angegebene Zeile #False zurückgibt
;Außerdem wird in der dritten Spalte zwischen X und O gewechselt
Procedure requestEdit(GadgetID.l, row.l, column.l, *userdata)
  If column = 0 Or row = *userdata
    ProcedureReturn #False
  EndIf
  
  If column = 3
    If GetGadgetItemText(GadgetID, row, column) = "X"
      SetGadgetItemText(GadgetID, row, "O", column)
    Else
      SetGadgetItemText(GadgetID, row, "X", column)
    EndIf
    ProcedureReturn #False
  EndIf
  
  ProcedureReturn #True
EndProcedure

Define li.ListIconGadget_S, i.l

If OpenWindow(0, 0, 0, 400, 300, "´bla", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
  If CreateGadgetList(WindowID(0))
    
    li\id = ListIconGadget(#PB_Any, 0, 0, 400, 300, "Column0", 75, #PB_ListIcon_FullRowSelect | #PB_ListIcon_GridLines)
      AddGadgetColumn(li\id, 1, "Column1", 75)
      AddGadgetColumn(li\id, 2, "Column2", 75)
      AddGadgetColumn(li\id, 3, "Column3", 75)
    
    For i = 0 To 100
      AddGadgetItem(li\id, i, "Row" + Str(i) + Chr(10) + "C1" + Chr(10) + "C2" + Chr(10) + "X")
    Next
    
    ListIconGadget_SetEditable(li, @requestEdit(), 2)
    
    Repeat
    Until WaitWindowEvent() = #PB_Event_CloseWindow
  EndIf
EndIf
End
Viel Spaß damit!

Bei Bugs oder Wünschen einfach melden! :allright:
Benutzeravatar
milan1612
Beiträge: 810
Registriert: 15.04.2007 17:58

Beitrag von milan1612 »

NicTheQuick hat geschrieben:Naja, da muss man auch die Window-ID angeben
Schon mal was von GetParent_() gehört? Damit kann man das wunderbar umgehen...
Bin nur noch sehr selten hier, bitte nur noch per PN kontaktieren
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

Ich brauche das in meinem Code kein einziges Mal. Außerdem nutzt
netmaestro die ID des Hauptfensters und nicht das Handle, was das ganze
mit 'GetParent_()' noch erschweren würde. Schlimmer sind sowieso die globalen Variablen. :wink:

Hat jemand mal meinen Code getestet?
Benutzeravatar
Kiffi
Beiträge: 10714
Registriert: 08.09.2004 08:21
Wohnort: Amphibios 9

Beitrag von Kiffi »

NicTheQuick hat geschrieben:Hat jemand mal meinen Code getestet?
ja, grade eben ;-)

Was mir auf Anhieb auffällt: Wenn man eine zu editierende Zelle nach oben
scrollt, so schiebt sie sich unschön über den Column-Header.

Und noch eine kleine Unschönheit: Wenn ich den Cursor in eine Zelle setze,
so flacker die gesamte Zeile kurzzeitig auf.

Ansonsten: :allright:

Grüße ... Kiffi
a²+b²=mc²
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

Komisch, das mit dem Hoch- und Runterscrollen hab ich eigentlich
unterbunden. Siehe '#WM_VSCROLL'. Naja, muss dann wohl mal noch weiter
testen.

Vielleicht morgen wieder. Bis dann und Danke für's Testen, Kiffi. :allright:
Benutzeravatar
techniker
Beiträge: 184
Registriert: 27.01.2016 11:08
Wohnort: BY

Re: Editierbares ListIconGadget

Beitrag von techniker »

Ich finde, dass der Code als Ausgangslage super funktioniert - Danke! :allright:

Eine Frage hätte ich allerdings noch:
Wie kann ich die Hintergrundfarbe der aufgeplappten Listbox oder des Edit-Feldes z.B. hellgelb einfärben?
Somit könnte man sofort sehen, dass etwas editiert wird. Funktional natürlich nicht wichtig, wäre aber schöner.. :D

Danke!
Never change a running system - Never run a changed system!
(PB 6.20 LTS [x86])
Antworten