Drag & Drop Regeln definieren, welche Steuerelemente sich im Designer wohin ziehen lassen.
Am Bespiel der Ribbon bar Navigation sieht man in der Toolbox Steuerelemente, die sich in den Desgin-Bereich ziehen lassen. Bei allen Navigationen (außer Anwendungsaktionen) ist z.B. erforderlich, dass sich alle allgemeinen Controls innerhalb einer doppelt geschachtelten Navigationsgruppe befinden.
Welche Regeln gelten für die Navigation?
- Der Hauptknoten kann Navigationsgruppen und erweiterte Navigationsgruppen enthalten
- Navigationsgruppen dürfen so auch wieder Navigationsgruppen enthalten
- Innerhalb einer Navigationsgruppe dürfen alle möglichen Arten von Controls sein, aber nur wenn diese mindestens auf der 4. Ebene liegen
=> Zählweise ist hier:- Ebene 1: Hauptebene
- Ebene 2: Navigationsgruppe
- Ebene 3: Navigationsgruppe
- Ebene 4: … alle möglichen anderen Controls
- Eines dieser Controls aus Ebene 4. kann das Aufklappelement sein
- Dieses Element darf wieder alle möglichen anderen Controls haben, aber nicht wieder ein Aufklappelement
- Außerdem gibt es noch die Erweiterte Navigation, diese darf nichts enthalten
Definition
Üblicherweise werden Regeln in einer eigenen Datei über Assembly Attribute definiert. In BA stehen die Regeln für die Navigation in der “NavigationDndRules”–Datei.
Assembly Attribute werden niemals in eine Klasse oder in einen Namespace geschrieben. Der Vorteil gegenüber normalen Attributen ist, dass sie zur Assembly (.dll) gehören und nicht zu einem bestimmten Datentyp. Daher können Projekte zu BA-Typen Regeln anlegen oder bestehende überschreiben.
Das hierfür verwendete Attribut ist DnDRuleAttribute
, welches folgende Parameter hat:
public DnDRuleAttribute(Type targetType, Type sourceType, string configurationType, string configurationSubType = null, DndBehavior dndBehavior = DndBehavior.Allow, int minLevel = 0, int maxLevel = 0)
TargetType
definiert den Datentyp des Controls, auf das ein Control (Target) gezogen (gedraggt) wird. Das gedraggte Control wird dann Kind des Targets.SourceType
definiert das gedraggte ControlConfigurationType
definiert, für welchen Konfigurationstypen diese Regel erstellt wird. Die dort zu hinterlegenden Typen sind inEnumConfigurationType
zu finden.ConfigurationSubType
ist für Konfigurationen notwendig, bei denen unterschiedliche Regelwerke gebraucht werden. Aktuell ist das nur bei derNavigationConfiguration
für dieApplicationActions
benötigt werden.DndBehavior
definiert, ob es sich um eine Erlauben- oder Verbieten-Regel handelt. Grundsätzlich ist alles erst einmal verboten, solange es keine Erlauben-Regel (DnDBehavior.Allow
) gibt. Man kann jedoch z.B. auf eine Basisklasse oder ein Interface eine Erlauben-Regel anlegen und dann einen konkreten Typen wieder verbieten. Für “Verbieten” gibt es hier zwei Optionen:DenyCascade
lässt auch unter dem verbotenen Element keine weiteren Children zu,DenySource
verbietet nur genau das zu draggende Element. Falls dieses in der Zielstruktur aber schon vorhanden ist, kann es trotzdem noch die dazu vorgesehenen Children haben. Benötigt wurdeDenySource
bei den Datensatz-Validatoren, die zu festen, nicht veränderbaren Spalten hinterlegt sind.
MinLevel
undMaxLevel
erlauben denSourceType
nur auf bestimmten Ebenen (Zählweise siehe vorheriger Abschnitt)
Generell gilt:
Sowohl SourceType
als auch TargetType
können entweder direkte Datentypen des Controls, einer ihrer Basisdatentypen oder auch ein Interface sein. Die Kombination SourceType
und TargetType
ist pro configurationType
/ configurationSubType
Kombination eindeutig. Sprich, wenn es mehrere Regeln dazu gibt, wird die zuletzt gelesene andere überschreiben. Es ist garantiert, dass Projekte gegen BA gewinnen.
Außerdem gewinnen Deny-Regeln gegen Allow-Regeln.
Für MinLevel
und MaxLevel
gilt eine weitere Besonderheit: Auch wenn die Definition an jeder Regel möglich ist, kann es pro Steuerelement (in SourceType
) immer nur einen Min- und einen Maxwert je ConfigurationType
/ ConfigurationSubType
geben! MinLevel
/ MaxLevel
berechnen sich nach folgender Regel:
- Es werden alle in Frage kommenden Regeln betrachtet.
- Wie beschrieben gewinnen bei gleichem Source/Target die Projekt-Regeln.
- Aus dem sich ergebenden Regelset werden nur die Source-Typen beachtet, Target ist hier nicht relevant.
- Sollte es zu einem Source-Typ mehrere Regeln geben, gewinnt die Letzte, sofern sie für min oder max etwas anderes als 0 definiert hat. Definiert sie -1 sagt sie damit, dass es egal ist, aber es ist definiert [wird also von vorhergehenden nicht überschrieben]. Min und Max können aus unterschiedlichen Regeln stammen.
- Daraus resultiert dann wieder ein neues Regelset mit nur noch einem Eintrag aus Min und Max pro
SourceType
.
Ein Steuerelement ist immer in einer Klasse, in beliebig vielen Basisklassen, sowie beliebig vielen Interfaces. Für jedes Steuerelement wird zunächst geschaut, ob es in dem soeben berechneten Regelset einen passenden Eintrag zu genau der Klasse gibt. Ist dies nicht so, wird in den Basisklassen in aufsteigender Reihenfolge und danach in den Interfaces gesucht. Hierbei wird der erste Minwert ungleich 0, sowie der erste Maxwert ungleich 0 verwendet.
Die eigentliche Definition sieht dann z.B. so aus, für Navigation
- Hauptknoten mit Target null
[assembly: DnDRule(null, typeof(NavigationConfiguration), EnumConfigurationType.NavigationConfigurationGuid)]
- Darunter bis auf maximal Ebene 3 die Navigationsgruppe und einmal die Erweiterte Navigationsgruppe. Letztere erbt übrigens die Ebene, da
ExtendedNavigationGroupControl
vonNavigationGroupControl
erbt (ist hier aber nicht relevant)
[assembly: DnDRule(typeof(NavigationConfiguration), typeof(NavigationGroupControl), EnumConfigurationType.NavigationConfigurationGuid, minLevel: -1, maxLevel: 3)]
[assembly: DnDRule(typeof(NavigationConfiguration), typeof(ExtendedNavigationGroupControl), EnumConfigurationType.NavigationConfigurationGuid)]
NavigationGroupControl
kann auch wieder einNavigationGroupControl
beinhalten (Ebenenbeschränkung von 2) bleibt beibehalten
[assembly: DnDRule(typeof(NavigationGroupControl), typeof(NavigationGroupControl), EnumConfigurationType.NavigationConfigurationGuid)]
- Diversen Controls über ihre Basisklassen auf Ebene erlauben, Kinder von
NavigationGroupControl
zu sein
[assembly: DnDRule(typeof(NavigationGroupControl), typeof(ServerActionBase), EnumConfigurationType.NavigationConfigurationGuid, minLevel: 4)]
[assembly: DnDRule(typeof(NavigationGroupControl), typeof(ClientActionBase), EnumConfigurationType.NavigationConfigurationGuid, minLevel: 4)]
[assembly: DnDRule(typeof(NavigationGroupControl), typeof(ToggleButtonBase), EnumConfigurationType.NavigationConfigurationGuid, minLevel: 4)]
- Verbieten, dass die
ExtendedNavigation
ein Kind vonNavigation
wird. Das wäre ansonsten erlaubt, daExtendedNavigation
vonNavigation
erbt und diese das dürfte
[assembly: DnDRule(typeof(NavigationGroupControl), typeof(ExtendedNavigationGroupControl), EnumConfigurationType.NavigationConfigurationGuid, null, DndBehavior.DenyCascade)]
- Aufklappelement darf Kind von der Navigationsgruppe sein ab Ebene 4
[assembly: DnDRule(typeof(NavigationGroupControl), typeof(DropDownAction), EnumConfigurationType.NavigationConfigurationGuid, minLevel: 4)]
- Alles, was wir eben erlaubt haben, muss nun für die
ExtendedConf
wieder verboten werden aufgrund der Vererbung
[assembly: DnDRule(typeof(ExtendedNavigationGroupControl), typeof(NavigationGroupControl), EnumConfigurationType.NavigationConfigurationGuid, null, DndBehavior.DenyCascade)]
[assembly: DnDRule(typeof(ExtendedNavigationGroupControl), typeof(ServerActionBase), EnumConfigurationType.NavigationConfigurationGuid, null, DndBehavior.DenyCascade)]
[assembly: DnDRule(typeof(ExtendedNavigationGroupControl), typeof(ClientActionBase), EnumConfigurationType.NavigationConfigurationGuid, null, DndBehavior.DenyCascade)]
[assembly: DnDRule(typeof(ExtendedNavigationGroupControl), typeof(ToggleButtonBase), EnumConfigurationType.NavigationConfigurationGuid, null, DndBehavior.DenyCascade)]
[assembly: DnDRule(typeof(ExtendedNavigationGroupControl), typeof(DropDownAction), EnumConfigurationType.NavigationConfigurationGuid, null, DndBehavior.DenyCascade)]
- Das Aufklappelement darf die gleichen Elemente beinhalten, wie in 4) genannt
[assembly: DnDRule(typeof(DropDownAction), typeof(ServerActionBase), EnumConfigurationType.NavigationConfigurationGuid)]
[assembly: DnDRule(typeof(DropDownAction), typeof(ClientActionBase), EnumConfigurationType.NavigationConfigurationGuid)]
[assembly: DnDRule(typeof(DropDownAction), typeof(ToggleButtonBase), EnumConfigurationType.NavigationConfigurationGuid)]
Bestehende modifizieren
Definiert man in einem Projekt eine Regel, die dasselbe Source und Target hat, wie die Regeln aus BA, kann man diese verändern, z.B aus einer Allow Rule eine Deny Rule machen oder die Ebenen begrenzen.
Es könnte aber auch vorkommen, dass ein Projekt die Regeln umfangreicher ändern will, ohne diese überschreiben zu müssen, also ggf. Regeln komplett entfernen.
Möchte ein Projekt so etwas tun, muss es das Interface IDragAndDropRulesModifier
in einer beliebigen Klasse implementieren. Dort muss die Methode ModifyDnDRules
implementiert werden.
void ModifyDnDRules(Guid configurationType, Guid? configurationSubType, Dictionary<DndRuleKey, DndRuleOrderedAttribute> dragAndDropRules);
Hier kann man das übergebene Dictionary frei anpassen. Dies sollte nur in Ausnahmefälle getan werden. In der Regel müssen die Attribute verwendet werden.
Das übergebene Dictionary enthält alle Regeln, die ohne dieses Interface aktiv würden, also inklusive derer, die im Projekt dazu gekommen sind und ohne die, die überschrieben wurden.
Fehlersuche
http://…/bacrm/bamaintain/CreateRules
Über den Aufruf kann man sich ausgeben lassen, mit welchen Regeln das System arbeitet. Diese Regeln wurden dazu bereits in JSON Format übersetzt, damit sie der Client verstehen kann. Es kann z.B. gut sein, dass man einfach eine Regel definiert hat, die nicht erreichbar ist, weil Zwischenknoten fehlen.
Es empfiehlt sich, den Code über ein Notpad++ Plugin oder eine Webseite, wie https://jsonformatter.curiousconcept.com/ hübsch formatieren zu lassen.
Untersuchen per Browser auf dem jeweiligen Control
Hier kann man erkennen, welche Klasse, Basisklassen und Intefaces ein Control benutzt, sowie welche Werte als erlaubte Verschachtelungstiefen errechnet wurden.
Ein Control ist nicht immer das Control, von dem man annimmt, dass es das ist
Dieses Beispiel kommt aus der FormConfiguration
.
Angenommen es gibt folgende Regeln:
[assembly: DnDRule(typeof(GroupControl), typeof(TabContainerControl), EnumConfigurationType.FormConfigurationGuid)]
[assembly: DnDRule(typeof(GroupControl), typeof(IDefaultControl), EnumConfigurationType.FormConfigurationGuid)]
Und erreicht werden soll die Verschachtelung Gruppe > Gruppe > Tab-Container
Dann ist Gruppe > Gruppe bereits möglich, da GroupControl
auch IDefaultControl
implementiert.
Gruppe > Tab-Container ist ebenfalls möglich, da dies in der ersten Rule definiert wurde.
Gruppe > Gruppe > Tab-Container ist aber nicht möglich, weil nach den angegebenen Regeln die zweite Gruppe nicht als „Gruppe“ bekannt ist, sondern nur als IDefaultControl
. Und da kein zulässiges Mapping von IDefaultControl
nach TabContainerControl
existiert, ist die innere Verschachtelung nicht zulässig.
Möchte man dies doch erreichen, benötigt man zusätzlich die Rule
[assembly: DnDRule(typeof(GroupControl), typeof(GroupControl), EnumConfigurationType.FormConfigurationGuid)]
Damit kann die zweite Gruppe auch eine Gruppe sein und nicht nur ein IDefaultControl
und damit dann auch TabContainer
beinhalten.
Grundsätzlich werden Rekursionen automatisch erkannt, und es wäre auch eine Verschachtelung von 100 Gruppen im Designer möglich … wie auch immer das dann in der UI (Form) aussieht.
Client debuggen
Hat alles nichts geholfen, kann man versuchen, im Browser beim Drag and Drop zu debuggen. Dazu im F12 den Aufruf der Methode FindAllowedClasses
mit einem Breakpoint versehen.
Dann F12-Debugger wieder wegdrücken und mit dem Drag beginnen, bis man direkt über oder unter dem gewünschten Zielknoten ist. Maus nicht los lassen, während dem Dragvorgang erneut F12 drücken und die Maus dann auf den Zielknoten schieben.
Wenn bei Destination-Class-Tree an Position 0 das Control steht, dass man als Target hat, hat man die richtige Position erwischt und kann durchsteppen. Ansonsten wieder 2x F12 drücken und erneut versuchen.