„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;
});
}