„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.

Ein Maskensteuerelement kann bis zu zwei Renderer-Klassen besitzen, die je nach Situation ausgewählt werden. Es besteht die Möglichkeit, eine Renderer-Klasse für die Anzeige des Steuerelementes in der Maske im Browser einzurichten und eine zweite, die verwendet wird, wenn die Maske, die das Steuerelement beinhaltet, ausgedruckt werden soll.

Renderer-Klassen müssen von ihrer jeweiligen Basisklasse ableiten. Für Anzeige-Renderer ist das die Klasse BA.Core.Configuration.Form.Renderers.ControlDefaultRendererBase und für Druck-Renderer BA.Core.Configuration.Form.Renderers.ControlPrintRendererBase.

Die Zuordnung eines Renderers zu einem bestimmten Steuerelement erfolgt über ein entsprechendes Attribut, welches der Renderer-Klasse zugeordnet wird. In diesem Attribut wird ebenfalls der Verwendungszweck des Renderers angegeben. Der Verwendungszweck ist optional, als Standardwert wird hier „Default“ verwendet, was der Verwendung zur Anzeige in einem Webbrowser entspricht.
Beispiel des Druck-Renderers des Dateianhangelements:

[FormControlRenderer(typeof(AttachmentControl), EnumFormControlRendererUsage.AttributeValues.Print)]

Sie können durch die Angabe dieses Attributs in einem Projekt-Modul auch existierende Renderer übersteuern, indem Sie einen existierenden Renderer einfach neu definieren. Die wäre anwendbar, wenn Sie beispielsweise wollen, dass Textfelder immer rote Schrift haben. Sie würden dann einen neuen Renderer für die Klasse TextEditControl definieren.

Renderer beinhalten drei Methoden Edit(), Read() und Bind(), von denen Edit() und Read() zwingend implementiert werden müssen.

Die Edit-Methode sorgt für die korrekte Ansteuerung und Verwendung der visuellen Komponenten (DevExpress) im Bearbeitenmodus und die Read-Methode im Lesemodus. Von den Möglichkeiten unterscheiden sich diese beiden 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, falls das angezeigte Feld im Browser den gleichen Namen wie das in C# implementierte Steuerelement hat. Dies funktioniert immer dann, wenn Werte aus dem Feld im Webbrowser exakt übernommen werden können.

Beispiel

Die Render() Methode ist das Komplizierteste hieran, daher kümmern wir uns um diese ganz zum Schluss. 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 : ControlDefaultRendererBase
    {
(… Klasseninhalt …)
    }
}

Die Klasse selbst erbt vom ControlDefaultRendererBase und ist über das Attribut FormControlRenderer als Renderer für Steuerelemente des Typs ValueAndAliasControl gekennzeichnet.
Der „Klasseninhalt“ besteht folglich aus den Methoden Edit(), Read() und Bind().

Bind

Schauen wir zunächst auf die Bind() Methode:

Die Bind() Methode ist dafür zuständig, dass der Wert, der vom propertyDescriptor beschrieben wird, in Verbindung mit dem bindValuePrefix aus dem bindValueProvider geholt und im gewünschten Property des bindObject abgelegt wird. Falls eventuell benötigt, wird hier zusätzlich noch eine Liste mit allen auf der Maske verwendeten Steuerelementen formControls geliefert.

Die Funktion der Bind() Methode ist relativ einfach: Das Steuerelement zeigt in der Benutzeroberfläche zwei Felder, von denen eines <Feldname>_Value heißt und ein anderes <Feldname>_Alias. Diese beiden Werte müssen aus den vom Browser kommenden Daten ausgelesen, zusammengerechnet und in das bindObject geschrieben werden, vorzugsweise unter Verwendung des übergebenen propertyDescriptors. Beim Zusammenrechnen wird das Konfigurierte Trennzeichen des Controls verwendet. Das für die Ermittlung des Trennzeichens benötigte Steuerelement wird anhand des aktuellen Feldnamens aus der Liste der formControls herausgesucht.

Diese Funktion wird aufgerufen, wenn bei einem Speichervorgang (o. ä.) Daten eingehen, die zu unserem neuen Steuerelement gehören.

public override bool Bind(object bindObject, System.Web.Mvc.IValueProvider bindValueProvider, string bindValuePrefix, PropertyDescriptor propertyDescriptor, List<ControlBase> formControls)
{
    // Berechnung der UI-Feldnamen
    String modelFieldName = propertyDescriptor.Name;
    String bindValueFieldName_Value = bindValuePrefix + modelFieldName + "_Value";
    String bindValueFieldName_Alias = bindValuePrefix + modelFieldName + "_Alias";

    // Auslesen der Inhalte aus den Daten vom Client
    String value = bindValueProvider.GetValue(bindValueFieldName_Value)?.AttemptedValue ?? "";
    String alias = bindValueProvider.GetValue(bindValueFieldName_Alias)?.AttemptedValue ?? "";

    // Ermittlung des dazugehörigen Steuerelements
    ValueAndAliasControl valueAndAliasControl = (ValueAndAliasControl) formControls.First (ff => (ff is ValueAndAliasControl) && ((ValueAndAliasControl)ff).OrmFieldName == modelFieldName);

    // Setzen des Wertes in den Datensatz
    propertyDescriptor.SetValue(bindObject, value + valueAndAliasControl.Delimiter + alias);

    return true;
} 

