Überblick

Das Grafikansicht-Steuerelement ist für das Rendern von Aktivitäten und System-Layern, die Bearbeitung von Aktivitäten, Ereignisbenachrichtigungen, Hit-Detection, die Verwaltung von System-Layern und die Unterstützung von Kontextmenüs verantwortlich.

Grafik

Rendering

Das Grafik-Steuerelement verwendet den Canvas-Knoten und dessen direkte Zeichen-API (im Gegensatz zum verzögerten Rendering über den Scenegraph). Der Grund dafür sind die großen Datenmengen, die in Gantt-Diagrammen häufig dargestellt werden. Eine Aktivität direkt in ein Bitmap zu rendern ist deutlich schneller, als den Scene Graph zu aktualisieren, CSS-Styling erneut anzuwenden und Knoten neu zu layouten. Das Grafik-Steuerelement von FlexGanttFX verwendet eine austauschbare Renderer-Architektur, bei der Renderer-Instanzen Aktivitätstypen zugeordnet werden können – sehr ähnlich zu dem Ansatz, den Swing bei List- und Table-Cell-Renderern verwendet hat. Der folgende Code zeigt, wie ein benutzerdefinierter Renderer für einen bestimmten Aktivitätstyp „Flight“ und einen bestimmten Layout-Typ registriert wird. Beachten Sie bitte, dass die Grafikansicht Aktivitäten in drei verschiedenen Layouts darstellen kann; daher muss auch der Layout-Typ an die Methode übergeben werden.

GanttChart ganttChart = new GanttChart();
GraphicsBase<?> graphics = ganttChart.getGraphics();
graphics.setActivityRenderer(
    Flight.class, // the type of activities that will be rendered
    GanttLayout.class, // the type of layout where the renderer will be used
    new FlightRenderer(graphics)); // the actual renderer instance

System-Layer

Nicht nur Aktivitäten müssen dargestellt werden. Hinzu kommen beispielsweise die aktuelle Zeit („now“), Rasterlinien, innere Linien, Agenda-/Chart-Linien und vieles mehr. All diese Elemente werden von sogenannten System-Layern gerendert. Das Grafik-Steuerelement verwaltet zwei Listen dieser Layer: eine Liste für Hintergrund-Layer und eine Liste für Vordergrund-Layer.

Hintergrund-Layer werden „hinter“ Aktivitäten gezeichnet, Vordergrund-Layer „über“ Aktivitäten. Beide Listen sind bereits vorbelegt, können aber von der Anwendung verändert werden. Weitere Informationen zu den verfügbaren System-Layern finden Sie in deren jeweiliger Dokumentation.

System-Layer können direkt über die API des Grafik-Steuerelements ein- und ausgeschaltet werden. Für jeden Layer existiert eine boolesche Eigenschaft. Deren Werte lassen sich über Methoden nach dem Muster setShowXYZLayer setzen. Auf diese Weise gesteuerte System-Layer werden mit einer Ein-/Ausblendanimation angezeigt bzw. ausgeblendet, während ein direkter Aufruf von SystemLayer.setVisible(boolean) ohne Animation erfolgt.

Bearbeitung

Zwei unterschiedliche Callbacks steuern das Bearbeitungsverhalten von Aktivitäten. Der erste ordnet ein Mausereignis bzw. eine Mausposition einem GraphicsBase.EditMode zu und wird über setEditModeCallback(Class, Class, Callback) registriert. Der zweite Callback bestimmt, ob ein bestimmter Bearbeitungsmodus bzw. eine bestimmte Operation überhaupt auf eine Aktivität angewendet werden darf. Dieser Callback wird über setActivityEditingCallback(Class, Callback) registriert. Die meisten Anwendungen werden nur mit dem zweiten Callback arbeiten und die Standardpositionen für die Edit-Modi beibehalten (z. B. rechte Kante zum Ändern der Endzeit, linke Kante zum Ändern der Startzeit).

Ereignisse

Ereignisse vom Typ ActivityEvent werden immer dann gesendet, wenn der Benutzer in der Grafikansicht eine Änderung vornimmt. Anwendungen, die diese Ereignisse empfangen möchten, können entweder eine der Methoden setOnActivityXYZEvent() aufrufen oder einen Event-Handler direkt über addEventHandler(ActionEvent.ACTIVITY_XYZ, ...) hinzufügen. Ereignisse werden während der Änderung und nach deren Abschluss ausgelöst. Daher definiert die Klasse ActivityEvent Ereignistypen mit den beiden Endungen CHANGING und CHANGED.

Hit-Point-Detection

Die Grafikansicht unterstützt das Ermitteln von Informationen zu einer bestimmten Position. Aktivitäten lassen sich über getActivityBoundsAt(double, double) oder getActivityRefAt(double, double) finden. Die Zeit an einer x-Koordinate kann über getTimeAt(double) ermittelt werden. Auch die Gegenrichtung ist verfügbar: Eine Position zu einer gegebenen Zeit lässt sich über getLocation(Instant) bestimmen.

Kontextmenü

Kontextmenüs können in JavaFX für jedes Steuerelement gesetzt werden. Aufgrund der Komplexität der Grafikansicht ist zusätzliche eingebaute Unterstützung jedoch sinnvoll. Durch den Aufruf von setContextMenuCallback(Callback) kann ein kontextmenüspezifischer Callback beim Grafik-Steuerelement registriert werden. Dieser Callback wird aufgerufen, wenn der Benutzer das Kontextmenü auslöst. Dem Callback wird ein Parameterobjekt (siehe GraphicsBase.ContextMenuParameter) übergeben, das bereits mit den wichtigsten Werten für den Aufbau eines Kontextmenüs befüllt ist.

System-Layer

System-Layer werden im Hintergrund und Vordergrund jeder Zeile verwendet. Ein Hintergrund-Layer wird vor den Aktivitäten gezeichnet, ein Vordergrund-Layer danach. Jeder Layer ist auf die Darstellung eines bestimmten Informationstyps spezialisiert: aktuelle Zeit, ausgewählte Zeitintervalle, Rasterlinien usw. Die Grafikansicht verwaltet die Layer in zwei Listen und stellt Komfortmethoden bereit, um sie einfach nachzuschlagen.

  • getBackgroundSystemLayers() – gibt die vollständige Liste der im Hintergrund von Aktivitäten verwendeten System-Layer zurück.
  • getForegroundSystemLayers() – gibt die vollständige Liste der im Vordergrund von Aktivitäten verwendeten System-Layer zurück.
  • getBackgroundSystemLayer(Class) – gibt die Instanz des Hintergrund-System-Layers des angegebenen Typs zurück.
  • getForegroundSystemLayer(Class) – gibt die Instanz des Vordergrund-System-Layers des angegebenen Typs zurück.
  • getSystemLayer(Class) – gibt die System-Layer-Instanz des angegebenen Typs zurück, unabhängig davon, ob es sich um einen Vordergrund- oder Hintergrund-Layer handelt.

Layer können zur Grafikansicht hinzugefügt oder daraus entfernt werden, indem sie in die Vordergrund- oder Hintergrundliste aufgenommen bzw. daraus entfernt werden. Sobald Sie einen Layer nachgeschlagen haben, können Sie seine Eigenschaften anpassen, um sein Aussehen zu konfigurieren. Die gebräuchlichsten Eigenschaften steuern Linienfarben und -breiten.

Beispiel für einen System-Layer

GraphicsBase<?> graphics = ganttChart.getGraphics();
NowLineLayer nowLayer = graphics.getBackgroundSystemLayer(NowLineLayer .class);
nowLayer.setStroke(Color.ORANGE);
nowLayer.setLineWidth(3); // thick line

System-Layer vs. Modell-Layer

