Custom controls in ViewModel aided Views

I have written on numerous occasions on how to mold data from your model into viewmodels using the ViewModel-designer in Modlr. I have also made a point on more than one occasion how such viewmodels lend themselves to act as the complete definition of a view if you just dress them up with a few user interface hints (UI-Hints) – and how easy that is in the Modlr viewmodel designer. I have also stressed at many times how this is a very effective way to get an administrative UI up and running in minutes instead of hours.

This article is taking things further still – the ability to merge in your own displays in the viewmodel aided view.

Consider this simple model:

image

Accompanied by this viewmodel:

image

 

What I did here was to display a list of all flowers by name, and a some details on the focused flower – the name and the number of leaves – but also a ViewModelColumn called “HowItLooks”.

The “HowItLooks” column has the flag Content-Override set. When this flag is set the well-behaving ViewModelUserControl should allow for the developer to inject their own display – with a callback event or the like – this is not new. What is new is that you now can associate a WPF ui control in design time.

When the ViewModelColumn is selected the property inspector looks like this:

image

The ContentOverride and ContentOverrideTypeDesignTimePath are shown whenever the ContentOverride is true.

The ContentOverride expects the Assembly without the dll extension, a semi colon , and the namespace and type of the UserControl you want to display here. <AssemblynamewithoutDLLExtension>;<NameSpace>.<TypeOfExternalUIControl>

The type you want to use must implement this interface:

namespace Eco.ViewModel.Runtime
{
  public interface IExternalWECPOFUIComponent
  {
    // Summary:
    //     Go like this: (vmc.SpawnedComponents["control"] as Grid).Children.Add(this);
    void InstallYourSelf(ViewModelColumn vmc, bool isDesignTime);
    string TheRequirementsToShowDeveloperInDesignTime();
  }
}

A simple sample

We will start slowly. It would be nice if the NumberOfLeaves could be visualized in some other way than just an integer – it would be nice if I could see the leaves.

In WPF it is fairly simple to draw something that looks like a flower:

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

The code above draws the flower simulation based on a the value in a NumberOfLeafs property.

We want to be able to bind this property to a datasource – in WPF that implies a DependencyProperty:

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

// Using a DependencyProperty as the backing store for NumberOfLeafs.  This enables animation, styling, binding, etc...
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 code above handles all the details of how to turn data into display. But we want to bind our data from the viewmodel to the properties of this custom control. This is where the IExternalWECPOFUIComponent comes in. Keep in mind – we do not want our flower simulation to need to know too much – or rather anything – about our model. After all – the flower simulation might be useful in many other places in this model – and also in many other models we will create in the future. If your flower simulation saves someone else some time consider to license the rights to use your logic for money.

This is what we need to do to make our flower simulation work in a viewmodel ui:

public void InstallYourSelf(Eco.ViewModel.Runtime.ViewModelColumn vmc,bool designTime)
{
  // add yourself to the viewmodel spawnedartifacts
  (vmc.SpawnedArtifacts["control"] as Grid).Children.Add(this);
  // I want the background transparent
  (vmc.SpawnedArtifacts["control"] as Grid).Background = null; 
  // Bind ViewModelColumn to the NumberOfLeafsProperty
  this.SetBinding(NumberOfLeafsProperty, 
         new Binding(vmc.Name) { Source = vmc.OwningDisplayClass.BindingSource });      
}


public string TheRequirementsToShowDeveloperInDesignTime()
{
  // Explain in text what your control assumes about the data
  return "This control needs the column to be of type int";
}

Done – let us run;

image

image

Cool – whenever I change the data – the flower simulation is updated.

A slightly more complex sample

In the sample above I showed how just a single property could be visualized. The real challenge comes when there are several properties and even data structure like lists and nestings. In this sample I will show how that can be handled.

Consider a case where you have data in both directions in a grid – this is typically called a pivot. A pivot can be used in many scenarios – dates in x axis, parts in y axis and price in the cells. I am sure you can think of multiple usages for such a component. This sample is driven from a real life case where we needed a good way to edit data like this. Normal viewmodel ui grids have the column decided in design time – and only the rows made up of data. That would not cut the mustard in our use case.

Consider this model:

image

It might not look like much – but it covers all the complexity of a pivot. A list of values (CellThing) that are mapped onto a grid of ColumnThings and RowThings. Can we display and edit this successfully we can edit and display any and all pivot needs.

Creating a viewmodel with UI hints for the maintaince of this data could look like this:

image

Before you react to the complexity of this viewmodel please keep in mind that it is handling 4 axis of information “PivotExample”, “ColumnThing”,”RowThing” and “CellThings” – it further allow for inplace comboboxes to be used for associating the Cellthings to one ColumnThing and one RowThing. 

Lets run it and add some data:

image

Now when I have this simple UI to populate the data I would like to use a pivot view to see the data values plotted in a grid of row and column things.

How???

Again I add ViewModelColumn and set Content-Override to true – then I fill in the control I want to use. This time I also let Modlr know the path to the assembly so that Modlr can show the component already in design time:

image

When Modlr can load the control in design time – it renders it and will display hints about usage when you point at it:

