Search
Tutorial 3: Creating Custom Appointment Classes

This tutorial shows how to create a custom schedule item class. For simplicity, the sample demonstrates that by inheriting a class from Appointment.

1. Create and initialize a new Java project

Follow steps 1 and 2 from Tutorial 1: Getting Started.

2. Create new class for the custom item

Create a new class in the project (for example MyApp). Inherit the new class from Appointment.

The custom class will support a new property of type boolean (called Kept) that will indicate whether the appointment has been kept or cancelled. The implementation of the property is trivial. Simply add a private field of type boolean and a property getter and setter. The following code illustrates this:

Java  Copy Code

public class MyApp inherits Appointment
{
    public MyApp()
    {
        _kept = true;
    }

    public boolean getKept()
    {
        return _kept;
    }

    public void setKept(boolean value)
    {
        _kept = value;
    }


    private boolean _kept;
}

 Note

You must provide constructor that takes no argument with your custom item class. JPlanner creates item class instances using reflection and an exception will be thrown if there is no such constructor available.

3. Set the custom item as default

By default the Calendar control instantiates items of type Appointment when the user creates items interactively (by typing). To make the calendar create items of the new class, we need to add the following line of code:

Java  Copy Code

calendar.setInteractiveItemType(MyApp.class);

Now all items created by the user at runtime will be instances of MyApp.

4. Test

To test whether the calendar actually creates MyApp instances, listen to the control's itemClick event. Add the following as a body of the event method:

Java  Copy Code

if (e.getItem() instanceof MyApp)
{
    calendar.resetDrag();
    JOptionPane.showMessageDialog(MainWindow.this, "This is our item.");
}

Clicking on an item now should display the message box.

5. Serialization

Serialization of custom items requires additional efforts. Attempts to save a schedule containing items of type MyApp will result in an InvalidOperationException exception. In order to support serialization, we have to register MyApp with the schedule. For this purpose, add the following line of code somewhere in the window's constructor:

Java  Copy Code

Schedule.registerItemClass(MyApp.class, "myapp", 1);

Now the exception won't be thrown, but we still need to override the saveTo and loadFrom methods of the Appointment class in order to serialize the custom property Kept. The code below illustrates how this is done.

Java  Copy Code

@Override
public void saveTo(Element element, XmlSerializationContext context)
{
    super.saveTo(element, context);
    context.writeBool(_kept, "kept", element);
}

@Override
public void loadFrom(Element element, XmlSerializationContext context)
{
    super.saveTo(element, context);
    _kept = context.readBool("kept", element);
}

6. Recurrence

Recurrence of items from a custom class is not handled wholly automatically. Let's take a closer look at how recurrence works in JPlanner. When a recurrence pattern is associated with an existing item, the item is said to become the master of the recurrence. Every time the occurrences of a master item must be displayed in a given time range, they are generated by instantiating new objects of the same type as that of the master. This is necessary in order to support infinite recurrences, where the generation of all recurrence instances is not possible. When the occurrences are generated, their properties are copied from the master item. This is done automatically for the built-in item properties, but not for those added by a derived item class. In order to copy the custom properties from the master item to a particular occurrence, you need to override the copyOccurrence method. The following code sample shows how to do this.

Java  Copy Code

@Override
protected void copyOccurrence(Item master)
{
    super.copyOccurrence(master);
    _kept = ((MyApp)master).getKept();
}

In addition, it is recommended to mark an item from a recurrent series as an exception when the value of a custom property changes. Otherwise, the change will be lost the next time the instance is generated.

Java  Copy Code

//...
public void setKept(boolean value)
{
    _kept = value;
    if (getRecurrence() != null)
        getRecurrence().markException(this, false);
}
//...

7. Interactive cloning

The users can clone items by holding down a particular key (the SHIFT key by default). To enable your custom items to be cloned interactively, you have to override the clone method, create a new object from your custom type and duplicate the fields of the prototype item. If you do not override the clone method, JPlanner will still create copies of the item, but they will be of type Appointment instead of the custom item type. Here is a sample implementation of the clone method.

Java  Copy Code

//...
@Override
public MyApp clone()
{
    MyApp clone = new MyApp();

    // The following code replicates the code used in
    // the Appointment's Clone method
    clone.setAllDayEvent(this.getAllDayEvent());
    clone.setDescriptionText(this.getDescriptionText());
    clone.setEndTime(this.getEndTime());
    clone.setHeaderText(this.getHeaderText());
    clone.setLocation(this.getLocation());
    clone.setLocked(this.getLocked());
    clone.setPriority(this.getPriority());
    clone.setReminder(this.getReminder());
    clone.setSelectedStyle(this.getSelectedStyle().cloneShallow());
    clone.setStartTime(this.getStartTime());
    clone.setStyle(this.getStyle().cloneShallow());
    clone.setTag(this.getTag());
    clone.setTask(this.getTask());
    clone.setVisible(this.getVisible());

    for (Resource resource: this.getResources())
        clone.getResources().add(resource);

    for (Contact contact: this.getContacts())
        clone.getContacts().add(contact);

    // Now copy the custom fields
    clone.setKept(this.getKept());

    return clone;
}
//...

8. Undo and Redo Support

In order to enable undo/redo of changes done on MyApp, create an ItemState-derived class that contains member corresponding to the additional properties defined by MyApp:

Java  Copy Code

class MyAppState extends ItemState
{
    @Override
    public boolean equals(Object obj)
    {
        if (obj == null || !(obj instanceof MyAppState))
            return false;

        MyAppState other = (MyAppState)obj;

        if (kept != other.kept)
            return false;

        return super.equals(other);
    }

    public boolean getKept()
    {
        return kept;
    }

    public void setKept(boolean value)
    {
        kept = value;
    }


    private boolean kept;
}

Finally, override the createState, saveState and restoreState methods in the custom appointment class:

Java  Copy Code

@Override
protected ItemState createState()
{
    return new MyAppState();
}

@Override
protected ItemState saveState()
{
    MyAppState state = (MyAppState)super.saveState();
    state.setKept(this.getKept());
    return state;
}

@Override
protected void restoreState(ItemState state)
{
    MyAppState myState = (MyAppState)state;
    this.setKept(myState.getKept());
    super.restoreState(state);
}