Bitte beachten Sie, dass System-Layer in keiner Weise mit Modell-Layern zusammenhängen. Ein System-Layer ist im Wesentlichen ein Renderer für grafisches Feedback, während ein Modell-Layer zum Gruppieren von Aktivitäten verwendet wird.

Verfügbare Hintergrund-Layer

Die folgende Tabelle listet alle mit FlexGanttFX ausgelieferten Hintergrund-Layer auf.

Layer Beschreibung
AgendaLinesLayer Zeichnet die horizontalen Rasterlinien für eine Zeile, wenn die Zeile oder eine ihrer inneren Linien das Agenda-Layout verwendet.
CalendarLayer Zeichnet die Einträge, die von den Kalendern zurückgegeben werden, die einer Zeile oder der gesamten Grafikansicht zugeordnet sind. Der CalendarLayer verwendet austauschbare Renderer, die den Eintragstypen zugeordnet werden. Anwendungen können eigene Renderer über CalendarLayer.setCalendarActivityRenderer() registrieren.
ChartLinesLayer Zeichnet die horizontalen Rasterlinien für eine Zeile, wenn die Zeile oder eine ihrer inneren Linien das Chart-Layout verwendet.
DSTLineLayer Zeichnet eine vertikale Linie zu dem Zeitpunkt, an dem die Sommerzeit umgestellt wird.
GridLinesLayer Zeichnet die vertikalen Rasterlinien auf Basis der aktuell in der Dateline vorhandenen Skalenauflösungen. Der Layer kann so konfiguriert werden, dass 0 bis 3 Rasterlinienebenen angezeigt werden. Wenn die Dateline beispielsweise Tage und Wochen anzeigt, zeichnet eine Ebene 2 Rasterlinien für Tage und Wochen, während Ebene 1 nur Rasterlinien für Tage rendert.
HoverTimeIntervalLayer Zeichnet das von der Dateline angegebene Hover-Zeitintervall. Wenn sich der Mauszeiger über einer Woche in der Dateline befindet, füllt der Layer das durch diese Woche definierte Zeitintervall mit einer Hervorhebungsfarbe.
InnerLinesLayer Zeichnet Trennlinien zwischen inneren Linien. Standardmäßig ist die Linienstärke dieses Layers auf 0 gesetzt, sodass keine Linien gezeichnet werden. Um dies zu ändern, setzen Sie einfach eine Linienstärke größer als 0.
NowLineLayer Zeichnet eine vertikale Linie an der Position der aktuellen Zeit bzw. der Now-Zeit. Die aktuelle Zeit ist im Timeline-Modell definiert.
RowLayer Zeichnet den Hintergrund jeder Zeile. Der Layer kann mit austauschbaren Renderern konfiguriert werden, die dem Typ der Zeile zugeordnet sind. Anwendungen können eigene Renderer über RowLayer.setRowRenderer() registrieren. Weitere Informationen finden Sie in 3.4.7 Zeilen-Rendering.
SelectedTimeIntervalsLayer Zeichnet die Zeitintervalle, die vom Benutzer (oder von der Anwendung) in der Dateline ausgewählt wurden.
ZoomIntervalLayer Zeichnet das von der Timeline definierte Zoom-Intervall. Das Zoom-Intervall wird vom Benutzer mithilfe des Timeline-Lassos erstellt.

Verfügbare Vordergrund-Layer

Die folgende Tabelle listet alle mit FlexGanttFX ausgelieferten Vordergrund-Layer auf.

Layer Beschreibung
LayoutLayer Zeichnet die Auffüllbereiche eines Layouts. Jedes Layout kann oben und unten zusätzliche Abstände besitzen. Dieser Layer füllt diese Bereiche mit einer Vollfarbe.
ScaleLayer Zeichnet eine Skala für eine gesamte Zeile oder für jede Linie innerhalb der Zeile. Die Skalen variieren abhängig vom für die Zeile bzw. Linie verwendeten Layout. Die Skala für das Chart-Layout zeigt Minimal- und Maximalwerte an, während die Skala für das Agenda-Layout eine Zeitskala darstellt (8 Uhr, 9 Uhr, 10 Uhr, ...). Die Beschriftungen und Markierungen im ScaleLayer müssen perfekt mit den Linien des AgendaLinesLayer und des ChartLinesLayer ausgerichtet sein.

Drag-and-Drop

Die schwergewichtigen, plattformseitig bereitgestellten Drag-and-Drop-Funktionen (DnD) werden in FlexGanttFX nur verwendet, um eine Aktivität von einer Zeile in eine andere zu verschieben. Alle anderen Bearbeitungsvorgänge werden über normale Mausereignisse (pressed, dragged) abgewickelt. Die neue Zeile kann sich sogar in einem anderen Gantt-Diagramm befinden. Standardmäßig wird ein DnD-Vorgang gestartet, indem der Mauszeiger bei gedrückter SHIFT-Taste in die Mitte einer Aktivität bewegt wird. Falls diese Art der Bearbeitung für die Zielaktivität unterstützt wird (siehe auch „Bearbeitung von Aktivitäten“), ändert sich der Cursor in den DnD-Cursor. Der DnD-Vorgang endet, sobald der Benutzer die Maustaste loslässt.

Ereignisse

Wie alle anderen Bearbeitungsvorgänge löst auch DnD während seiner Ausführung mehrere Ereignisse aus. Die folgende Liste beschreibt sie:

  • DRAG_STARTED, DRAG_ONGOING, DRAG_FINISHED – diese Ereignistypen werden ausgelöst, wenn der Bearbeitungsvorgang EditMode.DRAGGING ist.
  • VERTICAL_DRAG_STARTED, VERTICAL_DRAG_ONGOING, VERTICAL_DRAG_FINISHED – diese Ereignistypen werden ausgelöst, wenn der Bearbeitungsvorgang EditMode.DRAGGING_VERTICAL ist.

Der Edit-Modus DRAGGING_HORIZONTAL verwendet kein plattformspezifisches DnD. Daher sind die Ereignistypen HORIZONTAL_DRAG_STARTED / ONGOING / FINISHED oben nicht aufgeführt.

Drag-and-Drop-Info-Eigenschaft

In der Grafikansicht steht eine spezielle Eigenschaft namens dragAndDropInfo zur Verfügung, um den DnD-Vorgang zu überwachen. Sie ergänzt die oben genannten Standard-Ereignistypen. Die in dieser Eigenschaft gespeicherten Informationen liefern der Anwendung die wichtigsten Details zur gezogenen Aktivität.

Feld Beschreibung
row Die Zeile, über der sich der Mauszeiger bzw. die gezogene Aktivität aktuell befindet.
activityBounds Die Begrenzungen der gezogenen Aktivität (enthält eine Aktivitätsreferenz und die eigentliche Aktivität).
dragEvent Das letzte Drag-Ereignis (laufender Drag-Vorgang oder abgelegte Aktion).
dropInterval Das Zeitintervall, in dem die Aktivität abgelegt würde oder tatsächlich abgelegt wurde.
offset Der Offset, an dem die Aktivität mit der Maus gegriffen wurde (wird für das visuelle Feedback während des Ziehens benötigt).

Feedback-Typen

FlexGanttFX bietet unterschiedliche Möglichkeiten, das DnD-Feedback zu visualisieren. Der Enum-Typ DragAndDropFeedback definiert die folgenden Werte, die über die Methode setDragAndDropFeedback() auf GraphicsBase gesetzt werden können.

