🚀 Welcome to MDriven Learn –  MDriven is now on Discord!  Don’t miss the latest Release Notes.
Custom Controls in ViewModel Aided Views
This page was created by Wikiadmin on 2026-07-29. Last edited by Wikiadmin on 2026-07-29.

You can inject a custom WPF control into a generated ViewModel-aided view when a standard generated editor or grid cannot present the data you need.

A ViewModel defines the data and interaction perspective. A content override lets a platform-specific control render one ViewModel column while the rest of the view remains generated. Use this approach for a focused custom visualisation, such as a flower drawing for an integer value or a pivot grid for nested data. For standard layouts, first consider declarative ViewModel placing hints.

When to use a custom control

Use a content override when the generated UI can handle most of the ViewModel but one column needs custom rendering or editing.

Requirement Recommended approach
Arrange generated fields in a form or administrative screen Use ViewModel placing hints. See Training:Taking It Further Still.
Replace one generated column with a custom WPF visual or editor Set Content override on that ViewModel column and provide a WPF control that implements IExternalWECPOFUIComponent.
Replace a complete AngularJS Turnkey view Create a ViewModel-named .cshtml override page. See Documentation:Turnkey session 9: View Override.
Add custom content to one AngularJS Turnkey column Use the AngularJS content-override mechanism described in Documentation:UIOverride.

Prepare the ViewModel

Define the data contract before writing the control. The control should depend on ViewModel columns and nesting, not on model classes. This keeps it reusable: a flower control can render any integer column that represents a leaf count, regardless of which model class supplies it.

For example, create a ViewModel that contains:

  • A list of flowers, showing each flower's Name.
  • A focused-flower detail area with Name and NumberOfLeaves.
  • A ViewModel column named HowItLooks that is the host for the custom flower control.

To mark the host column:

  1. Select the ViewModel column that will contain the custom control.
  2. In the property inspector, set Content override to true.
  3. Set ContentOverrideType to identify the control type. The value format is <AssemblyNameWithoutDllExtension>;<Namespace>.<ControlType>.
  4. Optionally set ContentOverrideDesignTimePath so MDriven Designer can load and display the control while you design the ViewModel.

When Content override is enabled, the generated designer surface represents the column as an override area. The design-time path is optional: it affects design-time rendering only and is not required for runtime use.

Implement the WPF control

The WPF control implements Eco.ViewModel.Runtime.IExternalWECPOFUIComponent. The interface gives the generated ViewModel UI a way to install the control and gives the control a way to explain its required ViewModel structure to the developer at design time.

namespace Eco.ViewModel.Runtime
{
  public interface IExternalWECPOFUIComponent
  {
    void InstallYourSelf(ViewModelColumn vmc, bool isDesignTime);
    string TheRequirementsToShowDeveloperInDesignTime();
  }
}

Install the control and bind it

In InstallYourSelf, add the control to the grid created for the override column. Then bind the control to the ViewModel data through the column's owning display class binding source.

The following example installs a flower control and binds its NumberOfLeafsProperty dependency property to the ViewModel column named NumberOfLeafs.

public void InstallYourSelf(
  Eco.ViewModel.Runtime.ViewModelColumn vmc,
  bool isDesignTime)
{
  (vmc.SpawnedArtifacts["control"] as Grid).Children.Add(this);
  (vmc.SpawnedArtifacts["control"] as Grid).Background = null;

  this.SetBinding(
    NumberOfLeafsProperty,
    new Binding("NumberOfLeafs")
    {
      Source = vmc.OwningDisplayClass.BindingSource
    });
}

The binding source is the generated ViewModel UI context. The custom control does not need to navigate the underlying model to find the value.

Declare the control's data requirements

Return clear, actionable text from TheRequirementsToShowDeveloperInDesignTime. MDriven Designer can show this text when it renders the control at design time. State the expected column type, required column names, and required nesting.

public string TheRequirementsToShowDeveloperInDesignTime()
{
  return "This control requires a column named NumberOfLeafs of type int.";
}

This method is the contract between a reusable control and a ViewModel author. It should explain requirements; it should not describe a particular business model.

Example: render an integer as a flower

A WPF flower control can draw one ellipse per leaf and redraw when the bound leaf count changes. Expose the leaf count as a WPF dependency property so it supports binding.

public int NumberOfLeafs
{
  get { return (int)GetValue(NumberOfLeafsProperty); }
  set { SetValue(NumberOfLeafsProperty, value); }
}

public static readonly DependencyProperty NumberOfLeafsProperty =
  DependencyProperty.Register(
    "NumberOfLeaf",
    typeof(int),
    typeof(UserControl1),
    new UIPropertyMetadata(NumberOfLeafsChanged));

private static void NumberOfLeafsChanged(
  DependencyObject source,
  DependencyPropertyChangedEventArgs e)
{
  (source as UserControl1).DrawingArea.InvalidateVisual();
}

The drawing logic can then use NumberOfLeafs to create the petals:

