Skip to main content Skip to footer

Scheduler Data Storage Scenarios

With ComponentOne Scheduler for WinRT XAML you can build a scheduling app in record time. The control handles the entire appointment creation and editing process, so all you have to do is handle the back end. At some point your schedule will need to store appointments to be accessed later. The C1Scheduler control has a few useful features that help in this area. In this blog post we will look at two different local data storage scenarios. This post also includes instructions on data binding which you may find useful on its own.

Data Storage Scenarios

When writing the data storage logic for your scheduling app, there are a few approaches you can take with the C1Scheduler control. The first scenario is to the use the ImportAsync and ExportAsync methods to persist data as XML or iCal format. The second scenario is to serialize the data in your own code (such as a View Model) and simply map the C1Scheduler control to your custom business object. Let’s take a deeper look into each of these scenarios.

Import/Export as XML

By calling the ImportAsync or ExportAsync methods on the DataStorage property, you can asynchronously load and save the appointment data as XML or iCal format. The following code snippet silently exports C1Scheduler data to the local application folder.


// Export XML to app local folder  
var folder = Windows.Storage.ApplicationData.Current.LocalFolder;  
var file = await folder.CreateFileAsync("appointments.xml", Windows.Storage.CreationCollisionOption.OpenIfExists);  
IRandomAccessStream stream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);  
await sched1.DataStorage.ExportAsync(stream.AsStreamForWrite(), C1.C1Schedule.FileFormatEnum.XML);  
await stream.FlushAsync();  
stream.Dispose();  

To export as iCal format change the C1.C1Schedule.FileFormatEnum parameter of the ExportAsync method. To silently import the same XML file use the following code snippet.


// Import XML from app local folder  
var folder = Windows.Storage.ApplicationData.Current.LocalFolder;  
try  
{  
    var file = await folder.GetFileAsync("appointments.xml");  
    IRandomAccessStream stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);  
    await sched1.DataStorage.ImportAsync(stream.AsStreamForRead(), C1.C1Schedule.FileFormatEnum.XML);  
    stream.Dispose();  
}  
catch { }  

The benefit to using Import/Export is that the amount of code you need to write is minimal. The C1Scheduler control handles importing and exporting ALL aspects of the scheduler. The latest version of C1Scheduler is always backwards compatible to previous versions of the schema.

Data Binding and Mapping

The second scenario is to bind and map C1Scheduler to some appointment collection and handle the saving and loading outside of the control. The C1Scheduler control provides complete two-way data binding so it’s very easy to set up. For instance, you could handle the serialization in your View Model as you would with any collection of objects bound to any items control. To demonstrate data-binding C1Scheduler to a collection of appointment objects, we need to first set up a View Model class. This is following the popular MVVM design pattern. In the example below, MainViewModel includes an ObservableCollection, Appointments, of type MyAppointment.


public class MainViewModel  
{  
    public ObservableCollection<MyAppointment> Appointments { get; private set; }  

    public MainViewModel()  
    {  
    }  
}  

The MyAppointment class has several fields that map nicely to the C1Scheduler model, including a Start Time, End Time, Subject, Body (Description), and Location. It’s recommended to also include an Id field (Guid) and a Properties field (string). These properties provide additional data to the C1Scheduler control, such as labels, reminders and mappings to other scheduler objects. Notice that MyAppointment also implements the INotifyPropertyChanged interface, which is necessary with MVVM.


public class MyAppointment : INotifyPropertyChanged  
{  
    public MyAppointment()  
    {  
        Id = Guid.NewGuid();  
    }  
    public Guid Id { get; set; }  

    private string _subject = "";  
    public string Subject  
    {  
        get { return _subject; }  
        set  
        {  
            if (_subject != value)  
            {  
                _subject = value;  
                OnPropertyChanged("Subject");  
            }  
        }  
    }  