Wert Beschreibung
NATIVE Es wird ein Schnappschussbild der Aktivität erstellt und unter dem Mauszeiger platziert. Das Bild wird in dem Moment gesetzt, in dem die Drag-Geste erkannt wird. Optional kann ein Drag-Image-Provider verwendet werden. Die Größe des Bildes kann von der Größe der Aktivität abweichen (plattformabhängig).
RENDERED Die gezogene Aktivität wird fortlaufend auf einer separaten Canvas über dem Grafikbereich gerendert. Die Aktivität behält dabei garantiert ihre ursprüngliche Größe.
RENDERED_GRID_SNAPPED Die gezogene Aktivität wird fortlaufend auf einer separaten Canvas über dem Grafikbereich gerendert. Die Aktivität behält dabei garantiert ihre ursprüngliche Größe. Das aktuell aktive Raster wird verwendet, damit die gezogene Aktivität an Rasterpositionen einrastet.

Drag-Image-Provider

Wenn der DnD-Feedback-Typ auf NATIVE gesetzt wurde, kann ein benutzerdefiniertes Bild für den Drag-Vorgang übergeben werden. Dies geschieht, indem auf GraphicsBase per setDragImageProvider() ein Drag-Image-Provider gesetzt wird. Diese Methode akzeptiert einen Callback in Form eines Lambda-Ausdrucks. Die Eingabe des Callbacks ist ein ActivityRef, die Ausgabe ein Bild.

GraphicsBase<?> graphics = ganttChart.getGraphics();
graphics.setDragImageProvider(ref -> createImage(ref));

Das Standardbild ist ein Schnappschuss der Aktivität in dem Moment, in dem der Drag-Vorgang beginnt.

Drop-Layer-Provider

Drag-and-Drop-Vorgänge können zwischen zwei unterschiedlichen Gantt-Diagrammen ausgeführt werden, wobei jedes Diagramm seine eigene Layer-Liste verwaltet. Standardmäßig wird eine abgelegte Aktivität auf denselben Layer gelegt, von dem sie gezogen wurde. Enthält das Ziel-Gantt-Diagramm diesen Layer jedoch nicht, muss der Anwendung mitgeteilt werden, welcher Layer als neue Heimat der Aktivität dienen soll. Das geschieht, indem auf der Zielinstanz von GraphicsBase ein „drop layer provider“-Callback gesetzt wird.

GraphicsBase<?> graphics = targetGanttChart.getGraphics();
targetGraphics.setDropLayerProvider(info -> targetLayer); // info is of type DragAndDropInfo

Die Standardimplementierung dieses Callbacks sieht wie folgt aus:

info -> info.getActivityRef().getLayer(); // use same layer as before

Wenn der Drop-Layer-Provider keinen Layer zurückgibt oder der zurückgegebene Layer nicht dem Ziel-Gantt-Diagramm bzw. der Ziel-Grafik hinzugefügt wurde, erscheinen Meldungen wie die folgenden.

  • "the drop layer provider has returned no layer for the dropped activity"
  • "the drop layer provider has returned a layer that does not exist in the Gantt chart"

Ereignisbehandlung

Die Grafikansicht löst Standard-JavaFX-Ereignisse aus, damit Anwendungen auf Änderungen reagieren können. Die in FlexGanttFX verwendeten Konzepte zur Unterstützung von Event-Handlern entsprechen denen der Standard-JavaFX-Steuerelemente.

Aktivitätsereignisse

Aktivitätsereignisse werden ausgelöst, wenn der Benutzer eine Aktivität löscht oder bearbeitet. Um ein Aktivitätsereignis zu empfangen, registrieren Sie über eine der Komfortmethoden einen Event-Handler bei der Grafikansicht.

Einzelner Aktivitäts-Event-Handler

GraphicsBase<?> graphics = ganttChart.getGraphics();
graphics.setOnActivityChangeFinished(evt -> 
            System.out.println("An activity has changed"));

Wenn Sie mehr als einen Handler für einen bestimmten Ereignistyp registrieren möchten, verwenden Sie diesen Ansatz:

Mehrere Aktivitäts-Event-Handler

GraphicsBase<?> graphics = ganttChart.getGraphics();
graphics.addEventHandler(ActivityEvent.ACTIVITY_CHANGE_FINISHED, 
                            evt -> System.out.println("Listener 1"));
graphics.addEventHandler(ActivityEvent.ACTIVITY_CHANGE_FINISHED, 
                            evt -> System.out.println("Listener 2"));

Die folgenden Tabellen listen alle unterstützten Aktivitätsereignistypen und die Komfort-Setter-Methoden der Grafikansicht auf. Diese Methoden dienen dazu, schnell einen Event-Handler für die verschiedenen Ereignistypen zu registrieren.

