Coproject - a RIA Caliburn.Micro demo, part 12

by Augustin Šulc

In this part, I would like to use RIA Services a little more and then show you how to customize Caliburn.Micro conventions. Remember to visit Coproject Codeplex site for latest news.

Display metadata

If you remember Part 2, RIA Services use metadata to describe what data should be transferred to the client. But that is not all – you can add things like label text or validation. So, open Coproject.Web/Services/CoprojectService.metadata.cs.

Let’s say we want to change labels in ToDoItem detail:
image

So in ToDoItemMetadata, update the following properties:

[Display(Name="Task", Description="Describe the To-do task.")]
public string Content { get; set; }

[Display(Name = "Deadline", Description = "The latest possible date the task must be finished.")]
public Nullable<DateTime> DueDate { get; set; }

[Include]
[Association("FK_ToDoItems_Users", "UserID", "UserID", IsForeignKey = true)]
[Display(Name="Assigned user")]
public User User;

If you run the application now you will see the metadata applied:
image

Note that you could use other named parameters of [Display] to modify auto-generation of DataForms: AutoGenerateField, Order, Groupname. There are other useful parameters – you can check them on MSDN.

Validation metadata

Matadata can be also used for setting validation restrictions. Update the following:

[Display(Name = "Task", Description = "Describe the To-do task.")]
[Required]
[StringLength(40)]
public string Content { get; set; }

[Range(typeof(DateTime), "1.1.2010", "31.12.2019")]
[Display(Name = "Deadline", Description = "The latest possible date the task must be finished.")]
public Nullable<DateTime> DueDate { get; set; }

And now, when you run the application, you can see something like that (note that the Save button is disabled as there are validation errors):
image

You should look into System.ComponentModel.DataAnnotations for other useful attributes. For example if we set this on DueData:

[Editable(false)]

The field will be disabled for editing even in edit mode. That is how you set your domain on the server so that there is as little as possible of specific code concerning your domain in your presentation layer.

Shared code

As you can see, the User field shows only last name of the user and that is not what we want. This a great opportunity to show you how to use shared code between server and client. All you need to do is to call the file you want to be shared as *.shared.cs. So in Coproject.Web project, create a new file Models/User.shared.cs and set its content as follows:

namespace Coproject.Web.Models
{
	public partial class User
	{
		public string FullName
		{
			get
			{
				return string.Format("{0} {1}", this.FirstName, this.LastName);
			}
		}
	}
}

It is especially important to use this namespace (so that you can extent the partial class created by RIA Services) and to remove using of System.Web (since tis file is copied to the client as-is and there is no reference to this assembly). Now, we can edit ToDoItemView as follows:

<dataForm:DataField PropertyPath="User">
	<TextBlock Text="{Binding User.FullName}" />
</dataForm:DataField>

Note that you can share validators and other logic this way.
image

Filter on Enter

Another thing I would like to tweak a little bit would be to start filtering right when user hits Enter in the filter text box in ToDoListsView. The easiest way you are probably thinking of would be to add this handler to the Filter text box:

cal:Message.Attach="[KeyDown] = [HandleKeyInFilter($eventargs)]"

and then add this to view model:

public IEnumerable<IResult> HandleKeyInFilter(System.Windows.Input.KeyEventArgs eventargs)
{
	if (eventargs.Key == System.Windows.Input.Key.Enter)
	{
		yield return LoadData().ToSequential();
	}

	yield break;
}

Although this approach would work prefectly, it might not be a good idea to make view model dependent on KeyEventArgs. The best approach would be to customize Caliburn.Micro.Parser.CreateTrigger to support other action s than Event (like EnterPressed, or Gesture Key: Enter that is supported by original Caliburn). But I don’t want to change C.M source here. The last solution I can think of would be to create ExtendedTextBox and add EnterKeyDown event to it:

namespace Coproject.Controls
{
	public class ExtendedTextBox : TextBox
	{
		public event EventHandler EnterKeyDown;

		protected override void OnKeyDown(KeyEventArgs e)
		{
			base.OnKeyDown(e);

			if (e.Key == Key.Enter)
			{
				OnEnterKeyDown();
			}
		}

