Search
Tutorial 5: Custom Item Viewers and Editors

This tutorial demonstrates how to add custom viewers and editors to schedule items in MindFusion.Scheduling. The tutorial extends Tutorial 4: Creating Custom Appointment Classes and if you have not done so already, complete that one first.

1. Define custom item class

Custom items and viewers in MindFusion.Scheduling are associated with items on class level (that is, the viewers and the editors are associated with the item's class, and not to distinct item). For that reason, if you want to use custom viewers or editors you need to implement new item class. We will use the class MyApp that we created in the previous tutorial step.

2. Create the viewer

Custom viewers implement the IItemViewer interface. This interface exposes a single method - Draw, which is called each time an item must be repainted. A sample implementation of a custom item viewer is shown below. Make sure you include System.Drawing, MindFusion.Scheduling and MindFusion.Scheduling.WinForms namespaces.

C#  Copy Code

public class MyAppViewer : IItemViewer
{
    public virtual void Draw(ItemDrawContext context)
    {
        Rectangle b = context.Bounds;
        Graphics g = context.Graphics;
        Style s = context.Style;
        Brush headerBrush = new SolidBrush(s.HeaderTextColor);
        Pen strokePen = new Pen(Color.Red, 2);
        MyApp item = context.Item as MyApp;

        // If the item is selected, draw a faint background
        if (context.Calendar.ItemSelection.Contains(item))
            g.FillRectangle(Brushes.PaleGoldenrod, b);

        // Draw the item's text
        g.DrawString(context.Item.HeaderText, context.Style.HeaderFont,
            headerBrush, new RectangleF(b.Left, b.Top, b.Width, b.Height));

        // Stroke the text if the appointment is not kept
        if (!item.Kept)
        {
            int width = (int)Math.Ceiling(
                g.MeasureString(context.Item.HeaderText, context.Style.HeaderFont, b.Width).Width);
            g.DrawLine(strokePen,
                b.Left, b.Top + b.Height / 2,
                b.Left + width, b.Top + b.Height / 2);
        }

        strokePen.Dispose();
        headerBrush.Dispose();
    }
}

Visual Basic  Copy Code

Public Class MyAppViewer
    Implements IItemViewer

    Public Sub Draw(ByVal context As ItemDrawContext) Implements IItemViewer.Draw

        Dim b As Rectangle = context.Bounds
        Dim g As Graphics = context.Graphics
        Dim s As Style = context.Style
        Dim headerBrush As Brush = New SolidBrush(s.HeaderTextColor)
        Dim strokePen As Pen = New Pen(Color.Red, 2)
        Dim item As MyApp = CType(context.Item, MyApp)

        ' If the item is selected, draw a faint background
        If context.Calendar.ItemSelection.Contains(item) Then
            g.FillRectangle(Brushes.PaleGoldenrod, b)
        End If

        ' Draw the item's text
        g.DrawString(context.Item.HeaderText, context.Style.HeaderFont, _
            headerBrush, new RectangleF(b.Left, b.Top, b.Width, b.Height))

        ' Stroke the text if the appointment is not kept
        If Not item.Kept Then

            Dim width As Integer = CType(Math.Ceiling( _
                g.MeasureString(context.Item.HeaderText, context.Style.HeaderFont, b.Width).Width), Integer)
            g.DrawLine(strokePen, _
                b.Left, b.Top + CInt(b.Height / 2), _
                b.Left + width, b.Top + CInt(b.Height / 2))

        End If

        strokePen.Dispose()
        headerBrush.Dispose()

    End Sub

End Class

Since we will associate MyAppViewer only with the MyApp item custom class, we may safely assume, that the item provided with the context parameter is of type MyApp.

The viewer simply draws the item's header text in the item's bounding rectangle. If the item is selected, its background is drawn with a faint yellow color. If the appointment is not kept (that is, Kept is set to false) the viewer strikes the text with a thick red line.

3. Associate the viewer with the custom item class

The association of a custom viewer with a particular item class is done via the ItemViewerAttribute class. The following sample demonstrates how to use it.

C#  Copy Code

[ItemViewer(typeof(MyAppViewer))]
public class MyApp : Appointment
{
//...

Visual Basic  Copy Code

<ItemViewer(GetType(MyAppViewer))> _
Class MyApp
    Inherits Appointment
'...

Now all MyApp instances will be drawn using MyAppViewer.

4. Create the editor

We will define a custom editor, capable of changing not only the item's header text but the item's Kept property as well. The easiest way to define a custom editor is by deriving from UserControl. To create a new UserControl, click the Project -> Add User Control menu, type in the name of the custom editor class (for example MyAppEditor) and click 'OK'. The environment opens the designer of the new control where you can arrange its child controls.

Drag a new TextBox in the control's area (the Toolbox is displayed via View -> Toolbox menu command). Select the TextBox and change the Text property to an empty string and the name to headerText. Position the TextBox at the top of the user control, and expand it to span from left to (right - 16). Then set its anchor to 'Top, Left, Right' and its BorderStyle to None. Finally make the height of the user control equal to the height of the TextBox. So far the control should look similar to this:

Now drag in a CheckBox from the toolbox and position it on the right of the edit box. Change the name of the ChekBox to kept and its Anchor to 'Top, Right'. The final layout of the control should look like this:

Right-click in the designer area and select 'View Code'. The first thing to do is to make sure that the newly created class implements the IItemEditor interface. The following line of code illustrates this.

 Note

Don't forget to include both MindFusion.Scheduling and MindFusion.Scheduling.WinForms namespaces.

C#  Copy Code

public class MyAppEditor : System.Windows.Forms.UserControl, IItemEditor
{
//...

Visual Basic  Copy Code

Public Class MyAppEditor
    Inherits System.Windows.Forms.UserControl
    Implements IItemEditor
'...

The IItemEditor interface exposes 2 methods and a single property.

The method StartEdit is invoked by the Calendar control when the user initiates in-place editing of an item (usually by single-clicking on the item). By the time the method is invoked, the control is instantiated, properly positioned and displayed. This is the place to put code that initializes child controls with the properties of the edited item. The following code shows sample implementation of this method.

C#  Copy Code

public void StartEdit(ItemEditContext context)
{
    MyApp item = context.Item as MyApp;

    headerText.Text = item.HeaderText;
    kept.Checked = item.Kept;

    headerText.Focus();
    headerText.Select(headerText.Text.Length, 0);
}

Visual Basic  Copy Code

Public Sub StartEdit(ByVal context As ItemEditContext) Implements IItemEditor.StartEdit

    Dim item As MyApp = CType(context.Item, MyApp)

    headerText.Text = item.HeaderText
    kept.Checked = item.Kept

    headerText.Focus()
    headerText.Select(headerText.Text.Length, 0)

End Sub

As with the viewer, we can safely assume that the item, passed with the method argument is of type MyApp. The first two lines initialize the child controls of the editor with the values of the particular item. The next lines set the focus to the header text and position the input cursor at the end of the text.

The method EndEdit is invoked by the Calendar control when the editor is about to be closed. The second parameter specifies whether the changes are to be accepted or not. EndEdit can also be invoked by the custom editor (for example in response to a button click). In this case however you need to keep a copy of the ItemEditContext to pass to it. Below is given a sample implementation of EndEdit.

C#  Copy Code

public void EndEdit(ItemEditContext context, bool accept)
{
    if (accept)
    {
        context.Accept();

        MyApp item = context.Item as MyApp;

        item.HeaderText = headerText.Text;
        item.Kept = kept.Checked;
    }
    else
    {
        context.Cancel();
    }
}

Visual Basic  Copy Code

Public Sub EndEdit(ByVal context As ItemEditContext, ByVal accept As Boolean)

    If (accept) Then

        context.Accept()

        Dim item As MyApp = CType(context.Item, MyApp)

        item.HeaderText = headerText.Text
        item.Kept = kept.Checked

    Else

        context.Cancel()

    End If

End Sub

As you can see above, it is necessary to invoke Accept or Cancel on the context object, depending on whether the operation has been accepted or not. This notifies the Calendar control of the result of the editing operation as well as causes the editor object to be removed and destroyed.

Finally, the implementation of the Text property simply returns the contents of the TextBox.

C#  Copy Code

public new string Text
{
    get { return headerText.Text; }
}

Visual Basic  Copy Code

Public Shadows ReadOnly Property Text() As String

    Get
        Return headerText.Text
    End Get

End Property

We use the keyword new because this property hides the UserControl.Text property.

5. Associate the editor with the custom item class

The association of a custom editor with a particular item class is done via the ItemEditorAttribute class. The following sample demonstrates how to use it.

C#  Copy Code

[ItemEditor(typeof(MyAppEditor))]
public class MyApp : Appointment
{
//...

Visual Basic  Copy Code

<ItemEditor(GetType(MyAppEditor))> _
Class MyApp
    Inherits Appointment
'...

Now all MyApp instances will be edited using MyAppEditor.

6. Test

Make sure the default item in the calendar is MyApp by specifying typeof(MyApp) (or GetType(MyApp) in Visual Basic) for the calendar's InteractiveItemType property. Set the CurrentView to WeekRange. Then build and run the application. Here is a screenshot of what the application output should look like after creating several items.