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.

DnD Rules Navigation Example

Welche Regeln gelten für die Navigation?

  1. Der Hauptknoten kann Navigationsgruppen und erweiterte Navigationsgruppen enthalten
  2. Navigationsgruppen dürfen so auch wieder Navigationsgruppen enthalten
  3. 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:
    1. Ebene 1: Hauptebene
    2. Ebene 2: Navigationsgruppe
    3. Ebene 3: Navigationsgruppe
    4. Ebene 4: … alle möglichen anderen Controls
  4. Eines dieser Controls aus Ebene 4. kann das Aufklappelement sein
    1. Dieses Element darf wieder alle möglichen anderen Controls haben, aber nicht wieder ein Aufklappelement
  5. 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 Control
  • ConfigurationType definiert, für welchen Konfigurationstypen diese Regel erstellt wird. Die dort zu hinterlegenden Typen sind in EnumConfigurationType zu finden.
  • ConfigurationSubType ist für Konfigurationen notwendig, bei denen unterschiedliche Regelwerke gebraucht werden. Aktuell ist das nur bei der NavigationConfiguration für die ApplicationActions 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 wurde DenySource bei den Datensatz-Validatoren, die zu festen, nicht veränderbaren Spalten hinterlegt sind.
  • MinLevel und MaxLevel erlauben den SourceType 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

  1. Hauptknoten mit Target null
    [assembly: DnDRule(null, typeof(NavigationConfiguration), EnumConfigurationType.NavigationConfigurationGuid)]
  2. Darunter bis auf maximal Ebene 3 die Navigationsgruppe und einmal die Erweiterte Navigationsgruppe. Letztere erbt übrigens die Ebene, da ExtendedNavigationGroupControl von NavigationGroupControl 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)]
  3. NavigationGroupControl kann auch wieder ein NavigationGroupControl beinhalten (Ebenenbeschränkung von 2) bleibt beibehalten
    [assembly: DnDRule(typeof(NavigationGroupControl), typeof(NavigationGroupControl), EnumConfigurationType.NavigationConfigurationGuid)]
  4. 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)]
  5. Verbieten, dass die ExtendedNavigation ein Kind von Navigation wird. Das wäre ansonsten erlaubt, da ExtendedNavigation von Navigation erbt und diese das dürfte
    [assembly: DnDRule(typeof(NavigationGroupControl), typeof(ExtendedNavigationGroupControl), EnumConfigurationType.NavigationConfigurationGuid, null, DndBehavior.DenyCascade)]
  6. Aufklappelement darf Kind von der Navigationsgruppe sein ab Ebene 4
    [assembly: DnDRule(typeof(NavigationGroupControl), typeof(DropDownAction), EnumConfigurationType.NavigationConfigurationGuid, minLevel: 4)]
  7. 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)]
  8. 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.

DnD Rules Client

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.

DnD Rules Client Debugging