Wednesday, June 10, 2015

Binding ScheduleView to Database part 1: QuickStart

In this article we will create simple solution of Telerik ScheduleView control, that his data source is Entity framework.

This article is part of a guide, which aims to explain how to use Telerik ScheduleView control with a database. See here the agenda.

The source code of this article, is available on GitHub.

OK, let's start.


1. Create the project


1.1 Open visual studio, and create a new Telerik WPF project.





1.2 In the wizard, check Use implicit styles, adn press Next.




1.3. Scroll down to Telerik.Window.Controls.ScheduleView, and check it. Press Next.



1.4. Check Copy Xaml files to project, Select your favorite theme, and press Finish.



New project created!

1.5. Install Entity framework [6.0 version or higher] package.
1.6 Finnaly, install Prism.Mvvm NuGet package [or your favorite MVVM framework like MvvmLight]

OK! now all is ready.


2. Model


The most important model is Appointment. What Appointment mean? Take a diplomatic answer: Appointment is an object that implements the IAppointment interface :) It is not a joke, but correct answer. Every appointment shown in The ScheduleView control is a class that implements the IAppointment interface.

We can implement the IAppointment ourselves, but now we go the short way, and use ready class of Telerik, named .... Appointment! [surpricing name :)]


Definition of AppointmentModel Object

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Telerik.Windows.Controls.ScheduleView;

namespace DatabaseBindingSample.Models
{
    [Table("Appointments")]
    public class AppointmentModel : Appointment
    {
        [Key]
        public int Id { get; set; }
    }
}


As you can see, our class is derived from the Appointment class, and we do only one change: we add a Id property. This property will be mapped to primary-key column.
Notice, that we will add the 'Model' suffix to our models, to avoid confusing with Telerik's classes

Definition of DbContext

using System.Data.Entity;

namespace DatabaseBindingSample.Models
{
    public class SchedulingDbContext : DbContext
    {
        public SchedulingDbContext()
            : base("YOUR_CONNECTION_STRING")
        {
        }

        public DbSet<Appointmentmodel> Appointments { get; set; }


        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            var appointmentBuilder = modelBuilder.Entity<AppointmentModel>();

            appointmentBuilder
                .Ignore(app => app.TimeZone)
                .Ignore(app => app.Location)
                .Ignore(app => app.Url)
                .Ignore(app => app.UniqueId);
        }
    }
}


Replace "YOUR_CONNECTION_STRING" with your actual connection string, or name of connection string defined in App.Config file.


OK, the model is ready, and we can create the database from the model. [I assume that you have experience with Code first and migrations]

Alternatively, if  you prefer the 'Database first' approach, you can run this DDL script:
CREATE TABLE dbo.Appointments(
    Id INT NOT NULL IDENTITY PRIMARY KEY,
    Body NVARCHAR(MAX) NULL,
    [Subject] NVARCHAR(MAX) NULL,
    [Start] DATETIME NOT NULL,
    [End] DATETIME NOT NULL,
    IsAllDayEvent BIT NOT NULL,
    Importance INT NOT NULL
)



3. View


Open the MainWindow, and update it like the following Xaml code:
<Window x:Class="DatabaseBindingSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
        Title="MainWindow" Height="350" Width="525"
        WindowState="Maximized"

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        
        <telerik:RadScheduleView AppointmentsSource="{Binding Appointments}"
                                 VisibleRangeChangedCommand="{Binding LoadAppointmentsCommand}"
                                 VisibleRangeChangedCommandParameter="{Binding VisibleRange, RelativeSource={RelativeSource Self}}"

                                 Grid.Row="0">
            <telerik:RadScheduleView.ViewDefinitions>
                <telerik:MonthViewDefinition />
                <telerik:DayViewDefinition />
            </telerik:RadScheduleView.ViewDefinitions>
        </telerik:RadScheduleView>
        
        <Button Grid.Row="1" Content="Save" Command="{Binding SaveCommand, Mode=OneTime}" Margin="6" Padding="10,6" HorizontalAlignment="Center" />
    </Grid>
</Window>


We have three bindings to the view:

  • AppointmentsSource, is the collection of Appointments.
  • VisibleRangeChangedCommand : This command executed when the user change the VisibleRange of the ScheduleView control [For example: when the user go to next month, or change the ViewDefinition from MonthViewDefinition to DayViewDefinition]
  • The button Command.
VisibleRangeChangedCommandParameter bound to actual range of the control, and it passed to VisibleRangeChangedCommand command.



4. ViewModel


This is our MainViewModel:

public class MainViewModel
{
    #region ctor; fields

    public MainViewModel()
    {
        this.context = new SchedulingDbContext();
        this.appointments = new RadObservableCollection<AppointmentModel>();
        this.loadAppointmentsCommand = new DelegateCommand<IDateSpan>(LoadAppointments);
        this.saveCommand = new DelegateCommand(Save);

        this.appointments.CollectionChanged += appointments_CollectionChanged;
    }

    private readonly SchedulingDbContext context;
    private readonly RadObservableCollection<AppointmentModel> appointments;
    private readonly DelegateCommand<IDateSpan> loadAppointmentsCommand;
    private readonly DelegateCommand saveCommand;

    #endregion // ctor; fields

    #region properties

    public RadObservableCollection<AppointmentModel> Appointments
    {
        get { return appointments; }
    }

    #endregion // properties

    #region commands

    public DelegateCommand<IDateSpan> LoadAppointmentsCommand
    {
        get { return loadAppointmentsCommand; }
    }

    public DelegateCommand SaveCommand
    {
        get { return saveCommand; }
    }

    #endregion // commands

    #region methods

    private void LoadAppointments(IDateSpan dateSpan)
    {
        appointments.Clear();
        IQueryable<AppointmentModel> query = context.Appointments.Where(app => app.Start >= dateSpan.Start && app.End <= dateSpan.End);
        appointments.AddRange(query);
    }

    private void Save()
    {
        context.SaveChanges();
    }

    void appointments_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
        {
            context.Appointments.AddRange(e.NewItems.Cast<AppointmentModel>());
        }

        else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
        {
            context.Appointments.RemoveRange(e.OldItems.Cast<AppointmentModel>());
        }
    }

    #endregion // methods
}


5. Finally


Now we need set the MainViewModel as DataContext of MainWindow. Don't do it in MainWindow.xaml to avoid querying the database at design time.

Therefore, Add this code to App.xaml.cs file:
protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    var viewModel = new ViewModels.MainViewModel();
    var mainWindow = new MainWindow
    {
        DataContext = viewModel
    };

     this.MainWindow = mainWindow;
     this.MainWindow.Show();
}


That's it! we do it!

You can run the application, and Read/Create/Update/Delete appointments. When you click Save button, all your changes saved in database.


Conclusion


In this article we saw the shortest way how to bind ScheduleView control to database. In the next article, we will review the functionality of this road, and how to improve it.

No comments:

Post a Comment