Markup Controls with Code
Sometimes you need to pass some parameters in the markup control.
For example, you don't need to display the Phone field in the Billing address, and you want also the control from the last sample to generate the fieldset
and legend
tags itself, so the control needs to know the text you need to display in the legend
element.
You can create a markup control using the Add > New Item in the Solution Explorer context menu and after you choose the name of the control, click on the checkbox which generates the code behind file for you.
The code behind class will look like that. Notice that it derives from DotvvmMarkupControl
which is required for all markup controls.
public class AddressEditor : DotvvmMarkupControl
{
}
Declaring Control Properties
Now we want to add two properties into the control. The first one is Title
, which will appear inside the legend
tag.
The second is DisplayPhoneNumber
property which will show or hide the Phone field.
The properties in a DotVVM control cannot be simple C# properties with default getter and setter. The reason is that the properties can contain data-bindings. If you declare for example a int
property, it is not possible to store data-binding inside.
To make data-binding work, you have to expose those properties as DotvvmProperty
objects which contains metadata about the property. It is similar to dependency properties in WPF.
DotVVM for Visual Studio contains an easy-to-use code snippet, so you only need to type dotprop
and press Tab. The property declaration will be generated for you.
If you are using Resharper and type
dotprop
, it will not see the code snippet and it will match theDotvvmProperty
class instead. If this happens, press Escape before pressing Tab and the snippet will work.
After you invoke the dotprop
code snippet, you can change the name to Title
, the type to string
, the containing class to AddressEditor
and the default value to "Address"
:
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DotvvmProperty TitleProperty
= DotvvmProperty.Register<string, AddressEditor>(c => c.Title, "Address");
The second property called DisplayPhoneNumber
of type bool
with default value true
will look like this:
public bool DisplayPhoneNumber
{
get { return (bool)GetValue(DisplayPhoneNumberProperty); }
set { SetValue(DisplayPhoneNumberProperty, value); }
}
public static readonly DotvvmProperty DisplayPhoneNumberProperty
= DotvvmProperty.Register<bool, AddressEditor>(c => c.DisplayPhoneNumber, true);
ControlProperty Binding
Now, you can access the value of these properties using the {controlProperty: Title}
binding in the markup.
Notice that the markup control must declare the @baseType
directive that specifies the code behind class.
The .dotcontrol
file will look like this:
@viewModel DotvvmDemo.Model.IAddress, DotvvmDemo
@baseType DotvvmDemo.Controls.AddressEditor, DotvvmDemo
<fieldset><legend>{{controlProperty: Title}}</legend>
<table>
<tr>
<td>Street: </td>
<td><dot:TextBox Text="{value: Street}" /></td>
</tr>
...
<tr Visible="{controlProperty: DisplayPhoneNumber}">
<td>Phone: </td>
<td><dot:TextBox Text="{value: Phone}" /></td>
</tr>
</table>
</fieldset>
In the page, we can now use the control like this:
<cc:AddressEditor DataContext="{value: BillingAddress}"
Title="Billing Address"
DisplayPhoneNumber="false" />
<cc:AddressEditor DataContext="{value: DeliveryAddress}"
Title="Delivery Address" />
Note that you can also put a data-binding as a value of the Title
and DisplayPhoneNumber
properties.
Important fact about control properties
The properties of markup controls do not store the value. They are only references to the value or the data-binding specified on the place where the control is used.
If you set a value of the Title
property from the code-behind, there are 3 situations that can happen:
- In the page, the
Title
property is not set:<cc:AddressEditor DataContext="{value: DeliveryAddress}" />
.
The value will not be stored anywhere and the Title
will have its default value on the next postback.
- In the page, the
Title
property is set to a static value:<cc:AddressEditor DataContext="{value: DeliveryAddress}" Title="Delivery Address" />
.
The value will not be stored anywhere and the Title
will have the value "Delivery Address"
on the next postback.
- In the page, the
Title
property is bound to some property in the viewmodel:<cc:AddressEditor DataContext="{value: DeliveryAddress}" Title="{value: DeliveryAddressTitle}" />
.
Only in this case the value will be persisted. If you set the Title
property from the code-behind, the value will be written into the DeliveryAddressTitle
property in the viewmodel and you will find it there on the next postback.
If you need to persist any state information in the markup control, it must be done by data-binding to some viewmodwl property.
ControlCommand Binding
If you need to add custom logic in the markup control, you can declare a method (e.g. ClearAddress
) in the code behind file and invoke it using controlCommand: ClearAddress()
binding.
Alternatively, we could have this method in the viewmodel, but in that case the IAddress
interface would have to declare this method and all classes that implement the interface would have to implementat also the ClearAddress
method.
In this case, the ClearAddress
can be declared in the code behind file because it does the same thing in all of the implementations.
We can add a button in the AddressEditor.dotcontrol
file:
<dot:Button Text="Clear Address" Click="{controlCommand: ClearAddress()}" />
And then, add this method to the AddressEditor
code behind class:
public void ClearAddress()
{
var target = (IAddress)DataContext;
target.Street = "";
target.City = "";
...
}
Notice that you can access the binding context using the DataContext
property. We can safely cast it to IAddress
because the @viewModel
directive of the control specifies that the binding context must implement this interface.
Updating the ViewModel Properties
Data-binding in DotVVM can do one more thing - update the source property.
Imagine we have a NumericUpDown
control which has one textbox and two buttons. The buttons increase or decrease the value of a number inside the textbox.
In the page, the control can be used like this:
<cc:NumericUpDown Value="{value: MyNumber}" />
The buttons in the control are using controlCommand
bindings to call the Up
and Down
methods.
The Up
method looks like this:
public void Up()
{
Value++;
}
Because the Value
property is bound to a property in the viewmodel, DotVVM will update the MyNumber
property too.