Ereignistypen Beschreibung
ACTIVITY_DELETED Wird ausgelöst, wenn der Benutzer eine Aktivität über die Rücktaste löscht.
ACTIVITY_CHANGE Der übergeordnete Ereignistyp für alle Aktivitätsänderungen. Kann verwendet werden, um Benachrichtigungen über jede Art von Aktivitätsänderung zu erhalten.
ACTIVITY_CHANGE_STARTED, ACTIVITY_CHANGE_ONGOING, ACTIVITY_CHANGE_FINISHED Wird ausgelöst, wenn eine Aktivitätsänderung begonnen hat, andauert oder abgeschlossen wurde.
CHART_HIGH_VALUE_CHANGE_STARTED, CHART_HIGH_VALUE_CHANGE_ONGOING, CHART_HIGH_VALUE_CHANGE_FINISHED Wird ausgelöst, wenn der Benutzer mit der Bearbeitung des „high“-Werts einer High-/Low-Chart-Aktivität begonnen hat, diese gerade bearbeitet oder die Bearbeitung abgeschlossen hat.
CHART_LOW_VALUE_CHANGE_STARTED, CHART_LOW_VALUE_CHANGE_ONGOING, CHART_LOW_VALUE_CHANGE_FINISHED Wird ausgelöst, wenn der Benutzer mit der Bearbeitung des „low“-Werts einer High-/Low-Chart-Aktivität begonnen hat, diese gerade bearbeitet oder die Bearbeitung abgeschlossen hat.
CHART_VALUE_CHANGE_STARTED, CHART_VALUE_CHANGE_ONGOING, CHART_VALUE_CHANGE_FINISHED Wird ausgelöst, wenn der Benutzer mit der Bearbeitung eines Chart-Werts einer Chart-Aktivität begonnen hat, diese gerade bearbeitet oder die Bearbeitung abgeschlossen hat.
DRAG_STARTED, DRAG_ONGOING, DRAG_FINISHED Wird ausgelöst, wenn der Benutzer begonnen hat, eine Aktivität per plattformseitigem Drag & Drop zu ziehen, sie gerade zieht oder den Vorgang abgeschlossen hat. Dieser Ereignistyp wird verwendet, wenn die Aktivität frei vertikal und horizontal verschoben werden kann.
END_TIME_CHANGE_STARTED, END_TIME_CHANGE_ONGOING, END_TIME_CHANGE_FINISHED Wird ausgelöst, wenn der Benutzer mit der Änderung der Endzeit einer Aktivität begonnen hat, diese gerade ändert oder die Änderung abgeschlossen hat.
HORIZONTAL_DRAG_STARTED, HORIZONTAL_DRAG_ONGOING, HORIZONTAL_DRAG_FINISHED Wird ausgelöst, wenn der Benutzer mit der Änderung des Zeitintervalls (Start- und Endzeit) einer Aktivität begonnen hat, diese gerade ändert oder die Änderung abgeschlossen hat. Eine Änderung dieses Zeitintervalls verschiebt die Aktivität horizontal nach rechts oder links.
PERCENTAGE_CHANGE_STARTED, PERCENTAGE_CHANGE_ONGOING, PERCENTAGE_CHANGE_FINISHED Wird ausgelöst, wenn der Benutzer mit der Änderung des Werts „percentage complete“ einer Aktivität begonnen hat, diesen gerade ändert oder die Änderung abgeschlossen hat.
START_TIME_CHANGE_STARTED, START_TIME_CHANGE_ONGOING, START_TIME_CHANGE_FINISHED Wird ausgelöst, wenn der Benutzer mit der Änderung der Startzeit einer Aktivität begonnen hat, diese gerade ändert oder die Änderung abgeschlossen hat.
VERTICAL_DRAG_STARTED, VERTICAL_DRAG_ONGOING, VERTICAL_DRAG_FINISHED Wird ausgelöst, wenn der Benutzer begonnen hat, eine Aktivität per plattformseitigem Drag & Drop zu ziehen, sie gerade zieht oder den Vorgang abgeschlossen hat. Dieser Ereignistyp wird verwendet, wenn die Aktivität nur vertikal gezogen werden kann (Neuzuordnung zu einer anderen Zeile).
Ereignismethoden Beschreibung
setOnActivityDeleted() Wird ausgelöst, wenn der Benutzer eine Aktivität über die Rücktaste löscht.
setOnActivityChanged() Der übergeordnete Ereignistyp für alle Aktivitätsänderungen. Kann verwendet werden, um Benachrichtigungen über jede Art von Aktivitätsänderung zu erhalten.
setOnActivityChangeStarted() setOnActivityChangeOngoing() setOnActivityChangeFinished() Wird ausgelöst, wenn eine Aktivitätsänderung begonnen hat, andauert oder abgeschlossen wurde.
setOnChartHighValueChangeStarted() setOnChartHighValueChangeOngoing() setOnChartHighValueChangeFinished(); Wird ausgelöst, wenn der Benutzer mit der Bearbeitung des „high“-Werts einer High-/Low-Chart-Aktivität begonnen hat, diese gerade bearbeitet oder die Bearbeitung abgeschlossen hat.
setOnChartLowValueChangeStarted() setOnChartLowValueChangeOngoing() setOnChartLowValueChangeFinished(); Wird ausgelöst, wenn der Benutzer mit der Bearbeitung des „low“-Werts einer High-/Low-Chart-Aktivität begonnen hat, diese gerade bearbeitet oder die Bearbeitung abgeschlossen hat.
setOnChartValueChangeStarted() setOnChartValueChangeOngoing() setOnChartValueChangeFinished(); Wird ausgelöst, wenn der Benutzer mit der Bearbeitung eines Chart-Werts einer Chart-Aktivität begonnen hat, diese gerade bearbeitet oder die Bearbeitung abgeschlossen hat.
setOnActivityDragStarted() setOnActivityDragOngoing() setOnActivityDragFinished(); Wird ausgelöst, wenn der Benutzer begonnen hat, eine Aktivität per plattformseitigem Drag & Drop zu ziehen, sie gerade zieht oder den Vorgang abgeschlossen hat. Dieser Ereignistyp wird verwendet, wenn die Aktivität frei vertikal und horizontal verschoben werden kann.
setOnActivityEndTimeChangeStarted() setOnActivityEndTimeChangeOngoing() setOnActivityEndTimeChangeFinished(); Wird ausgelöst, wenn der Benutzer mit der Änderung der Endzeit einer Aktivität begonnen hat, diese gerade ändert oder die Änderung abgeschlossen hat.
setOnActivityHorizontalDragStarted() setOnActivityHorizontalDragOngoing() setOnActivityHorizontalDragFinished(); Wird ausgelöst, wenn der Benutzer mit der Änderung des Zeitintervalls (Start- und Endzeit) einer Aktivität begonnen hat, diese gerade ändert oder die Änderung abgeschlossen hat. Eine Änderung dieses Zeitintervalls verschiebt die Aktivität horizontal nach rechts oder links.
setOnActivityPercentageChangeStarted() setOnActivityPercentageChangeOngoing() setOnActivityPercentageChangeFinished(); Wird ausgelöst, wenn der Benutzer mit der Änderung des Werts „percentage complete“ einer Aktivität begonnen hat, diesen gerade ändert oder die Änderung abgeschlossen hat.
setOnActivityStartTimeChangeStarted() setOnActivityStartTimeChangeOngoing() setOnActivityStartTimeChangeFinished(); Wird ausgelöst, wenn der Benutzer mit der Änderung der Startzeit einer Aktivität begonnen hat, diese gerade ändert oder die Änderung abgeschlossen hat.
setOnActivityVerticalDragStarted() setOnActivityVerticalDragOngoing() setOnActivityVerticalDragFinished(); Wird ausgelöst, wenn der Benutzer begonnen hat, eine Aktivität per plattformseitigem Drag & Drop zu ziehen, sie gerade zieht oder den Vorgang abgeschlossen hat. Dieser Ereignistyp wird verwendet, wenn die Aktivität nur vertikal gezogen werden kann (Neuzuordnung zu einer anderen Zeile).

Hierarchie der Aktivitätsereignisse

Die in der Klasse ActivityEvent definierten Ereignistypen bilden eine Ereignishierarchie. Alle Ereignisse sind Input-Events (InputEvent.ANY) und beziehen sich auf Änderungen an der Aktivität. Einige werden beim Start der Änderung ausgelöst, andere während des laufenden Vorgangs und weitere nach dessen Abschluss.

  • InputEvent.ANY
  • ACTIVITY_CHANGE
    • ACTIVITY_DELETED
    • ACTIVITY_CHANGE_STARTED // Alle Ereignistypen, die einen „Start“ signalisieren
    • CHART_VALUE_CHANGE_STARTED
      • CHART_HIGH_VALUE_CHANGE_STARTED
      • CHART_LOW_VALUE_CHANGE_STARTED
    • DRAG_STARTED
    • END_TIME_CHANGE_STARTED
    • HORIZONTAL_DRAG_STARTED
    • PERCENTAGE_CHANGE_STARTED
    • START_TIME_CHANGE_STARTED
    • VERTICAL_DRAG_STARTED
    • ACTIVITY_CHANGE_ONGOING // Alle Ereignistypen, die einen laufenden Vorgang signalisieren
    • CHART_VALUE_CHANGE_ONGOING
      • CHART_HIGH_VALUE_CHANGE_ONGOING
      • CHART_LOW_VALUE_CHANGE_ONGOING
    • DRAG_ONGOING
    • END_TIME_CHANGE_ONGOING
    • HORIZONTAL_DRAG_ONGOING
    • PERCENTAGE_CHANGE_ONGOING
    • START_TIME_CHANGE_ONGOING
    • VERTICAL_DRAG_ONGOING
    • ACTIVITY_CHANGE_FINISHED // Alle Ereignistypen, die einen Abschluss signalisieren
    • CHART_VALUE_CHANGE_FINISHED
      • CHART_HIGH_VALUE_CHANGE_FINISHED
      • CHART_LOW_VALUE_CHANGE_FINISHED
    • DRAG_FINISHED
    • END_TIME_CHANGE_FINISHED
    • HORIZONTAL_DRAG_FINISHED
    • PERCENTAGE_CHANGE_FINISHED
    • START_TIME_CHANGE_FINISHED
    • VERTICAL_DRAG_FINISHED

Eigenschaften von Aktivitätsereignissen

Anwendungen interessieren sich natürlich für die Attribute einer Aktivität – nicht nur für deren neue Werte (zum Beispiel die neue Startzeit), sondern auch für die alten Werte (die Startzeit vor der Änderung). Die neuen Werte sind bereits an der Aktivität verfügbar, da sie während der Benutzeraktion gesetzt werden. Die alten Werte werden im Ereignisobjekt gespeichert. Die folgende Tabelle listet die Methoden von ActivityEvent auf, mit denen sich diese Werte abrufen lassen.

