Databinding A Complex Multi-Table Silverlight Control In LightSwitch: ComponentOne Scheduler
Introduction
In this article, we will tackle a complex multi-table control in their Silverlight Scheduler Control. This is a full featured control that has a ton of functionality. It is definitely worth the effort to make it work with LightSwitch. The point of this article is to demonstrate methods you can use when faced with integrating a complex control.
Background
A strong point about ComponentOne is their ability to make great Silverlight controls that aggregate data. The LightSwitch Help Website previously covered their LightSwitch extension: Using OLAP for LightSwitch. That control is designed to be mostly configured by end-users. The article: The LightSwitch Control Extension Makers Dilemma explains how LightSwitch Control Extensions are easy to use, but have limited configuration.
The reason the control is so complex is that it communicates with several tables at the same time. It allows you to store Contacts, Resources, and Appointments. When incorporated into your own LightSwitch application, these tables would point to your data. Using this control, you could, for example, create an application that allowed employees to reserve meeting rooms.
LightSwitch
The Scheduler control is a normal Silverlight control. What LightSwitch 'brings to the table' is the ability to create the application in minutes. Once I read the directions for the control, I was able to create the tables needed in 5 minutes. At that point, 90% of the application was done.
The Scheduler Control
When you run the sample (you can download the code on the downloads page), you will see a popup indicating you are using the evaluation version.
Click the ‘X’ to close the popup.
Double-click on a day to open a popup that allows you to create or edit an Appointment.
You can also indicate Resources to attach to the Appointment. You can select one of the existing Resources, or add and delete Resources.
You can also indicate Contacts to attach to the Appointment. You can select one of the existing Contacts, or add and delete Contacts.
The same options are available for Categories. In this example, we turned off the ability to add new Categories.
The Scheduler Control really shines in its ability to group this data and show calendars by Contact…
…or Resource…
…or Category.
It also displays data by Day, Week, and Working Week.
You can create recurring appointments.
It will even pop up reminders.
More On The Scheduler Control
You can see a live demonstration of all of ComponentOne’s controls at this link: http://demo.componentone.com/Silverlight/ControlExplorer/
You can get full documentation on the Scheduler Control at: http://helpcentral.componentone.com/nethelp/c1schedulerSL/.
However, if you download and install the Demo Package…
… and click on the Samples that are installed…
You will find ready to run projects. However, these are normal Silverlight projects, so you need to know how to bind them in LightSwitch. The good news is that as we demonstrated, LightSwitch is capable of working with any Silverlight Control no matter how complex.
Implementing The Control
The entire Solution consists of three Entities (tables), and a Silverlight project that contains the Schedule Control. The Schedule Control is the only item placed on the Calendar Screen which is the only Screen in the application.
The schema for the Entities was determined by looking at the data sources, and the fields, that the control is designed to use (and by reading the documentation and looking at the sample code).
The Silverlight Project
(Note: See: Creating A LightSwitch Custom Silverlight Control for step-by-step directions on implementing a Silverlight Control in LightSwitch).
In the Silverlight project, we add references to the ComponentOne assemblies and the LightSwitch assemblies. Also note that when the Control is placed on the LightSwitch Screen, the same assemblies need to be referenced again. For an example of this, see the steps used to implement this control: Using the Telerik Rich Text Editor In Visual Studio LightSwitch.
There is a lot of XAML borrowed from the sample ComponentOne code to display the drop downs and buttons, but the following XAML is all that is used to display the actual Scheduler Control:
<c1:C1Scheduler Grid.Row="2" Grid.Column="1"
x:Name="sched1"
GroupPageSize="2"
StyleChanged="sched1_StyleChanged"
UserDeletingAppointment="sched1_UserDeletingAppointment">
<c1sched:C1Scheduler.Settings>
<c1sched:C1SchedulerSettings
AllowContactsEditing="True"
AllowResourcesEditing="True"
AllowCategoriesEditing="False"
AllowCategoriesMultiSelection="True"
AllowResourcesMultiSelection="True"
AllowContactsMultiSelection="True"
FirstVisibleTime="08:00:00" />
</c1sched:C1Scheduler.Settings>
</c1:C1Scheduler>
Normally we would have binding code in the XAML, however, this control throws errors when we do that because LightSwitch will initially bind to a control but not actually pass any data.
Normally with binding, if a binding is bad or in error, it silently fails. The Scheduler Control is complex and throws an error. We therefore code the binding in code behind to resolve this issue.
Simply, we will not actually set any binding until we know that LightSwitch actually has data to supply (yes, this may also be possible to accomplish using Silverlight Value Converters).
We wire up a property that will call the following method when LightSwitch binds data to the control:
private static void OnScheduleControlPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScheduleControl objScheduleControl = (ScheduleControl)d;
IContentItem contentItem = (IContentItem)objScheduleControl.DataContext;
if (contentItem.Details != null)
{
objScheduleControl.cmbGroup.Items.Add("None");
objScheduleControl.cmbGroup.Items.Add("Category");
objScheduleControl.cmbGroup.Items.Add("Resource");
objScheduleControl.cmbGroup.Items.Add("Contact");
objScheduleControl.cmbGroup.SelectedIndex = 0;
objScheduleControl.cmbView.Items.Add("Day");
objScheduleControl.cmbView.Items.Add("Working Week");
objScheduleControl.cmbView.Items.Add("Week");
objScheduleControl.cmbView.Items.Add("Month");
objScheduleControl.cmbView.SelectedIndex = 3;
BaseObjectMappingCollection<Contact> ContactMappings =
objScheduleControl.sched1.DataStorage.ContactStorage.Mappings;
ContactMappings.IndexMapping.MappingName = "Id";
ContactMappings.IdMapping.MappingName = "GuidId";
ContactMappings.CaptionMapping.MappingName = "Caption";
ContactMappings.ColorMapping.MappingName = "Color";
ContactMappings.TextMapping.MappingName = "Text";
objScheduleControl.sched1.DataStorage.ContactStorage.DataMember = "Contacts";
objScheduleControl.sched1.DataStorage.ContactStorage.DataSource = contentItem.Screen;
BaseObjectMappingCollection<Resource> ResourceStorage =
objScheduleControl.sched1.DataStorage.ResourceStorage.Mappings;
ResourceStorage.IndexMapping.MappingName = "Id";
ResourceStorage.IdMapping.MappingName = "GuidId";
ResourceStorage.CaptionMapping.MappingName = "Caption";
ResourceStorage.ColorMapping.MappingName = "Color";
ResourceStorage.TextMapping.MappingName = "Text";
objScheduleControl.sched1.DataStorage.ResourceStorage.DataMember = "Resources";
objScheduleControl.sched1.DataStorage.ResourceStorage.DataSource = contentItem.Screen;
AppointmentMappingCollection AppointmentMappings =
objScheduleControl.sched1.DataStorage.AppointmentStorage.Mappings;
AppointmentMappings.IndexMapping.MappingName = "Id";
AppointmentMappings.IdMapping.MappingName = "GuidId";
AppointmentMappings.Subject.MappingName = "Subject";
AppointmentMappings.Body.MappingName = "Body";
AppointmentMappings.End.MappingName = "TimeEnd";
AppointmentMappings.Start.MappingName = "TimeStart";
AppointmentMappings.Location.MappingName = "Location";
AppointmentMappings.AppointmentProperties.MappingName = "Properties";
objScheduleControl.sched1.DataStorage.AppointmentStorage.DataMember = "Appointments";
objScheduleControl.sched1.DataStorage.AppointmentStorage.DataSource =
contentItem.Screen;
}
Note that we also indicate how each data source is mapped to our Entities in LightSwitch.
Every control is different, so the important concepts are that you can get an instance of the LightSwitch DataContext, check that it actually has data, and bind to your Silverlight control.
Trouble Deleting
With complex controls, you may run into problems where the control does not communicate with LightSwitch in the way it expects, and you get errors. This happens when you attempt to delete an Appointment.
When the Scheduler Control deletes an Appointment, it is necessary to delete the record by locating it by GUID, and explicitly deleting it in LightSwitch.
This is the code that is used:
private void sched1_UserDeletingAppointment(object sender,
C1.Silverlight.Schedule.AppointmentActionEventArgs e)
{
var objDataContext = (IContentItem)this.DataContext;
var Screen =
(Microsoft.LightSwitch.Client.IScreenObject)objDataContext.Screen;
Screen.Details.Dispatcher.BeginInvoke(() =>
{
string strGuid = Convert.ToString(e.Appointment.Key[0]);
Guid DeletedGuidId = new Guid(strGuid);
LightSwitchApplication.DataWorkspace DataWorkspace =
(Screen.Details.DataWorkspace as LightSwitchApplication.DataWorkspace);
LightSwitchApplication.Appointment DeletedAppointment =
DataWorkspace.ApplicationData.Appointments
.Where(x => x.GuidId == DeletedGuidId).FirstOrDefault();
if (DeletedAppointment != null)
{
DeletedAppointment.Delete();
}
});
}
A new instance of the EntityObject class cannot be initialized because the ambient IDataWorkspace is not available. Please use the constructor that specifies an EntitySet.
If you have arrived at this blog article because you received the error above, the solution is to perform whatever action you are trying to do in the LightSwitch Screen code behind (rather than in the Silverlight Control its self).
The first step we perform is to add a public
property in the Silverlight Control that will allow the LightSwitch Screen code to access the Silverlight Control programmatically.
We then use the following in the LightSwitch Screen code:
using System;
using Microsoft.LightSwitch.Presentation;
using Microsoft.LightSwitch.Presentation.Extensions;
using SilverlightLibrary;
namespace LightSwitchApplication
{
public partial class Calendar
{
partial void Calendar_Created()
{
IContentItemProxy schedProxy = this.FindControl("Appointments");
schedProxy.ControlAvailable +=
new EventHandler<ControlAvailableEventArgs>(schedProxy_ControlAvailable);
}
void schedProxy_ControlAvailable(object sender, ControlAvailableEventArgs e)
{
ScheduleControl sc = e.Control as ScheduleControl;
sc.Scheduler.DataStorage.AppointmentStorage.AddingNew += (obj, args) =>
{
Appointment app = new Appointment
(this.DataWorkspace.ApplicationData.Appointments);
args.NewObject = app;
};
sc.Scheduler.DataStorage.ContactStorage.AddingNew += (obj, args) =>
{
Contact app = new Contact(this.DataWorkspace.ApplicationData.Contacts);
args.NewObject = app;
};
sc.Scheduler.DataStorage.ResourceStorage.AddingNew += (obj, args) =>
{
Resource app = new Resource(this.DataWorkspace.ApplicationData.Resources);
args.NewObject = app;
};
}
}
}
Most Controls Won't Be This Hard
I decided to tackle this control because I knew it would be hard. My hope was to demonstrate that LightSwitch is capable of handling complex business applications. Anything that can be done in a normal Silverlight application can be done in LightSwitch.
Special Thanks
This article would not have been possible without the assistance of ComponentOne.
An extra special thank you to Raleigh Johnson of ComponentOne for providing code required to properly create new Appointments.
I would also like to thank Rich Dudley of ComponentOne.
More LightSwitch Scheduler Controls
Paul Patterson has a tutorial on using the Telerik Scheduler here:
More LightSwitch Tutorials Are On The LightSwitchHelpWebsite.com
http://LightSwitchHelpWebsite.com