    private string _location = "";  
    public string Location  
    {  
        get { return _location; }  
        set  
        {  
            if (_location != value)  
            {  
                _location = value;  
                OnPropertyChanged("Location");  
            }  
        }  
    }  

    private DateTime _start;  
    public DateTime Start  
    {  
        get { return _start; }  
        set  
        {  
            if (_start != value)  
            {  
                _start = value;  
                OnPropertyChanged("Start");  
            }  
        }  
    }  

    private DateTime _end;  
    public DateTime End  
    {  
        get { return _end; }  
        set  
        {  
            if (_end != value)  
            {  
                _end = value;  
                OnPropertyChanged("End");  
            }  
        }  
    }  

    private string _description = "";  
    public string Description  
        {  
            get { return _description; }  
            set  
            {  
                if (_description != value)  
                {  
                    _description = value;  
                    OnPropertyChanged("Description");  
                }  
            }  
        }  

    private string _properties = "";  
    public string Properties  
    {  
        get { return _properties; }  
        set  
        {  
            if (_properties != value)  
            {  
                _properties = value;  
                OnPropertyChanged("Properties");  
            }  
        }  
    }  

    public event PropertyChangedEventHandler PropertyChanged;  
    protected void OnPropertyChanged(string propertyName)  
        {  
            if (PropertyChanged != null)  
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));  
        }  
}  

To help with data binding in XAML, add an instance of your view model to your application resources. For instance, add the following XAML in your App.xaml file.


<Application.Resources>  
    <local:MainViewModel x:Key="mainViewModel" />  
</Application.Resources>  

Now we’re ready to bind C1Scheduler to the Appointments collection on our View Model. The property we set our source collection to is c1Schedule.DataStorage.AppointmentStorage.DataSource. To set this in XAML we’ll use a NestedPropertySetter helper.


<c1:C1Scheduler x:Name="scheduler">  
    <c1:NestedPropertySetter  
        PropertyName="DataStorage.AppointmentStorage.DataSource"  
        Value="{Binding Appointments, Source={StaticResource mainViewModel}}" />  

</c1:C1Scheduler>  

Now the Appointments collection is bound to C1Scheduler, but we need to also map each of our objects properties to the C1Schedule model. We can accomplish this by setting more nested properties. Below is the complete scheduler markup.


<c1:C1Scheduler x:Name="scheduler">  
    <c1:NestedPropertySetter  
        PropertyName="DataStorage.AppointmentStorage.DataSource"  
        Value="{Binding Appointments, Source={StaticResource mainViewModel}}" />  
    <c1:NestedPropertySetter  
        PropertyName="DataStorage.AppointmentStorage.Mappings.AppointmentProperties.MappingName"  
        Value="Properties"/>  
    <c1:NestedPropertySetter  
        PropertyName="DataStorage.AppointmentStorage.Mappings.Body.MappingName"  
        Value="Description"/>  
    <c1:NestedPropertySetter  
        PropertyName="DataStorage.AppointmentStorage.Mappings.End.MappingName"  
        Value="End"/>  
    <c1:NestedPropertySetter  
        PropertyName="DataStorage.AppointmentStorage.Mappings.IdMapping.MappingName"  
        Value="Id"/>  
    <c1:NestedPropertySetter  
        PropertyName="DataStorage.AppointmentStorage.Mappings.Location.MappingName"  
        Value="Location"/>  
    <c1:NestedPropertySetter  
        PropertyName="DataStorage.AppointmentStorage.Mappings.Start.MappingName"  
        Value="Start"/>  
    <c1:NestedPropertySetter  
        PropertyName="DataStorage.AppointmentStorage.Mappings.Subject.MappingName"  
        Value="Subject"/>  
</c1:C1Scheduler>  

You could instead set each of these nested properties in code. Now you have a fully bound C1Scheduler control and you can serialize the collection of appointments in your View Model.

Appointment Serialization

