AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Vermeiden von globalen Variablen

Ein Thema von Scurra · begonnen am 13. Feb 2015 · letzter Beitrag vom 18. Feb 2015
Antwort Antwort
Scurra

Registriert seit: 19. Jan 2015
81 Beiträge
 
Delphi 10.3 Rio
 
#1

Vermeiden von globalen Variablen

  Alt 13. Feb 2015, 21:23
Hallo zusammen,

da ich noch ein Neuling in Delphi bin, habe ich die letzten Tage zum Üben ein Programm geschrieben, das in gewisser Hinsicht den Explorer von Windows nachahmt. Die grafische Oberfläche habe ich als Bild angehängt.
Genauer gesagt soll mein Programm folgendes machen: Ich habe einen Button ("Verzeichnis wählen"). Klickt man ihn an, kann man ein Verzeichnis wählen. In der ScrollBox, werden dann alle Bilder mit bestimmten Dateiformaten angezeigt, die sich in diesem Verzeichnis befinden. Angefangen habe ich damit, um mit dem Resize-Event zu "spielen", wenn ich nämlich die Breite des Formulars und damit die Breite der ScrollBox ändere, dann sollen die Elemente darin (anfangs waren das einfach nur Panels ohne einen Inhalt. Die Panels erkennt man, wenn man - wie im Bild im Anhang - die Bilder mit gedrückter Maustaste selektiert, die Panels sind dann blau) entsprechend neu angeordnet werden, so dass man auf keinen Fall horizontal scrollen muss. Um das zu erreichen, habe ich erst einmal ein paar globale Variablen definiert (obwohl ich globale Variablen nicht mag, da sie ein Programm fehleranfällig machen). In diesen Variablen ist dann z. B. gespeichert, wie weit die linke Spalte der Panels vom linken Rand der Scrollbox entfernt ist, wie groß die Panels überhaupt sind, wie groß die horizontalen und vertikalen der Abstände der Panels zueinander sind etc. Das kann man so machen, wenn man für jedes Panel gleiche Werte verwenden möchte wie ich in meinem Programm.

In einem weiteren Schritt habe ich Objekte der Klasse TImage auf die Panels gesetzt, worin die Bilder angezeigt werden. Auch hierfür habe ich wieder globale Variablen verwendet, um die Abstände des Bildes zu den Rändern des Panels festzulegen. Im nächsten Schritt habe ich unten noch Labels auf die Panels gelegt, um den Dateinamen anzuzeigen. Auch hier gibt es wieder globale Variablen. Ich wollte nun noch Edit-Felder über die Panels legen, um ggf. den Dateinamen zu ändern (das ganze sollte am Ende eben ähnlich aussehen wie in Windows).

Da der Quellcode inzwischen relativ unübersichtlich geworden ist, habe ich mir heute Gedanken darüber gemacht, "meine" Panels als eine eigene Klasse zu definieren. Dann spare ich mir den ganzen Aufwand in meinem Programm, die Eigenschaften von den TImage-Komponenten, den Labels etc. zu setzen. Dann brauch ich ledigich noch Bilder inkl. Dateinamen an die Instanzen meiner Klasse (die ich TIconPanel genannt habe) zu übergeben und die Klasse soll dann den Rest für mich erledigen.

Jetzt bin ich aber auf 2 Probleme gestoßen, die mich schon den ganzen Nachmittag aufgehalten haben, weil ich noch sehr unerfahren im Programmieren bin und nicht weiß, wie man es am besten implementieren sollte:

