„Renderer“ sind C#-Klassen, die sich um die visuelle Repräsentation eines Steuerelements bei der Verwendung in einer Maske kümmern. Sie definieren, ob der Benutzer beispielsweise ein Texteingabefeld oder ein Listenfeld sieht und auch, wie die vom Browser gesendeten Daten aufbereitet werden müssen, damit sie in das der Maske zugehörige Datenmodell gespeichert werden können.
Je nach Funktionsumfang muss die Basisklasse einer der folgenden sein
ControlDefaultRendererBase
: Renderer OHNE Unterteilung in Read- und EditMode-Methoden und OHNE Bind-UnterstützungControlDefaultBindableRendererBase
: Renderer OHNE Unterteilung in Read- und EditMode aber MIT Bind-UnterstützungControlDefaultReadEditRendererBase
: Renderer MIT Unterteilung in Read- und EditMode-Methoden und OHNE Bind-UnterstützungControlDefaultReadEditBindableRendererBase
: Renderer MIT Unterteilung in Read- und EditMode-Methoden und MIT Bind-Unterstützung
Die Zuordnung eines Renderers zu einem bestimmten Steuerelement erfolgt über ein entsprechendes Attribut:
[FormControlRenderer(typeof(AttachmentControl))]
Sie können durch die Angabe dieses Attributs in einem Projekt-Modul auch existierende Renderer übersteuern, indem Sie einen existierenden Renderer einfach neu definieren. Eine komplette neu Implementierung bespielsweise des TextEditControl
ist zwar möglich aber nicht zu empfehlen.
Der Renderer beinhaltet im einfachsten falls nur die Methode Render()
alternativ die beiden Methoden EditMode()
und ReadMode()
. Optional ist die Implementierung von Bind()
.
Die Render-Methoden sorgen für die korrekte Ansteuerung und Verwendung der visuellen Komponenten (bspw. DevExpress) für den entsprechenden Modus. Von den Möglichkeiten unterscheiden sich diese Methoden nicht. Der Entwickler ist dafür Verantwortlich in dem jeweiligen Modus, die entsprechenden Elemente für die UI zu generieren.
Die Bind-Methode sorgt dafür, dass die vom Browser gelieferten Daten korrekt in das Datenmodell der Maske geschrieben werden. Die Standardbehandlung von Bind()
überträgt den Wert eines angezeigten Steuerelements in ein Feld. Dies kann unter drei Bedingungen automatisch durchgeführt werden.
ViewState.ViewStateData
muss unter dem SchlüsselBindKeys.ModelProperty
den serverseitig verwendeten Feldnamen (Property-Namen auf demBindObject
) enhalten. Ist das Steuerlement einDataControlBase
, so wird dies vom Framework automatisch gemacht.ViewState.ViewStateData
muss unter dem SchlüsselBindKeys.ClientProperty
den clientseitig verwendeten Feldnamen (inkl.RenderParameter.NamePrefix
) enthalten.- Die ausgelesenen Daten, können ohne Typkonvertierung direkt in das Model geschrieben werden.
In der Bind-Methode hat man keinen Zugriff auf das Steuerelement selbst, daher müssen alle notwendigen Informationen für das Binden in ViewState.ViewStateData
geschrieben werden. Dies können nur serialisierbare Daten sein.
Beispiel
Werfen wir zunächst einen Blick auf das Gerüst der Klasse, verwenden sie bitte auch hier einen Ihrem Projekt entsprechenden Namespace.
namespace BA.Core.Configuration.Form.Renderers
{
[FormControlRenderer(typeof(ValueAndAliasControl))]
public class ValueAndAliasRenderer : ControlDefaultReadEditBindableRendererBase
{
(… Klasseninhalt …)
}
}
Die Klasse selbst erbt vom ControlDefaultReadEditBindableRendererBase
und ist über das Attribut FormControlRenderer
als Renderer für Steuerelemente des Typs ValueAndAliasControl
gekennzeichnet.
Der „Klasseninhalt“ besteht folglich aus den Methoden EditMode()
, ReadMode()
und in unserem Beispiel auch Bind()
.
EditMode
Die Feldnamen mit den Suffixen „_Value“ und „_Alias“ werden von der EditMode()
Methode festgelegt und in ViewStateData
für die Bind()
Methode hinterlegt. Die Methode verwendet eine private Hilfsmethode (CreateTextBoxItem
), um das eigentliche DevExpress-Texteingabesteuerelement zu erstellen und zu parametrisieren. Auf diese Methode gehen wir noch im Anschluss ein. Die hier dargestellte oberste Ebene der Methode erstellt zwei Dev Express-Felder, deren Feldnamen jeweils dem Feldnamen des Datenmodells mit unterschiedlichen Suffixen entsprechen. Ein eventuell im Datenmodell verfügbarer Wert wird ausgelesen und der CreateControl-Methode als Vorbelegung übergeben. Nähere Beschreibungen bieten auch die Kommentarzeilen im Beispielquellcode.
public override void EditMode()
{
ValueAndAliasControl valueAndAliasControl = (ValueAndAliasControl)RenderParameters.Control;
// Feldnamen definieren (Datenmodell)
string modelFieldName = valueAndAliasControl.OrmFieldName;
ViewState.ViewStateData.AddOrUpdate(BindKeys.ModelProperty, modelFieldName);
// Feldnamen definieren für die Felder im Browser definieren
string uiValueFieldName = modelFieldName + "_Value";
ViewState.ViewStateData.Add("ValueField", uiValueFieldName);
string uiAliasFieldName = modelFieldName + "_Alias";
ViewState.ViewStateData.Add("AliasField", uiAliasFieldName);
// zu verwendendes Trennzeichen
char delimiter = valueAndAliasControl.Delimiter.ToCharArray()[0];
ViewState.ViewStateData.Add("Delimiter", delimiter);
// Inhalt des gespeicherten Feldes holen und für die Felder vorbereiten
string fieldContent = (string)ConvertFromModelPropertyType(GetValueFromModel());
string[] splt = fieldContent?.Split(delimiter) ?? (new string[] { "", "" });
string fieldContentPart1 = splt?[0] ?? "";
string fieldContentPart2 = splt?.Length > 1 ? splt[1] : "";
// Erstes Feld erzeugen und als Objekt hinzufügen
MVCxFormLayoutItem mvcxItem = CreateTextBoxItem(valueAndAliasControl, uiValueFieldName, RenderParameters.RenderingContext.FormModel, "Wert", fieldContentPart1);
RenderParameters.MvcxControls.Add(mvcxItem);
// Zweites Feld erzeugen und als Objekt hinzufügen
mvcxItem = CreateTextBoxItem(valueAndAliasControl, uiAliasFieldName, RenderParameters.RenderingContext.FormModel, "Alias", fieldContentPart2);
RenderParameters.MvcxControls.Add(mvcxItem);
}
Konvention: Abruf der Werte in den Render-Methoden
Der Zugriff auf die Werte, muss in jedem Fall über die Deskriptoren erfolgen.
Für den Feldnamen des Steuerelementes auf dem Bind-Objekt für die Basisklasse PropertyControlBase
und damit auch für DataSelection
und DataControlBase
wird garantiert, dass beim Abrufen von Daten zunächst GetValueFromModel()
und anschließend ConvertFromModelPropertyType()
aufgerufen wird.
Sollten weitere Werte aus dem Bind-Objekt benötigt werden, so werden diese ebenfalls immer unter Verwendung von PrepareBindProperty()
und GetValueFromModel()
abgerufen; der Aufruf auf ConvertFromModelPropertyType()
ist in diesem Fall optional und muss von der Methode unterstützt werden.
Hieraus folgt:
Direkte Zugriffe auf RenderParameters.BindData
sind immer kritisch zu betrachten. Hier ist man nur richtig, wenn tatsächlich der top-level-Datensatz verwendet werden soll. Das gültige Bind-Objekt für das aktuell angesteuerte Property ist BindObject
! Was beispielsweise auch ein Teil-Datensatz sein kann.
Beispiel:
Der Feldname des Controls ist "Addresses[1].AddressType"
, BindData ist OrmCRMCompany.
Durch den Aufruf PrepareBindProperty("Addresses[1].AddressType")
passiert folgendes:
RenderParameters.BindData
bleibt wie es war eine Instanz vonOrmCRMCompany
BindObject
wird auf den angesteuerten Teildatensatz verschobenPreparedProperty
beinhaltet nun denPropertyDescriptor
des Feldes"AddressType"
für den Typen des Teildatensatzes
Bind()
Die Bind()
Methode ist dafür zuständig, dass der Wert, der vom Request des Browsers, in das Model übertragen wird.
Die Funktion der Bind()
Methode ist relativ einfach: Das Steuerelement zeigt in der Benutzeroberfläche zwei Felder, deren Namen aus ViewState.ViewStateData
ausgelesen werden. Diese beiden Felder müssen aus den vom Browser kommenden Daten ausgelesen, zusammengerechnet und in das Model geschrieben werden. Beim Zusammenrechnen wird das konfigurierte Trennzeichen des Steuerelementes verwendet, welches ebenfalls aus ViewState.ViewStateData
ausgelesen wird.
public override void Bind()
{
// Auslesen der Daten von dem Value
string bindValueFieldName_Value = (string)ViewState.ViewStateData["ValueField"];
string value = (string)ConvertToModelPropertyType(GetValueFromRequest(bindValueFieldName_Value) ?? "");
// Auslesen der Daten von dem Alias
string bindValueFieldName_Alias = (string)ViewState.ViewStateData["AliasField"];
string alias = (string)ConvertToModelPropertyType(GetValueFromRequest(bindValueFieldName_Alias) ?? "");
// Berechnung des Wertes für das Model
string delimiter = (string)ViewState.ViewStateData["Delimiter"];
string modelValue = value + delimiter + alias;
// Schreiben des Wertes in das Model
WriteValueToModel(modelValue);
}
Konvention: Binden von Werten
Um eigene Werte zu binden muss zunächst GetValueFromRequest()
, dann ConvertToModelPropertyType()
und schließlich WriteValueToModel()
aufgerufen werden.
CreateTextBoxItem
Die zuvor angesprochene Hilfsmethode CreateTextBoxItem()
erstellt unter Zuhilfenahme der übergebenen Werte ein DevExpress-Steuerelement zur Texteingabe. Auch hier wurden erklärende Kommentare in den Quellcode eingefügt, um nähere Erläuterungen zu bestimmten Code-Teilen zu bieten.
private MVCxFormLayoutItem CreateTextBoxItem(ValueAndAliasControl valueAndAliasControl,
string fieldName, DevExFormModel formModel, string label, string presetValue)
{
MVCxFormLayoutItem mvcxItem = new MVCxFormLayoutItem();
// Der Typ des neuen Controls => es ist eine "TextBox".
mvcxItem.NestedExtensionType = FormLayoutNestedExtensionItemType.TextBox;
// setzen verschiedener Einstellungen auf das MVCxItem, wie zB der Feldname, der im
// Webbrowser verwendet werden soll, die Beschriftung vor dem Feld, der Hilfetext, etc
mvcxItem.Name = fieldName;
mvcxItem.FieldName = fieldName;
//mvcxItem.HelpText = RenderingUtils.GetHelpText(Api.Text.Format(valueAndAliasControl.HelpText));
mvcxItem.ClientVisible = valueAndAliasControl.Visible;
mvcxItem.VerticalAlign = FormLayoutVerticalAlign.Top;
mvcxItem.Caption = label;
mvcxItem.CaptionSettings.Location = Api.Enum.EnumValueConverter.GetLabelPosition(valueAndAliasControl.LabelPosition);
mvcxItem.CaptionSettings.VerticalAlign = FormLayoutVerticalAlign.Top;
// Weitere bestimmte Einstellungen sind im sog. "Settings"-Unterobjekt verfügbar.
// Das betrifft zB die Editierbarkeit des Feldes, bestimmte Javascript-Events im Browser,
// die ausgeführt werden sollen, Abmessungen des Feldes, usw.
TextBoxSettings settings = (TextBoxSettings)mvcxItem.NestedExtensionSettings;
// Horizontale Ausrichtung des Controls
settings.ControlStyle.HorizontalAlign = HorizontalAlign.Left;
// Ist das Feld eventuell für Bearbeitung gesperrt?
if (!IsControlEditable())
{
settings.ClientEnabled = false;
ViewState.ShouldBind = false;
}
// Breite des Feldes
settings.Width = new Unit(valueAndAliasControl.Width, Api.Enum.EnumValueConverter.GetValueWidthType(valueAndAliasControl.WidthType));
// Feld kann als fehlerhaft markiert werden, falls die Validierung fehlschlägt
if (settings.Properties is EditProperties)
{
EditProperties props = (EditProperties)settings.Properties;
ApplicationSubConfigurationForm appFormSettings = Api.Config.ApplicationConfiguration().GetSubConfiguration<ApplicationSubConfigurationForm>();
props.ValidationSettings.Display = Api.Enum.EnumValueConverter.GetErrorFrameDisplayMode(appFormSettings.ErrorFrameDisplayMode);
props.ValidationSettings.ErrorTextPosition = Api.Enum.EnumValueConverter.GetErrorTextPosition(appFormSettings.ErrorTextPosition);
props.ValidationSettings.ErrorDisplayMode = Api.Enum.EnumValueConverter.GetErrorDisplayMode(appFormSettings.ErrorDisplayMode);
settings.ShowModelErrors = true;
}
// Der "placeholder", also der Text, der in einem leeren Feld angezeigt werden soll
settings.Properties.NullText = Api.Text.Format(valueAndAliasControl.NullText);
// vorbelegen des übergebenen im Datenmodell schon verfügbaren Wertes
if (!string.IsNullOrWhiteSpace(presetValue))
settings.PreRender = (sender, arg) =>
{
MVCxTextBox txt = sender as MVCxTextBox;
txt.Value = presetValue;
txt.DataBind();
};
// spezieller Fix für FireFox, damit Text in Feldern selektiert und kopiert werden
// können, die keine Eingabe erlauben
RenderingUtils.FixItemForFirefox(mvcxItem, settings, valueAndAliasControl.CssClass);
return mvcxItem;
}
Read
In der Read-Methode hat man prinzipiell die identischen Möglichkeiten wie in der Edit-Methode. An dieser Stelle sollte man nur Elemente verwenden, die das Ändern der Daten nicht ermöglichen. In diesem Fall wird ein “Label” zur Anzeige verwendet.
public override void ReadMode()
{
string namePrefix = RenderingUtils.GetUIReadyContextPrefix(RenderParameters.RenderingContext);
ValueAndAliasControl valueAndAliasControl = (ValueAndAliasControl)RenderParameters.Control;
// Feldnamen setzen
string modelFieldName = valueAndAliasControl.OrmFieldName;
// Alternativ kann ein MVCxFormLayoutItem auf diese Weise erstellt werden.
RenderParameters.MvcxControls.Add(item =>
{
// Typ des Items definieren und Settings casten
item.NestedExtensionType = FormLayoutNestedExtensionItemType.Label;
LabelSettings labelSettings = (LabelSettings)item.NestedExtensionSettings;
// Wert aus dem Datensatz auslesen und in als Inhalt festlegen
char delimiter = valueAndAliasControl.Delimiter.ToCharArray()[0];
string fieldContent = (string)ConvertFromModelPropertyType(GetValueFromModel());
fieldContent = fieldContent?.Split(delimiter)?[0] ?? "";
labelSettings.Text = fieldContent;
// Weieter Einstellungen festlegen
labelSettings.Name = namePrefix + valueAndAliasControl.ControlInternalName + RenderParameters.MvcxControls.Count;
labelSettings.Width = new Unit(valueAndAliasControl.Width, Api.Enum.EnumValueConverter.GetValueWidthType(valueAndAliasControl.WidthType));
labelSettings.ControlStyle.CssClass = valueAndAliasControl.CssClass;
item.HorizontalAlign = FormLayoutHorizontalAlign.Left;
item.Caption = valueAndAliasControl.Caption?.Translate() ?? string.Empty;
item.CaptionSettings.Location = Api.Enum.EnumValueConverter.GetLabelPosition(valueAndAliasControl.LabelPosition);
RenderingUtils.SetHelpText(valueAndAliasControl, item);
item.Visible = valueAndAliasControl.Visible;
// Label setzen, falls konfiguriert
if (string.IsNullOrWhiteSpace(item.Caption))
item.ShowCaption = DevExpress.Utils.DefaultBoolean.False;
});
}