Methode Beschreibung Ereignistypen
getOldTime Gibt die bisherige Start- oder Endzeit der Aktivität zurück. END_TIME_CHANGE
START_TIME_CHANGE
getOldTimeInterval Gibt die bisherige Start- und Endzeit der Aktivität zurück. DRAG
HORIZONTAL_DRAG
VERTICAL_DRAG
getOldRow Gibt die bisherige Zeile zurück, in der sich die Aktivität zuvor befand. DRAG
VERTICAL_DRAG
getOldValue Gibt den bisherigen Wert von „percentage complete“ oder „chart value“ zurück. CHART_VALUE_CHANGE
CHART_HIGH_VALUE
CHART_LOW_VALUE
PERCENTAGE_CHANGE

Lasso-Ereignisse

Der Benutzer kann mit einem Lasso Aktivitäten auswählen. Dabei werden Ereignisse ausgelöst. Um ein Lasso-Ereignis zu empfangen, registrieren Sie einfach über eine der Komfortmethoden einen Event-Handler bei der Grafikansicht.

Einzelner Lasso-Event-Handler

GraphicsBase<?> graphics = ganttChart.getGraphics();
graphics.setOnLassoFinished(evt -> System.out.println("The lasso was used"));

Wenn Sie mehr als einen Handler für einen bestimmten Ereignistyp registrieren möchten, verwenden Sie diesen Ansatz:

Mehrere Lasso-Event-Handler

GraphicsBase<?> graphics = ganttChart.getGraphics();
graphics.addEventHandler(LassoEvent.SELECTION_FINISHED, 
                            evt -> System.out.println("Listener 1"));
graphics.addEventHandler(LassoEvent.SELECTION_FINISHED, 
                            evt -> System.out.println("Listener 2"));

Die folgende Tabelle listet die Ereignistypen und die Komfort-Setter-Methoden der Grafikansicht auf.

Ereignistyp Methode Beschreibung
ALL setOnLassoSelection Jeder Lasso-Vorgang (Start, laufend, beendet).
SELECTION_STARTED setOnLassoSelectionStarted Der Benutzer hat die Maustaste gedrückt und mit dem Ziehen begonnen. Das Lasso ist sichtbar geworden.
SELECTION_ONGOING setOnLassoSelectionOngoing Der Benutzer ändert die Größe des Lassos.
SELECTION_FINISHED setOnLassoSelectionFinished Der Benutzer hat die Lasso-Auswahl abgeschlossen. Das Lasso ist nicht mehr sichtbar.

Hierarchie der Lasso-Ereignisse

Die in der Klasse LassoEvent definierten Ereignistypen bilden eine Ereignishierarchie. Alle Ereignisse sind Input-Events (InputEvent.ANY).

* InputEvent.ANY
    * LassoEvent.ALL
        * LassoEvent.SELECTION_STARTED
        * LassoEvent.SELECTION_ONGOING
        * LassoEvent.SELECTION_FINISHED

Lasso-Info

Das Lasso führt automatisch Aktivitätsauswahlen durch. Manchmal möchte man jedoch mehr über die genaue Art dieser Auswahl erfahren oder das Lasso für einen anderen Anwendungsfall einsetzen (z. B. zum Erstellen neuer Aktivitäten). Aus diesem Grund stellen Instanzen von LassoEvent zusätzlich ein Objekt vom Typ LassoInfo bereit, das zahlreiche Attribute enthält, auf die die Anwendung entsprechend reagieren kann. Die Lasso-Informationen lassen sich über LassoEvent.getInfo() abrufen. Die folgende Tabelle listet die Attribute von LassoInfo auf.

Methode Beschreibung
List<ActivityRef<?>> getActivities Gibt alle Aktivitäten zurück, die mit dem Lasso ausgewählt wurden.
Instant getStartTime;, Instant getEndTime Gibt die Start- und Endzeit des Lassos entsprechend der Position seiner linken und rechten Kante zurück.
LocalTime getLocalStartTime, LocalTime getLocalEndTime Gibt die lokale Start- und Endzeit zurück. Diese Werte werden nur bereitgestellt, wenn sich die obere oder untere Kante des Lassos in einem Bereich befindet, der das AgendaLayout verwendet.
List<Row<?,?,?>> getRows Gibt die Zeilen zurück, die vom Lasso berührt wurden.

Links / Weiterführende Informationen

Oracle-JavaFX-Dokumentation Beispiele zur Ereignisbehandlung

Bearbeitung von Aktivitäten

Zwei unterschiedliche Callbacks in der Grafikansicht steuern das Bearbeitungsverhalten von Aktivitäten. Der erste ordnet ein Mausereignis bzw. eine Mausposition einem Bearbeitungsmodus zu. Der zweite Callback bestimmt, ob ein bestimmter Bearbeitungsmodus bzw. eine bestimmte Operation überhaupt auf eine Aktivität angewendet werden kann. Die meisten Anwendungen werden nur mit dem zweiten Callback arbeiten und die Standardpositionen für die Edit-Modi beibehalten (zum Beispiel: rechte Kante zum Ändern der Endzeit, linke Kante zum Ändern der Startzeit). Das Enum GraphicsBase.EditMode listet alle verfügbaren Bearbeitungsvorgänge auf, die auf einer Aktivität ausgeführt werden können.

Modus Beschreibung
AGENDA_ASSIGNING Weist eine Aktivität im AgendaLayout einer anderen Zeile zu.
AGENDA_DRAGGING Zieht eine Aktivität im AgendaLayout nach oben, unten oder seitlich innerhalb derselben Zeile.
AGENDA_END_TIME_CHANGE Ändert die Endzeit einer Aktivität im AgendaLayout.
AGENDA_START_TIME_CHANGE Ändert die Startzeit einer Aktivität im AgendaLayout.
CHART_VALUE_CHANGE Ändert den Wert einer ChartActivity.
CHART_VALUE_HIGH_CHANGE Ändert den „high“-Wert einer HighLowActivity.
CHART_VALUE_LOW_CHANGE Ändert den „low“-Wert einer HighLowActivity.
DRAGGING Führt ein Drag-and-Drop in alle Richtungen für eine Aktivität aus.
DRAGGING_HORIZONTAL Verschiebt eine Aktivität horizontal innerhalb ihrer eigenen Zeile (Änderung von Start- und Endzeit).
DRAGGING_VERTICAL Führt ein Drag-and-Drop für eine Aktivität nur in vertikaler Richtung aus.
END_TIME_CHANGE Ändert die Endzeit einer Aktivität.
NONE Führt keine Aktion aus.
PERCENTAGE_COMPLETE_CHANGE Ändert den Wert „percentage complete“ einer CompletableActivity.
START_TIME_CHANGE Ändert die Startzeit einer Aktivität.

Edit-Mode-Callback

Der Edit-Mode-Callback wird verwendet, um den Bearbeitungsmodus an der gegebenen Mausposition zu bestimmen. Instanzen dieses Callbacks können über die Methode GraphicsBase.setEditModeCallback() registriert werden, die den Callback einer Kombination aus Aktivitätstyp und Layout-Typ zuordnet.

public final void setEditModeCallback(
    Class<? extends MutableActivity> activityType,
    Class<? extends Layout> layoutType,
    Callback<EditModeCallbackParameter, EditMode> callback);

Edit-Mode-Callback-Parameter

Das an den Edit-Mode-Callback übergebene Parameterobjekt hat den Typ EditModeCallbackParameter und enthält die folgenden Informationen:

Feld Beschreibung
activityBounds Die Begrenzungen der Aktivität, über der sich der Mauszeiger befindet. Die x- und y-Koordinaten sind relativ zum Koordinatensystem der Zeile, in der die Aktivität dargestellt wird.
mouseEvent Das Mausereignis, das die Ermittlung des Edit-Modus ausgelöst hat (normalerweise ein MOUSE_OVER).

Beispiel für einen Edit-Mode-Callback

Das Folgende ist ein einfaches Beispiel für einen Callback für den Bearbeitungsmodus.

