Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - Benutzeroberfläche hell/dunkel


hRb - Mo 21.11.22 16:55
Titel: Benutzeroberfläche hell/dunkel
Hallo zusammen,
Unter den Compilereinstellungen Projekt/Optionen/Erscheinungsbild kann man ein gegenüber Windows-Standard abweichende Farbdarstellung der grapfischen Oberfläche wählen, z.B. Windows-Dark. Ja nach Tageslicht hätte ich gern einmal eine normale dann eine dunkle Darstellung. Wirksam wird jedoch eine hellle/dunkle Darstellung erst nach dem Compilieren und ist fest im Programm verankert.
Frage: gibt eine einfache Möglichkeit zur Laufzeit die Farbdarstellung zu wechseln?
MfG hRb


jaenicke - Mo 21.11.22 23:07

Meinst du vielleicht TStyleManager.TrySetStyle [https://docwiki.embarcadero.com/Libraries/de/Vcl.Themes.TStyleManager.TrySetStyle]?


hRb - Mi 30.11.22 01:38

Hallo jaenicke
Vielleicht ist es das. Konnte es aber nicht testen. Habe keine Erfahrung mit Class-Definitionen
Folgenden Code versuchte ich

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
procedure TForm1.Style1Click(Sender: TObject);
begin //normal hell
  TStyleManager.Setstyle('');
end;

procedure TForm1.Style2Click(Sender: TObject);
begin  //dunkel
  TStyleManager.Setstyle('Windows10 Dark');
end;

Dies brachte Laufzeitfehler, mit der Meldung "Stil 'Windows10 Dark' " bzw "Stil '' " nicht gefunden.
Wollte dann den aktuellen Stil ermitteln mit.

Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TForm1.Style2Click(Sender: TObject);
var s :string;
begin
  s:= TStyleManager.Setstyle;
end;

Dies liefert beim Compilieren Fehler: E2010 Inkompatible Typen: 'string' und 'TCustomStyleServices', d.h. es fehlt die overload-Anweisung wie in der Hilfe beschrieben.

Delphi-Quelltext
1:
2:
3:
4:
  public
    { Public-Deklarationen }
    class procedure SetStyle(const Name: string); overload;
  end;

Hier komme ich nicht weiter. Setstyle ist nicht bekannt An welcher Stelle im Programm muss eine dieser Anweisungen stehen?

Delphi-Quelltext
1:
2:
3:
class procedure SetStyle(const Name: string); overload;
class procedure SetStyle(Style: TCustomStyleServices); overload;
class procedure SetStyle(Handle: TStyleServicesHandle); overload;

Weiß also nicht nicht wie man die in Delphi vorgegebenen Stile anspricht. Gibt es dafür Constanten?
siehe SetStyle [https://docwiki.embarcadero.com/Libraries/Alexandria/de/Vcl.Themes.TStyleManager.SetStyle]


Th69 - Mi 30.11.22 09:01

Ich habe einfach mal die TStyleManager Eigenschaften [https://docwiki.embarcadero.com/Libraries/Alexandria/de/Vcl.Themes.TStyleManager_Properties] in der Doku durchgeschaut:
- StyleNames [https://docwiki.embarcadero.com/Libraries/Alexandria/de/Vcl.Themes.TStyleManager.StyleNames]
- Style[Name] [https://docwiki.embarcadero.com/Libraries/Alexandria/de/Vcl.Themes.TStyleManager.Style]
bzw. gezielt
- SystemStyleName [https://docwiki.embarcadero.com/Libraries/Alexandria/de/Vcl.Themes.TStyleManager.SystemStyleName]

PS: Da SetStyle [https://docwiki.embarcadero.com/Libraries/Alexandria/de/Vcl.Themes.TStyleManager.SetStyle] eine Prozedur ist, mußt du entweder nach einer passenden Eigenschaft oder der zugehörigen Get-Funktion suchen, in diesem Fall also GetStyle [https://docwiki.embarcadero.com/Libraries/Alexandria/de/Vcl.Themes.TStyleManager.GetStyle] (nur ist diese hier [eigenartigerweise] protected, also bleibt nur der Zugriff über die public Eigenschaften).


jaenicke - Mi 30.11.22 09:20

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Dies brachte Laufzeitfehler, mit der Meldung "Stil 'Windows10 Dark' " bzw "Stil '' " nicht gefunden.
Hast du die entsprechenden Stile denn in den Projektoptionen angehakt? Sonst werden diese nicht mit einkompiliert.

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Folgenden Code versuchte ich

Delphi-Quelltext
1:
2:
3:
4:
procedure TForm1.Style1Click(Sender: TObject);
begin //normal hell
  TStyleManager.Setstyle('');
end;
Das wird nicht gehen. Leere Stilnamen gibt es nicht.

Tipp:
Den Namen für den Standardstil findest du unten in der Combobox: Windows ;-)


hRb - Mi 30.11.22 23:06

Zitat:
Hast du die entsprechenden Stile denn in den Projektoptionen angehakt? Sonst werden diese nicht mit einkompiliert.

Ok, war mir nicht bewusst. Dachte mit der Einbindung der Unit VCL.Themes auch die Standard-Style dabei zu haben. Nach dem Hinweis die Styles "einzubinden" d,h, angekreuzt zu lassen, war auch klar dass ein Style-Typ ohne Name [''] nicht funktioniert: korrekt muss es 'Windows' lauten.
Folgendes Ergebnis erhalte nach Einbindung:
Bei Klick auf Style 2 (Dunkel) = 'Windows10 Dark' erscheint nachstehende Fehlermeldung (s. Anhang Image2)
Nach Fortsetzen erscheint noch eine Meldung über Zugriffsverletzung (s. Anhang Image3), aber die gewünschte Oberfläche erscheint.
Nach diesen beiden (ignorierten) Meldungen kann ich beliebig zwischen Style1 und Style2 hin- und herschalten OHNE WEITERE FEHLERMELDUNG!
Bleibt also nur noch die Frage, warum die Fehlermeldung.
Allerdings: nehme ich meine exe aus dem Ordner des Compilers heraus in einen fremden Ordner, gibt es keine Meldungen, d.h es könnte trotz dieser Meldungen alles ok sein.
Ein Problem mit verschiedenen Styles besteht noch (ist auch in den Hilfen erwähnt). Ich habe in meiner Anwendung ein Panel alButtom, in dem ich Anwenderfehlermeldungen anzeige. Dieses Panel färbe ich je nach Fehlergrad unterschiedlich ein. Dieses Umfärben funktioniert bei 'Windows10 Dark' nicht mehr. Weiterhin färben sich Icon-Symbole nicht mit um, d.h. schwarze Pixel werden nicht weiß. Also so easy wie es zunächst mit anderen Styles aussieht, ist es nicht.


hRb - Mi 30.11.22 23:16

Noch eine Ergänzung:
Laut Doku werden Styles in vsf-Dateien gespeichert. Habe schon geglaubt mit Loadfromfile arbeiten zu müssen. Dies ist mit dem Hinweis von jaenicke, die gewünschten Styles anzukreuzen, offenbar nicht notwendig (sofern man sich mit den Varianten unter Objekt/Optionen begnügt).


jaenicke - Mi 30.11.22 23:33

Ich bekomme hier keine Fehler. Vielleicht hast du Komponenten, die mit den VCL-Styles nicht klarkommen?
Wenn du das Problem in einem Beispielprojekt reproduzieren kannst, das du hier anhängen kannst, schaue ich es mir gern an.

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Ein Problem mit verschiedenen Styles besteht noch (ist auch in den Hilfen erwähnt). Ich habe in meiner Anwendung ein Panel alButtom, in dem ich Anwenderfehlermeldungen anzeige. Dieses Panel färbe ich je nach Fehlergrad unterschiedlich ein.
Dafür ist ein Panel nicht geeignet. Versuche es mal mit TShape.


hRb - Di 06.12.22 18:28

Hallo jaenicke,
1. das Angebot klingt gut. Inzwischen habe ich alle möglichen Farbdesign durchprobiert und nun tritt der Fehler auch bei mir nicht mehr auf (auch bei einem komplett neu erstellten Programmbeispiel). Also lassen wir es als Phänomen stehen. Vielleicht kommt es bei einem anderen Programm wieder mal vor.
2.
Zitat:
Vielleicht hast du Komponenten, die mit den VCL-Styles nicht klarkommen?

Ja, z.B. die Hintergrundfarbe eines Panel oder bei Richedit.
Zitat:
Dafür ist ein Panel nicht geeignet. Versuche es mal mit TShape.

(Warum zeichnen?, will doch nur Text ausgeben?)
Auf dem Panel liegt natürlich noch ein Label zur Textausgabe.
Bei Warnmeldungen z.B.: Panel = Rot und Labelcolor = weiß,
Bei Hinweismeldungen: Panel = Grün und Labelcolor = schwarz
Dies funktioniert bei Style Windows wie gewünscht, nicht aber bei anderen Styles.
Ich habe mal ein kleines Testprogramm beigefügt. Unter Style Windows ist alles ok aber schon bei Windows10 funktioniert es nicht wie programmiert. Da scheint so manches offen, bzw bei Style-Wechsel muss vieles nachgearbeitet werden.
Bei der Gelegenheit: Warum bekommt ein komplett neu erzeugtes Programm Dateinamen mit Nr 4 (project4.dproj)?


jaenicke - Di 06.12.22 22:32

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
(Warum zeichnen?, will doch nur Text ausgeben?)
Auf dem Panel liegt natürlich noch ein Label zur Textausgabe.
Ein Panel hat mit Themes per Definition keine Hintergrundfarbe, ein TShape aber schon. Wenn du das TShape hinter das Label auf das Panel legst, kannst du Brush.Color setzen, um die Hintergrundfarbe zu setzen.

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Bei der Gelegenheit: Warum bekommt ein komplett neu erzeugtes Programm Dateinamen mit Nr 4 (project4.dproj)?
Dann liegen bereits 3 Projekte im Projekte-Ordner.


hRb - Mo 12.12.22 23:47

Ich habe mal versucht mit Tshape zu arbeiten (statt das Panel umzufärben). Die Hintergrund-Umfärbung funktioniert auch, aber auch TLabel scheint die Umstellung von 'Windows' auf 'Tablett Dark' nicht mitzumachen.
Folgender Code ändert im Dunkel-Mode die Textfarbe nicht auf black, d.h. wenn Col<>clRed ist, ist immer Font-Color immer white.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
Procedure PutAlarm(Fehlertext: string; Col: TColor);
{ Programm- oder Bedien-Fehlermeldungen anzeigen }
var Rect : TRect;
begin
  with Form1 do
  begin
    if bell then exit; // keine zweite Meldung solange erste aktiv
    Shape1.Brush.Color := col;
    Shape1.Brush.Style := bsSolid; //bsDiagCross;
    if Col = clRed
      then LabelFehler.Font.Color := clWhite
      else LabelFehler.Font.Color := clblack;
    LabelFehler.Caption := Fehlertext;
    bell := true; // Anzeige aktiv, (immer nur die erste)
  end;
end;

Ein Versuch den Text nicht mit Label.Caption sondern mit Canvas.textout direkt auf das Shape zu schreiben zeigt keinen Text. Da mache ich wohl etwas falsch - aber was? (HG-Umfärben funktionier)

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
Procedure PutAlarm(Fehlertext: string; Col: TColor);
{ Programm- oder Bedien-Fehlermeldungen anzeigen }
var Rect : TRect;
begin
  with Form1 do
  begin
    if bell then exit; // keine zweite Meldung solange erste aktiv
    Canvas.Pen.Width := 1;
    Canvas.Font.Size:=11;
    Shape1.Brush.Color := col;
    Shape1.Brush.Style := bsSolid; //bsDiagCross;
    if Col = clRed
      then Canvas.Pen.Color := clWhite
      else Canvas.Pen.Color := clBlack;  
// dieser Text erscheint nicht (ist zumindest nicht sichtbar)
    Canvas.textout(Panelfehler.Left + 2, Panelfehler.Top+2, Fehlertext);
    bell := true; // Anzeige aktiv, (immer nur die erste)
  end;
end;


jaenicke - Di 13.12.22 00:57

Eine Ausgabe auf die Canvas Zeichenfläche musst du immer im OnPaint machen, sonst wird diese ggf. gleich wieder überschrieben.

Und zur Farbe:
Entferne einfach seFont aus der Property StyleElements des Labels, damit der Style die Farbwahl für das Label nicht übernimmt.


hRb - Mi 14.12.22 01:03

Hallo jaenicke
Zitat:
Entferne einfach seFont aus der Property StyleElements des Labels,

Ach, was kann programmieren so schön sein, wenn man alle Tricks kennt und so mühsam, wenn es anders ist.
Die "Panel-Shape-Label-Variante" funktioniert jetzt bestens. Danke !!!
Zitat:
Eine Ausgabe auf die Canvas Zeichenfläche musst du immer im OnPaint machen

Diese Info lese ich zwar, ist mir in der Umsetzung unklar bzw. "zu hoch". Weder Panel, noch Shape, noch Label besitzen ein OnPaint-Ereignis. Wüsste also nicht, unter welchem Objekt (OnPaint) der Text ausgegeben werden soll. Unter Form1?
Aber eine Lösung genügt mir. Wichtiger:

Ich traue mich kaum noch eine letzte Frage zum Thema nachzuschieben. Die Wirkung der StyleElements habe ich ausgiebig getestet. Dennoch verbleibt noch ein Problem mit einem Listview-Objekt. Füge einfach zwei Bilder bei. Die zeigen besser als 1000 Worte wo es noch klemmt. Ein markiertes Imagebild zeigt normal einen Farbrahmen, um das Icon schnell zu erkennen. Das funktioniert bei dunkler Darstellung nicht.
Frage: Gibt es neben den StyleElements-Parametern noch weitere Parameter um den Rahmen hervorzuheben? Oder muss man unter das Listview auch noch ein anderes Objekt legen (ein TShape)? Geht wohl nicht, denn dann bräuchte man für jedes Icon eine eigene "Unterlage".
Image1Image2


jaenicke - Mi 14.12.22 12:16

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Ach, was kann programmieren so schön sein, wenn man alle Tricks kennt und so mühsam, wenn es anders ist.
Ich hatte keine Ahnung wie die Styles funktionieren und kannte auch die Property nicht. Ich habe aber im Debugger nachgeschaut, wo die Schriftfarbe des Labels beim Zeichnen verwendet wird und habe dann diese Codestelle gefunden:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TStyleHook.DrawControlText(Canvas: TCanvas; Details: TThemedElementDetails;
  const S: stringvar R: TRect; Flags: Cardinal);
[...]
begin
[...]
  if LStyle.GetElementColor(Details, ecTextColor, ThemeTextColor) then
  begin
    if not Control.Enabled or (seFont in Control.StyleElementsthen
      Canvas.Font.Color := ThemeTextColor;
    LStyle.DrawText(Canvas.Handle, Details, S, R, TextFormat, Canvas.Font.Color);
[...]

Dort habe ich dann gesehen, dass die Theme-Farbe nur verwendet wird, wenn seFont gesetzt ist.

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Wüsste also nicht, unter welchem Objekt (OnPaint) der Text ausgegeben werden soll.
Wenn ein Control kein OnPaint hat, kann man die procedure Paint in einer abgeleiteten Klasse überschreiben. Alternativ kann man eine TPaintBox und deren OnPaint nehmen.

user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Frage: Gibt es neben den StyleElements-Parametern noch weitere Parameter um den Rahmen hervorzuheben? Oder muss man unter das Listview auch noch ein anderes Objekt legen (ein TShape)? Geht wohl nicht, denn dann bräuchte man für jedes Icon eine eigene "Unterlage".
Ich weiß nicht, ob man das anders erledigen kann, als es selbst zu zeichnen. Mir geht es gerade nicht gut, deshalb kann ich auch gerade nicht nachschauen.
Eine Markierung sollte aber schon zu sehen sein, nur nicht der Rahmen außen um das Bild herum.


hRb - Do 15.12.22 22:43

Kann leider nur in Etappen antworten.
Zitat:
Eine Markierung sollte aber schon zu sehen sein, nur nicht der Rahmen außen um das Bild herum.

Nein, im dunklen Style nicht, den farblichen Rahmen sieht man nur im hellen Style Der farbliche Rahmen ist Hinweis, dass dieses Item den Fokus hat. Ansteuern lässt sich dies mit dem Befehl

Delphi-Quelltext
1:
ListView1.ItemIndex := i; // setzt Fokus auf Item i - wenn i=0, dann erstes Item                    

Im dunklen Style wird das Item nur "milchig", wenn man mit der Maus darauf klickt. Beim hellen Style erscheint dauerhaft der Rahmen.
Nachdem ich alle Parameter des StyleElements getestet habe, will ich das Thema jetzt abschließen - viel ohnehin in den Bereich "nice to have". Habe dennoch viel gelernt.
Danke für alle bisherige Unterstützung.
jaenicke wünsche ich gute Besserung.
Für diejenigen, die ähnliche Aufgaben bearbeiten füge ich die zip-Datei bei (mit Code).
Das Programm leistet: Laden von Dateien (bevorzugt Bilder), einfaches optisches Sortieren, ggf. Umbenennen, Speichern der Bild-Liste, Vollbilddarstellung in Sinne einer Diashow.
PS: um das Programm voll funktionsfähig zu bekommen sind im Listview1.StyleElements alle Elemente zu entfernen.
PS2: wer noch weiter am Thema arbeiten will, siehe nachstehend. Habe ich soeben noch gefunden:
Überblick über VCL-Stile [https://docwiki.embarcadero.com/RADStudio/Sydney/de/%C3%9Cberblick_%C3%BCber_VCL-Stile]
TStyleManager.LoadFromFile [https://docwiki.embarcadero.com/Libraries/Alexandria/de/Vcl.Themes.TStyleManager.LoadFromFile]

Moderiert von user profile iconTh69: URL-Titel hinzugefügt.