1. Ich möchte die vielen globalen Variablen weg haben, aber die Daten müssen ja trotzdem in irgendeiner Weise zur Verfügung gestellt werden. Wenn man die Werte für die Abstände etc. variabel halten möchte, würde ich einfach Attribute definieren, die dann jede Instanz der Klasse TIconPanel trägt. Dann kann man über über Properties darauf zugreifen und ggf. die Werte ändern.
Eine andere Möglichkeit wäre (wenn man die Abstände, Größen etc. konstant halten möchte), keine Set-Methoden zu implementieren, so dass man nur zum Lesen auf die Attribute zugreifen kann. Die Werte werden im Konstruktor festgelegt und bleiben immer gleich, solange die Werte niemand im Quellcode ändert
Was ich mir als erstes überlegt habe (und ich habe es auch so implementiert, aber das hat der Übersichtlichkeit geschadet), war, die globalen Variablen in Form von Klassenfeldern zu definieren und dann mit Klasseneigenschaften darauf zuzugreifen. Diese Idee schien mir am naheliegensten, weil ich erst einmal alle Abstände, Größen etc. konstant halten wollte, d. h. diese Parameter sind nicht individuell für jede Instanz sondern sollen für die gesamte Klasse "gelten", es sind also Eigenschaften der Klasse, nicht der Instanzen. Nachdem ich es implementiert habe, habe ich jedoch festgestellt, dass das kaum etwas an meiner Programmstruktur geändert hat. Ich habe beim Öffnen des Formulars einfach den Klasseneigenschaften die gewünschten Werte zugewiesen und dann eben diese Klasseneigenschaften anstatt den globalen Variablen verwendet. Mir kamen diese Klasseneigenschaften jedoch genau wie die gloaben Variablen vor, nur dass ich eben nicht extra Variablen in meinem Formular definieren musste, sondern die "globalen" Klasseneigenschaften der Klasse verwendet habe.
Ich hoffe, dass ich mein Problem klar genug darstellen konnte und mir jemand einen Rat geben kann. Mir geht es hier nicht darum, dass mir jemand Quelltext schreibt (außer, es hilft beim Verständnis ), sondern darum, welches Prinzip sich am besten eignet, mein Problem zu implementieren, das Programm werde ich später vmtl. sowieso nie brauchen Evtl. gibt es noch eine andere gute Möglichkeit, an die ich noch gar nicht gedacht habe.

2. Nun zu meinem zweiten Problem: Eine meiner globalen Variablen ist ein "array of TPanel", das die Panels, die angezeigt werden sollen, enthält. Damit kann man leicht arbeiten: Man kann auf einzelne Panels mittels Index zugreifen, man kann Panels hinzufügen, löschen, die Sortierung ändern etc. Deshalb möchte ich diese Art der Datenspeicherung gar nicht aufgeben. Trotzdem stellt sich mir die Frage, ob es sinnvoll ist, auch hier die globale Variable durch etwas anderes zu ersetzen. Ich hatte wieder die Idee, der Klasse TIconPanel ein array of TIconPanel als Klassenfeld zu übergeben. Beim Konstruktor der Klasse soll die erzeugte Instanz ans array angehängt werden, beim Destruktor wieder entfernt werden. Ist es sinnvoll, das so zu machen? Ich finde es einerseits sinnvoll, andererseits habe ich dann keine Möglichkeit, in meinem Formular z. B. eine zweite Scrollbox ebenfalls mit TIconPanels als Inhalt hinzuzufügen (die z. B. Bilder eines anderen Verzeichnisses anzeigt), weil ich nur ein array für die Klasse habe.
Was erscheint euch hier am sinnvollsten? Kann man überhaupt allgemein sagen, welche Art der Implementierung optimal ist, ohne jetzt schon genau zu wissen, wohin das Programm noch führt und welche Anforderungen ich an die Klasse später vllt. einmal habe? Sollte ich mich jetzt schon endgültig festlegen, wofür ich die Klasse am Ende brauche und was sie können muss, um nun eine "gute" Implementierung zu finden? Über einen Rat wäre ich auch hier sehr froh.

Schon jetzt vielen Dank an alle, die sich das durchgelesen haben und mir evtl. helfen
Miniaturansicht angehängter Grafiken
explorer.jpg  