Edit

Die Feldnamen mit den Suffixen „_Value“ und „_Alias“ werden von der Edit() Methode festgelegt. 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 ausgewertet und der CreateControl-Methode als Vorbelegung übergeben. Nähere Beschreibungen bieten auch die Kommentarzeilen im Beispielquellcode.

Aufgrund der schieren Menge der eventuell zum Rendern notwendigen Daten, hat die Edit() Methode ein eigenes Parameterdatenmodell, welches die verschiedensten Informationen beinhaltet.

public override void Edit(FormControlRendererParameterModel parameters)
 {
   FormControlRendererUiParameterModel parametersUi = (FormControlRendererUiParameterModel)parameters;
   ValueAndAliasControl valueAndAliasControl = (ValueAndAliasControl)parametersUi.Control;
   FormRenderingContextUi renderingContext = (FormRenderingContextUi)parametersUi.RenderingContext;

   // Feldnamen definieren (Datenmodell)
   String modelFieldName = valueAndAliasControl.OrmFieldName;

   // Feldnamen definieren für erstes Feld (Browser UI)
   String uiFieldName = modelFieldName + "_Value";

   // zu verwendendes Trennzeichen
   char delimiter = valueAndAliasControl.Delimiter.ToCharArray()[0];

   // Inhalt des gespeicherten Feldes holen und für das erste Feld bearbeiten
   String fieldContent = (String)parametersUi.BindData.GetPropertyValue(modelFieldName);
   fieldContent = fieldContent?.Split(delimiter)?[0] ?? "";

   // Erstes Feld erzeugen und als Objekt hinzufügen
   MVCxFormLayoutItem mvcxItem = CreateTextBoxItem(valueAndAliasControl, uiFieldName, renderingContext.FormModel, "Wert", fieldContent);
   parametersUi.MvcxControls.Add(mvcxItem);

   // Inhalt des gespeicherten Feldes holen und für das zweite Feld bearbeiten
   fieldContent = (String)parametersUi.BindData.GetPropertyValue(modelFieldName);
   String[] splt = fieldContent?.Split(delimiter) ?? (new String[] { "", "" });
   if (splt.Length < 2)
       splt = fieldContent?.Split(delimiter) ?? (new String[] { "", "" });
   fieldContent = fieldContent?.Split(delimiter)?[1] ?? "";

   // Feldnamen definieren für zweites Feld (Browser UI)
   uiFieldName = modelFieldName + "_Alias";

   // Zweites Feld erzeugen und als Objekt hinzufügen
   mvcxItem = CreateTextBoxItem(valueAndAliasControl, uiFieldName, renderingContext.FormMoel, "Alias", fieldContent);
   parametersUi.MvcxControls.Add(mvcxItem);
}

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?
   Dictionary<AttributeEnum, Boolean> attributes = RenderingUtils.GetFormAttributesForControl(formModel.AdditionalAttributes, valueAndAliasControl);
   if (attributes.ContainsKey(AttributeEnum.ReadOnly) || valueAndAliasControl.ReadOnly)
       settings.ClientEnabled = false;

   // Breite des Feldes
   settings.Width = new Unit(valueAndAliasControl.Width, Api.Enum.EnumValueConverter.GetValueWidthType(valueAndAliasControl.WidthType));

   // ist es ein Pflichtfeld?
   if (valueAndAliasControl.Required)
       settings.Properties.ValidationSettings.RequiredField.IsRequired = true;

   // Feld kann als fehlerhaft markiert werden, falls die Validierung fehlschlägt
   if (settings.Properties is EditProperties)
   {
       EditProperties props = (EditProperties)settings.Properties;
       ApplicationSubConfigurationForm appFormSettings = Configurations.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);
   }

   // Wenn das Feld aktiv wird, müssen eventuell die Menüs des HTML-Controls ausgeblendet
   // werden (das geht technisch leider nicht, wenn man das HTML-Control verlässt, daher 
   // müssen sich alle anderen Controls darum kümmern)
   settings.Properties.ClientSideEvents.GotFocus = "BA.Ui.RibbonUtils.HideHtmlEditorTabs";

   // 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. Andiser Stelle sollte man nur Eelemente generieren, die das Bearbeiten nicht ermöhlichen. In diesem Falls wird ein “Label” verwendet.

public override void ReadMode(FormControlRendererParameterModel parameters)
{
    FormControlRendererUiParameterModel parametersUi = (FormControlRendererUiParameterModel)parameters;
    FormRenderingContextUi renderingContext = (FormRenderingContextUi)parametersUi.RenderingContext;
    String namePrefix = RenderingUtils.GetUIReadyContextPrefix(renderingContext);
    ValueAndAliasControl valueAndAliasControl = (ValueAndAliasControl)parametersUi.Control;

    // Feldnamen setzen
    String modelFieldName = valueAndAliasControl.OrmFieldName;

    // Alternativ kann ein MVCxFormLayoutItem auf diese Weise erstellt werden.
    parametersUi.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)parametersUi.BindData.GetPropertyValue(modelFieldName);
        fieldContent = fieldContent?.Split(delimiter)?[0] ?? "";
        labelSettings.Text = fieldContent;

        // Weieter Einstellungen festlegen
        labelSettings.Name = namePrefix + valueAndAliasControl.ControlInternalName + parametersUi.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;
    });
}