[QUOTE=rokli;1414823]
Ich habe aber folgenden Frage dazu: Woher bekommst Du denn anschließend die Anwendung dazu? Das Du auf die Art und Weise Felder in die Anwendung bekommst, ist schon klar. Aber was machst Du mit dem Feld dann? Nehmen wir an Du erzeugst ein neues TEdit. Ok. Nun hat der Anwender ein neues Eingabefeld und schreibe da z. B. "Peter" rein. Und nun? Was macht die Software nun mit "Peter"? Wie legst Du diese Information nun beispielsweise in einer
DB ab, ohne Dein Programm zu ändern und ohne es neu zu compilieren? Irgendwoher müsste ja nun ein INSERT oder ein UPDATE kommen, der den "Peter" in die
DB schreibt - oder?
Gruß
Moin, ja, dafür ist jahreslanges Know How erforderlich ... (nein, blödsinn, eigentlich genau so einfach zu erklären , daher versuch ich das mal)
Stellen wir uns vor, wir haben eine einfache Tabelle Mitarbeiter, bestehend aus
create table mitarbeiter
(ID Int64,
Vorname Varchar(80),
Nachname Varchar(80),
geburtsdatum date)
Deine Basis Software, welches die
db öffnet, beginnt also einfach mit dem dynamischen erstellen eines Menuitems im Mainmenu oder popupmenu mit der Caption Mitarbeiter, weil es in der
DB eine solche Tabelle gibt (in firebird einfach rdb$relations durchsuchen nach Einträgen ohne $ im Namen). Den Menuitem nennst du so wie die Tabelle, nur mit einem prefix, zB mnuMITARBEITER.
Der Menuitem bekommt ein Click Event, das für alle anderen dynamisch erzeugten Menuitem dazu auch benutzt wird
Code:
var mnu:TMenuItem;
....
begin
...
qry.sql.text:='select distinct rdb$relation_name from rdb$relations where rdb$relation_name not containing ''$''';
qry.open;
while not qry.eof do
begin
mnu:=TMenuItem.create(popupmenu1);
popupmenu1.items.add(mnu);
mnu.name:='mnu'+qry.fields[0].AsString;
mnu.caption:=qry.fields[0].AsString;
mnu.onclick:=mnuClick; //dafür kannst du ein bereits existierendes click event nehmen oder einfach neu erstellen in gleicher Syntax wie das automatisch erstellte aussehen würde
qry.next;
end
qry.close;
end;
procedure TForm1.mnuClick(Sender: TObject);
var tn:String;
begin
tn:=copy(TMenuitem(sender).name,4,31);
qry.sql.text:='select * from '+tn;
qry.open;
while not qry.eof do
begin
//hier kannst du nun für jeden zeile einen eintrag in eine Listbox machen, bei Firebird könntest du dafür eine Computed Feld benutzen, das vorname udn Nachname kombiniert anzeigt
//alternativ auch einfach ein datasource angebundenes dbgrid nehmen, das mach ich aber nicht
dataObj:=TDataObj.create;
dataObj.id:=fields[0].AsInteger;
//hier kommt dann später noch mehr in der Erklärung weiter unten bei der Erklärung getDataFromQry
dataObj.getDataFromQry;
listbox1.items.addObject(dataObj.FullText,dataObj);
qry.next;
end
qry.close;
end
Soweit sollte jeder mit grundlegenden Delphi/Lazarus Kenntnissen das eigentlich hinbekommen. Ich hab mir dabei erlaubt das einfach so runter zu tippen.
getDataFromQry macht nun folgendes bei mir (die instanzen der TStringlist werden im Constructor von TDataObj instanziiert)
-jedes TDataObj hat eine Eigenschaft fn (fieldnames, als einfach 0 ist ID, 1 ist VORNAME, 2 ist NACHNAME, 3 ist GEBURTSDATUM) und wird gefüllt aus qry.fields[i].Fieldname
-jedes TDataObj hat eine Eigenschaft cn (classnames, als einfach 0 ist TIntegerField, 1 ist TStringField, 2 ist TStringField, 3 ist TDateField) und wird gefüllt aus qry.fields[i].classname
-jedes TDataObj hat eine Eigenschaft val (values, als einfach 0 ist fields[0].AsString, 1 ist fields[1].AsString, 2 ist fields[2].AsString, 3 ist fields[3].AsString) und wird gefüllt aus qry.fields[i].AsString
Damit hast du schon mal unabhängig von den Feldern der Tabelle alles im Speicher und musst nicht mal das dataset offen halten.
Ein ListBox.Onlick kann nun folgendes machen
Code:
dataObj:=TDataObj(TListbox(sender).objects[TListbox(sender).itemindex]);
dataobj.ShowDataOnSbx(sbx:TScrollBox);
was macht nun ShowDataOnSbx?
Die Anzahl der Felder hast du in dataobj.fn.count, du kannst also in einer Routine ShowDataOnSbx
einfach durch alle Feldnamen durchgehen und passend zum Eintrag in cn[i] für das ID Feld ein Label erzeugen (nicht editierbar, weil aus
db kommend, meist sogar unsichtbar, für alle TStringFields je ein TEdit und dessen Eigenschaft Text mit dem Inhalt aus val[i] füllen, den Namen zB aus dem prefix edt und dem feldnamen aus fn[i] erzeugen. Bei Geburtsdatum wäre das dann ein TDateEdit mit edt.date:=StrToDate(val[i]);
Außerdem erzeuge ich auch automatisch zu jedem Control noch ein TLabel mit der Caption aus FN[i]
Die Positionen gehen in Phase 1 einfach von oben nach unten, später dann kommt der beschrieben Designmode dazu und die Positionierung kommt dann eben optional aus
der
db (siehe oben)
Damit hast du nun die Edit felder mit passenden daten gefüllt und nicht nur beim Listbox onclick sondern auch beim navigieren in der Listbox werden die
ggf vorhandene Objekte auf der Scrollbox entweder gelöscht und neu erzeugt oder mit sbx.findcomponent gesucht, weil der name ja immer identisch
ist innerhalb der gleichen tabellenfelder. Technisch macht eine datasource das nicht viel anders, ist aber auf offene Datasets angewiesen.
Nun aber der Haupt Aspekt deiner Frage: Ich änder was und wie geht der Kram wieder zurück in die
db?
Bei mir bekommen alle editierbaren Controls ein zentrales onchange event, wodurch ich sofort beim editoren weiss, das ich speichern sollte, daher wird in dem moment
auch sofort ein Speichern Button enabled. Der Speichern vorgang selber erstellt aus dem Table name und den Feldnamen nur für jeden Tabelle ein
sql zusammen, was
in etwa wie folgt aussieht
Code:
UPDATE OR INSERT INTO MITARBEITER(ID,VORNAME,NACHNAME,GEBURTSDATUM) VALUES (:ID,:VORNAME,:NACHNAME,:GEBURTSDATUM) matching (ID);
Wie du den String zusammenbaust sollte dir klar sein,wenn du noch mal FN[i] durchschaust. Damit nix verloren geht wird beim navigate immer gespeichert, wenn
der speichern button enabled ist.
für jede zeile in FN[] von 0 bis FN.count-1 suchst du nun auf dem sbx mit findcomponent nach dem edt und schreibst das was da drin steht wieder in val[i]
Anschliessend bekommt die
query die parameter werte in qry.params[i].Value:=val[i] und mit qry.execsql schreibst du das in die
DB.
für löschen kannst du einfach delete from ... where id=:ID machen und nur den VAL[0] benutzen
für neu Anlegen eines Datensatzes erzeugst du ein neues leeres TDataObj, holst dir über eine GetNeueID Methode einen neue unbenutzten
Generator Wert und mit dem anschliessenden speichern und erneutem lesen würden auch bereits in der Datenbank existierende Trigger
ausgeführt sein und vorgabewerte aus der
DB bereits eingetragen sein.
Binary Blobs behandel ich sowieso immer anders, aber mit Textblobs geht genau das gleiche Verfahren. Ein Blob Subtype Text erstellen
die meisten Queries als TMemoField und beim TMemoField in cn[i] kannst du dann eben ein TMemo als Kompnente benutzen.
Das schöne an dem Konzept ist, das du nun mit
Code:
alter table mitarbeiter add beruf varchar(40);
in der Datenbank ein neues Feld anlegen kannst und das beim nächsten Klick auf den o.a. Menuitem schon angezeigt wird, ohne das
du an deiner exe irgendwas anpassen musst.
Auch eine neue Tabelle wäre mit dem o.a. Verfahren sofort nach dem Neustart der exe als neuer menuitem verfügbar.
Prozesse wie buchen von daten, umwandeln von lieferscheinen in rechnungen, etc. werden bei mir eh immer in der
DB realisiert, weil
es immer viel schneller ist als im externen client.
Das es so einen Prozess gibt, kann man daran umsetzen, das man passende zum tabellen namen
einfach Stored procedures in der Datenbank mit entsprechenden Namenskonventionen erzeugt und denen immer nur die
ID vom aktuell geöffneten Datensatz als Parameter übergibt, den Rest kann sich die SP dann selber zusammensuchen. Ggf erforderliche
zusatzparameter können dynamisch abgefragte Inputparameter in der SP sein, und alle SP, zu denen der Anwender Rechte hat, scheinen
eben als Menuitem, oder als Button oder wie auch immer, aber auch alles dynamisch erzeugt.
Ein passender name um einen Lieferschein in eine Rechnung zu übernehmen, wäre dabei zum Beispiel der SP Name LIEFERSCHEIN_RECHNUNG
udn bei der Anzeige von Lieferscheinen werden für alle SP, die mit LIEFERSCHEIN beginnen, button dynamisch erzeugt. Ob der
Lieferschein schon berechnet wurde, ob es einzelrechnungen gibt, einen abweichenden Rechnungsempfänger, oder Sammelrechnungen
oder was auch immer, ist nicht Aufgabe vom Front End. Das macht die SP und niemand anders.
Das schöne an dem Konzept, mit dem ich seit mittlerweile 7 Jahren eine schon ziemlich komplexe kundenspezifsche Anwendung für
mehrere Fertigungsunternehmen, aber auch für den Eigenbedarf erstellt habe, ist, das es relativ egal ist, mit der Front End
umgesetzt wird.
Delphi egal in welcher Version eignet sich dafür genau so gut wie lazarus und bei den beiden kannst du dafür sogar noch den code
parallel benutzen. Mit TMS Webcore geht das auch als Webapplication, könnte aber auf Basis der
DB Rules auch direkt mit php, javascript,
c# oder sonstwas umgesetzt werden.
Die Referenz dafür ist bei mir halt immer die
DB und mit Tools wie IEBxpert und Firebird kannst du in der
DB auch sehr komplexe
Aufgaben lösen und das ganze steht dir trotzdem nicht nur als ein monolithischer Quellcode für alle Kunden zur Verfügung.
Jeder Kunde darf selber entscheiden, was er haben möchte und nach entsprechender Einweisung in die
DB kann er auch selber
Reports erstellen oder anpassen, oder sogar Tabellenfelder ergänzen oder sogar ganz neue Tabellen erstellen, ohne mich dafür
fragen zu müssen. und ich muss nicht überlegen, wie ich den ein Formular für alle aufbaue mit dutzende seiten auf Page
Controls, damit alle Kunden ihre Wünsche erfüllt bekommen. Komplizierte Events im Front End versuch in zu evrmeiden,
aber technisch lassen die sich auch aus der
DB mit Hilfe einer der oben genannten Scripting engine realisieren.
Customizing Beispiel:
Wenn ein Kunde bei jedem Lieferschein spezielle Barcode Etiketten auf ganz bestimmten Druckern in bestimmten Mengen
haben möchte, muss ich dafür nicht mal den Quellcode des Frontsends anpassen, sondern erstellen zum Beispiel nur
für diesen Kunden in der
DB eine Definitionstabelle seine Parameter, einen Trigger, der daraus bei neuen Lieferscheinen
einen Job in eigene Jobtabellen erstellt und ein Script zB auf Basis von IBExpert, das dann aus den Jobs in den
Tabellen serverseitig die definiterten Printqueus füllt, so das ich mich im Front end auch nicht damit
herumärgern muss, ob der nun einen funktionierenden Druckertreiber auch für win7 oder win10 oder sonst was hat ...
So, feddich, mal wieder länger geworden, aber ich hoffe das hilft ein wenig zum Verständnis.
Wenn Interesse an der Technik besteht, können wir dazu auch gerne Firmenschulungen oder bei ausreichend
Interesse auch öffentliche Schulungen anbieten (Meldet euch dafür ggf einfach bei mir
hklemt@ibexpert.com)
Evtl. mach ich aus dem Front End nach einem Code Cleanup nächstes Jahr auch mal ein Open Source Projekt
mit einigen Beispieldatenbanken. Kunden, die bei uns Schulungen machen, können das ggf dann schon ab
der Schulung zur weiteren Analyse bekommen. Der Front End ist in Lazarus programmiert, es ist aber eigentlich
nichts drin, was nicht auch in Delphi ähnlich umsetzbar ist.