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 den Datentyp des gedraggten Controls.
  • 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.
    • Allow (default) erstellt eine Erlauben-Regel.
    • 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 bekommen und auch diese können ggf. auch weitere Controls bekommen, sofern es passende Erlauben-Regeln gibt.
    • DenyCascade lässt auch unter dem verbotenen Element keine weiteren Children zu, und zwar rekursiv für alle eventuellen weiteren Children.
      Das Hinzufügen von Controls ist dann also für den kompletten Teilbaum ab dem Source-Control verboten, unabhängig von der Art der Children.
    • Default Diese Regel entfernt eine Regel. Das ist nur beim überschreiben von Core-Regeln in Projekten sinnvoll. Das Regelwerk wird dann so behandelt, als ob es diese Regel niemals gegeben hätte. Falls für das betreffende Control noch allgemeinere Regeln existieren, gelten nun diese.
  • MinLevel und MaxLevel erlauben den SourceType nur auf bestimmten Ebenen (Zählweise siehe vorheriger Abschnitt)

Generell gilt:

  • Grundsätzlich ist alles erst einmal verboten, solange es keine Erlauben-Regel (DnDBehavior.Allow) gibt.
  • Die Kombination SourceType und TargetType ist pro configurationType / configurationSubType Kombination eindeutig. Wenn es mehrere Regeln dazu gibt, wird die zuletzt gelesene andere überschreiben. Es ist garantiert, dass Projekte gegen BA gewinnen.
  • Sowohl SourceType als auch TargetType können entweder direkte Datentypen des Controls, einer ihrer Basisdatentypen oder auch ein Interface sein.
    Regeln für konkretere Datentypen gewinnen gegenüber Regeln für Basisklassen, und die gewinnen gegen Regeln für Interfaces.
    Man kann also z.B. auf eine Basisklasse oder ein Interface eine Erlauben-Regel anlegen und dann einen konkreteren Typen wieder verbieten und sogar dann für einen exakten Typ wieder erlauben.
  • Wenn eine Regel für SourceType spezieller und für TargetType allgemeiner ist als eine andere Regel, gewinnt erstere.

MinLevel und MaxLevel

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.

Beispiel

Die eigentliche Definition sieht dann z.B. so aus, für Navigation

  1. 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)]
  2. NavigationGroupControl kann auch wieder ein NavigationGroupControl beinhalten (Ebenenbeschränkung von 2) bleibt beibehalten
    [assembly: DnDRule(typeof(NavigationGroupControl), typeof(NavigationGroupControl), EnumConfigurationType.NavigationConfigurationGuid)]
  3. 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)]
  4. 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)]
  5. Aufklappelement darf Kind von der Navigationsgruppe sein ab Ebene 4
    [assembly: DnDRule(typeof(NavigationGroupControl), typeof(DropDownAction), EnumConfigurationType.NavigationConfigurationGuid, minLevel: 4)]
  6. Das Aufklappelement darf die gleichen Elemente beinhalten, wie in 3) 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 Regeln 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 oder eine Regel komplett entfernen.

Es könnte aber auch vorkommen, dass ein Projekt die Regeln umfangreicher ändern will, ohne diese überschreiben zu müssen, also ggf. Regeln komplett umbauen oder eine eigene Prioritätsreihenfolge implementieren.
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, List<DnDRuleAttribute> dragAndDropRules);

Hier kann man die übergebene Liste frei anpassen. Dies sollte nur in Ausnahmefälle getan werden. In der Regel sollten die Attribute verwendet werden.

Das übergebene Liste enthält alle Regeln, die ohne dieses Interface aktiv würden in der Reihenfolge ihrer Priorisierung, 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

Client debuggen

Hat alles nichts geholfen, kann man versuchen, im Browser beim Drag and Drop zu debuggen. Dazu im F12 in der Function InitDAD in die anonyme Function für accepts einem Breakpoint setzen.

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.