TMS FNC Planner Editing

Intro

Since the first release, TMS FNC UI Pack has been constantly evolving. Version 3.2 added a lot of great new functionality and new components (https://tmssoftware.com/site/blog.asp?post=695). Today, we wanted to focus on a component that has been there since v1.0: TTMSFNCPlanner.

The TTMSFNCPlanner is a scheduling component with various built-in time-axis options, i.e. a day, week, month, period, half-day period, timeline as well as custom time-axis mode where you can fully control the duration of each timeslot. The TTMSFNCPlanner supports single and multi resource views and can have the time-axis in horizontal or vertical orientation. Even when targeting mobile devices, the TTMSFNCPlanner will automatically use a touch-friendly approach. Today’s blog post focuses on a feature that is essential for an application that uses the maximum potential TTMSFNCPlanner has to offer: editing.

Editing

Editing is divided into 3 parts:

  • Inplace editing
  • Dialog editing
  • Custom editing

Inplace editing

Inplace editing is enabled by default. Clicking in the planner item notes area will start the inplace editor. The default inplace editor is a TMemo, but can be configured to whatever inplace editor you want to use, with the OnGetInplaceEditor event. See the topic “Custom editing” below for an example. After the editor is started, text can be entered, and committed via the F2 key, or by clicking on another part of the planner. Cancelling is done via the Escape key.

Changing the behavior of inplace editing can be done via one of the various properties under Interaction.

Dialog editing

Dialog editing can be enabled by changing the property Interaction.UpdateMode to pumDialog. When doing the same kind of interaction (selecting the item, clicking on the notes area), the dialog will be shown which can fully edit the item. Whereas inplace editing can only edit the notes, the dialog can edit title, notes, start & end time of the item.

There is also support for a built-in recurrency editor dialog, which offers the same basic features as the default dialog, and extends this with recurrency options. This is typically used in combination with a dataset (see planner database demo included in the distribution). To use this dialog editor, you can drop an instance of TTMSFNCPlannerItemEditorRecurrency on the form, assign it to the ItemEditor property and click on the item to show the editor, as you would normally do after setting Interaction.UpdateMode to pumDialog.

Custom editing

For both of the above editing types, customization is possible. Let’s start with inplace editing. We want to create a custom editor that uses a TTMSFNCRichEditor and a TTMSFNCRichEditorFormatToolBar. We start by implementing a custom class wrapper that creates and holds a reference to a set of richeditor related classes for importing and exporting HTML as well as a toolbar and the richeditor itself. The planner item supports HTML and we need a way to convert the HTML coming from the item to the rich editor and vice versa.

type
  TCustomInplaceEditor = class(TTMSFNCCustomControl)
  private
    FHTMLImport: TTMSFNCRichEditorHTMLIO;
    FRichEditor: TTMSFNCRichEditor;
    FRichEditorToolBar: TTMSFNCRichEditorFormatToolBar;
    FScrollBox: TScrollBox;
  public
    constructor Create(AOwner: TComponent); override;
  end;

implementation

{ TCustomInplaceEditor }

constructor TCustomInplaceEditor.Create(AOwner: TComponent);
begin
  inherited;
  FScrollBox := TScrollBox.Create(Self);
  FScrollBox.Parent := Self;
  FScrollBox.Align := TAlignLayout.Client;

  FRichEditor := TTMSFNCRichEditor.Create(FScrollbox);
  FRichEditor.Parent := FScrollbox;

  FRichEditorToolBar := TTMSFNCRichEditorFormatToolBar.Create(FScrollbox);
  FRichEditorToolBar.RichEditor := FRichEditor;
  FRichEditorToolBar.Parent := FScrollbox;

  FRichEditor.Position.Y := FRichEditorToolBar.Height;

  FRichEditor.Width := FRichEditorToolBar.Width;

  FHTMLImport := TTMSFNCRichEditorHTMLIO.Create(Self);
  FHTMLImport.RichEditor := FRichEditor;
end;

To show the new inplace editor, implement the OnGetInplaceEditor event and return the TCustomInplaceEditor class. When clicking on the item, the TCustomInplaceEditor instance will be created and shown.

procedure TForm1.TMSFNCPlanner1GetInplaceEditor(Sender: TObject; AStartTime,
  AEndTime: TDateTime; APosition: Integer; AItem: TTMSFNCPlannerItem;
  var AInplaceEditorClass: TTMSFNCPlannerInplaceEditorClass);
begin
  AInplaceEditorClass := TCustomInplaceEditor;
end;

As you can see, the TTMSFNCRichEditor does not show the text that was originally visible in the item. We need to import the TTMSFNCPlanner item text as HTML. As the TCustomInplaceEditor is actually too big, we also set the size here so there is enough space available to edit the text and get access to the TTMSFNCRichEditorFormatToolbar.

procedure TForm1.TMSFNCPlanner1AfterOpenInplaceEditor(Sender: TObject;
  AStartTime, AEndTime: TDateTime; APosition: Integer;
  AItem: TTMSFNCPlannerItem; AInplaceEditor: TControl;
  AInplaceEditorRect: TRectF);
var
  c: TCustomInplaceEditor;
begin
  c := (AInplaceEditor as TCustomInplaceEditor);
  c.SetBounds(c.Left, c.Top, c.FRichEditorToolBar.Width + 20, 200);
  c.FHTMLImport.InsertHTML(AItem.Text);
end;

To update the item, after editing the text we’ll need another event: OnBeforeUpdateItem. This event is called after closing the editor. Here we need to convert the text from the TTMSFNCRichEditor to HTML, to import back into the item.

procedure TForm1.TMSFNCPlanner1BeforeUpdateItem(Sender: TObject; AStartTime,
  AEndTime: TDateTime; APosition: Integer; AItem: TTMSFNCPlannerItem;
  var ATitle, AText: string; var ACanUpdate: Boolean);
var
  c: TCustomInplaceEditor;
  sl: TStringList;
begin
  c := (TMSFNCPlanner1.GetInplaceEditor as TCustomInplaceEditor);
  sl := TStringList.Create;
  try
    sl.Text := c.FRichEditor.ContentAsPlainHTML;
    AText := sl.Text;
  finally
    sl.Free;
  end;
end;

As with inplace editors, dialogs can also be fully customized. If the default supported dialogs are not sufficient, you can use a set of events to show a custom dialog. In this sample, the TTMSFNCPlanner shows an overview of cardiologists and patients that need to have surgery. A cardiologist can click on a planner item, and edit it to confirm the patient is successfully operated. To start, we have created a custom dialog.

This dialog is going to be shown instead of the default dialog when editing an item. We are also going to dynamically edit the item based on dialog and vice versa. In this code snippet we are going to hide the remove button, as we don’t want users to remove the scheduled patient we are going to edit.

procedure TForm1.TMSFNCPlanner1AfterOpenUpdateDialog(Sender: TObject;
  AStartTime, AEndTime: TDateTime; APosition: Integer;
  AItem: TTMSFNCPlannerItem);
begin
  TMSFNCPlanner1.GetEditingDialog(AItem.Index).ButtonRemove.Visible := False;
end;

The next step is to link the item to the custom dialog. This makes sure that the properties that are available at item level are displayed in the custom dialog.

function FindChild(AContainer: TControl; AName: String): TControl;
var
  I: Integer;
begin
  Result := nil;
  for I := 0 to AContainer.ControlsCount - 1 do
  begin
    if AContainer.Controls[I].Name = AName then
    begin
      Result := AContainer.Controls[I];
      Break;
    end;
    Result := FindChild(AContainer.Controls[I], AName);
  end;
end;

procedure TForm1.TMSFNCPlanner1ItemToCustomContentPanel(Sender: TObject;
  AItem: TTMSFNCPlannerItem; AContentPanel: TControl);
var
  l: TLabel;
  c: TCheckBox;
  img: TImage;
  b: TColorComboBox;
begin
  l := FindChild(AContentPanel, 'lblPatient') as TLabel;
  l.Text := 'Patiënt: ' + AItem.Title;
  c := FindChild(AContentPanel, 'chkOperation') as TCheckBox;
  c.IsChecked := AItem.DataBoolean;
  img := FindChild(AContentPanel, 'imgPatient') as TImage;
  img.Bitmap.Assign(TMSFMXBitmapContainer1.FindBitmap(AItem.DataString));
  b := FindChild(AContentPanel, 'cboCategory') as TColorComboBox;
  b.Color := AItem.Color;
end;

After editing the item, we also want to save the settings from our custom dialog back into the item.

procedure TForm1.TMSFNCPlanner1CustomContentPanelToItem(Sender: TObject;
  AContentPanel: TControl; AItem: TTMSFNCPlannerItem);
var
  c: TCheckBox;
  b: TColorComboBox;
begin
  c := FindChild(AContentPanel, 'chkOperation') as TCheckBox;
  b := FindChild(AContentPanel, 'cboCategory') as TColorComboBox;
  AItem.BeginUpdate;
  AItem.Color := b.Color;
  AItem.DataBoolean := c.IsChecked;
  AItem.EndUpdate;
end;

Note that we are using custom properties such as Color and DataBoolean, so we can update and edit custom information about our patient.

Last but not least, we need to make sure the planner picks up the dialog when clicking on the item.

procedure TForm1.TMSFNCPlanner1GetCustomContentPanel(Sender: TObject;
  AItem: TTMSFNCPlannerItem; var AContentPanel: TControl);
begin
  if Assigned(AItem) and (AItem.Resource = 1) then
    AContentPanel := Panel1;
end;

We hope this blog post gives you an insight on what is available specifically for editing planner items. There is much more to discover, so check out the demos and documentation.