		protected void OnEnterKeyDown()
		{
			var handler = EnterKeyDown;
			if (handler != null)
			{
				handler(this, EventArgs.Empty);
			}
		}
	}
}

In order to have the default text box style applied to this new control too, we also have to add this to Custom.xaml:

<Style TargetType="local:ExtendedTextBox" BasedOn="{StaticResource DefaultTextBoxStyle}" />

And then we can easily use this new text box in ToDoListsView:

<local:ExtendedTextBox x:Name="Filter" Style="{StaticResource FilterTextBoxStyle}" 
						cal:Message.Attach="[EnterKeyDown] = [LoadData]" />

I, personally, like this way more because view and view models don’t have any clue about views implementation.

Customizing Caliburn.Micro conventions

Since we created a new control, we would like to save some configuration about attaching actions to it and use C.M convention instead. So, open AppBootstrapper, add a call to InitializeConvention() into Configure() and implement it as follows:

private void InitializeConventions()
{
	ConventionManager.AddElementConvention<BusyIndicator>(BusyIndicator.IsBusyProperty, "IsBusy", "Loaded");
	ConventionManager.AddElementConvention<ExtendedTextBox>(ExtendedTextBox.TextProperty, "Text", "EnterKeyDown");
}

The first argument configures to what property should be the respective view model property bound to. So if we name a BusyIndicator ‘Busy’, its IsBusy property will be bound to (viewModel).Busy.

The second arguments describes what property of the control should be taken if this control is set as an action parameter. If you remember LoadData(string filter) then this argument is responsible that the Text property of ‘Filter’ text box was passed.

The third argument configures the default event to start actions.

Now, we can update these two controls in ToDoListsView as follows:

<local:ExtendedTextBox x:Name="Filter" Style="{StaticResource FilterTextBoxStyle}" cal:Message.Attach="LoadData" />
<toolkit:BusyIndicator x:Name="Busy_IsBusy"  Grid.RowSpan="2" />

Note: I am not saying that this is exactly what you should do in your projects, it was just a great example for showing how these things work. You have to think about it and decide yourself – concrete decision depends on many factors.

Opened details counter

I found this quite useful when experimenting with details opening and closing so if you want to, add this code to ToDoListsView, above Toolbar:

<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,-25,0,0">
	<TextBlock Text="Opened details: " Style="{StaticResource StatusTextBlockStyle}" />
	<TextBlock x:Name="Items_Count" Style="{StaticResource StatusTextBlockStyle}" />
</StackPanel>

I think that by now, you are so familiar with the concept that there is no need to describe what this does.

Tags: Silverlight, Caliburn, Ria, Coproject

4 Comments

  • John R said

    Hi,
    I just got finished reading all the 12 current articles and wanted to give you a big thank you for the excellent project and articles - I have learnt a lot, and am very aware of the considerable time it takes to write them. Your articles are a great addition to Rob's own documentation. I do have one suggestion - include a copy of the articles as rtf or doc, in your source code on Codeplex, as allows easy printing (I like to read on the train!).
    Thanks
    John

  • Rick said

    Nice tutorial, cover most of the details of building an RIA app with CM... I would love to see a example on how to use a combobox inside the ToDoItemView DataForm. Something like a table on the database with the tasks priorities and the user can select a priority when editing/inserting a task, i'm having a hard time to do this with ria/mvvm/cm without ugly hacks :(

  • Mamerto said

    Thanks for this. Huge help in learning CM and RIA to build business applications.
    Wish I found this earlier :)

  • Tommy said

    I hate creating a new derived TextBox just to handle the Enter KeyDown. I would rather use attached properties and bypass Caliburn.Micro all together (Unless there is a way to incorporate Caliburn with Attached Properties).
    With the Attached Property approach If you put the Attached property on a Grid all the TextBoxes inside the Grid will do the "Action" on the Enter Key Down. Also this will work with all Items not just TextBoxes
    See the code at
    http://www.pastie.org/2424760

Add a Comment