image

Remember the TheRequirementsToShowDeveloperInDesignTime method of the IExternalWECPOFUIComponent interface:

public string TheRequirementsToShowDeveloperInDesignTime()
{
  return 
  "<ViewModelClass>  \r\n"+
  "  XAxis   -> nesting to <ViewModelClass> Header\r\n"+
  "  YAxis   -> nesting to <ViewModelClass> Header\r\n"+
  "  Values (THIS IS THE COLUMN THAT HAS UIOverride)  -> nesting to <ViewModelClass> X:string,Y:string,Value\r\n"+
  "\r\n"+
  "Follow the pattern above to present data in pivot table";
}

Reading this information is actually crucial – these are the assumptions the control need to make on our data in order to do its generic – one size fits all – display.

This is a very important concept: The control knows nothing about your model – it does however require certain things of your viewmodel. Since the Viewmodel is a tool/concept to mold model data into a situation or perspective or view – this is what we do here. And the text in the TheRequirementsToShowDeveloperInDesignTime method describes the limitations.

image

Fulfilling this “contract” should be enough. Lets run:

image

Works! You can even edit in the pivot. Try and add X and Y axis objects.

This article is not about writing components like this – it is more about using them. But some pointers on how it is done:

public void InstallYourSelf(Eco.ViewModel.Runtime.ViewModelColumn vmc, bool isDesignTime)
{

  (vmc.SpawnedArtifacts["control"] as Grid).Children.Add(this);
  _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 });
  this.SetBinding(BindableValuesProperty, new Binding() { Source = _Values.DetailAssociation.BindingSource });

  Binding binding2 = new Binding() { Source = _pivotCells };
  DataGrid.SetBinding(DataGrid.ItemsSourceProperty, binding2);

  DataGrid.CanUserAddRows = false;
  DataGrid.AutoGenerateColumns = false;
  DataGrid.CellEditEnding += new EventHandler<DataGridCellEditEndingEventArgs>(DataGrid_CellEditEnding);
  DataGrid.BeginningEdit += new EventHandler<DataGridBeginningEditEventArgs>(DataGrid_BeginningEdit);
  DataGrid.SelectedCellsChanged += new SelectedCellsChangedEventHandler(DataGrid_SelectedCellsChanged);
  DataGrid.SelectionUnit = DataGridSelectionUnit.Cell;


Another important piece of code fixes the feedback to WECPOF so that actions are highlighted etc:

void DataGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
  IElement x,y,v;
  if (DataGrid.CurrentCell == null || DataGrid.CurrentCell.Column==null)
  {
    x=null;
    y=null;
    v=null;
  }
  else
  {
    x = _colDict[DataGrid.CurrentCell.Column];
    y = (DataGrid.CurrentCell.Item as OneRow).Element;
    v=null;
    ICustomTypeDescriptor value;
    if ((DataGrid.CurrentCell.Item as OneRow).TryGetTypeDesc(DataGrid.CurrentCell.Column.Header as string, out value))
    {          
      v = (value as IElementProvider).Element;
    }
  }
    _XAxis.ViewModel.SetOclVariableValueFromName(_XAxis.ViewModelClass.NameOfCurrentVariable(), x);
    _YAxis.ViewModel.SetOclVariableValueFromName(_YAxis.ViewModelClass.NameOfCurrentVariable(), y);
    _Values.ViewModel.SetOclVariableValueFromName(_Values.ViewModelClass.NameOfCurrentVariable(), v);
}

And to apply edit back into the data shown:

void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
  if (e.EditAction == DataGridEditAction.Commit)
  {
    string x = e.Column.Header as string;
    (e.Row.Item as OneRow).UpdateValue(x, (e.EditingElement as TextBox).Text);
  }
}

Summing up

I have shown how you can get a custom built WPF UI into an otherwise strictly declarative model driven solution. This will enable you to separate the concerns of display from the concerns of data handling. Surely this promise has been made by numerous techniques before – like Apples Opendoc, Microsofts OLE or standalone WPF for that matter. But what we do now is to actually allow for fully bound model driven data structures to be used by WPF controls that in turn separate style from function. In a glance you might think that this is a solution to the same problem that OLE/ActiveX solved back in the days but besides being fully managed this goes much further in allowing data into the component and truly absorb and solve a business problem with minimal effort and without being too specialized so it cannot be reused. I think it is awesome – big time – and far better than what I had expected it to be.

What is next

I will create a control that shows data in this Gantt chart

This entry was posted in Declarative, Modlr, ViewModel, WECPOF, WPF. Bookmark the permalink.

4 Responses to Custom controls in ViewModel aided Views

  1. seg says:

    Important step in creating a rich UI.
    Very useful.
    Thanks a lot!

  2. Pingback: Following up the post on Custom controls in ViewModel aided views | CapableObjects

  3. Pingback: Turnkey Angular notes | CapableObjects

  4. Pingback: UIOverride, ContentOverride–creating and injecting your own controls in MDriven Turnkey generated UI | CapableObjects

Leave a Reply

Your email address will not be published. Required fields are marked *

*