Geändert von Scurra (13. Feb 2015 um 21:32 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.014 Beiträge
 
Delphi 12 Athens
 
#2

AW: Vermeiden von globalen Variablen

  Alt 13. Feb 2015, 21:39
Zitat von Blaise Pascal:
Ich habe diesen Brief nur deshalb länger gemacht, weil ich nicht Muße hatte ihn kürzer zu machen.
Hast du schon mal überlegt, Software Dokumentation zu schreiben?

Viele hier im Forum erfassen ein Problem schneller, wenn sie etwas Code zu lesen bekommen.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Scurra

Registriert seit: 19. Jan 2015
81 Beiträge
 
Delphi 10.3 Rio
 
#3

AW: Vermeiden von globalen Variablen

  Alt 13. Feb 2015, 22:20
Zitat:
Hast du schon mal überlegt, Software Dokumentation zu schreiben?
Danke für das kompliment . Es ist in der Tat so, dass ich gerne alles möglichst exakt beschreibe Jetzt habe ich jedoch erst kürzlich als Software-Entwickler begonnen

Zitat:
Viele hier im Forum erfassen ein Problem schneller, wenn sie etwas Code zu lesen bekommen.
Ich weiß leider nicht, wie ich meinen Code auf das Wesentliche zusammenfassen kann und ich möchte eigentlich niemandem zumuten, sich durch meinen Code zu arbeiten. Für den Fall, dass es hilft, schicke ich den gesamten Code (lässt sich aber nicht kompilieren, da die Unit "StringTools" fehlt und ich diese Unit auf meinem Rechner zu Hause nicht habe). Vielleicht wird anhand der langen Liste von globalen Variablen schon deutlich, warum ich ein paar globale Variablen los werden möchte. Die Klasse TIconPanel habe ich noch nicht eingebaut und wie die Unit StringTools habe ich den Code dazu nicht auf meinem Rechner zu Hause. Aber In der Klasse TIconPanel habe ich ohnehin noch nichts Aufregendes implementiert, weil ich wie oben (ausführlich) beschrieben nicht weiß, wie ich die Klasse am besten aufbauen soll.


Im Prinzip kann man sich in der Datei im Anhang auf die Prozedur TfrmMain.btnShowImagesClick(Sender: TObject) konzentrieren. Darin passiert das Wichtigste.
Angehängte Dateien
Dateityp: pas Main.pas (21,0 KB, 9x aufgerufen)
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.014 Beiträge
 
Delphi 12 Athens
 
#4

AW: Vermeiden von globalen Variablen

  Alt 13. Feb 2015, 22:45
Die globalen Variablen wirst du zumindest formell einfach los, wenn du sie als Felder innerhalb der Form-Klasse anlegst. Das verbessert aber die Struktur nur unwesentlich.

Anstatt eine neue Panel-Klasse zu deklarieren (was natürlich auch geht), kannst du auch einen Frame anlegen (Datei - Neu - Weitere... - Delphi-Projekte - Delphi-Dateien - VCL-Frame). Darin platzierst du das Image, das Label und das Edit und was du sonst noch alles brauchst. Hier kannst du auch mit dem Align, AlignWithMargin, Margins und Padding der einzelnen Controls spielen um das gewünschte Layout zu erreichen.

Im Form erzeugst du dann kein TPanel sondern eben einen solchen Frame (Frame-Unit in Uses aufnehmen) und platzierst ihn an die passende Stelle. Hier wäre eventuell ein TFlowPanel als Parent brauchbar, das sich automatisch um die fließende Anordnung kümmert. Das würde deinen Code sicher deutlich reduzieren.

Ich kann das jetzt leider nicht so genau beschreiben, wie du das gern machst. Also musst du sicher noch einiges durch Ausprobieren herausfinden.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Benutzerbild von Luckie
Luckie

Registriert seit: 29. Mai 2002
37.621 Beiträge
 
Delphi 2006 Professional
 
#5

AW: Vermeiden von globalen Variablen

  Alt 13. Feb 2015, 22:54
Um mal allgemein zu antworten. Globale Variablen wird man am ehesten los, wenn man mit Klassen arbeitet. Dort können die Werte mit Hilfe von privaten Feldern gut den einzelnen Methoden zu Verfügung gestellt werden. Im Konstruktor werden sie zu gewiesen/belegt und stehen dann den Methoden zur Verfügung.

Denk eventuell mal in diese Richtung.
Michael
Ein Teil meines Codes würde euch verunsichern.
  Mit Zitat antworten Zitat
Scurra

Registriert seit: 19. Jan 2015
81 Beiträge
 
Delphi 10.3 Rio
 
#6

AW: Vermeiden von globalen Variablen

  Alt 14. Feb 2015, 09:21
Danke für die Antworten! Das hilft mir schon einmal weiter.

Zitat:
Im Form erzeugst du dann kein TPanel sondern eben einen solchen Frame (Frame-Unit in Uses aufnehmen) und platzierst ihn an die passende Stelle. Hier wäre eventuell ein TFlowPanel als Parent brauchbar, das sich automatisch um die fließende Anordnung kümmert. Das würde deinen Code sicher deutlich reduzieren.
Nur um zu überprüfen, ob ich das auch richtig verstanden habe: Was ich in Form einer Klasse umsetzen wollte, würde dann durch einen Frame ersetzt, indem ich das Layout festlege. Am Montag werde ich mir das mal anschauen, auch wenn ich wahrscheinlich bei meiner Klasse bleibe, allein deshalb, um das Erstellen von Klassen zu üben.

Zitat:
Um mal allgemein zu antworten. Globale Variablen wird man am ehesten los, wenn man mit Klassen arbeitet. Dort können die Werte mit Hilfe von privaten Feldern gut den einzelnen Methoden zu Verfügung gestellt werden. Im Konstruktor werden sie zu gewiesen/belegt und stehen dann den Methoden zur Verfügung.
Ok, das war auch eine meiner Ideen. Bleibt noch eine Sache: Am liebsten würde ich in meiner Klasse auch schon programmieren, was passieren soll, wenn ich z. B. auf eines der Panels klicke. In meinem Code sieht das bisher so aus:

Code:
procedure TfrmMain.SelectByClick(Sender: TObject);
var
  obj   : TPanel;
  pnlIdx : Integer;
begin
  obj := Sender as TPanel;
  if GetKeyState(VK_Control) < 0 then // Strg pressed; enable multiselect
  begin
    if obj.Color = selectedPnlColor then obj.Color := defaultPnlColor
    else obj.Color := selectedPnlColor;
  end
  else // Strg not pressed; disable multiselect
  begin
    for pnlIdx := 0 to Length(panelsArray) -1 do
    begin
      panelsArray[pnlIdx].Color := defaultPnlColor;
    end;
    obj.Color := selectedPnlColor;
  end;
end;
Ähnlich wie im Explorer von Windows möchte ich, dass selektierte Panels blau (selectedPnlColor) werden. Wenn nun schon andere Panels selektiert sind, dann sollen diese Panels nach einem Klick wieder die Standardfarbe (defaultPnlColor) bekommen (falls STRG nicht gedrückt wird). Wie im Code oben zu sehen, greife ich hierzu auf panelsArray zu, das array of TPanel, das alle Panels in meiner Scrollbox enthält.
Wenn ich eine solche Prozedur in meiner Klasse implementieren möchte, dann brauche ich also in meiner Klasse schon ein array in Form einer Klassenvariablen. In diesem Fall hätte ich vmtl. kein Problem, die Prozedur zu implementieren. Aber das schränkt meine Klasse dafür stark ein. Ich kann in meinem Projekt ohne Weiteres nur eine Scrollbox mit Panels erzeugen. Ist es in meinem Fall sinnvoll, das array als Klassenvariable zu definieren oder gibt es noch andere Möglichkeiten, um die Prozedur im Code-Beispiel oben auch ohne Klassenvariable innerhalb meiner Klasse zu implementieren.

Vielleicht beantworte ich meine Frage jetzt schon selbst, aber als ich den Beitrag geschrieben habe, kam mir die Idee, meiner Klasse TIconPanel eine weitere Eigenschaft mitzugeben, z. B. ein Integer FGroupIndex. Dann könnte ich IconPanels, die zusammen gehören sollen, allen den gleichen Gruppen-Index verpassen und so aus allen Panels im array die heraussuchen, die einen bestimmen Wert bei FGroupIndex haben.

P.S.: Zusammenfassend kann man vllt. sagen, dass meine "Probleme" neben meiner fehlender Erfahrung auch daher kommen, dass ich mich nicht entscheiden kann, ob ich meine Klasse allgemein halten möchte oder spezieller machen möchte. Ist sie allgemein, dann kann ich die Klasse für viele verschiedene Anwendungen verwenden, dafür erledigt die Klasse wenig Arbeit für mich. Implementiere ich die Klasse hingegen eher spezieller, dann bin ich bei der Verwendung stärker eingeschänkt, aber dafür erledigt die Klasse schon fast alles für mich.
  Mit Zitat antworten Zitat
Benutzerbild von jfheins
jfheins

Registriert seit: 10. Jun 2004
Ort: Garching (TUM)
4.579 Beiträge
 
#7

AW: Vermeiden von globalen Variablen

  Alt 14. Feb 2015, 10:07
Zitat:
P.S.: Zusammenfassend kann man vllt. sagen, dass meine "Probleme" neben meiner fehlender Erfahrung auch daher kommen, dass ich mich nicht entscheiden kann, ob ich meine Klasse allgemein halten möchte oder spezieller machen möchte. Ist sie allgemein, dann kann ich die Klasse für viele verschiedene Anwendungen verwenden, dafür erledigt die Klasse wenig Arbeit für mich. Implementiere ich die Klasse hingegen eher spezieller, dann bin ich bei der Verwendung stärker eingeschränkt, aber dafür erledigt die Klasse schon fast alles für mich.
Tendenziell würde ich (aus Erfahrung) sagen: Stelle deine aktuellen Anforderungen zusammen, und dann mach' die Klassen eher spezieller. Oft kommt man dann an Punkte, bei denen man die Klasse etwas allgemeiner machen könnte, "falls es dann irgendwann doch erweitert wird". Mach' das nicht, denn das passiert fast nie. Zumindest nicht in dem Bereich, den du vorher antizipierst

Klar, deine Anforderungen werden sich ändern. Aber meistens sind das dann inkrementelle Änderungen (xyz soll hier ne Spezialbehandlung bekommen, also ein Feature wie "Wenn jemand Freitag den 13. im Kalender auswählt, soll hier ein roter Warnhinweis erscheinen") und nicht solche Sachen wie "Wir möchten eine Datenbank in der man in einem bestimmten Format Warnungsregeln für beliebige Kalendertage definieren kann." - so eine Anforderung ist meistens früh bekannt.

Und wenn du vorzeitig "universell einsetzbaren" Code schreibst, dann sind in deiner Anwendung viele Stellen, in denen du dann wieder einen Spezialfall bauen musst. An der Stelle sieht das dann umständlich aus.

Wenn du etwas vielleicht in anderen Projekten auch mal brauchen kannst, dann programmiere das erst mal so, dass es aktuell passt. Für das andere Projekt kannst du ja die Klasse kopieren und erweitern (wenn du genau weißt, was für eine Erweiterung du jetzt brauchst).

Geändert von jfheins (14. Feb 2015 um 10:24 Uhr)
  Mit Zitat antworten Zitat
Jumpy

Registriert seit: 9. Dez 2010
Ort: Mönchengladbach
1.733 Beiträge
 
Delphi 6 Enterprise
 
#8

AW: Vermeiden von globalen Variablen

  Alt 18. Feb 2015, 10:40
Delphi-Quellcode:
if obj.Color = selectedPnlColor then obj.Color := defaultPnlColor
else obj.Color := selectedPnlColor;
Dies sieht ja so aus, als ob du über die Panel-Farbe feststellst, ob eine Datei (für die ja das Panel steht) selektiert ist oder nicht. Hier in der DP wird dir sicher noch sehr oft gepredigt (nicht negativ gemeint) werden, dass es sinnvoll ist, die Logik von der Darstellung zu trennen. Hier könnte das so aussehen, das du unabh. von der GUI eine Liste von Klassen hast, jede steht für eine Datei und in der Klasse könnte es eine Eigenschaft Selected geben. Oder du hast eine Liste aller Klassen/Dateien und eine in der die selektierten Klassen/Dateien stehen.
Wird nun in der GUI geklickt schaust du welche Datei/Klasse dies betrifft. Schaust ob sie bereits selektiert ist oder nicht und änderst diesen Zustand entsprechend.
Anschließend wird die GUI "refreshed".
Ralph
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#9

AW: Vermeiden von globalen Variablen

  Alt 18. Feb 2015, 10:49
@Jumpy & TE

Gerade in Bezug auf die Controls wird hier explizit gepredigt, dass diese Controls zur Darstellung eines Status sehr wohl geeignet sind aber eben nicht um den Status-Wert dort abzulesen bzw. als Status-Speicher zu missbrauchen.

Daten präsentieren: Ja
Daten speichern: Nein

Grund:

Die Präsentation ist immer eine Interpretation von Daten. Um von dem interpretierten und präsentierten Wert wieder auf den Ursprungswert zu kommen ist durch die Brust ins Auge, aufwändig und somit auch extrem fehleranfällig.

Eine Liste mit den echten Informationen und diese Liste einfach in den Controls abbilden ist erheblich einfacher, als von dem Control wieder die richtigen Daten heraus zu interpretieren.
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)

Geändert von Sir Rufo (18. Feb 2015 um 10:53 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 00:07 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz