Ribbon Menü

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
jacdelad
Beiträge: 107
Registriert: 03.02.2021 13:39
Computerausstattung: Ryzen 5800X, 108TB Festplatte, 32GB RAM, Radeon 7770OC
Wohnort: Riesa
Kontaktdaten:

Ribbon Menü

Beitrag von jacdelad »

Das Thema Ribbons wurde schon mehrfach angesprochen. Am Ende wurde ich darauf verwiesen es selbst zu programmieren. Ok. Hier ist es. Es ist noch nicht fertig und ich werde für die Strukturierung des Codes bestimmt Ohrfeigen und Briefbomben bekommen, aber vielleicht lässt sich ja was daraus machen. Das Projekt ist noch nicht am Ende, ich hatte aber in den letzten 2 Monaten keine Zeit daran zu arbeiten. Ich setze die Ribbons aber bereits in einigen Projekten erfolgreich ein.

Was ihr bekommt:
- Ein Ribbon-Menü
- Anpassbar in puncto Aufbau, Farben und Eigenschaften
- Das Menü lässt sich während der Laufzeit verändern
- Die einzelnen Steuerelemente ordnen sich automatisch an
- Buttons, Checkboxen, Texte...
- Einklappbar
- Fast alles automatisiert (inkl. Größenanpassung, wenn das Fenster in der Größe verändert wird)
- Eine theoretisch unbegrenzte Anzahl Ribbons nutzbar (natürlich nur eins pro Fenster)
- Compiliert direkt ins Programm und als DLL (z.Z. z.B. für XProfan)

Was noch fehlt:
- Noch mehr Tests
- Eine bessere Dokumentation (die bisherige ist...naja...anwesend und für XProfan geschrieben)
- Die Erstellung von Subfenstern (Fenster, die sich auf Druck auf einen Button öffnen) ist noch nicht wirklich erprobt
- Mehr Steuerelemente (Listen, Dropdownlisten...)

Was ihr gern machen dürft:
- Den Code unverändert in euren Programmen verwenden (mit Hinweis auf den Urheber), von mir aus auch in kommerziellen Programmen
- Mich bei der Entwicklung unterstützen oder Tipps/Kritik/etc. anbieten
- Funktioniert wegen API-Aufrufen nur unter Windows. Vielleicht lässt sich das noch portieren.

Was ich nicht möchte:
- Dass der Code irgendwo anders auftaucht
- Vor allem nicht verändert!

Bedenkt bitte, dass ich erst im August mit PureBasic angefangen habe. Strukturierung und Kommentierung war noch nie mein Ding, weil ich es schlicht nie gebraucht habe.

Download: http://jacdelad.bplaced.net/Ribbon.zip (Das Paket enthält einige Zusatzdateien für die, die auch XProfan benutzen.)
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6840
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Ribbon Menü

Beitrag von STARGÅTE »

