Oct
13
Written by:
Michael Washington
10/13/2011 10:44 PM
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.
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.
The reason the control is so complex, is that is 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.
Note: Also see A Groundbreaking Control - ComponentOne Scheduler LightSwitch Extension (for a version of this control that does not require any code)
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.
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)
{
// Get the DataContext
ScheduleControl objScheduleControl = (ScheduleControl)d;
IContentItem contentItem = (IContentItem)objScheduleControl.DataContext;
if (contentItem.Details != null)
{
// Fill the Group drop down
objScheduleControl.cmbGroup.Items.Add("None");
objScheduleControl.cmbGroup.Items.Add("Category");
objScheduleControl.cmbGroup.Items.Add("Resource");
objScheduleControl.cmbGroup.Items.Add("Contact");
objScheduleControl.cmbGroup.SelectedIndex = 0;
// Fill the Views drop down
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;
// Set mapping between ContactStorage and Contact table columns
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;
// Set mapping between ResourceStorage and Resource table columns
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;
// Set mapping between AppointmentStorage and Appointment table columns
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)
{
// Get a reference to the LightSwitch DataContext
var objDataContext = (IContentItem)this.DataContext;
// Get a reference to the LightSwitch Screen
var Screen =
(Microsoft.LightSwitch.Client.IScreenObject)objDataContext.Screen;
// Delete the appointment in LightSwitch
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 post 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
{
// The following methods are by Raleigh Johnson
// http://ComponentOne.com
partial void Calendar_Created()
{
// Get an instance of the ComponentOne Scheduler Control
IContentItemProxy schedProxy = this.FindControl("Appointments");
// Create a handler to fire when the control is actually available
schedProxy.ControlAvailable += new EventHandler<ControlAvailableEventArgs>(schedProxy_ControlAvailable);
}
void schedProxy_ControlAvailable(object sender, ControlAvailableEventArgs e)
{
// Get an instance of the ComponentOne Scheduler Control
ScheduleControl sc = e.Control as ScheduleControl;
// Create an event that will fire when an Appointment is being created
sc.Scheduler.DataStorage.AppointmentStorage.AddingNew += (obj, args) =>
{
// Create a new Appointment in LightSwitch
Appointment app = new Appointment(this.DataWorkspace.ApplicationData.Appointments);
// Set the NewObject in the ComponentOne Scheduler Control
args.NewObject = app;
};
// Create an event that will fire when a Contact is being created
sc.Scheduler.DataStorage.ContactStorage.AddingNew += (obj, args) =>
{
// Create a new Contact in LightSwitch
Contact app = new Contact(this.DataWorkspace.ApplicationData.Contacts);
// Set the NewObject in the ComponentOne Scheduler Control
args.NewObject = app;
};
// Create an event that will fire when a Resource is being created
sc.Scheduler.DataStorage.ResourceStorage.AddingNew += (obj, args) =>
{
// Create a new Resource in LightSwitch
Resource app = new Resource(this.DataWorkspace.ApplicationData.Resources);
// Set the NewObject in the ComponentOne Scheduler Control
args.NewObject = app;
};
}
}
}
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.
Most Controls Wont 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.
Note: Also see A Groundbreaking Control - ComponentOne Scheduler LightSwitch Extension (for a version of this control that does not require any code)
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:
http://www.paulspatterson.com/technology/lightswitch/ls-telerik-radscheduler/
Download Code
The LightSwitch project is available at http://lightswitchhelpwebsite.com/Downloads.aspx
10 comment(s) so far...
Did you figure out yet how to bring Doctors into the UI as Appointment Owner's for grouping? I saw you had created a table for Doctors, but the LightSwitch auto-generated foreign key name of Appointment_Doctor is giving me fits.
AppointmentMappings.OwnerIndexMapping.MappingName = "Appointment_Doctor"
doesn't seem to work. I have a similar application of this for a Property Management office with several Partners. The MultiUser sample gives hints but I haven't found the key to wire it up yet.
By Bob Baker on
10/15/2011 4:46 AM
|
@Bob Baker - I didn't try to use Owners, but I did connect to Contacts and the pattern should be the same, see how I mapped to my Contacts table.
By Michael Washington on
10/15/2011 4:49 AM
|
I don't think it's the same. Owner is a 2011 v2 addition and is essentially a special type of Contact (also has GuidId, Text, Caption, and Color), and the MultiUser example uses a foreign key in the Appointments table explicitly by setting the property I indicated above. In LightSwitch, that foreign key would be named Appointment_Doctor in your case, or Appointment_Owner in mine (I hadn't seen your Doctor table before I created an Owner table). I had started out with just copying the Contacts mapping, and changing it to the OwnersStorage and Mapping, but you need that additional step of setting the OwnerIndexMapping (or OwnerIdMapping if using Guids) pointing to the foreign key in the Appointments datasource. I have since got the 'Owners' loading like the MultiUser example, but save does not appear to pass thru. You did not explicitly implement delete as far as I could tell in your example. I implemented an AppointmentAdded event handler and the new Appointment gets saved to the Appointments table, but does not show up in the Scheduler (possibly because I did not implement any XML Properties). As you might suspect, the MultiUser example does not implement any CUD. Back to the docs...
By Bob Baker on
10/15/2011 2:10 PM
|
@Bob Baker - Start a thread in the forums, it would make it easier to work this out. I don't have a Doctor table, also I did explicitly implement the delete :) My example handles the XML Properties automatically, are you looking at my code? Again, please post to the forums, the comments section in this blog is horrible for a conversation.
By Michael Washington on
10/15/2011 2:13 PM
|
Michael, I have 3 questions 1) How long did it take to prepare this? 2) Related to the first, do you believe (or maybe know) it's going to be released as a LS extension? Is complexity prohibiting? 2) If you were asked to suggest a scheduler control (I am interested in acquiring a commercial solution in the near future) what would be the top 3 (if you can answer)?
Thanks in advance
By kchristo on
10/16/2011 12:51 AM
|
@kchristo 1) It took hours to figure out, but now that I know more I could create this in 15 minutes 2) I don't know but it could 3) All I can say is at this time this one would be my number one
By Michael Washington on
10/16/2011 5:24 AM
|
Hello, I have one bug.
How we can get an instance of the ComponentOne Scheduler Control?
In LightSwitch I have :
ScheduleControl sc = e.Control as ScheduleControl;
// Create an event that will fire when an Appointment is being created
sc.Sheduler.DataStorage.AppointmentStorage.AddingNew += (obj, args) => ............
But I get the error:
C1.Silverlighjt.Schedule.C1Scheduler does not contain a definition for "DataStorage"......
Any ideia? Thanks
By mwilson on
12/22/2011 5:23 AM
|
@mwilson - See: http://lightswitchhelpwebsite.com/Forum/tabid/63/aft/454/Default.aspx
By Michael Washington on
12/22/2011 6:16 AM
|
LightSwitchApplication.DataWorkspace DataWorkspace = (Screen.Details.DataWorkspace as LightSwitchApplication.DataWorkspace);
Hi Michael
How is that line of code in the above version of Lighswtich contained in VS 2013?
Apparently something changed in the disposition of this dll's causing me to have problems at this point, could you help me?
By Victor Perez on
7/23/2014 8:12 PM
|
@Victor Perez - I am sorry I have not done any Silverlight development in years. I honestly do not know the answer. However, a post on the official Microsoft Forums should find you some help: http://social.msdn.microsoft.com/Forums/vstudio/en-US/home?forum=lightswitch
By Michael Washington on
7/23/2014 9:26 PM
|