void DrawingArea_LayoutUpdated(object sender, EventArgs e)
{
  if (NumberOfLeafs != _drawnleaves)
  {
    DrawingArea.Children.Clear();
    Point center = new Point(
      DrawingArea.ActualWidth / 2,
      DrawingArea.ActualHeight / 2);
    double leafdeg = 0;

    for (int i = 0; i < NumberOfLeafs; i++)
    {
      Ellipse oneleaf = new Ellipse();
      oneleaf.Width = 10;
      oneleaf.Height = center.Y;
      oneleaf.RenderTransform = new RotateTransform(leafdeg, 5, 0);
      oneleaf.Stroke = new SolidColorBrush(Colors.Black);
      oneleaf.Fill = new SolidColorBrush(Colors.Yellow);
      Canvas.SetLeft(oneleaf, center.X - 5);
      Canvas.SetTop(oneleaf, center.Y);
      DrawingArea.Children.Add(oneleaf);
      leafdeg = (i + 1) * 360 / NumberOfLeafs;
    }

    Ellipse pistills = new Ellipse()
    {
      Width = 20,
      Height = 20,
      Fill = new SolidColorBrush(Colors.Black)
    };
    DrawingArea.Children.Add(pistills);
    Canvas.SetLeft(pistills, center.X - 10);
    Canvas.SetTop(pistills, center.Y - 10);
    _drawnleaves = NumberOfLeafs;
  }
}

When the ViewModel value changes, the binding updates NumberOfLeafs; the dependency-property callback invalidates the drawing area; and the control redraws the flower.

Example: define a reusable pivot contract

A pivot displays values at the intersections of two dynamic axes. Generated grids normally have columns fixed at ViewModel design time, whereas a pivot derives visible rows and columns from data.

Define the ViewModel structure needed by the pivot rather than coupling the control to model-specific classes. One possible contract is:

ViewModel member Required content
XAxis Nesting to items that expose Header.
YAxis Nesting to items that expose Header.
Values The content-override column; nesting to items that expose X, Y, and Value.

For an editable pivot, the maintenance ViewModel can also expose the associations that let users choose a row item and column item for each cell value. The pivot control then reads the axis and value collections from the ViewModel rather than from model objects.

Describe that contract in the control:

public string TheRequirementsToShowDeveloperInDesignTime()
{
  return
    "<ViewModelClass>\r\n" +
    "  XAxis -> nesting to <ViewModelClass> Header\r\n" +
    "  YAxis -> nesting to <ViewModelClass> Header\r\n" +
    "  Values (the column with Content override) -> " +
    "nesting to <ViewModelClass> X:string, Y:string, Value\r\n\r\n" +
    "Follow this pattern to present data in a pivot table.";
}

A pivot can obtain the required ViewModel columns by name and bind its axis collections to their detail-association binding sources:

_XAxis = vmc.ViewModelClass.ColumnFromName("XAxis");
_YAxis = vmc.ViewModelClass.ColumnFromName("YAxis");
_Values = vmc.ViewModelClass.ColumnFromName("Values");

this.SetBinding(
  BindableColumnsProperty,
  new Binding() { Source = _XAxis.DetailAssociation.BindingSource });
this.SetBinding(
  BindableRowsProperty,
  new Binding() { Source = _YAxis.DetailAssociation.BindingSource });

If axis headers are not unique, add HeaderKey columns to both axis ViewModel classes and use those keys for matching instead of the displayed Header value.

Runtime integration choices

The content-override marker belongs to the ViewModel, but the control implementation belongs to the target platform. Every target can support an override only when the supplied control supports that target platform.

WPF Turnkey fat client

In a WPF Turnkey fat client, you can handle the ViewMetaBase.ContentOverride event, identify the target ViewModel and column, create the WPF control, place it using the supplied grid coordinates, bind it, and add it to the target 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);
  }
}

This approach is appropriate when you compile and deliver the custom WPF control with the client. For the broader WPF override mechanism, see Documentation:UIOverride.

AngularJS MDriven Turnkey

For an AngularJS Turnkey column override, specify the AngularUIOverride tagged value on the content-override column and provide the named .cshtml file under <YourSite>\Views\EXT_OverridePages. The override has access to ViewModel data through the AngularJS scope variable data.

For example, a partial can display the leaf count with:

<h1>{{data.NumberOfLeaves}}</h1>

If you need to replace the complete ViewModel page rather than a column, use a ViewModel-named .cshtml page override instead. See Documentation:Overriding AngularJS MDriven Turnkey Views and Documentation:Turnkey session 9: View Override.

Design and maintenance rules

  • Keep business rules, validation, and data selection in the ViewModel and model. Keep drawing, layout, and input behavior in the custom control.
  • Treat column names, types, and nesting as the control's public contract. Document them in TheRequirementsToShowDeveloperInDesignTime.
  • Use a content override for a specific region. Do not replace an entire page when the generated ViewModel UI can still render the remaining fields and actions.
  • Make the control update through bindings and dependency properties. Do not assume that a value is static after the control is installed.
  • Set a design-time path only when you need the control rendered in MDriven Designer. Ensure the designer can load the referenced assembly and control.

See also