Finde die Umsetzung echt gut. Aktuell kann ich kein Ribbons gebrauchen, aber irgendwann vielleicht schon.
(Ich mach da immer so 'n Phasen durch, dass ich die Dinger liebe und dann wieder Hasse)

Ich hab mir noch nicht alles im Detail angesehen aber vorab ein paar Fragen/Anmerkungen:
  • Was ist mit "Einklappbar" gemeint?
  • Die Größenanpassung des Ribbons selbst sehe ich, allerdings verschwinden die Elemente trotzdem rechts, bzw. werden über den Rand gezeichnet. Ist es geplant die Items noch in der größe dynamisch zu schrumpfen wenn das Ribbon klein wird. (Vergleich Windows Word oder so).
Ich werde mich später mal durch die einzelnen Funktionen wurschteln und nach Bugs suchen.

PS: "Eine bessere Dokumentation (die bisherige ist...naja...anwesend und für XProfan geschrieben)"
Immerhin, das PDF ist schon mal viel Wert ich m.M. nach auch angenehmer als PB-Kommentare im Include.
Zuletzt geändert von STARGÅTE am 26.02.2021 22:50, insgesamt 1-mal geändert.
PB 5.73 ― Win 10, 20H2 ― Ryzen 9 3900X ― Radeon RX 5600 XT ITX ― Vivaldi 4.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
jacdelad
Beiträge: 107
Registriert: 03.02.2021 13:39
Computerausstattung: Ryzen 5800X, 108TB Festplatte, 32GB RAM, Radeon 7770OC
Wohnort: Riesa
Kontaktdaten:

Re: Ribbon Menü

Beitrag von jacdelad »

Hallo Stargate,
vielen Dank erstmal. Das Ribbon hat eine unveränderliche Standardhöhe von 128 Pixeln. Einklappbar bedeutet, dass man rechts oben einen Pfeil hat, mit dem das Ribbon auf die Titelzeile eingeschrumpft wird, die genaue Höhe habe ich gerade nicht im Kopf.
Stimmt, wenn die breite zu klein ist überlagert sich alles, das ist eine der Baustellen. In meinen jetzigen Programmen ist das mit einer Mindestgröße der Fenster gelost. Ich würde es gern scrollend lösen, wenn sich die Buttons und so noch in der Größe ändern kommt sehr viel Arbeit auf mich zu.
Ich habe viel Wert darauf gelegt, dass der Programmierer nur das Nötigste machen muss und der Rest von allein geschieht.
Benutzeravatar
dige
Beiträge: 1084
Registriert: 08.09.2004 08:53

Re: Ribbon Menü

Beitrag von dige »

@jacdelad: sieht sehr gut aus! Vielen Dank!
"Papa, mein Wecker funktioniert nicht! Der weckert immer zu früh."
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6840
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Ribbon Menü

Beitrag von STARGÅTE »

Hallo jacdelad,

habe den Code jetzt mal ein bisschen getestet und mir sind folgende Sachen aufgefallen:
  • Vorab, dein Event-Handling um Beispielcode ist nicht sehr "vorbildlich". Funktionen wie EventGadget() usw. sollten nur dann ausgewertet werden, wenn es auch tatsächlich ein Gadget-Event gab, also #PB_Event_Gadget.
  • Du verwendest ja dein eigenes CheckRibbonActivity() um Events auf dem Gadget zu Checken. Da dein Ribbon aber ein normales Gadget ist, könntest du dich da nicht in den Event-Strom von PureBasic reinhängen? Jedes Mausklicken wird ja auch in die PB-Eventliste durchgereicht und falls du eigene Events brauchst, wäre da noch PostEvent() mit eigenen Typen die dann auch von EventType() zurückgegeben werden.
  • Wenn ich folgenden eigenen Code ausführe, bei dem ich denke, dass alle notwenigen Funktionen zum Rendern aufgerufen werden, dann sehe ich keine Texte der Buttons, sondern nur den Hover-Effekt:

    Code: Alles auswählen

    XIncludeFile "RibbonGadget.pbi"
    UseModule RibbonGadget
    
    Enumeration
    	#Window
    	#Gadget
    EndEnumeration
    
    Enumeration 1
    	#Tab1
    	#Tab2
    	#Group1
    	#Container1
    	#Item1
    	#Item2
    	#Item3
    EndEnumeration
    
    OpenWindow(#Window, 0, 0, 800, 450, "Ribbon Gadget", #PB_Window_MaximizeGadget|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget|#PB_Window_ScreenCentered)
    RibbonGadget(#Window, #Gadget, #Ribbon_Color_Auto, #Ribbon_Style_Black, #Ribbon_Flag_FullHover)
    AddRibbonItem(#Gadget, #Tab1, #Ribbon_Type_Category, "Category 1")
    	AddRibbonItem(#Tab1, #Group1, #Ribbon_Type_Group, "Group 1")
    		AddRibbonItem(#Group1, #Container1, #Ribbon_Type_Container)
    			AddRibbonItem(#Container1, #Item1, #Ribbon_Type_Button, "Button 1", #Ribbon_Image_None)
    			AddRibbonItem(#Container1, #Item2, #Ribbon_Type_Button, "Button 2", #Ribbon_Image_None)
    			AddRibbonItem(#Container1, #Item3, #Ribbon_Type_Checkbox, "Checkbox 3", #Ribbon_Image_None, #Ribbon_Status_Checked)
    AddRibbonItem(#Gadget, #Tab2, #Ribbon_Type_Category,"Category 2")
    
    SetRibbonMetric(#Gadget, #Ribbon_Metric_UpdateMode, 0)
    RenderRibbon(#Gadget, #Ribbon_Render_Force)
    
    Repeat
    	Select WaitWindowEvent()
    		Case #PB_Event_CloseWindow
    			Break
    		Case #PB_Event_Gadget
    			CheckRibbonActivity()
    	EndSelect
    ForEver
    
    End
  • Wäre es möglich auch für die ID deiner Items #PB_Any zu ermöglichen? Zum Beispiel der #Ribbon_Type_Separator macht eine ID irgendwie überflüssig, trotzdem muss man eine einmalige ID festlegen, da er sonst verschwindet.
  • Erstelle ich mehr als 3 Items in einem #Ribbon_Type_Container dann werden diese weiter unten angehangen und verschwinden damit, diese sollten sich dann nach rechts verteilen, ähnlich wie es bei den Buttons der Fall ist.
  • Anhand deines Codes sehe ich, dass du das Image was optional übergeben wird, direkt verwendest. Das führt jedoch beim löschen des Items dazu, dass logischerweise auch das übergebene Image freigegeben wird, was nicht immer gewollt ist. Denn dadurch müsste ich immer Kopien an deine Funktion übergeben und kann ein Image nicht mehreren Items zuweisen.
  • SetRibbonItemImage() scheint bei #Ribbon_Type_Category Items nicht zu funktionieren. Wenn ich in den Code gucke, weiß ich auch warum, da wird irgendein tempimage gesetzt, was aber 0 ist, statt mein Image:

    Code: Alles auswählen

    Procedure SetRibbonItemImage(id,image)
      Protected old,tempimage,dimensions.bitmap,temp
      Select SetPointerToItem(id)
        Case 1
          old=ribbons()\Categories()\image
          ribbons()\Categories()\image=tempimage
    
  • Es gibt einen Fehler bei bei RemoveRibbonItem(), wenn kein Image gesetzt ist. Der Code hat dort folgende Zeilen:

    Code: Alles auswählen

    If ribbons()\...\image<>0:FreeImage(ribbons()\...\image):EndIf
    Das Problem ist nun aber, dass du beim Erstellen der Items, die #Ribbon_Image_None (= -1) einfach als Image setzt, und dieses dann logischerweise <> 0 ist. Somit wird FreeImage() ausgeführt und zwar mit -1 was in PB dann #PB_All heißt und alle Images löscht. Guck da noch mal durch.
Bitte diese lange Liste nicht als "negative Kritik" oder so verstehen. Ich habe halt etwas rumgespielt und logischerweise pickt man sich dann Sache die nicht ganz rund funktionieren. Rausgekommen ist hat dieses Feedback mit Bug-Reports und Verbesserungsvorschlägen.
PB 5.73 ― Win 10, 20H2 ― Ryzen 9 3900X ― Radeon RX 5600 XT ITX ― Vivaldi 4.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
jacdelad
Beiträge: 107
Registriert: 03.02.2021 13:39
Computerausstattung: Ryzen 5800X, 108TB Festplatte, 32GB RAM, Radeon 7770OC
Wohnort: Riesa
Kontaktdaten:

Re: Ribbon Menü

Beitrag von jacdelad »

Hallo STARGÅTE,
vielen Dank für dein Feedback. Ich sehe absolut nicht als negativ, denn alles was du schreibst belegst du auch mit Fakten. Und die kann ich gut nachvollziehen.
Ich hab erst im August mit PureBasic angefangen und sehe mich daher noch als Anfänger. Es unterscheidet sich von XProfan, was ich vorher verwendet habe, gerade beim handlen von Events und Messages.
Ich schaue mir auf jeden Fall alle Punkte, die du angesprochen hast, an. Da ich bis Ende des Jahres Kurzarbeit habe wird sich sicher Zeit dafür finden, zumal ich gerade ein größeres Projekt abgeschlossen habe. Wenn ich was vorweisen kann oder weitere Fragen habe melde ich mich wieder.

Nebenbei bastel ich schon an dem Problem, dass Steuerelemente abgeschnitten oder gar nicht angezeigt werden, wenn das Ribbon breiter als das Fenster ist.
Benutzeravatar
jacdelad
Beiträge: 107
Registriert: 03.02.2021 13:39
Computerausstattung: Ryzen 5800X, 108TB Festplatte, 32GB RAM, Radeon 7770OC
Wohnort: Riesa
Kontaktdaten:

Re: Ribbon Menü

Beitrag von jacdelad »

Ich hab einiges schon angepasst, aber noch nicht alles. Hochgeladen hab ich noch nichts, das mache ich wenn alles umgesetzt ist.

Bereits fertig:
- Mittels Statusflag #Ribbon_Status_UseOriginalImage kann festgelegt werden, dass das Eingangsbild genommen wird, ansonsten wird eine Kopie für das Steuerelement angelegt. Das war zwar schon drin, aber nicht komplett umgesetzt, da war ich mittendrin abgestorben.
- Es wird nicht mehr versucht Icons in der Größe zu ändern.
- Bilder werden jetzt für alle Steuerelementtypen korrekt übernommen.
- Das Entfernen von Bildern klappt jetzt wie gewünscht.
- Steuerelemente können mit #PB_Any angelegt werden.

Was noch fehlt:
- Der Ribboncontainer kann weiterhin nur 3 Elemente enthalten.
- Ich habe den Fehler in besagtem Beispiel noch nicht gefunden. Er liegt aber vermutlich in der PBI, nicht im Beispiel selbst.
- Ich hab keine Ahnung, wie das Eventhandling verbessert bzw. In den Event-Strom reingehängt werden soll. vor allem, ohne dem Programmierer zu viel zuzumuten. Da bräuchte ich einen Schubs.
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6840
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Ribbon Menü

Beitrag von STARGÅTE »

Hallo jacdelad,

ich habe dir mal ein Beispiel geschrieben, wie so ein Event-Handling aussehen könnte.
Das Speichern von Gadgets, Ribbons und Windows ist etwas unglücklich, dass wird dann später wie du es auch schon machst in einer eigenen Liste gemacht.
In dem Beispiel geht es aber darum, dass der Benutzer praktisch nix außer CreateRibbon() aufruft und Zeichnen, und die aktualisierung automatisch läuft (mit den BindEvent und BindGadgetEvent) wenn zB. das Fenster vergrößert wird.
Außerdem sendet die Ribbon_EventHandling() Prozedur nun eigene Events die sich in die PB-Events einfügen. So kann der Nutzer Ribbons nutzen wir Gadget, Menüs oder sonst was. Er bekommt ein #PB_Event_Ribbon Event und dann alle weiteren Infos abfragen, wie z.B. EventType() oder deine eigene RibbonItemID().

Code: Alles auswählen

Enumeration
	#Window
	#Ribbon
EndEnumeration

Enumeration #PB_Event_FirstCustomValue
	#PB_Event_Ribbon                      ; Eigenes Event in PB
EndEnumeration

Enumeration #PB_EventType_FirstCustomValue
	; ... für Später
EndEnumeration

Global MouseX.i, MouseY.i



Procedure Ribbon_Draw(Gadget.i, Highlight.i=-1)
	
	; Hier wird das ganze Drawing-Zeug durchgeführt.
	
	Protected Button.i
	
	If StartDrawing(CanvasOutput(Gadget))
		Box(0, 0, OutputWidth(), OutputHeight(), $F0F0F0)
		For Button = 0 To 9
			If Highlight = Button
				Box(OutputWidth()*Button/10+5, 5, OutputWidth()/10-10, 30, $80FF80)
			Else
				Box(OutputWidth()*Button/10+5, 5, OutputWidth()/10-10, 30, $C0C0C0)
			EndIf
		Next
		StopDrawing()
	EndIf
	
EndProcedure

Procedure Ribbon_EventHandling()
	
	; Hier wird das ganze Event-Handling durchgeführt, also Ereignisse verarbeitet und eigene Erzeugt.
	
	Protected Button.i
	Protected Gadget.i = EventGadget()
	Protected Ribbon.i = GetGadgetData(Gadget)
	Protected Width.i = GadgetWidth(Gadget)
	Protected Highlight.i
	
	MouseX = GetGadgetAttribute(Gadget, #PB_Canvas_MouseX)
	MouseY = GetGadgetAttribute(Gadget, #PB_Canvas_MouseY)
	Highlight = -1
	For Button = 0 To 9
		If MouseX > Width*Button/10+5 And MouseX < Width*(Button+1)/10-5 And MouseY > 5 And MouseY < 35
			Highlight = Button
			If EventType() = #PB_EventType_LeftButtonDown
				; Einer der Button wurde gedrückt, also Event senden.
				PostEvent(#PB_Event_Ribbon, #PB_All, Button, #PB_EventType_Change)  ; Das Window ist hier unwichtig, könnte aber das echte Window sein.
			EndIf
		EndIf
	Next
	Ribbon_Draw(Gadget, Highlight)
	
EndProcedure

Procedure Ribbon_SizeCallback()
	
	; Callback wenn das Fenster sich vergrößert.
	
	Protected Gadget.i = GetWindowData(EventWindow())
	
	ResizeGadget(Gadget, 0, 0, WindowWidth(Window), 60)
	Ribbon_Draw(Gadget)
	
EndProcedure

Procedure CreateRibbon(Ribbon.i, Window.i)
	
	; Erstellung des Ribbons mit allen Daten und Callbacks
	
	Protected OldWindow.i
	Protected Gadget.i
	
	OldWindowID = UseGadgetList(WindowID(Window))
	Gadget = CanvasGadget(#PB_Any, 0, 0, WindowWidth(Window), 60, #PB_Canvas_Keyboard)
	SetGadgetData(Gadget, Ribbon) ; Ribbon ID im Gadget speichern (in diesem Beispiel einfacher als eine LinkedList aller Ribbons)
	SetWindowData(Window, Gadget) ; Gadget ID im Window speichern (in diesem Beispiel einfacher als eine LinkedList aller Ribbons)
	Ribbon_Draw(Gadget)
	BindEvent(#PB_Event_SizeWindow, @Ribbon_SizeCallback(), Window)
	BindGadgetEvent(Gadget, @Ribbon_EventHandling())
	UseGadgetList(OldWindowID)
	
EndProcedure

Procedure RibbonItemID() ; Ermitting des Gedrückten Items (hier die Buttons)
	ProcedureReturn EventGadget() ; Laut Hilfe darf EventGadget() für eigene Event benutzt werden.
EndProcedure

;- Beispiel

OpenWindow(#Window, 0, 0, 800, 450, "Event Handling", #PB_Window_MaximizeGadget|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget|#PB_Window_ScreenCentered)
CreateRibbon(#Ribbon, #Window)

Repeat
	
	Select WaitWindowEvent()
		
		Case #PB_Event_CloseWindow
			Break
		Case #PB_Event_Ribbon
			Select EventType()
				Case #PB_EventType_Change
					Debug RibbonItemID()
			EndSelect
	EndSelect
	
ForEver

End
PB 5.73 ― Win 10, 20H2 ― Ryzen 9 3900X ― Radeon RX 5600 XT ITX ― Vivaldi 4.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
jacdelad
Beiträge: 107
Registriert: 03.02.2021 13:39
Computerausstattung: Ryzen 5800X, 108TB Festplatte, 32GB RAM, Radeon 7770OC
Wohnort: Riesa
Kontaktdaten:

Re: Ribbon Menü

Beitrag von jacdelad »

Ich hab den Fehler gefunden, es lag nicht an deinem Beispiel, sondern am Modul. Ist gefixt. Ich hab eine aktualisierte Version hochgeladen, der Link im ersten Beitrag wurde aktualisert.
Das Messagehandling schaue ich mir heute an, wenn sich das Ribbon komplett in den Strom einfügt ist es natürlich besser zu bedienen.

Wie verhält es sich, wenn eine andere Include-Datei oder so auf #PB_Event_FirstCustomValue oder #PB_EventType_FirstCustomValue zugreifen wollen? Verschiebt PB die automatisch?
Benutzeravatar
Bisonte
Beiträge: 2391
Registriert: 01.04.2007 20:18

Re: Ribbon Menü

Beitrag von Bisonte »

jacdelad hat geschrieben:Wie verhält es sich, wenn eine andere Include-Datei oder so auf #PB_Event_FirstCustomValue oder #PB_EventType_FirstCustomValue zugreifen wollen? Verschiebt PB die automatisch?
Nein.... Aber wenn man mit "Labels" "enumeriert" geht sowas, weil sich somit der Zählerstand gemerkt wird...

Code: Alles auswählen

Enumeration EnumEventType #PB_EventType_FirstCustomValue
  #EventType_MeinErster
  #EventType_MeinZweiter
EndEnumeration

Enumeration EnumEventType
  #EventType_MeinDritter
EndEnumeration
Enumeration #PB_EventType_FirstCustomValue
  #EventType_MeinVierter
EndEnumeration

Debug #EventType_MeinErster
Debug #EventType_MeinDritter

; Dieser hier ist wie der Erste
Debug #EventType_MeinVierter
Edit:

Daher nutze ich ein "Common" Module, wo alle Konstanten und Variablen die von ALLEN modulen erreichbar sein sollen definiert sind.
Und dann sehen meine "DeclareModule" Anfänge immer so aus.....

Code: Alles auswählen

DeclareModule MeinModul

  EnableExplicit
  
  CompilerIf Defined(Common, #PB_Module)
    UseModule Common
  CompilerElse
    Enumeration EnumEventType #PB_EventType_FirstCustomValue
    EndEnumeration
  CompilerEndIf
  
  Enumeration EnumEventType
    #EventType_MeinErster
  EndEnumeration
  
EndDeclareModule
Also wenn das Module Common vorhanden ist, soll er es nutzen, ansonsten diese Definition selber machen...
PureBasic 5.73 LTS (Windows x86/x64) | Windows10 Pro x64 | Asus TUF X570 Gaming Plus | R9 5900X | 64GB RAM | GeForce RTX 3080 TI iChill X4 | HAF XF Evo | build by vannicom​​
Antworten