in
Forums
Blogs
Files
Devexpress.Com
Client Center
Support Center
DevExpress Channel

Scheduler

  • Multi-user scheduling and Outlook Synchronization

     

    Multi-User Scenario

    In multi-user scenarios, each application user has limited access to common data.  This means that the application logic doesn't allow a user to view or edit appointments that belong to other users.

    A multi-user scheduler scenario:

    Data storing

    MS Outlook calendar must contain data for all users. Common data for all users must be stored in a database. Each appointment must have data related to the User.

    Client application

    Applications must be configured for a personal user. For instance, a current value of a UserID may be stored in application settings.  Otherwise, an application can have a login form with user credential information. In this situation, the UserID can be retrieved from the database, or as an entry parameter.

    Data access restriction

    Users must have access only to their own appointments. This can be achieved by using SQL statements with parameters, by handling necessary events (e.g. FilterAppointments, AllowAppointmentXXX, etc.), by creating read-only form descendants, or by something else.

    Solution

    How to perform outlook synchronization only for a specific user and cancel all data operations for another user?

    To link an XtraScheduler appointment to a specific user, use the CustomFields technique. The UserID value will be stored in the corresponding CustomField.

    As for MS Outlook AppointmentItem, its properties can be used to achieve the same result. To accomplish this, we can create a UserProperty in an Outlook appointment. This is an extended property for storing user information. In our scenario, the application code will store the current UserID to this outlook object. This information is useful to define which outlook data needs to be updated and which one must be skipped.

     

    As such, the UserID can be used in the client application logic. This code demonstrates this:

    void synchronizer_AppointmentExportSynchronizing(object sender, AppointmentSynchronizingEventArgs e) {
        OutlookAppointmentSynchronizingEventArgs args = (OutlookAppointmentSynchronizingEventArgs)e;
        // Prevent appointments with other UserID from being deleted
        if (e.Operation == SynchronizeOperation.Delete) {
            if (GetOutlookAppointmentUserId(args.OutlookAppointment) != UserID) {
                e.Cancel = true;
    	        return;
            }
       }
       // Mark the new Outlook appoinment item with UserID
       if (e.Operation == SynchronizeOperation.Create) {
           SetOutlookAppointmentUserId(args.OutlookAppointment);
       }
    }
    void synchronizer_AppointmentImportSynchronizing(object sender, AppointmentSynchronizingEventArgs e) {
        if (e.Operation == SynchronizeOperation.Delete)
            return;
    
        // prevent appointments with the other UserID from being created or updated
        OutlookAppointmentSynchronizingEventArgs args = (OutlookAppointmentSynchronizingEventArgs)e;
        e.Cancel = GetOutlookAppointmentUserId(args.OutlookAppointment) != UserID;
    }
    
    private void SetOutlookAppointmentUserId(_AppointmentItem aptItem) {
        OutlookInterop.AppointmentItem olApt = (OutlookInterop.AppointmentItem)aptItem;
        OutlookInterop.UserProperty prop = olApt.UserProperties.Add(OutlookUserPropertyName, 
            OutlookInterop.OlUserPropertyType.olNumber, false, System.Reflection.Missing.Value);
        try {
            prop.Value = UserID;
        } finally {
            Marshal.ReleaseComObject(prop);
        }
    }
    

    Note: Outlook appointments that don't have a UserID will be ignored during synchronization.

    Now you can perform outlook synchronization for several users separately.

    For a complete example, see the "How to implement a multi-user scheduling with the ability to synchronize appointments with Outlook independently?" KB article

  • Using XtraScheduler Services - Part 1

    The current v2008 vol 1 release of the XtraScheduler Suite provides the capability of using services to perform common tasks, such as date-time navigation, navigation through resources, keyboard and mouse event handling.

    An introductory article concerning service basics is available. Review it for details of a service concept. Do not miss the Services section of our help when looking for more information.

    The Knowledge Base How to implement simple time navigation via services in Scheduler views  article illustrates the use of IDateTimeNavigationService to navigate a displayed time frame forward and back.

    In this post, we'll focus on keyboard and mouse services, and explain the service substitution technique.

    The DevExpress.XtraScheduler.Services.KeyboardHandlerService provides the capability to determine which key is pressed or released. It implements the IKeyboardHandlerService interface with the following delegates:

    • OnKeyDown(System.Windows.Forms.KeyEventArgs e) is called when a key is pressed. KeyDown events are repeated if the key is held down.
    • OnKeyUp(System.Windows.Forms.KeyEventArgs e) is called when a key is released.
    • OnKeyPress(System.Windows.Forms.KeyPressEventArgs e) is called after the key is pressed.

    To handle these events you should first create a new class inherited form the DevExpress.XtraScheduler.Services.KeyboardHandlerServiceWrapper, and override its methods as needed. The following code snippet illustrates how this can be done:

    using DevExpress.XtraScheduler.Services;
    //...
    public class MyKeyboardHandlerService : KeyboardHandlerServiceWrapper  
    { 
        IServiceProvider provider;
    
        public MyKeyboardHandlerService(IServiceProvider provider, IKeyboardHandlerService service)
            : base(service)
        {
            this.provider = provider;            
        }
    
        public override void OnKeyDown(KeyEventArgs e)
        {
            if (e.Control)
            {
                // Do something when CTRL key is pressed.
            }
            base.OnKeyDown(e);
        }
    }
    

    We should replace the existing key handling service to put our changes into effect. Get a service, remove it and then add a new custom service.

    IKeyboardHandlerService oldKeyboardHandler = (IKeyboardHandlerService)schedulerControl1.GetService(typeof(IKeyboardHandlerService));
    if (oldKeyboardHandler != null)
    {
        MyKeyboardHandlerService newKeyboardHandler = new MyKeyboardHandlerService(schedulerControl1, oldKeyboardHandler);
        schedulerControl1.RemoveService(typeof(IKeyboardHandlerService));
        schedulerControl1.AddService(typeof(IKeyboardHandlerService), newKeyboardHandler);
    }
    

    That's it! Mouse handling service works in the same manner. The following picture illustrates a service substitution. When you replace a default service with MyMouseService class (blue dotted lines) a call is routed to overrides and then to default method implementation. A wrapper class enables you to override only the method you need - the OnMouseWheel method, a call's route is shown below.

     

    For information on the role of wrapper classes refer to the How to: Use Service Substitution in the Scheduler topic.

    The XtraScheduler's mouse service overview:

    The DevExpress.XtraScheduler.Services.MouseHandlerService provides the capability to respond to mouse events. It implements the IMouseHandlerService interface with the following delegates:

    • OnMouseMove(MouseEventArgs e) - occurs when the user moves the mouse pointer over a control.
    • OnMouseDown(MouseEventArgs e) - occurs when the user presses the mouse button.
    • OnMouseUp(MouseEventArgs e) - occurs when the user releases the mouse button.
    • OnMouseWheel(MouseEventArgs e) - occurs when the mouse wheel moves.

    Review the Knowledge Base article How to zoom in and out in the Day View using mouse wheel and Scheduler services and attached projects to see keyboard and mouse services substitution technique in action.

  • XtraScheduler as a Service Container

    Functionality enhancements of XtraScheduler may require intense code modifications, and result in properties and methods becoming obsolete. To minimize the chance of breaking changes in future versions, we decided to separate the actual implementation of a feature in code from the implementation of methods used to access this feature. We can manage this task via extensive use of interfaces. It was also necessary to find a way to break apart dependencies in the application, and achieve looser coupling between its parts. This approach usually results in greater extensibility.

    To achieve this, we decided to follow a concept of services and service containers, implemented in .NET framework.

    What is a service? Service is a class with a known interface, addressable by its type. A service can be obtained from a service provider, instantiated and stored within a service container. The XtraScheduler incorporates classes and interfaces that inherit from base interfaces, contained within the System.ComponentModel namespace of .NET - first of all, System.IServiceProvider and System.ComponentModel.Design.IServiceContainer. Since the Scheduler implements the IServiceContainer interface, you can obtain a service by supplying the type of the service to the GetService method, as illustrated below:

    IDateTimeNavigationService navigationService = (IDateTimeNavigationService)scheduler1.GetService(typeof(IDateTimeNavigationService));
    if (navigationService != null)
    {
        // Use methods of service:
        navigationService.NavigateBackward();
        navigationService.NavigateForward();
        navigationService.GotoToday();
        // ... and so on.
    }
    

    This code tries to get a reference to a service which is responsible for moving backwards and forwards to different points in time within the scheduler's coordinate system. An implicit cast is required because GetService method returns an Object.

    Note that the service availability is not guaranteed. Whenever you query a service type by calling the GetService method, you must always check if GetService returned a valid object.

    Services can present challenges to a developer because of the service's obscure nature - you should possess a prior knowledge of the service type and its methods to use it. There is no Intellisense to guide you.

    You can add your own services to the service container via the AddService method, and use them throughout your application. Do not forget to remove them using a RemoveService method for the proper disposal of a container when it is no longer needed.

    When XtraScheduler services emerge, we'll announce and document them to make new options available to developers.

    For deeper insight into service containers and related concepts, refer to the "Lightweight Containers and Plugin Architectures: Dependency Injection and Dynamic Service Locators in .NET" article by Daniel Cazzulino. He analyzes an article "Inversion of Control Containers and the Dependency Injection pattern" by Martin Fowler in light of .NET, and elaborates this idea.

  • Synchronization with MS Outlook

    The time has come to explain the recommended technique for performing MS Outlook calendar synchronization with XtraScheduler.

    1. Synchronizer

    The XtraScheduler Suite provides a class designed specifically for export/import operations - the AppointmentSynchronizer. In real life you should use two derived classes - OutlookImportSynchronizer and OutlookExportSynchronizer. The synchronizer class is bound to a data field via its ForeignIdFieldName property, as illustrated in the picture below:

    The synchronization procedure starts with the creation of the required type of synchronizer. To accomplish this, use either simple constructors or specialized methods - CreateOutlookExportSynchronizer and CreateOutlookImportSynchronizer.

    Then, it is necessary to specify initial parameters for the synchronizer to ensure its correct operation. It has two major properties - the ForeignIdFieldName and the CalendarFolderName. The first property, as illustrated above, specifies a database field's name to store the Outlook AppointmentItem's EntryID value. The CalendarFolderName property can be left unassigned. In this instance, it points to the default MS Outlook Calendar, and has a "\\Personal Folders\Calendar" value. To modify this property, use the ISupportCalendarFolders interface of the synchronizer object, as in the following code snippet:

    AppointmentImportSynchronizer synchronizer = schedulerStorage.CreateOutlookImportSynchronizer();
    ((ISupportCalendarFolders)synchronizer).CalendarFolderName = "\\Personal Folders\MyCalendar";
    

    You can take advantage of the OutlookExchangeHelper class and its GetOutlookCalendarPaths method, to determine how the calendar can be reached if MS Outlook is installed. This method returns an array of strings representing calendar locations. The OutlookExchangeHelper class resides within the DevExpress.XtraScheduler.Outlook namespace. The code sample below illustrates this approach:

    using DevExpress.XtraScheduler.Outlook;
    // ...
    
    public static string[] OutlookCalendarPaths { 
        get {
            if (outlookCalendarPaths != null)
                return outlookCalendarPaths;
    
            try {
                outlookCalendarPaths = OutlookExchangeHelper.GetOutlookCalendarPaths();
            } catch {
                ReportOutlookError("get calendars from MS Outlook");
                outlookCalendarPaths = new string[0];
            }
            return outlookCalendarPaths;
         }
    }
    

    2. Synchronization

    To start the process, simply call the AppointmentSynchronizer.Synchronize method. But to make the most of the process execution, you are advised to handle the corresponding events, and decide for each calendar item how they should be handled. This approach is flexible enough to implement any kind of synchronization mechanism, so you are not restricted by any predefined settings.

    It's worth mentioning that the SourceObjectCount property of the synchronizer returns the number of items to process. It assists you in organizing your work, and preparing supplementary elements, such as a progress bar to indicate the process completion.

    As usual, there is an event pair for synchronization activity - AppointmentSynchronizing occurs before and AppointmentSynchronized occurs after the item is synchronized. Handling the event enables you to make a decision based on the current item properties. You can leave the item intact, modify it or perform other actions that suit your synchronization model.

    Setting the OutlookAppointmentSynchronizingEventArgs.Cancel to true within the AppointmentSynchronizing event handler cancels the operation. In this instance, the AppointmentSynchronized event won't occur.

    3. Event Handling

    Examine synchronizer events in more detail. The SynchronizeOperation enumeration defines the available operation types - SynchronizeOperation.Create, SynchronizeOperation.Replace and SynchronizeOperation.Delete. The operation type indicates what will happen with the target calendar item - should it be deleted, modified or a new item is created.

    The following tables present default variants of synchronization behavior. The checkmark sign indicates the presence of appointment, the cross sign - its absence.

    Import from Outlook

    Outlook item (source)

    XtraScheduler item (destination)

    Operation

    Create

    Replace

    Delete

     

    Export to Outlook

    Outlook item (destination)

    XtraScheduler item (source)

    Operation

    Delete

    Replace

    Create

    By analyzing the AppointmentSynchronizingEventArgs.Operation value and OutlookAppointmentSynchronizingEventArgs.Appointment properties, you can implement virtually any appointment conflict resolution scenario, while synchronizing XtraScheduler with MS Outlook. If the target calendar application contains both previous and new appointments, and you want to leave the existing appointments intact, you can set e.Cancel to true to stop the operation when e.Operation is equal to SynchronizeOperation.Replace.

    Other scenarios may require modifying the operation type within the AppointmentSynchronizing event handler. Instead of Replace you may choose to Delete the calendar item. This can be easily accomplished by changing the AppointmentSynchronizingEventArgs.Operation from Replace to Delete.

    4. Appointment

    You do not have to create a custom field and specify a custom mapping to enable the export/import appointment functionality. The appointment's foreign ID relationship is maintained internally. Also, there is IGetAppointmentForeignId interface with the GetForeignId method, which provides a foreign ID value for the specified appointment.

    The export and import operations are schematically represented in the following pictures:

    Export to MS Outlook

    Import from MS Outlook

    Please note that the export operation updates the OutlookEntryID field of the appointments data source. Ensure that all changes are committed to the data source when export or import operations are completed.

     

  • XtraScheduler tools - find a meeting time

    Today we feature a new XtraScheduler helper function, which enables you to find free time slots within the specified time interval.

    One of our customers asked whether it is possible to find free time periods suitable for a new meeting arrangement. The time interval should be queried based on a specific resource.Our developers appreciated this functionality and decided that it is essential for real-life calendar applications. So the FreeTimeCalculator class was born.

    This class resides within the DevExpress.XtraScheduler.Tools namespace, starting with the 7.3.6 version of DevExpress Suite. It enables you to find all available free intervals within the specified period of time. Also, it can be used to find the nearest free slot with the specified duration. The search technique calculates the time in use based on the appointments currently contained within the Scheduler Storage.

    The FreeTimeCalculator class provides the following methods and properties:

    1. CalculateFreeTime method - finds all intervals that are not in use.

    public TimeIntervalCollection CalculateFreeTime(TimeInterval interval) - performs a search within the specified interval.
    public TimeIntervalCollection CalculateFreeTime(TimeInterval interval, Resource resource) - performs a search within the specified interval for the specified resource only.

    The method returns a collection of intervals or the empty collection, if free intervals are not found.

    The following code sample illustrates the implementation of the method used to search the visible interval for free slots associated with the specified resource.

    TimeIntervalCollection CalculateFreeTime(Resource resource) {
    	FreeTimeCalculator calculator = new FreeTimeCalculator(schedulerControl1.Storage);
    	TimeInterval interval = schedulerControl1.ActiveView.GetVisibleIntervals().Interval;
    	return calculator.CalculateFreeTime(interval, resource);
    }
    

    2. FindFreeTimeInterval method searches for the nearest free interval with a specified constant duration. It has two overrides:

    public TimeInterval FindFreeTimeInterval(TimeInterval interval, TimeSpan duration, bool forward) - performs a search for the slot with the specified duration within the specified interval.
    public TimeInterval FindFreeTimeInterval(TimeInterval interval, Resource resource, TimeSpan duration, bool forward) - performs a search for the slot with the specified duration within the specified interval, and assigned to the specified resource.

    The forward flag sets the search direction. If "forward" is set to true, the search starts at the interval.Start and continues forward in time. If "forward" is set to false, it starts at interval.End, and continues backwards.

    The method returns an interval which meets the conditions or the TimeInterval.Empty value, if such an interval is not found.

    The following code sample illustrates the implementation of the method used to find a free slot with specified duration before the end of the current work week.

    TimeInterval FindFreeTimeInterval(TimeSpan duration) {
    	FreeTimeCalculator calculator = new FreeTimeCalculator(schedulerControl1.Storage);
    	DateTime startOfWeek = DevExpress.XtraScheduler.Native.DateTimeHelper.GetStartOfWeek(DateTime.Now);
    	TimeInterval interval = new TimeInterval(DateTime.Now, startOfWeek.AddDays(5));
    	return calculator.FindFreeTimeInterval(interval, duration, true);
    }
    

    3. ApplyAppointmentFilters property.

    This property enables selection of the appointments to use when the search is performed. If ApplyAppointmentFilters is set to true, the filter is applied to the appointment collection within the storage before the search starts. Otherwise, all appointments contained within the storage are used in the free time calculations. Default value is true.

    4. IntervalFound event.

    Enables you to change a free interval after it is found. This event is raised for each interval before it is added to the collection.

    The FreeIntervals property of IntervalFoundEventArgs object provides access to a TimeIntervalCollectionEx collection, which contains a single element - the located time interval. You can edit this interval by modifying its start, end and duration. The Add method of the TimeIntervalCollectionEx class allows intersecting time intervals to be joined and represented within the collection as a single time interval item. The Remove method excludes the specified time interval from the existing time interval, so the original interval is changed or split into two parts.

    The following example illustrates how the IntervalFound event can be used to find the spare time periods in work time only within the work week.

    To accomplish this, subscribe to the event and then call the FindFreeTimeInterval method.

    private TimeInterval FindInterval(TimeInterval interval, TimeSpan duration) {            
        FreeTimeCalculator calculator = new FreeTimeCalculator(schedulerControl1.Storage);
        calculator.IntervalFound += new IntervalFoundEventHandler(OnIntervalFound);            
        TimeInterval freeInterval = calculator.FindFreeTimeInterval(interval, duration, true);
        return freeInterval;
    }
    

    The event handler filters the discovered free time intervals.

    private void OnIntervalFound(object sender, IntervalFoundEventArgs args)
    {   TimeIntervalCollectionEx freeIntervals = args.FreeIntervals;
        DateTime start = freeIntervals.Start.Date.AddDays(-1);
        DateTime end = freeIntervals.End;
        while (start < end) {
            RemoveSpareTime(freeIntervals, start);
            RemoveNonworkingDays(freeIntervals, start);
            start += TimeSpan.FromDays(1);
        }
    }        
    private void RemoveSpareTime(TimeIntervalCollectionEx freeIntervals, DateTime date) {
        TimeInterval spareTime = new TimeInterval(date.AddHours(18), TimeSpan.FromHours(15));            
        freeIntervals.Remove(spareTime);
    }
    private void RemoveNonworkingDays(TimeIntervalCollectionEx freeIntervals, DateTime date) {
        bool isWorkDay = schedulerControl1.WorkDays.IsWorkDay(date);
        if (!isWorkDay)
            freeIntervals.Remove(new TimeInterval(date, TimeSpan.FromDays(1)));            
    }
    

    In a busy schedule, shown in the picture below, we have to find a free one-hour interval in work time. The search starts at the selected time cell and goes forward.

    Start the search

    The following screen shot displays the result. A time for a new one hour meeting is found in the next day's work time. It is indicated by a time cell selection, the message box provides information on the time interval.

    Free Time Found

    You can find the source code of the sample project in the Knowledge Base article How to find free time intervals for a meeting arrangement.

  • FetchAppointments Event - Tackling Large Datasets

    The new XtraScheduler control implementation for web pages - the ASPxScheduler - generates numerous questions from our customers. One major concern is the amount of data requested by the control for displaying appointments.

    What happens if there is a huge set of data retrieved over a long period of time by the XtraScheduler?  Is there any way to control how the data is queried? Basically, the user does not need all the data at one time.

    Tackling the problem of large datasets is the reason for user attempts to find the appropriate control's event, capable of data pre-filtering or pre-fetching. The FetchAppointment event appears to be the most common answer given by our support team.

    Now we've come to the conclusion that the function and the technique of this event requires clarification.

    The purpose of the FetchAppointments event is usually misunderstood in some way. Basically, it was designed to reduce the amount of data fetched from the data source at one time. When the Scheduler changes its visible time interval, it requests Storage for the displayed appointments. If the FetchAppointment event is handled, it is raised; otherwise, the Storage operates according to its built-in logic. The Storage has a cache, which contains previously fetched appointments. Cache operations may result in an excessive amount of appointment data being retrieved. To prevent this, you can handle the FetchAppointment event to implement your own logic for populating the data source.

    How Appointments are Fetched

    This task can be accomplished in different ways.  One of the most obvious is the use of a parameterized query to fill the data adapters with data. In this instance, it fetches appointment data for the required time intervals only. You can decide to extend the time interval to ensure pre-fetching, and fill the SchedulerStorage's Appointment Cache with extra data. It depends on the view the user is switching to, and the assumptions on probable date navigation. This technique suits the XtraScheduler best.

    The ASPxScheduler reloads data with each page reload and refresh. The task is more complicated, and may require implementation of your own caching object, which would store the data inside, decide whether it is time to fetch a new download from a remote data source.

    Note that all requested time intervals should be populated with data, and it doesn't make any sense to differentiate, or even prevent, particular data from being fetched. The task is to optimize data access, and not to prevent data for specific intervals from being fetched at all. It's up to you to select and implement the caching strategy, as there are no common solutions suitable for all cases.

    To decrease the number of calls to FetchAppointment handler, you can turn the dates highlighting feature off for the DateNavigator control.

    To get into XtraScheduler architecture specifics, refer to the following article: XtraScheduler Objects and Data Structures.

  • Holiday Scheduling with XtraScheduler/ASPxScheduler

    Let's say you manage a call center or a nursing station in a hospital. President Lincoln's holiday is approaching but you need some employees to still cover the shift. In this scenario, you'll have to still award them their holiday but shift this with another working day. You might need to shift weekend days to the middle of the next week, and therefore add some new holidays instead of weekends. The ASPxScheduler and XtraScheduler can handle this scenario with relative ease. Let me show you how:

    Here is the initial situation illustrated below:

    To create a custom schedule, perform the following steps:

    Step 1. Add national holidays

    In the image you'll see the two holidays marked as red:

    private void AddNationalHolidays() {
    	WorkDaysCollection workDays = schedulerControl1.WorkDays;
    	workDays.BeginUpdate();
    	try {
    		workDays.AddHoliday(new DateTime(2008, 2, 12), "Lincoln's Birthday");
    		workDays.AddHoliday(new DateTime(2008, 2, 14), "Valentine's Day");
    	} finally {
    		workDays.EndUpdate();
    	}
    }

     

    Step 2. Convert weekends to working days

    To convert the weekend days into working days use the method below. On the image, Saturday the 9th and Sunday the 10th are converted to black to show that they are now working days.

    private void ConvertWeekendsToWorkDays() {
    	WorkDaysCollection workDays = this.schedulerControl1.WorkDays;
    	workDays.BeginUpdate();
    	try {
    		DateTime workingSat = new DateTime(2008, 2, 9);
    		workDays.Add(new ExactWorkDay(workingSat, "Working Saturday"));
    
    		DateTime workingSun = new DateTime(2008, 2, 10);
    		workDays.Add(new ExactWorkDay(workingSun, "Working Sunday"));
    	} finally {
    		workDays.EndUpdate();
    	}
    }

     

    Step 3. Create shifted weekend days

    To create new holidays instead of working weekend days:

    private void ShiftWeekendHolidays() {
    	WorkDaysCollection workDays = schedulerControl1.WorkDays;
    	workDays.BeginUpdate();
    	try {
    		workDays.AddHoliday(new DateTime(2008, 2, 13), "Shifted Saturday");
    		workDays.AddHoliday(new DateTime(2008, 2, 15), "Shifted Sunday");
    	} finally {
    		workDays.EndUpdate();
    	}
    }

    Now you have six continuous holidays and some very happy employees.

  • XtraScheduler Objects and Data Structures

    We've seen in the past that some customers have had some difficulties in using XtraScheduler, and that this stems from misunderstandings about the scheduler's data and class model. This article is meant to help clarify some of the basic concepts of the objects and data structures used within XtraScheduler. It provides information on the hierarchy of objects and their interactions at different layers. This information will help you avoid some common implementation mistakes, those that are often caused by a lack of a general idea concerning the product's inner structure and behavior.

    We'll start off with the scheduler's control model to gain some insight into which objects pertain to which layers in the suite. Then we'll discuss the technique of appointment inheritance and custom mapping that will enable you to modify the scheduler's inner structures. After that we'll present an overview of the scheduler's event model, especially with a description of the order in which events are raised.

    Logical layers of the Scheduler

    Internally, the Scheduler makes use of a set of layers to logically separate functionality. There are three logical layers: the data layer, the storage layer, and the control layer. Before an object, such as an appointment, is displayed within the Scheduler display area, it has to make its way through all these layers. At each layer, it gets transformed or wrapped into another object with its own specific attributes for that layer. These modifications are necessary, in particular for the case of recurring appointments, because their quantity and object hierarchy used do not correspond to the information contained within data source records.

    Data Layer

    The data layer contains a data source. Any object that implements the IList interface may become a data source for the Scheduler. Some of these objects are listed below:

    • Simple objects, such as lists, arrays, or collections. It could be a list of user-defined classes, representing the Appointment business object.
    • ADO .NET data objects - DataSet, DataTable with DataTableRow, DataView.
    • XPO object collections
    • ...

    The layer contains data objects from the application's namespace.

    When you use a Scheduler in its unbound mode, this layer is hidden from you. Appointment data manipulation (fetching, writing back, inserting, and deleting) is accomplished via an inner storage implementation.

    Storage Layer

    The storage layer stores collections of PersistentObjects. A PersistentObject is a proxy object automatically created for a business object when it is loaded from the data source. A PersistentObject has an internal property that serves to maintain a link (a row handle, if you will) to the data source in the data layer.

    The layer contains Appointments from the DevExpress.XtraScheduler namespace.

    Notice that you shouldn't mistake this layer for any storage mechanism you may have in the data layer. The storage layer here is temporary storage for the Scheduler itself.

    Control Layer

    The control layer contains the visual representations of the persistent objects from the previous layer. These visual objects are generated from the storage layer's data. The contents of the control layer are determined by the displayed time interval, the active Scheduler View, the control properties, and event handlers.

    The appointments and resources collections in this layer include objects which have passed multiple filters on their way from the database, as well as objects that are newly created as the result of end-user actions (such as in-place editing, drag-n-drop, and so on).

    The layer contains the following objects:

    • collections of appointments (AppointmentViewInfoCollection)
    • collections of resource containers (resource headers, day view columns, single weeks, timelines)

    They belong to the DevExpress.XtraScheduler.Drawing namespace.

    Layer Interaction

    The following picture summarizes the layer interactions within XtraScheduler.

    Data Layer - Storage Layer

    A simple mapping technique is used for establishing the communication between the data and storage layers. The mapping describes how the PersistentObject's properties contained within the storage layer correspond to the data fields at the data layer.

    In unbound mode, the data layer is hidden, and the property mapping is undefined.

    The storage layer establishes a two-way communication with data source objects, which means that any addition or modification to storage objects results in the equivalent actions being performed on the data layer objects, and vice versa.

    Storage Layer - Control Layer

    The control layer interacts only with the storage layer objects. It has no direct contact with the data layer objects.

    The addition, modification, or deletion of control layer objects results in the corresponding changes being applied to the storage objects, and vice versa.

    The Storage layer has an appointment cache, populated with appointments that satisfy the currently displayed visible interval. This is the part of the working area of the Scheduler control visible to the end-user. When the Scheduler control changes its view and the visible interval is not modified, the control fetches the appointments from the cache, helping to optimize the query route and reducing the time to refresh the display area.

    It is worth mentioning here that the DateNavigator control also has its own appointment cache, which is used to highlight dates with appointments. This cache is different from the one we just described for the storage layer and has no connection with it.

    Object Interaction

    The interactions of the various objects defined in the Scheduler are illustrated in the following image.

    Storage Layer objects – Control Layer objects

    A single PersistentObject can produce several views, for example in the case when an appointment occupies more than one day and is displayed split apart, or when an appointment does not belong to a specific resource and is displayed for each resource individually.

    Every AppointmentViewInfo object belongs to a single Appointment, and this appointment object is accessible via the Appointment property. You should use this property whenever it is necessary to get the related appointment for the visual object. This usually happens within Custom Draw event handlers, such as when you process mouse clicks to determine which object was clicked (hit testing) and so on.

    Data Layer objects – Storage Layer objects

    PersistentObjects at the storage layer are created from information provided by data objects in the data layer. The storage loads data according to the specified requirements.

    A PersistentObject is created for each data object obtained from the data source. Not every PersistentObject so constructed is included into the storage collection, however: recurrence exceptions are created along with other persistent objects and stored, not in the PersistentObjectCollection, but separately in the Exceptions collection of the Pattern object.

    If a PersistentObject is created from a data object, it contains a link to its data source record (the row). The corresponding object at the Data layer can be obtained via the PersistentObject.GetSourceObject(SchedulerStorageBase storage) method. The scope of this method is limited to persistent objects originating from data objects and not for the appointment occurrences and newly created appointments.

    There are certain scenarios in which persistent objects are created by replicating existing instances. It may be by the end-user interactively editing the PersistentObject's visual representation, or by them dragging and dropping an appointment to create a new one. These objects are not linked to any data layer item and the GetSourceObject method is inapplicable.

    The following storage layer methods are useful for a direct manipulation with linked objects at the Data layer:

    public object GetObjectRow(PersistentObject obj)
    public object GetObjectValue(PersistentObject obj, string columnName)
    public void SetObjectValue(PersistentObject obj, string columnName, object val)

    Extending the PersistentObject functionality

    Custom Fields and Mappings

    You have the ability to define custom data fields and map them to custom properties of persistent objects. This mechanism enhances your ability to control actions performed with PersistentObject instances. You can use these user-defined property values within your event handlers to select items for display, to modify their visual representation, and so on.

    Inheritance

    You can also implement appointment inheritance, by creating descendant classes via the IAppointmentFactory interface of the AppointmentStorage class. You can register your own appointment class factory by means of the AppointmentStorage.SetAppointmentFactory(IAppointmentFactory factory) method.

    This technique helps you extend the set of properties of an appointment object. Of course, you are then responsible for the correct realization of this extra functionality.

    Handling Data-Related Events

    Persistent objects publish two categories of data-related events.

    The first set includes events that are raised before the action takes place. They are usually named with the "ing" ending, and are used for pre-processing and cancelling the action. An event handler in this case receives one object, which already has been modified. If you set the Cancel parameter to true, the modification is rolled back.

    Several events do not enable you to get the object in the unmodified state, so you will have to query the corresponding data object directly to obtain property values before modification.

    Then there are events in this category that receive both the original and the changed objects. For example, the AppointmentDrag event receives both the EditedAppointment and SourceAppointment objects. You should clearly distinguish these two objects when handling this event.

    The second set of events includes events with the "ed" ending, which are fired when the action has been performed. They serve for notification purposes and post-processing.

    The two events that correspond to one action and that belong to different categories form an event pair. Events for PersistentObject collections, in contrast to single objects, do not come in pairs.

    Guidelines for correct event handling are listed below.

    1. A good practice is to use BeginUpdate() / EndUpdate() blocks, because it reduces time required for the initialization and prevents the excessive raising of events. It is especially effective when event handling takes a considerable amount of time.
    2. Do not remove the object instance for which the first event in a pair is fired (the "ing" event). Its properties can be changed within the BeginUpdate() / EndUpdate() block. Deleting an object in the middle of this action, in between events, can result in some hard-to-find errors.
    3. Pay special attention to explicit Storage.RefreshData calls. Data will be reloaded, and all custom field values that are not mapped will be lost. In particular, this may happen when a Scheduler Storage operates in unbound mode.
    4. When handling the FetchAppointments event, change data in the data source at the Data layer. Do not modify data at the Storage layer, because the Storage automatically reloads its data from the Data layer's data source when this event handler is executed.
    5. When the data source is changed, and the AutoReload property is set to true, the AutoReloading event occurs, and the Storage starts an automatic refresh. Use the AutoReloading event as a notification.

    The order of events raised in typical situations is illustrated in the following table:

    Action 

    Event 

    Description 

    Scheduler View initialization

    AppointmentCollectionAutoReloading
    AppointmentCollectionLoaded
    FetchAppointments
    FilterAppointments*

     

    Visible interval modification of a View or a Control

    FetchAppointments
    Filter Appointments*

     

    Clearing the PersistentObjectStorage

    AppointmentCollectionCleared

     

    PersistentObjectStorage.DataSource substitution

    AppointmentCollectionLoaded

     

    Adding a new appointment to a collection

    AppointmentInserting AppointmentsInserted
    FilterAppointments*

     

    Occurrence appointments modification, resulting in exceptions for a particular recurrence pattern.

    AppointmentChanging
    AppointmentsInserted
    AppointmentsChanged
    FilterAppointments

    The AppointmentsInserted event is raised for the newly created Changed Occurrence.

    Occurrence appointments removal, resulting in exceptions for a particular recurrence pattern.

    AppointmentDeleting
    AppointmentsInserted
    FilterAppointments*

    The AppointmentsInserted event is raised for the newly created Deleted Occurrence.

    Removing a pattern with two exceptions

    AppointmentDeleting
    AppointmentDeleting
    AppointmentDeleting
    AppointmentsDeleted
    FilterAppointments*

    The AppointmentDeleting event is first raised for the pattern and then - for all its exceptions. Note: if deleting of an exception is cancelled, the entire process is cancelled, and nothing is deleted.

    Modification of three appointments at once with an action cancelled for one of them (e.g. drag-move)

    AppointmentChanging
    AppointmentChanging
    AppointmentChanging
    AppointmentsChanged

    The AppointmentChanging event is raised three times (one for each appointment). The action is cancelled in one of them. In the AppointmentChanged event, we have only two modified appointments.

    * If the filter is applied, the FilterAppointments event is raised for each appointment that falls within the visible interval.

Copyright © 1998-2008 Developer Express Inc.
ALL RIGHTS RESERVED