public class MyEditModeCallback implements Callback<EditModeCallbackParameter, EditMode> {

    public EditMode call(EditModeCallbackParameter param) {
        MouseEvent event = param.getMouseEvent();
        ActivityBounds bounds = param.getActivityBounds();

        /*
         * If the mouse cursor is touching the left edge of the activity
         * then begin a change of the start time of the activity.
         */
        if (event.getX() - bounds.getMinX() < 5) {
            return EditMode.CHANGE_START_TIME;
        }

        return EditMode.NONE;
    }

Dieser Callback kann nun wie folgt registriert werden:

GraphicsBase<?> graphics = ganttChart.getGraphics();
    graphics.setEditModeCallback(
    ActivityBase.class,
    GanttLayout.class,
    new MyEditModeCallback());

Wir hätten für die gesamte Callback-Instanz auch einen Lambda-Ausdruck verwenden können, haben uns hier aber zugunsten einer ausführlicheren Darstellung dagegen entschieden.

Editing-Callback

Der Editing-Callback wird verwendet, um zu bestimmen, ob ein bestimmter Edit-Modus aktuell für eine gegebene Aktivität verwendbar ist. Instanzen dieses Callbacks können über die Methode GraphicsBase.setActivityEditingCallback() registriert werden, die den Callback einem Aktivitätstyp zuordnet.

public final void setActivityEditingCallback(
    Class<? extends MutableActivity> activityType,
    Callback<EditingCallbackParameter, Boolean> callback);

Editing-Callback-Parameter

Das an den Editing-Callback übergebene Parameterobjekt hat den Typ EditingCallbackParameter und enthält die folgenden Informationen:

Feld Beschreibung
activityRef Die Referenz auf die Aktivität, für die die Prüfung durchgeführt werden soll.
editMode Der Edit-Modus, der geprüft werden soll.

Beispiel für einen Editing-Callback

Das Folgende ist ein einfaches Beispiel für einen Editing-Callback.

public class MyEditingCallback implements Callback<EditingCallbackParameter, Boolean> {

    public Boolean call(EditingCallbackParameter param) {
        ActivityRef ref = param.getActivityRef();
        Activity activity = ref.getActivity();

        /*
         * Only allow editing for activities that that have not
         * started, yet.
         */
        if (activity.getStartTime().isAfter(Instant.now())) {

            /*
             * Only allow changes to the start and end time
             * of the activity.
             */
            switch (param.getEditMode()) {
                case CHANGE_START_TIME:
                case CHANGE_END_TIME:
                    return true;
                default:
                    return false;
            }
          } 

        return false;
    }
}

Dieser Callback kann nun wie folgt registriert werden:

GraphicsBase<?> graphics = ganttChart.getGraphics();
    graphics.setActivityEditingCallback(
    ActivityBase.class,
    new MyEditingCallback());

Bearbeitung von Zeilen

Die Grafikansicht unterstützt nicht nur die Bearbeitung von Aktivitäten, sondern auch von Zeilen. Wenn eine Zeile bearbeitet wird, wird die gesamte Zeile umgedreht und zusätzliche Steuerelemente werden auf der „Rückseite“ der Zeile sichtbar. Falls die Rückseite mehr Platz (Höhe) benötigt als die Vorderseite, wird die Höhe automatisch angepasst. Die folgende Tabelle listet die Methoden auf, die mit der Zeilenbearbeitung zusammenhängen:

Methode Beschreibung
void startRowEditing(R row); Startet den Bearbeitungsvorgang für die angegebene Zeile. Die Rückseite der Zeile wird sichtbar und zeigt Steuerelemente zum Ändern der Zeileneinstellungen an.
void stopRowEditing();
void stopRowEditing(R row);
Beendet die Zeilenbearbeitung für alle Zeilen oder nur für die angegebene Zeile. Die Vorderseite der Zeile wird wieder sichtbar.
ObjectProperty<RowEditingMode> rowEditingModeProperty();
void setRowEditingMode(RowEditingMode);
RowEditingMode getRowEditingMode();
Speichert, setzt und liefert den Zeilenbearbeitungsmodus. Das Enum GraphicsBase.RowEditingMode bestimmt, ob der Benutzer überhaupt Zeilen bearbeiten kann, jeweils nur eine Zeile oder mehrere Zeilen gleichzeitig.
ObservableList<R> getRowsEditing(); Eine beobachtbare Liste aller Zeilen, die aktuell bearbeitet werden (deren Rückseite sichtbar ist).
BooleanProperty animateRowEditor();
void setAnimateRowEditor(boolean);<br>boolean isAnimateRowEditor();
Speichert, setzt und liefert ein Flag, das angibt, ob das Anzeigen der Rückseite der Zeile sofort oder animiert erfolgt.

Row-Editor-Factory

Die Row-Editor-Factory wird verwendet, um die Steuerelemente für eine bestimmte Zeile genau in dem Moment zu erzeugen, in dem der Benutzer die Bearbeitung dieser Zeile anfordert. Die Factory ist eine Callback-Methode, die mit einem Objekt vom Typ GraphicsBase.RowEditorParameter aufgerufen wird. Dieses Parameterobjekt enthält einige Felder, die beim Erstellen der Editor-Steuerelemente nützlich sein können, sowie eine Methode zum Beenden der Zeilenbearbeitung.

Methode Beschreibung
GraphicsBase getGraphics(); Gibt eine Referenz auf die Grafikansicht zurück, in der die Bearbeitung stattfindet.
R getRow(); Gibt die Zeile zurück, für die der Zeileneditor erstellt wird.
void stopEditing(); Eine Komfortmethode für die Steuerelemente des Zeileneditors, mit der signalisiert werden kann, dass der Benutzer die Bearbeitung der Zeile abgeschlossen hat. Diese Methode wird typischerweise über eine Art Schließen-Schaltfläche in der Editor-UI aufgerufen.

Eine Row-Editor-Factory könnte beispielsweise so aussehen:

public class MyRowEditorFactory implements Callback<RowEditorParameter<R>, Node> {

