You can replace the generated control for a ViewModel column with your own UI while keeping the column bound to the same ViewModel data; use UIOverride when a standard editor, grid, or picklist is not the right presentation.
What UIOverride does
MDriven generates standard UI for ViewModel columns. Set Content override on a ViewModel column when you want your application to provide the content rendered at that column's position instead.
Use a content override to replace one part of a generated page, dialog, or form. For example, a NumberOfLeaves column can remain a normal ViewModel value while a custom control draws a flower or star from that value. Changes made elsewhere in the UI update the custom control through data binding, including undo and redo.
A content override does not replace the whole view. For page-level replacement, see Documentation:Turnkey session 9: View Override and Documentation:Turnkey Session 11: More on View Override.
Configure the ViewModel column
- Open the ViewModel in MDriven Designer.
- Select the ViewModel column that is the root of the data your custom UI will use.
- Select the Content override checkbox.
- Save the model.
MDriven Designer displays the overridden area as a grey rectangle. This indicates where the custom content will appear in generated UI.
Optional design-time component
When you select Content override, MDriven Designer exposes these properties in the property inspector:
| Property | Purpose |
|---|---|
| ContentOverrideType | Identifies a C# type that can render the control in MDriven Designer. |
| ContentOverrideDesignTimePath | Identifies the assembly location used to load that type at design time. |
These properties are optional. You do not need a design-time component for the runtime override to work. Provide one when you want MDriven Designer to render the component and show its usage requirements while you design the ViewModel.
A reusable control should know the ViewModel structure it requires, not the names of domain-model classes. For example, a pivot control can require ViewModel columns named XAxis, YAxis, and Values. You then shape the ViewModel to that contract. For a complete design-time and WPF external-control example, see HowTos:Custom Controls in ViewModel Aided Views.
Implement the runtime override
The Content override checkbox only marks the location. Your target client must supply a control or HTML implementation that supports that platform.
| Target | Implementation approach | Details |
|---|---|---|
| WPF Turnkey Fat Client | Subscribe to ViewMetaBase.ContentOverride, create a WPF control, bind it to the ViewModel column, and add it to the target grid.
|
The control is compiled and delivered with the client application. |
| MDriven Turnkey AngularJS | Use an EXT Component for current component-based overrides, or use the AngularUIOverride tagged value for the file-based override pattern.
|
The replacement HTML accesses ViewModel data through Angular's data scope variable.
|
WPF Turnkey Fat Client
In the client code, subscribe to the content-override event. In the handler, identify the intended ViewModel column, prevent the original generated control from being used, create your control, place it using the supplied grid coordinates, and bind it to the ViewModel value.
The following example handles the NumberOfLeaves column in the UIOverride view. It creates ExampleUIOverrideTest, binds its NumberOfLeafsProperty dependency property to the ViewModel's NumberOfLeaves value, and inserts it into the generated grid.
static WindowWecpofTest()
{
ViewMetaBase.ContentOverride += HandleContentOverride;
}
public static void HandleContentOverride(object sender, ContentOverrideEventArgs args)
{
if (args.ViewName == "UIOverride" &&
args.Owner == "UIOverride" &&
args.Name == "NumberOfLeaves")
{
args.ContinueWithOrg = false;
ExampleUIOverrideTest myOverride = new ExampleUIOverrideTest();
Grid.SetColumn(myOverride, args.X);
Grid.SetRow(myOverride, args.Y);
Grid.SetColumnSpan(myOverride, args.Colspan);
Grid.SetRowSpan(myOverride, args.Rowspan);
Binding b = new Binding("NumberOfLeaves");
myOverride.SetBinding(
ExampleUIOverrideTest.NumberOfLeafsProperty, b);
(args.StreamCli.TargetForUI as Grid).Children.Add(myOverride);
}
}
WPF implementation notes
- Set
args.ContinueWithOrg = falsewhen your control replaces the generated control. If you leave it enabled, the standard control can still be rendered. - Use
args.X,args.Y,args.Colspan, andargs.Rowspanto preserve the layout position assigned by the generated UI. - Bind the control to the ViewModel column. Do not copy the value into a separate variable if the control must stay synchronized with other editors and with undo and redo.
- A custom control that supports selection or editing must also return relevant interaction state to the ViewModel runtime. The pivot-control example in HowTos:Custom Controls in ViewModel Aided Views shows this pattern.
MDriven Turnkey AngularJS
The AngularJS override receives ViewModel data through the data scope variable. For example, this replacement HTML displays the value of NumberOfLeaves:
<h1>Control has access to data: {{data.NumberOfLeaves}}</h1>
For an editable replacement, bind an input to the column:
<input ng-model="data.NumberOfLeaves" />
Use an EXT Component
Use EXT Components when your override needs HTML together with JavaScript and CSS.
- In the Turnkey Web application, create a component folder under
EXT_Components. The folder name is the component name. - Add a CSHTML file named
<component name>.cshtmlfor the component's HTML. - Add CSS files as needed. If both
<name>.cssand<name>.min.cssexist, Turnkey uses the minified file. - Add JavaScript as needed. Scripts whose filenames contain
_moduleare loaded when the Angular application starts. - On the content-overridden ViewModel column, set the tagged value
Angular_Ext_Componentto the component name.
For a generic simple-column component, use the placeholder replaced by Turnkey when it renders the override:
<input ng-model="data.[ViewModelColumnName]" />
Turnkey also replaces [ViewModelClassName] and [ViewModelColumnLabel]. This lets one component bind to the current ViewModel column instead of requiring a separate component for each column name. See Documentation:EXT Components for the component structure and directive example.
File-based AngularUIOverride pattern
The earlier file-based pattern uses the tagged value AngularUIOverride on the content-overridden column. Set its value to the CSHTML file name and place that file in the web application under:
<YourSite>\Views\EXT_OverridePages\
For example, a tagged value that identifies AFlowerCtrlPartial.cshtml corresponds to:
<YourSite>\Views\EXT_OverridePages\AFlowerCtrlPartial.cshtml
You can deploy the override file with your web application. Use the data scope variable to read or bind the ViewModel values in that file.
React to a rendered value change
If a custom script must redraw when a value changes, one example is to observe a DOM element whose content is bound to that value. The following pattern observes TriggerToRedraw, reads its updated HTML, and calls drawStar with the resulting number.
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var myObserver = new MutationObserver(mutationHandler);
var obsConfig = {
childList: true,
subtree: true
};
$(document).ready(function () {
myObserver.observe(document.getElementById("TriggerToRedraw"), obsConfig);
});
function mutationHandler(mutationRecords) {
var str = $("#TriggerToRedraw")[0].innerHTML;
var numleaves = Number(str);
drawStar(100, 100, numleaves, 30, 15);
}
The observed element must exist before calling observe. Keep the displayed value bound to the ViewModel so that the rendered content changes when the underlying ViewModel value changes.
Choose the right override scope
| Need | Use |
|---|---|
| Replace one generated editor, grid area, or other column content | Content override on a ViewModel column |
| Add reusable AngularJS HTML, JavaScript, and CSS for a column override | EXT Component with Angular_Ext_Component
|
| Replace an entire generated page or view | View Override |