Next, I will show a simple serialization exercise you can use in your scheduling apps. Consider the following static class, ScheduleDataStorage. It contains two static methods: one to load appointments and the other to save them. Each takes advantage of the XmlSerializer class to quickly export our custom business objects into a readable XML file. The XML file is stored within the app’s local folder just like we did with the import and export methods earlier in this post.


public static class ScheduleDataStorage  
{  
    public static async Task<List<Appointment>> LoadAppointmentsAsync()  
    {  
        string _filename = "appointments.xml";  
        List<Appointment> appointments = new List<Appointment>();  
        XmlRootAttribute root = new XmlRootAttribute("Appointments");  
        System.Xml.Serialization.XmlSerializer serializer = new XmlSerializer(appointments.GetType(), root);  

        var folder = Windows.Storage.ApplicationData.Current.LocalFolder;  
        var file = await folder.CreateFileAsync(_filename, Windows.Storage.CreationCollisionOption.OpenIfExists);  
        IRandomAccessStream stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);  

        try  
        {  
            appointments = serializer.Deserialize(stream.AsStreamForRead()) as List<Appointment>;  
        }  
        catch { }  

        return appointments;  

    }  

    public static async Task SaveAppointments(List<Appointment> appointments)  
    {  
        string _filename = "appointments.xml";  
        var folder = Windows.Storage.ApplicationData.Current.LocalFolder;  
        var file = await folder.CreateFileAsync(_filename, Windows.Storage.CreationCollisionOption.OpenIfExists);  
        XmlRootAttribute root = new XmlRootAttribute("Appointments");  
        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(appointments.GetType(), root);  
        IRandomAccessStream stream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);  

        serializer.Serialize(stream.AsStreamForWrite(), appointments);  
        await stream.FlushAsync();  
        stream.Dispose();  
    }  
}  

Now, in true MVVM-fashion, we can modify our MainViewModel class to manage the saving and loading where it needs to. Consider the modified View Model:


public class MainViewModel  
{  
    public ObservableCollection<MyAppointment> Appointments { get; private set; }  

    public async Task LoadAppointments()  
    {  

        List<MyAppointment> appointments = await ScheduleDataStorage.LoadAppointmentsAsync();  
        this.Appointments = new ObservableCollection<MyAppointment>();  
        foreach (MyAppointment a in appointments)  
        {  
            this.Appointments.Add(a);  
        }  
    }  

    public async Task SaveAppointments()  
    {  
        List<MyAppointment> appointments = new List<MyAppointment>();  
        foreach (MyAppointment a in this.Appointments)  
        {  
            appointments.Add(a);  
        }  
        await ScheduleDataStorage.SaveAppointments(appointments);  
    }  

    public MainViewModel()  
    {  
        // create sample appointment  
        Appointments = new ObservableCollection<MyAppointment>();  
        Appointments.Add(new MyAppointment { Subject = "New Appointment", Start = DateTime.Now, End = DateTime.Now.AddHours(1) });  
    }  
}  

Throughout your app you can now easily save and load appointments directly from your view model, whether it be a button command or performed at initialization. The benefit to serializing the appointment data yourself is that you have complete control over the file manipulation and format for your app. For instance, you may create an app that allows the user to create multiple schedules. You would want each schedule to have its own storage file. This approach would allow such solutions.

Conclusion

With ComponentOne Scheduler for WinRT XAML we can build a scheduling app in record time. Since the control handles the entire appointment creation and editing process, all we have to do is handle the back end. This article has covered two scenarios for storing data locally. The techniques shown can even be used for other storage scenarios, such as saving to the cloud. The import/export approach is the fastest and the Binding approach is more “free.” While each scenario may not be too different as far as performance or technique goes, one will be better for your app. You can download a sample below that shows the full implementation of the binding and mapping approach using MVVM. Check the installed samples or the online documentation for more examples on the ImportAsync and ExportAsync methods. Download Sample

ComponentOne Product Manager Greg Lutz

Greg Lutz

comments powered by Disqus