    public Node call(RowEditorParameter<R> param) {
        VBox box = new VBox();

        /*
         * Bind the text property of the textfield to the name
         * property of the row. This allows us to change the name
         * of the row.
         */
        TextField nameField = new TextField();
        Bindings.bindBidirectional(param.getRow().nameProperty(), 
                nameField.textProperty());

        /*
         * A close button to invoke the stopEditing() method
         * on the parameter object.
         */
        Button closeButton = new Button("Close");
        closeButton.setOnAction(evt -> param.stopEditing());
        box.getChildren().addAll(nameField, closeButton);

        /*
         * Return the vbox node.
         */
        return box;
    }
}

Zeileneditoren können wie folgt registriert werden:

GraphicsBase<?> graphics = ganttChart.getGraphics();
graphics.setRowEditorFactory(new MyRowEditorFactory());

Row-Controls-Factory

Um die Bearbeitung einer Zeile auszulösen, muss die Benutzeroberfläche entsprechende Steuerelemente bereitstellen. Das kann auf viele Arten geschehen, zum Beispiel über ein Kontextmenü auf einer Zeile. Eine andere Möglichkeit ist die eingebaute Unterstützung für sogenannte „row controls“. Diese Steuerelemente erscheinen bzw. verschwinden immer dann, wenn der Mauszeiger eine Zeile betritt oder verlässt. Sie werden von einer Callback-Implementierung erzeugt. Dieser Callback erhält ein Parameterobjekt vom Typ GraphicsBase.RowControlsParameter. Die folgende Tabelle listet die Felder dieses Typs auf.

Feld Beschreibung
graphics Die Grafikansicht, für die der Callback aufgerufen wird.
row Die Zeile, für die Steuerelemente erstellt werden.

Beispiel 1

Eine mögliche Implementierung dieses Callbacks kann wie folgt aussehen:

public class MyRowControlsFactory extends StackPane 
        implements Callback<RowControlsParameter, Node> {

    private Button button;

    public MyRowControlsFactory() {

        /*
         * Important: let mouse events pass through.
         */
        setMouseTransparent(true);
        button = new Button("Press Me");
        getChildren().add(button);
    }

    /*
     * Reuse the button. Simply exchange the action that will
     * happen when the user presses on it.
     */
    public Node call(RowControlsParameter param) {
       button.setOnAction(evt -> 
            System.out.println("Pressed on row " + 
                    param.getRow().getName());
        return this;
    }

Bitte beachten Sie, dass diese Factory ein Node-Objekt ist und sich bei jedem Aufruf der Methode call() selbst zurückgibt. Bei jedem Aufruf wird lediglich die Aktion der Schaltfläche ersetzt. Das ist sinnvoll, da Row Controls immer nur für eine Zeile gleichzeitig angezeigt werden (im Gegensatz zu Zeileneditoren, von denen mehrere gleichzeitig verwendet werden können).

Der Callback kann wie folgt registriert werden:

GraphicsBase<?> graphics = ganttChart.getGraphics();
graphics.setRowControlsFactory(new MyRowControlsFactory());

Beispiel 2

Im Folgenden sehen Sie den Code der Klasse RowControls aus dem FlexGanttFX-Projekt „Extras“. Sie fügt der Zeile eine einfache Schaltfläche „Edit“ hinzu. Beim Anklicken werden die Steuerelemente des Zeileneditors auf der Rückseite der Zeile angezeigt.

package com.flexganttfx.extras;


import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.util.Callback;
import com.flexganttfx.model.Row;
import com.flexganttfx.view.graphics.GraphicsBase.RowControlsParameter;

public class RowControls<R extends Row<?, ?, ?>> extends HBox implements
        Callback<RowControlsParameter<R>, Node> {


    private Button editButton;

    public RowControls() {
        setPickOnBounds(false);
        setMinSize(0, 0);
        setAlignment(Pos.TOP_RIGHT);
        setFillHeight(true);
        editButton = new Button("EDIT");
        editButton.getStyleClass().add("row-controls-button");
        getChildren().add(editButton);
    }

    @Override
    public Node call(RowControlsParameter<R> param) {
        editButton.setOnAction(evt -> param.getGraphics().startRowEditing(
                param.getRow()));
        return this;
    }
}

Das passende CSS für die Schaltfläche ist wie folgt definiert:

/*
 * Row controls button are shown when the mouse hovers over a row that can be
 * edited (flipped around).
 */
.row-controls-button {
  -fx-padding: 5 9 7 7;
  -fx-background-insets: 0 4 2 2;
  -fx-background-color: rgba(0,0,0,.5);
  -fx-background-radius: 0;
  -fx-text-fill: white;
  -fx-font-size: 8;
  -fx-font-weight: bold;
}

.row-controls-button:hover,
.row-controls-button:focused {
  -fx-padding: 5 9 7 7;
  -fx-background-insets: 0 4 2 2;
  -fx-background-color: rgba(0,0,0,.6);
  -fx-background-radius: 0;
  -fx-text-fill: white;
  -fx-font-size: 8;
  -fx-font-weight: bold;
}

.row-controls-button:pressed,
.row-controls-button:selected {
  -fx-background-color: rgba(0,0,0,.7);
  -fx-background-radius: 0;
}

Rendering von Aktivitäten

Die Grafikansicht verwendet die Canvas-API von JavaFX. Der Grund dafür ist die komplexe Natur eines Gantt-Diagramms und die großen Datenmengen, die darin häufig auftreten. Große Mengen von Aktivitäten direkt in ein Bitmap zu rendern ist wesentlich schneller, als den Scene Graph fortlaufend zu aktualisieren und CSS-Styling erneut anzuwenden. FlexGanttFX implementiert eine austauschbare Renderer-Architektur, bei der Renderer-Instanzen Aktivitätstypen zugeordnet werden können – sehr ähnlich zu dem Ansatz von Swing.

Der folgende Code zeigt, wie ein benutzerdefinierter Renderer für einen bestimmten Aktivitätstyp „Flight“ registriert wird. Beachten Sie bitte, dass die Grafikansicht Aktivitäten in verschiedenen Layouts darstellen kann; daher muss auch der Layout-Typ an die Methode übergeben werden.

GraphicsBase<?> graphics = ganttChart.getGraphics();
graphics.setActivityRenderer(
                Flight.class, 
                GanttLayout.class, 
                new FlightRenderer(graphics));

Üblicherweise übergeben wir dem Renderer beim Erzeugen auch die Grafikansicht. Das ist erforderlich, da Renderer ein erneutes Zeichnen der Grafik auslösen, wenn sich eine ihrer Eigenschaften ändert. Das unterscheidet sich deutlich vom Swing-Ansatz. Daraus folgt auch, dass Renderer-Instanzen nur für genau eine Grafikansicht verwendet werden sollten – nämlich für die, die an ihren Konstruktor übergeben wurde.

Für die Arbeit mit Renderern werden in GraphicsBase die folgenden Methoden verwendet:

Methode Beschreibung
void setActivityRenderer(...); Registriert einen neuen Renderer für den angegebenen Aktivitäts- und Layout-Typ.
ActivityRenderer getActivityRenderer(...); Gibt einen Renderer für den angegebenen Aktivitäts- und Layout-Typ zurück.

Zeichnen

Aktivitätsrenderer besitzen einen einzigen Einstiegspunkt für das Zeichnen: eine Methode namens draw(). Diese Methode ist final und kann nicht überschrieben werden. Nach dem Aufruf delegiert sie an verschiedene geschützte Methoden, die das tatsächliche Zeichnen ausführen. Die Aufrufhierarchie sieht wie folgt aus:

  • public final draw() .... calls ...
    • protected ActivityBounds drawActivity()
    • protected void drawBackground()
    • protected void drawBorder()

Unterklassen können jede der drei geschützten Methoden überschreiben, um das Erscheinungsbild der Aktivität anzupassen. Alle drawXXX()-Methoden haben dieselben Argumente:

ActivityRef<A> activityRef,  // the activity to draw
Position position,           // agenda layout only (first, middle, last, only)
GraphicsContext gc,          // the graphics context into which to draw
double x,                    // the location of the start time of the activity
double y,                    // the y coordinate (0 when drawn on row or line location)
double w,                    // end time location minus start time location
double h,                    // row or line height
boolean selected,            // is activity currently selected?
boolean hover,               // is mouse cursor currently hovering over it?
boolean highlighted,         // is activity currently blinking?
boolean pressed)             // is user currently pressing on it?

Standardrenderer

Die folgende Tabelle listet die verschiedenen Aktivitätsrenderer auf, die standardmäßig bereitgestellt werden.

Renderer-Klasse Beschreibung
ActivityRenderer Der grundlegendste Renderer für Aktivitäten. Zeichnet an der Position der Aktivität ein gefülltes Rechteck. Alle Standardrenderer sind Unterklassen dieses Typs.
ActivityBarRenderer Zeichnet einen Balken, anstatt den gesamten Bereich zu füllen. Die Höhe des Balkens kann festgelegt werden. Unterstützt außerdem Text an verschiedenen Positionen innerhalb und außerhalb des Balkens.
ChartActivityRenderer Zeichnet eine ChartActivity vertikal abhängig von ihrem Chart-Wert.
CompletableActivityRenderer Unterklasse des Balken-Renderers. Zeichnet eine CompletableActivity als Balken, bei dem ein Teil des Hintergrunds mit einer anderen Farbe gefüllt ist. Die Größe dieses Bereichs hängt vom Wert „percentage complete“ der Aktivität ab.

ActivityBounds

Jeder Aktivitätsrenderer ist dafür verantwortlich, nach dem Zeichnen der Aktivität eine Instanz von ActivityBounds zurückzugeben. Diese Bounds sind ein zentraler Bestandteil des Frameworks, und viele Operationen funktionieren nur korrekt, wenn sie gültig sind. Sie werden für die Bearbeitung von Aktivitäten, die Hit-Point-Detection, das Layouten von Verknüpfungen, Kontextmenüs usw. verwendet. Die folgende Tabelle listet die Attribute der Klasse ActivityBounds auf.

Attribut Beschreibung
activity Die Aktivität, zu der diese Bounds gehören.
activityRef Eine Aktivitätsreferenz, die auf die Aktivität verweist.
layer Der Layer, auf dem die Aktivität gezeichnet wurde.
layout Das Layout, das beim Zeichnen der Aktivität verwendet wurde.
lineIndex Der Index der Linie, auf der sich die Aktivität befindet (-1, wenn sich die Aktivität auf der Zeile und nicht auf einer Linie befindet).
position Die Position der Bounds, wenn die Aktivität im Agenda-Layout gezeichnet wurde (first, middle, layout). Dies ist erforderlich, weil dieselbe Aktivität in mehreren Teilstücken über mehrere Tage gerendert werden kann.
row Die Zeile, in der die Aktivität gezeichnet wurde.

Bitte ignorieren Sie die Attribute overlapColumn, overlapCount und die Liste overlapBounds. Sie werden intern für Operationen im Zusammenhang mit dem Agenda-Layout verwendet.

Eigenschaften

Alle Renderer definieren mehrere Eigenschaften, mit denen ihr Erscheinungsbild angepasst werden kann. Viele dieser Eigenschaften hängen vom „Pseudo-State“ der Aktivität ab: hover, pressed, selected, highlighted. Damit sich die richtige Farbe zum richtigen Zeitpunkt einfacher bestimmen lässt, stehen mehrere Komfortmethoden zur Verfügung:

  • Renderer: protected Paint getFill(boolean selected, boolean hover, boolean highlighted, boolean pressed);
  • Gibt die für den Hintergrund der Aktivität zu verwendende Farbe in Abhängigkeit von den übergebenen Pseudo-States zurück.
  • ActivityRenderer: protected Paint getStroke(boolean selected, boolean hover, boolean highlighted, boolean pressed);
  • Gibt die für den Rand der Aktivität zu verwendende Farbe in Abhängigkeit von den übergebenen Pseudo-States zurück.
  • ActivityBarRenderer: protected Paint getTextFill(boolean selected, boolean hover, boolean highlighted, boolean pressed);
  • Gibt die für Text zu verwendende Farbe in Abhängigkeit von den übergebenen Pseudo-States zurück.

Zeilen-Rendering

Der System-Layer RowLayer unterstützt austauschbare Renderer, um den Hintergrund jeder Zeile abhängig vom Zeilentyp anzupassen. Im Tutorial haben wir gesehen, dass es Aircraft-Zeilen und Crew-Zeilen geben kann. Zur besseren Unterscheidung könnten diese beiden Zeilentypen unterschiedliche Hintergrundfarben besitzen. Genau das lässt sich mit einem Zeilenrenderer umsetzen.

Zeilenrenderer

Alle Zeilenrenderer müssen von RowRenderer erben. Diese Klasse definiert eine finale öffentliche Methode namens draw(), die vom Framework aufgerufen wird. Anschließend ruft sie die geschützte Methode drawRow() auf, die von Unterklassen überschrieben werden kann. Eine mögliche Implementierung könnte so aussehen:

public class AircraftRowRenderer extends RowRenderer<Aircraft> {

    public AircraftRowRenderer(GraphicsBase<?> graphics) {
        super(graphics, "Aircraft Row Renderer");
    }

    protected void drawRow(Aircraft row,
                           GraphicsContext gc,
                           double w,
                           double h,
                           boolean selected,
                           boolean hover,
                           boolean highlighted,
                           boolean pressed) {
        gc.setFill(Color.ORANGE);
        gc.fillRect(0, 0, w, h);       
    }
}

Dieser Renderer kann nun wie folgt beim RowLayer registriert werden:

GraphicsBase<?> graphics = ganttChart.getGraphics();
graphics.getSystemLayer(RowLayer.class).setRowRenderer(
                    Aircraft.class, new AircraftRowRenderer());

Kontextmenü

Es gibt zwei Möglichkeiten, ein Kontextmenü bei der Grafikansicht zu registrieren: auf dem Standardweg über GraphicsBase.setContextMenu(ContextMenu) oder durch das Registrieren eines Kontextmenü-Callbacks über GraphicsBase.setContextMenuCallback(). Der Vorteil der zweiten Variante besteht darin, dass an die Callback-Methode ein Parameterobjekt vom Typ ContextMenuParameter übergeben wird. Dieses Parameterobjekt enthält die wichtigsten Parameter, die die meisten Kontextmenüs benötigen, um dem Benutzer Aktionen in der Grafikansicht anzubieten.

Bitte beachten Sie, dass ein Kontextmenü-Callback Vorrang vor einem Standard-Kontextmenü hat.

Codebeispiel

Der folgende Ausschnitt zeigt ein Beispiel für die Implementierung eines Kontextmenü-Callbacks. Hier fügen wir einfach für jede Aktivität, die an der Mausposition gefunden wurde, an der der Benutzer das Kontextmenü angefordert hat, einen Menüeintrag hinzu.

GraphicsBase<?> graphics = ganttChart.getGraphics();
graphics.setContextMenuCallback(param -> {
    ContextMenu menu = new ContextMenu();
    for (ActivityRef<?> ref : param.getActivities()) {
        Activity activiy = ref.getActivity();
        MenuItem item = new MenuItem("Move " + activity.getName());
        item.setOnAction(evt -> moveActivity(activity);
        menu.getItems().add(item);
    }
    return menu;
});

Zeilen-Header

Jede Zeile im Grafikbereich kann ihren eigenen Header anzeigen, der auf der linken Seite der Zeile dargestellt wird. Dieser Header bleibt immer an seiner Position und scrollt nicht mit, wenn sich der sichtbare Zeitbereich ändert (z. B. wenn der Benutzer nach links oder rechts scrollt).

Zeilen-Header

Der mit FlexGanttFX ausgelieferte Standard-Zeilen-Header kann Skalen anzeigen, wenn die Anwendung Chart- oder Agenda-Layouts verwendet. Die Implementierung des Standard-Headers befindet sich in der Klasse ScaleRowHeader.

Benutzerdefinierte Zeilen-Header können durch Ableiten von GraphicsBase.RowHeader erstellt werden. Die Eigenschaft item wird immer dann aktualisiert, wenn sich die Zeile ändert, für die der Header verwendet wird. Genau wie List- oder Table-Cells ist die Klasse für den Zeilen-Header eine Erweiterung von Label. Das bedeutet, dass Sie ihren Inhalt vollständig ersetzen können, indem Sie setGraphics(Node node) und setContentDisplay(ContentDisplay.GRAPHICS_ONLY) aufrufen.

Siehe auch:

public void setRowHeaderFactory(Callback<GraphicsBase<R>, RowHeader<R>> factory);
public void setShowRowHeaders(boolean showRowHeaders);

Hinweis: In älteren Versionen von FlexGanttFX befand sich die Funktionalität zum Anzeigen eines Zeilen-Headers bzw. von Skalen in der Klasse ScaleLayer. Mit der Einführung des Canvas-Buffer-Konzepts und des Scrollens über die Eigenschaft translateX wurde eine Änderung notwendig, da die Skalen abhängig vom Wert von translateX an unterschiedlichsten Positionen angezeigt wurden.