Seite 1 von 1

DataGrid bitte 'mal testen

Verfasst: 07.05.2015 20:05
von ProgOldie
Hallo,
ich habe inzwischen den Überblick über die vielen Datagrids im Forum verloren und daher ein eigenes geschrieben. Wer Lust hat, möge es bitte testen. Manches wie die Anpassung an eine Datenbank bzw. Löschen und Hinzufügen von Datensätzen fehlt noch, ist aber relativ leicht zu ergänzen.

Die Grundidee: Man definiert ein Listicongadget als Datagrid und zu jeder Spalte einen geeigneten Datentyp, für den automatisch ein passendes Gadget (z.B. Dategadget oder ComboboxGadget erstellt wird.Durch Doppelklick auf ein Listenelement wird das Gadget für die Datenänderung aktiviert. Klickt man im Datenfenster außerhalb des aktivierten Gadgets, um dieses zu verlassen, wird dessen eingestellter Wert in das Listicongadget übernommen. Schwer zu beseitigender Schönheitsfehler: Nach dem Verlassen ist die Zeile mit dem zuvor aktivierten Gadget markiert, auch, wenn man zuvor woanders hinklickt, d.h: Es ist ein Mausklick mehr als nötig.

Code: Alles auswählen

;DataGrid mit diversen Datentypen (Windows only....)
;Autor ProgOldie (Fremdprogrammteile s. Prozeduren)
;Grundidee mit jeweils einem Spaltengadget von hjBremer.
;Allerdings hier nicht ausschließlich mit Stringgadgets,
;sondern mit Gadgets, die an den Datentyp angepasst sind.

EnableExplicit

;Bezeichner für die letztlich im ListIcon benutzten Datentypen
Global ColTypes.s="|INTEGER|FLOAT|ENUM|STRING|BOOLEAN|DATE|"

Structure Column
  ColName.s
  ColType.s
  ColPref.s
  ColNr.i         ;Zählung beginnt ab 1
  ColGadNr.i      ;GadgetNr wie durch #PB_Any erzeugt
  changeableCol.i ;Änderungen in dieser Spalte erlaubt?
EndStructure

Structure GridProps
  x.i             ;Eigenschaften des zugehörigen ListiconGadgets
  y.i             ;x,y Position
  Width.i
  Height.i
  ListGadNr.i   ;GadgetNr wie durch #PB_Any erzeugt
  Cols.i        ;Anzahl der bereits definierten Spalten
  IDCol.i       ;Spalte, die eindeutig eine Zeile definiert
                ; falls ein Gadget zum Bearbeiten aktiviert ist
  activeCol.i
  activeLine.i
  List ColList.Column()
EndStructure


Procedure createGridProps(*GProps.GridProps,Title.s,x.i,y.i,Width.i,Height.i,IDCol.i)
  ;Die Zählung für SelectElement beginnt ab 0
  Protected res.i
  
  With *GProps
    \Cols=0    ; noch keine Spalten definiert
    \x=x
    \y=y
    \Width=\Width
    \Height=Height
    \activeLine=-1  ; noch kein Spaltengadget aktiv
    \activeCol=-1
  EndWith
  res=ListIconGadget(#PB_Any,x,y,Width,Height,Title,60,#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect)
  *GProps\ListGadNr=res
  GadgetToolTip(res,"Edit: Doppelklick auf Listenelement")
  If IDCol
    *GProps\IDCol=IDCol
  Else
    MessageRequester("Hinweis","Keine ID-Spalte definiert")
  EndIf
  If ListSize(*GProps\ColList())=0
    NewList *GProps\ColList()
  EndIf
EndProcedure

Procedure.i make_Gadget(*GProps.GridProps)  ;,DatType.s,Vorgabe.s,maxLen.i)
                                            ; Erzeugt an der Stelle x=0,y=0 im Fenster ein zu DatType passendes Gadget
                                            ; res ist die Nummer des Gadgets 
                                            ;Die Datentypen für die Spalte müssen definiert sein
  Protected res.i,W.i,H.i,Tip1.s,DatTyp1.s,ColPref.s,p.i,n.i
  
  ;Der Wert für H wird später noch automatisch angepasst
  W=100
  Tip1.s="<RET>:Ende <ESC>: Abbruch der Dateneingabe"
  
  ;ForEach *GProps\ColList()
  DatTyp1=*GProps\ColList()\ColType
  ColPref=*GProps\ColList()\ColPref
  Select DatTyp1
    Case "STRING"   ;**Besser einen Typzusatz definieren, nach dem der Wert nicht angezeigt wird (gut in ColPref())
      res=StringGadget(#PB_Any,0,0,W,H,"")
      GadgetToolTip(res,"Typ=STRING;"+Tip1)
    Case "INTEGER"     
      res=StringGadget(#PB_Any, 0,0,W,H,ColPref,#PB_String_Numeric)
      GadgetToolTip(res,"Typ=INTEGER "+Tip1)
    Case "DATE"
      res=DateGadget(#PB_Any,0,0, W,H,"%dd.%mm.%yyyy",0)
      GadgetToolTip(res,"Typ=DATE ")
    Case "ENUM" 
      res=ComboBoxGadget(#PB_Any,0,0,W,H)      
      ColPref=RTrim(LTrim(ColPref,","))       ; ggf. , am Anfang bzw. Ende eliminieren
      p=CountString(ColPref,",") +1           ; Zahl der Elemente ermitteln
                                              ;Fehler bei 0 Elementen noch nicht abgefangen
      For n=1 To p
        AddGadgetItem(res,-1,StringField(ColPref,n,","))
      Next   
      ;SetWindowLongPtr_(GadgetID(res),#GWL_STYLE,GetWindowLongPtr_(GadgetID(res),#GWL_STYLE) | #BS_BOTTOM)
      GadgetToolTip(res,"Typ=ENUM ")
    Case "FLOAT"
      res=StringGadget(#PB_Any, 0,0,W,H,ColPref)
      GadgetToolTip(res,"TYP=FLOAT "+Tip1)
    Case "BOOLEAN"
      res=ComboBoxGadget(#PB_Any,0,0, W,H) 
      ;SetWindowLongPtr_(GadgetID(res),#GWL_STYLE,GetWindowLongPtr_(GadgetID(res),#GWL_STYLE) | #BS_TOP)
      AddGadgetItem(res,-1,"JA")
      AddGadgetItem(res,-1,"NEIN")
      GadgetToolTip(res,"Typ=BOOLEAN ")
  EndSelect
  *GProps\ColList()\ColGadNr=res
  HideGadget(res,1)               ;Gadget für die Spalte zunächst unsichtbar
                                  ;Next
EndProcedure

Procedure addGridCol(*GProps.GridProps,Name.s,Type.s,Pref.s,changeable.i)
  ;changeable=1 : Die Spalte kann geändert werden.
  ;Trotzdem wird das zugehörige Gadget erzeugt (wg. ggf. neuem Datensatz)
  If FindString(ColTypes,"|"+Type+"|")
    AddElement(*GProps\ColList())
    With *GProps\ColList()
      \ColName=Name
      \ColType=Type
      \ColPref=Pref
      \ColNr=*GProps\Cols+1
    EndWith 
    If changeable
      *GProps\ColList()\changeableCol=1
    EndIf
    *GProps\Cols=*GProps\Cols +1  ;Zahl der Spalten um 1 erhöhen
    If *GProps\Cols=1             ;Falls 1. Spalte:Überschrift ändern
      SetGadgetItemText(*GProps\ListGadNr,-1,Name)
    Else
      AddGadgetColumn(*GProps\ListGadNr,*GProps\ColList()\ColNr,Name,80)  ; zusätzliche Spalte auch im Listicon
    EndIf
  Else
    MessageRequester(Type,"undefinierter Spaltentyp")
  EndIf
  make_Gadget(*GProps.GridProps)
EndProcedure

Procedure LvMausclick(lvid,*p.Point)
  ;von hjBremer
  ;Zeile und Spalte des Mausklicks im Gadget mit der ID lvid
  ;ermitteln und in p.Point notieren
  ;Wert steht dann in p\x bzw. p\y (Zählung ab Zeile,Spalte =0)
  Protected lvhit.LVHITTESTINFO
  GetCursorPos_(*p)  ;wo ist Maus
  MapWindowPoints_(0, lvid, *p, 1) ;Cursorpos mappen zum LV 
  lvhit\pt\x = *p\x
  lvhit\pt\y = *p\y
  SendMessage_(lvid, #LVM_SUBITEMHITTEST, 0, lvhit)               
  *p\y = lvhit\iItem      ;row ab 0
  *p\x = lvhit\iSubItem   ;col ab 0
EndProcedure  

Procedure.i ColHeight(ListIcGadNr.i)
  ;Autor Fluid Byte
  ;setzt voraus, dass eine Zeile existiert
  Protected lrc.RECT
  lrc\left = #LVIR_LABEL
  SendMessage_(GadgetID(ListIcGadNr),#LVM_GETITEMRECT,0,lrc)
  ProcedureReturn lrc\bottom -lrc\top
EndProcedure

Procedure.i headerHeight(GadNr.i) 
  Protected LV_Header.i,RECT.RECT
  LV_Header = SendMessage_(GadgetID(GadNr), #LVM_GETHEADER, 0, 0)
  GetWindowRect_(LV_Header, @RECT)
  ProcedureReturn RECT\Bottom-RECT\Top
EndProcedure

Procedure handleEvents(*Props.GridProps,aktDatWin.i)
  Protected Ev.i,EventWin.i,EvType.i,EvGad.i,visGadNr.i,LIcNr.i,p.POINT,ColBeginx.i,RowBeginy.i
  Protected Col.i,topIndex.i,aktColWidth.i,LGadHeight.i
  Protected visGadText.s
  AddKeyboardShortcut(aktDatWin,#PB_Shortcut_Return,13)  ; RETURN für Übernahme der Gadgeteingabe
  AddKeyboardShortcut(aktDatWin,#PB_Shortcut_Escape,27)  ; ESC für Abbruch der Gadgeteingabe
  LIcNr=*Props\ListGadNr
  Repeat
    Ev=WaitWindowEvent()
    If Ev
      EventWin=EventWindow()
      If EventWin=aktDatWin   ; Ereignis betrifft das Tabellenfenster
        If Ev=#PB_Event_Gadget
          EvType=EventType()
          EvGad=EventGadget()
          Select EvGad
              Case LIcNr  ; das Listicongadget wurde angeklickt
                LvMausclick(GadgetID(LIcNr),@p.Point)
                If (1+p\y)  ;Gibt es im LIcGadget überhaupt eine Zeile?
                  SelectElement(*Props\ColList(),p\x) ;Welche Eigenschaften hat die Spalte?
                  If *Props\ColList()\changeableCol
                    ColBeginx=GadgetX(LIcNr)  ;x-Offset Window-->ListiconGadget
                    RowBeginy=GadgetY(LIcNr)  ;y-Offset Window
                    For Col=0 To p\x -1       ;aktuelle Längen der vorhergehenden Spalten addieren
                      ColBeginx+SendMessage_(GadgetID(LIcNr),#LVM_GETCOLUMNWIDTH,Col,0)
                    Next
                    ;Horizontale Scrollposition abziehen
                    ColBeginx-GetScrollPos_(GadgetID(LIcNr),#SB_HORZ)
                    ;Doppelklick in Zeile=1+p\y und  Spalte=1+p\x  Zählung jeweils ab 1
                    topIndex=1+SendMessage_(GadgetID(LIcNr), #LVM_GETTOPINDEX, 0, 0)
                    RowBeginy+headerHeight(LIcNr)+(1+p\y-topIndex)*ColHeight(LIcNr)
                    aktColWidth=SendMessage_(GadgetID(LIcNr),#LVM_GETCOLUMNWIDTH,p\x,0)
                  EndIf
                EndIf
                Select EvType
                  Case #PB_EventType_LeftDoubleClick  ;Doppelklick auf ListiconGadget 
                    If *Props\ColList()\changeableCol    ;Spalte darf editiert werden
                      visGadNr=*Props\ColList()\ColGadNr
                      DisableGadget(visGadNr,0)          ;Spaltengadget aktivieren
                      HideGadget(visGadNr,0)
                      ResizeGadget(visGadNr,ColBeginx+1,RowBeginy+1,aktColWidth,ColHeight(LIcNr)+3)
                      visGadText=GetGadgetItemText(LIcNr,p\y,p\x)
                      ;+3, damit Schrift bei ComboBox besser lesbar ?
                      ;Gadget sichtbar machen, mit Wert aus ListiconGadget vorbelegen und Focus darauf                      
                      Select *Props\ColList()\ColType
                        Case "STRING"
                          SetGadgetText(visGadNr,visGadText) ;LIcon_Inhalt zunächst übernehmen 
                        Case "INTEGER"
                          SetGadgetText(visGadNr,visGadText) ;LIcon_Inhalt zunächst übernehmen 
                        Case "FLOAT"
                          SetGadgetText(visGadNr,visGadText) ;LIcon_Inhalt zunächst übernehmen 
                        Case "DATE"
                          SetGadgetState(visGadNr,ParseDate("%dd.%mm.%yyyy",visGadText))
                        Case "ENUM"
                          SetGadgetText(visGadNr,visGadText)
                        Case "BOOLEAN"
                          SetGadgetText(visGadNr,visGadText)
                      EndSelect
                      SetActiveGadget(visGadNr)
                      DisableGadget(LIcNr,1)
                      *Props\activeCol=p\x
                      *Props\activeLine=p\y
                    Else
                      MessageRequester("Forbidden action","Spalte darf nicht editiert werden")
                    EndIf
                EndSelect    
            EndSelect
          ElseIf Ev=#PB_Event_Menu And visGadNr  ; Menü im Datenfenster bei aktivem Spaltengadget 
            If GadgetType(visGadNr)=#PB_GadgetType_String
              Select EventMenu() 
                Case 13 ; Return-Taste gedrückt; Eingabe abgeschlossen; 
                  HideGadget(visgadNr,1)   ;SpaltenGadget unsichtbar machen
                  DisableGadget(LIcNr,0)   ;Listicon wieder aktivieren
                  SetActiveGadget(LIcNr)
                  ;If changed(*DWin,visGadNr,GetGadgetText(visGadNr),1+p\y,1+p\x) ;korrekt in DB übernommen?
                  SetGadgetItemText(LIcNr,p\y,GetGadgetText(visGadNr),p\x)
                  ;EndIf
                  visGadNr=0  ; Gadget nicht mehr sichtbar
                Case 27  ; ESC Abbruch Gadgetwert wird nicht übernommen
                  HideGadget(visGadNr,1)
                  DisableGadget(LIcNr,0)
                  SetActiveGadget(LIcNr)
                  visGadNr=0
              EndSelect 
            EndIf
          ElseIf Ev=#PB_Event_LeftClick And visGadNr  
            ;kein GadgetEvent, aber Mausklick im Fenster-->Klick außerhalb des aktiven Gadgets
            visGadText=GetGadgetText(visGadNr) ;Text des Spaltengadgets zwischenspeichern
            HideGadget(visgadNr,1)             ;SpaltenGadget unsichtbar machen
            DisableGadget(visGadNr,1)
            DisableGadget(LIcNr,0)   ;Listicon wieder aktivieren
            SetActiveGadget(LIcNr)
            ;If changed(*DWin,visGadNr,GetGadgetText(visGadNr),1+p\y,1+p\x) ;korrekt in DB übernommen?
            SetGadgetItemText(LIcNr,*Props\activeLine,visGadText,*Props\activeCol)
            *Props\activeCol=-1    ; ListGad_Eigenschaften:kein Spaltengadget mehr aktiv
            *Props\activeLine=-1
            visGadNr=0 
            ;Hier evntuell noch über mit WindowMouseX und WindowMouseY ggf. 
            ;Zeile und Spalte des Klicks zurückrechnen
            ;Debug "X="+Str(WindowMouseX(aktDatWin)) + "Y="+Str(WindowMouseY(aktDatWin))
          ElseIf Ev=#PB_Event_CloseWindow
            CloseWindow(aktDatWin)
          EndIf
        Else 
          ;****  anderes Fenster als Tabellenfenster 
        EndIf
      EndIf   
    Until EventWin=aktDatWin And Ev = #PB_Event_CloseWindow
  EndProcedure
  
  Procedure showGadParams(*GProps.GridProps)  
    ;Listet die Eigenschaften der GadgetSpalten für Testzwecke auf
    Protected ColText.s
    ColText="Sp Nr  , GadgetNr, SpName , SpTyp    ,  SpPref"+Chr(10)
    If ListSize(*GProps\ColList()) > 0  ;Spaltenliste nicht leer
      ForEach *GProps\ColList()
        ColText+"Sp"+Str(*GProps\ColList()\ColNr)+"   "
        ColText+Str(*GProps\ColList()\ColGadNr)+"  "
        ColText+*GProps\ColList()\ColName+"  "
        ColText+*GProps\ColList()\ColType+"  "
        ColText+*GProps\ColList()\ColPref+Chr(10)
      Next
      MessageRequester("Spaltenliste",ColText)
    Else
      MessageRequester("ListIconGadget","keine Spalten definiert")
    EndIf 
  EndProcedure
  
Und so wird das DataGrid definiert:

Code: Alles auswählen


   
  Define.GridProps TestGrid  ;def. Eigenschaften eines Datengrids
  OpenWindow(1,50,100,600,300,"Testwindow",#PB_Window_SystemMenu|#PB_Window_SizeGadget)
  createGridProps(@TestGrid,"Datenfenster",1,1,600,300,1)    ;def.TestGrid . Für spätere DB-Anwendungen (update):
                                                               ;Spalte 1 definiert hier eindeutig die Zeile (z.B. ID)
  
  ;Spaltentypen des DataGrids definieren
  addGridCol(@TestGrid,"ID","INTEGER","",0)     ;0: Diese Spalte darf nicht geändert werden
  addGridCol(@TestGrid,"ausgemustert","BOOLEAN","",1)
  addGridCol(@TestGrid,"Artikel","STRING","",1) ;1: Spalteninhalt kann geändert werden
  addGridCol(@TestGrid,"ArtikelNr","INTEGER","",1)
  addGridCol(@TestGrid,"Kaufdatum","DATE","3.5.2012",1)
  addGridCol(@TestGrid,"Zustand","ENUM","neu,gebraucht,wertlos",1)  ;ComboBox mit 3 Auswahlmöglichkeiten
  addGridCol(@TestGrid,"Anzahl","INTEGER","",1)
  addGridCol(@TestGrid,"Einzelpreis","FLOAT","",1) 
  addGridCol(@TestGrid,"Abverkauf","BOOLEAN","",1)  ;Für Boolean sind die Werte "JA,NEIN" vordefiniert
 
  ;DataGrid mit Werten füllen
  AddGadgetItem(TestGrid\ListGadNr,-1,"1"+Chr(10)+"JA"+Chr(10)+"Zange"+Chr(10)+"249"+Chr(10)+"13.02.2011"+Chr(10)+"neu"+Chr(10)+"5"+Chr(10)+"3.20"+Chr(10)+"NEIN")
  AddGadgetItem(TestGrid\ListGadNr,-1,"2"+Chr(10)+"JA"+Chr(10)+"Säge"+Chr(10)+"222"+Chr(10)+"25.07.2011"+Chr(10)+"gebraucht"+Chr(10)+"2"+Chr(10)+"5.50"+Chr(10)+"JA")
  AddGadgetItem(TestGrid\ListGadNr,-1,"4"+Chr(10)+"JA"+Chr(10)+"Beil"+Chr(10)+"504"+Chr(10)+"03.08.2012"+Chr(10)+"neu"+Chr(10)+"6"+Chr(10)+"16.50"+Chr(10)+"NEIN")
  AddGadgetItem(TestGrid\ListGadNr,-1,"5"+Chr(10)+"NEIN"+Chr(10)+"Pinzette"+Chr(10)+"249"+Chr(10)+"23.09.2011"+Chr(10)+"neu"+Chr(10)+"5"+Chr(10)+"1.54"+Chr(10)+"JA")
  AddGadgetItem(TestGrid\ListGadNr,-1,"3"+Chr(10)+"NEIN"+Chr(10)+"Axt"+Chr(10)+"222"+Chr(10)+"04.07.2011"+Chr(10)+"gebraucht"+Chr(10)+"2"+Chr(10)+"34.50"+Chr(10)+"NEIN")
  AddGadgetItem(TestGrid\ListGadNr,-1,"7"+Chr(10)+"NEIN"+Chr(10)+"Raspel"+Chr(10)+"504"+Chr(10)+"18.09.2012"+Chr(10)+"neu"+Chr(10)+"6"+Chr(10)+"16.50"+Chr(10)+"JA")
  
  handleEvents(@TestGrid,1)  ; dieser Aufruf darf erst nach Wertzuweisung zu einer Zeile erfolgen.
Ist die Lösung sinnvoll / fehlerfrei / nichts Neues / etc. ?

Re: DataGrid bitte 'mal testen

Verfasst: 09.05.2015 05:12
von Pelagio
Prima ProgOldie,
:bounce:
ich habe zwar im Augenblick wenig Zeit aber dein DataGrid wollte ich mir doch einmal anschauen und bin sehr erfreut darüber.
Wie gesagt, im Augenblick wenig Zeit, aber sobald ich eine Minute Zeit habe werde ich den Source intensiv ausprobieren.
Das was ich bis dato gesehen, ausprobiert habe hat mir sehr gefallen.
:allright:

Re: DataGrid bitte 'mal testen

Verfasst: 09.05.2015 07:56
von ProgOldie
Danke Pelagio,
ich sitze weiter daran. Momentan teste ich den Datentyp TEXT in Gestalt eines EditorGadgets, um auch mehrzeilige Texte bequem eingeben zu können. Andere Datentypen wie BLOB werden folgen.

Langfristig plane ich die Anbindung des DataGrids an verschiedene Datenbanken (Lesen und Schreiben), wobei die Datentypen zunächst manuell angegeben werden.
Zunächst aber muss das angegebene Grundgerüst sicher sein. Daher meine Bitte an alle:

**************** T E S T E N und Verbesserungsvorschläge ***********************

Re: DataGrid bitte 'mal testen

Verfasst: 09.05.2015 10:24
von Pelagio
Hallo ProgOldie,

ich habe mich jetzt doch noch nee Stunde mit dem DataGrid beschäftigt und dabei ist mir aufgefallen das der Zeichenfont, den ich gerne hätte sich noch nicht auf die Eingabe Gadget überträgt.

Code: Alles auswählen

	SetGadgetFont(#PB_Default, LoadFont(0, "Arial", 10))
	createGridProps(@TestGrid,"Datenfenster", 1, 1, 600, 300, 1)
	SetGadgetFont(#PB_Default, #PB_Default)
leider bin ich noch nicht soweit in den Source eingestiegen das ich dies ändern könnte und so möchte ich dir dies wenigstens schreiben. :allright:

Re: DataGrid bitte 'mal testen

Verfasst: 09.05.2015 11:12
von ProgOldie
Hallo Pelagio,
leider bin ich noch nicht soweit in den Source eingestiegen das ich dies ändern könnte..
Das musst du auch nicht. Solche Änderungen baue ich ein, wahrscheinlich bei den Eigenschaften des Grids ein. Eine kleine Prozedur sollte dann die Übergabe der Einstellungen erledigen.

Über Aktualisierungen informiere ich fortlaufend.

Edit1:Fontübernahme eingebaut (Veröffentlichung demnächst)

Re: DataGrid bitte 'mal testen

Verfasst: 12.06.2015 00:29
von chrischan
Hallo ProgOldie,

ich habe gerade durch Zufall deinen Code gefunden. Das ist genau das was ich schon lange gesucht hab, vielen Dank dafür.

Ich hab mir deinen Code mal ein bisschen angesehen, warum hast du die Return und Escape Taste nur bei String-Gadgets aktiviert, bei Combo o.ä. funktioniert das doch auch?
Da ich eigentlich auf der Suche nach einer Header-Sort-Funktion für ListIcons gewesne bin und dann von da hierher gekommen bin wäre das richtig cool, wenn das eingebaut werden könnte. Hier http://www.purebasic.fr/german/viewtopi ... =8&t=27694 habe ich ein gut funktionierendes Modul gefunden. Besteht interesse daran, ich wäre auch bereit das zu integrieren.

Gruß
Chrischan :D

__________________________________________________
Domain-Link angepasst
12.06.2015
RSBasic

Re: DataGrid bitte 'mal testen

Verfasst: 12.06.2015 07:09
von ts-soft
Und hier nochmal der richtige Link: http://purebasic.fr/german/viewtopic.php?f=8&t=27694
Ohne Umleitung und SID :D