View Builder

We have added a new experimental extension to build all sorts of views using a plain JSON representation. It will eventually allow us to integrate it with the designer and optionally serialize views to JSON or XML rather than code in InitializeComponent.

The Wisej.Web.Ext.ViewBuilder source code is available in our public GitHub extensions repository and on NuGet.

As a simple start, this is what you can do with the ViewBuilder:

this.form1.LoadView(stream).Show();
this.form2.LoadView(model).Show();
this.form2.LoadView(jsonString).Show();

// or

ViewBuilder.Create(stream).Show();
ViewBuilder.Create(model).Show();
ViewBuilder.Create(jsonString).Show();

In the code above you can see two ways to use the ViewBuilder: as an extension method on any ContainerControl type (Form, Page, UserControl) to create the child components inside the target control; or as a static method used to create a new view from the model.

You can also see three different input types: Stream, Object, and String:

  • Stream is any stream that returns a JSON string.

  • Model is any object of any kind, just like any data object or view model.

  • String is a JSON string.

In all three cases, the input is an object model that is used to build the view. The code below shows a simple form with a TextBox and a Button:

// This is a JSON string.

{
  "_type": "Wisej.Web.Form",
  "text": "Builder Test",
  "windowState": "Maximized",
  "controls": [
    {
      "_type": "TextBox",
      "dock": "Top",
      "validating": "Program.OnTextBoxValidating",
      "toolTip1.ToolTip": "Type something"
    },
    {
      "_type": "Button",
      "name": "button1",
      "dock": "Top",
      "backColor": "Green",
      "click": "this.button1_OnClick"
    }
  ],
  "components": [
    {
      "_type": "Wisej.Web.ToolTip",
      "name": "toolTip1"
    }
  ]
}

As you can see in the simple model above, the model is always the same. The representation can be JSON (could be XML or yaml or anything else) or directly a .NET object. These are the most important features to notice:

  • Lower case field names. The ViewBuilder recognizes lowercase names and resolves them to the correct property. It allows JSON models to follow the JavaScript camel casing convention.

  • Events are treated like properties. Assigning an handler is the same as calling += or AttachHandler.

  • Extension properties, like the ToolTip extender", are addressed using the name of the extender component: i.e. "toolTip1.ToolTip". (Note that you don't need to use the ToolTip extender for tooltips, all Wisej.NET controls have the ToolTipText property.)

  • When declaring a model property, extender properties use the underscore instead of the dot to address the component.

  • All non-numeric values are in strings. Including Point, Size, Color, Font, etc. The ViewBuilder uses the property's TypeConverter to parse the string.

Model

The object model is simply the same model as the target component, with just one system property: _type. Use "_type" to define the class of the component to create. It can be a fully qualified type name or a simple name. If it's a single string, Wisej.NET will search in the Wisej.Web. namespace first.

For the _type value, you may also use a fully assembly-qualified type name. It allows your view models to refer to external assemblies and load them dynamically when the ViewBuilder creates the view.

Collections are assigned as arrays. The ViewBuilder detects what kind of collection is expected on the target and either assigns an array or calls the collection's AddRange method.

Use the components collection at the root level of the top-level container to add Wisej.NET components and extenders: Animation, ToolTip, ErrorProvider, etc.

The ViewBuilder can also resolve references to other components in the same container by name. If you set a property that expects another component, for example, ContextMenu, or DropDownControl, you can use the name of the component or control in any location of the model.

{
    "contextMenu": "contextMenu1"
}

The ViewBuilder will search for the component or control name at all levels and assign the reference to the property.

Extender Properties

Wisej.NET supports the IExtenderProperty system at design time. Now it supports it also in this new ViewBuilder model. There are usually two kinds of extenders: a component and a container.

For example, when you drop a control in a TableLayoutPanel, you will notice that the control has "gained" new properties at design time: Column, Row, RowSpan, ColumnSpan, and others.

The ViewBuilder model fully supports both, extender properties defined by another component and extender properties directly on the container.

{
    "_type": "TableLayoutPanel",
    "controls": [{
        "_type": "Button",
        "name": "button1",
        "text": "Click Me!",
        "columSpan": 2,
        "columm": 0,
        "row": 1,
        "animation1.name": "slideLeftIn",
        "animation1.event": "appear",
    }],
    "components": [{
        "_type": "Animation:,
        "name": "animation1"
    }]
}

In the JSON sample above you can see that button1 sets properties it doesn't have: columnSpan, column and row. These properties are extender properties provided by its TableLayoutContainer.

It also sets two more properties it doesn't have: animation1.name and animation1.event. These properties are provided by the animation1 component declared in the components collection.

Data Binding

Data binding is also fully supported. We used a syntax similar to WPF or MAUI. Any property in the model can be assigned a string that starts with "{Binding" to become a data-bound property.

The complete syntax is:

"{Binding Name, Source=Source, Format=Format, OnParse=OnParseHandler, OnFormat=OnFormatHandler, SourceUpdateMode=SourceUpdateMode, ControlUpdateMode=ControlUpdateMode}"

Everything is optional except Name.

If the Source is not specified, the current container (this or Me) is assumed to be the source. When the Source is specified, it's also relative to this or Me, unless it's a reference to a component, a BindingSource, for example.

FieldDescriptionExample

Name

Name of the property in the data source to bind to.

{Binding UserName} {Binding Company.Name}

Source

Reference to the data source. Can be any object in the current context (this) or a component in the components collection.

{Binding Name, Sorce=DataContext} {Binding CompanyName, Source=bindingSource1}

Format

Any standard or custom .NET format string.

{Binding Amount, Format=Total: c}

OnParseHandler

An event handler for the Binding.Parse event.

{Binding Location, OnParse=ParseLocation}

OnFormatHandler

An event handler for the Binding.Format event.

{Binding FullName, OnFormat=FullNameFormat}

SourceUpdateMode

One of the DataSourceUpdateMode fields.

{Binding Name, SourceUpdateMode=Never}

ControlUpdateMode

One of the ControlUpdateMode fields.

{Binding Name, ControlUpdateMode=Never}

Data binding and parsing is a large and complex system. This is probably one of the areas on the ViewBuilder that will evolve the most in the next builds.

Event Handlers

Attach handlers to any event by name. You can refer to static methods using their name or fully qualified type name, or you can attach to local methods simply by name or using this or Me.

Event handlers are resolved through the ViewBuilder.ResolveEventHandler function , which is overridable. The default resolver looks up the method and returns its MethodInfo object.

You may also create your own, and attach to JavaScript handlers or compile the code on the fly!

{
    "_type": "Button",
    "click": "AlertBox.Show($\"{sender}\")"
}

Your ResolveEventHandler implementation (see Custom Resolution) receives the string assigned t the "click" event and it's up to you to return the MethodInfo to attach to the event.

Custom Resolution

The ViewBuilder class exposes two assignable methods: ResolveReference and ResolveEventHandler:

ResolveReference

It's invoked when the ViewBuiler needs to convert a string name to a reference to a component or a control. If you assign your resolver to this method then it's entirely up to your code to resolve those references.

ViewBuilder.ResolveReference = (container, component, propertyName, name) => {
    return null;
}

ResolveEventHandler

It's invoked when the ViewBuilder needs to convert a string to a MethodInfo reference in order to create a delegate and attach to the event.

If you assign your resolver to this method, then it's entirely up to your code to convert the string to a method. This handler allows you to attach any kind of handler, event one you build on the fly, to any event on any control created by the ViewBuilder.

ViewBuilder.ResolveEventHandler = (container, descriptor, name) => {
    return null;
}

Last updated