Diagrammfläche
Knoten und Kanten wie im Ausführungsplan und im Datenbankdiagramm von SSMS: Strg+Rad zoomt um den Mauspunkt, die mittlere Taste oder Leertaste und Ziehen verschieben die Ansicht, Klick, Strg+Klick und Rahmen wählen, die Pfeile springen von Knoten zu Knoten. Im Ausführungsplan zeigt die Dicke der Pfeile die Zeilen (Hinweis beim Darüberfahren), im Datenbankdiagramm lassen sich die Tabellen ziehen (Raster 10, Strg+Pfeile).


KXAML
DiagrammSeite.kxaml
<StackPanel xmlns="https://ui.kuonen.org/kxaml"
xmlns:x="https://ui.kuonen.org/kxaml/x"
x:Class="Kuonen.UI.Galerie.Seiten.DiagrammVorfuehrung"
Spacing="8">
<StackPanel Orientation="Horizontal" Spacing="8">
<ComboBox x:Name="Beispiel" MinWidth="160" />
<Button x:Name="Anordnen" Content="Anordnen" />
<Button x:Name="Einpassen" Content="Einpassen" />
<TextBlock x:Name="Status" Text="Nichts gewählt." VerticalAlignment="Center" />
</StackPanel>
<DiagramSurface x:Name="Flaeche" Height="360" />
</StackPanel>DiagrammSeite.kxaml.cs
using Kuonen.UI;
namespace Kuonen.UI.Galerie.Seiten;
public sealed partial class DiagrammSeite
{
protected override Func<UIElement>? KxamlFabrik => () => new DiagrammVorfuehrung();
}
/// <summary>KXAML-Fassung der Seite Diagrammfläche: der Aufbau steht in DiagrammSeite.kxaml, hier das Verhalten.</summary>
public sealed partial class DiagrammVorfuehrung
{
public DiagrammVorfuehrung()
{
InitializeComponent();
DiagrammSeite.Verdrahte(Flaeche, Beispiel, Anordnen, Einpassen, Status);
}
}C#
DiagrammSeite.cs
using Kuonen.UI;
using Kuonen.UI.Controls;
using Kuonen.UI.Layout;
namespace Kuonen.UI.Galerie.Seiten;
/// <summary>Galerie-Seite: die Diagrammfläche (<see cref="DiagramSurface"/>) als Ausführungsplan und als Datenbankdiagramm.</summary>
public sealed partial class DiagrammSeite : GalerieSeite
{
public override string Titel => "Diagrammfläche";
public override string Beschreibung =>
"Knoten und Kanten wie im Ausführungsplan und im Datenbankdiagramm von SSMS: Strg+Rad zoomt um den Mauspunkt, die mittlere Taste oder "
+ "Leertaste und Ziehen verschieben die Ansicht, Klick, Strg+Klick und Rahmen wählen, die Pfeile springen von Knoten zu Knoten. Im "
+ "Ausführungsplan zeigt die Dicke der Pfeile die Zeilen (Hinweis beim Darüberfahren), im Datenbankdiagramm lassen sich die Tabellen ziehen "
+ "(Raster 10, Strg+Pfeile).";
public override string Stichworte =>
"DiagramSurface Diagramm Graph Knoten Kanten Ausführungsplan Execution Plan Datenbankdiagramm ER Fremdschlüssel Zoom Sugiyama Anordnung NodeEditor";
public override string Datei => nameof(DiagrammSeite);
/// <summary>Der Ausführungsplan: Operator, Elternoperator (-1 für die Wurzel) und Zeilen, die zum Elternoperator fliessen.</summary>
public static readonly (string Name, int Eltern, long Zeilen)[] Plan =
[
("SELECT", -1, 0),
("Sort", 0, 1200),
("Hash Match (Inner Join)", 1, 1200),
("Clustered Index Scan (Kunden)", 2, 500),
("Index Seek (Rechnungen)", 2, 4800),
];
/// <summary>Das Datenbankdiagramm: Tabelle, Spalten und Lage.</summary>
public static readonly (string Name, string[] Spalten, double X, double Y)[] Tabellen =
[
("Kunden", ["Id", "Name", "Ort"], 0, 0),
("Rechnungen", ["Id", "KundeId", "Datum"], 220, 0),
("Positionen", ["Id", "RechnungId", "ArtikelId", "Menge"], 440, 0),
("Artikel", ["Id", "Bezeichnung", "Preis"], 440, 190),
];
// Fremdschlüssel: von der Tabelle mit dem Schlüssel zur Tabelle, auf die er zeigt.
private static readonly (int Von, int Nach, string Name)[] Fremdschluessel = [(1, 0, "FK_Rechnungen_Kunden"), (2, 1, "FK_Positionen_Rechnungen"), (2, 3, "FK_Positionen_Artikel")];
internal static readonly string[] Beispiele = ["Ausführungsplan", "Datenbankdiagramm"];
/// <summary>Das Verhalten beider Fassungen: Beispiel wählen, anordnen, einpassen, die Auswahl melden.</summary>
internal static void Verdrahte(DiagramSurface flaeche, ComboBox beispiel, Button anordnen, Button einpassen, TextBlock status)
{
beispiel.ItemsSource = Beispiele;
beispiel.SelectionChanged += (_, _) => Zeige(flaeche, beispiel.SelectedIndex);
beispiel.SelectedIndex = 0;
Zeige(flaeche, 0);
anordnen.Click += (_, _) => Ordne(flaeche, beispiel.SelectedIndex);
einpassen.Click += (_, _) => flaeche.ZoomToFit();
flaeche.SelectionChanged += (_, _) => status.Text = flaeche.SelectedNodes.Count == 0
? "Nichts gewählt."
: "Gewählt: " + string.Join(", ", flaeche.SelectedNodes.Select(k => k.Automation.Name));
}
private static void Zeige(DiagramSurface f, int beispiel)
{
f.Edges.Clear();
f.Nodes.Clear();
f.Zoom = 1;
if (beispiel == 1) Datenbankdiagramm(f);
else Ausfuehrungsplan(f);
Ordne(f, beispiel, nurPlan: true);
}
private static void Ordne(DiagramSurface f, int beispiel, bool nurPlan = false)
{
if (beispiel == 1 && nurPlan) return; // die Tabellen behalten ihre Lage
DiagramLayout.Layered(f, beispiel == 1 ? DiagramLayoutDirection.LeftToRight : DiagramLayoutDirection.RightToLeft, beispiel == 1 ? 80 : 50);
}
private static void Ausfuehrungsplan(DiagramSurface f)
{
f.CanMoveNodes = false;
f.GridSize = 0;
var knoten = Plan.Select(p => new DiagramNode { Content = p.Name }).ToArray();
foreach (var k in knoten) f.Nodes.Add(k);
for (int i = 0; i < Plan.Length; i++)
{
if (Plan[i].Eltern < 0) continue;
long zeilen = Plan[i].Zeilen;
f.Edges.Add(new DiagramEdge
{
Source = knoten[i],
Target = knoten[Plan[i].Eltern],
Thickness = Math.Clamp(1 + Math.Log10(Math.Max(1, zeilen)), 1, 8),
ToolTip = $"{zeilen} Zeilen",
});
}
}
private static void Datenbankdiagramm(DiagramSurface f)
{
f.CanMoveNodes = true;
f.GridSize = 10;
var knoten = Tabellen.Select(t =>
{
var inhalt = new StackPanel { Spacing = 2, Children = { new TextBlock { Text = t.Name, FontWeight = 600 } } };
foreach (var spalte in t.Spalten) inhalt.Children.Add(new TextBlock { Text = spalte });
var k = new DiagramNode { X = t.X, Y = t.Y, Content = inhalt, MinWidth = 120 };
k.Automation.Name = t.Name;
return k;
}).ToArray();
foreach (var k in knoten) f.Nodes.Add(k);
foreach (var (von, nach, name) in Fremdschluessel)
f.Edges.Add(new DiagramEdge { Source = knoten[von], Target = knoten[nach], Routing = EdgeRouting.Orthogonal, ToolTip = name });
}
public override UIElement Baue()
{
var flaeche = new DiagramSurface { Height = 360 };
var beispiel = new ComboBox { MinWidth = 160 };
var anordnen = new Button { Content = "Anordnen" };
var einpassen = new Button { Content = "Einpassen" };
var status = new TextBlock { Text = "Nichts gewählt.", VerticalAlignment = VerticalAlignment.Center };
Verdrahte(flaeche, beispiel, anordnen, einpassen, status);
var leiste = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Children = { beispiel, anordnen, einpassen, status } };
return new StackPanel { Spacing = 8, Children = { leiste, flaeche } };
}
}