There have been several iterations of the Delphi IDE's Welcome Page, or the first page that you see by default when you start Delphi without a project. This space has been used for keeping a list of favorite projects for convenient loading, marketing of related products by Embarcadero, and quick links to tutorials and resources. Delphi 11 introduced, in my opinion, the best interface yet as it's completely customizable by the user and provides an API for writing your own plug-ins to provide additional functionality.
There is not much documentation on how to use this API except for reading code comments while wading through the source in
ToolsAPI.WelcomePage.pas; those help but you don't really learn how to use the functions available until you dig into the sample Welcome Page plug-ins that come with Delphi. I had a real business reason to do that recently and am sharing what I learned here in hopes it helps others get started.
There are three sample projects installed under the
DEMOSDIR folder (
\Samples\Object Pascal\VCL\VCL WelcomePage):
- WP.BasicDemoPlugin - a simple frame with static text.
- WP.CaptionFrameDemoPlugin - a more useful frame showing some system information and presented with a standard "plug-in caption" frame created by the plug-in framework.
- WP.DemoPlugin - a ListView filled with the currently installed plug-ins and demonstrating interactivity, setting favorites, and clicking links; this one also creates its own "plugin caption" frame.
All Welcome Page plug-ins are design-time packages using the VCL framework. One global procedure named
register is called to create the plug-in class and register it. It does so by calling
WelcomePagePluginService.RegisterPluginCreator which takes a constructor to your class as its parameter. Your plug-in class must be a descendant of
TInterfacedObject and implement both the
This simple interface provides three functions:
GetPluginID: the unique ID by which your plug-in is referenced.
GetPluginName: the friendly name that shows in the list of "Available Plugins" and standard caption forms.
GetPluginVisibile: this Boolean function determines whether your plug-in is immediately showing when installed (True) or if it simply appears in the list of "Available Plugins" (False).
This interface tells the plug-in system what your plug-in will look like and how to create it by declaring the the functions
GetView, both returning a
TFrame. Typically, your class will define a private variable to keep a handle to your frame,
GetView will simply return that variable and
CreateView will create and initialize it.
The frame you create must be a
TFrame descendant; you can define it at design time in a separate unit, much like you would design any other form or frame for VCL applications, with controls and interactivity and logic. You do have to remember it's functionality is acting within the Delph IDE and keep in mind the constraints this implies.
The first two sample plug-ins create simple frames.
Instead of creating a
TFrame descendant yourself, you can call
WelcomePagePluginService.CreateListViewFrame to create a ListView frame. If you do that, you also have to create two other classes that define how and when the ListView gets loaded and the data structure for each of the items in the ListView.
The simplest of the sample plug-ins is nothing more than a simple VCL
TFrame with a label of static text placed over a panel aligned to
alClient. It introduces the concept of registering a plugin and shows how to tie into the current IDE theme by setting the panel's
BackgroundColor in the frame's constructor.
The next sample plug-in is very similar to the first in that it's still a simple
TFrame descendant with static text but the text is filled with system information in the constructor, like computer name, the amount of memory in the computer, the version of Windows, and so forth.
The interesting part is the introduction of a "Caption Frame" in which our defined
TFrame descendant is placed. The first plug-in did not define either a standard or custom "caption panel" so when displayed in the IDE, all you see is a bare panel with text; this one now displays the plug-in name in a standard caption area above the main plug-in view frame. It does this in the
CreateView function with a call to
WelcomePagePluginService.CreateCaptionFrame. The result of that function call becomes the result of the function and the owner of the custom frame containing the static text; additionally, it also has to call
SetClientFrame to associate the frame view with the caption frame.
The third sample Welcome Page plug-in differs significantly from the first two in that the main view of the plug-in is not a simple
TFrame but is a ListView created by the Welcome Page framework. In order to use this, the plug-in must define how the ListView is filled by declaring two classes, a "model" class and an "item data" class. This demo plug-in also provides a caption area for displaying the name by creating a custom
TFrame and placing the plug-in name on a label along with a couple of buttons.
Responding to IDE Theme Changes
One thing I noticed with the first two plugins that implemented simple frame-based views, the color scheme of the IDE is only checked during creation of the frame. If the user changes the theme from light to dark (or vice versa), the Embarcadero Welcome Page plug-ins respond and repaint themselves with the new colors but the first two plug-ins do not. If you re-install the plug-ins or restart Delphi, they'll pick up the changed color theme and color their frames accordingly during their creation but they're stuck with that throughout the life of the plug-in.
The ListView-based plug-in does respond to theme changes: the ListView colors are managed by the Welcome Page framework; the custom caption frame overrides the
PaintWindow procedure and utilizes
BorlandIDEServices to get the system color and then paints the frame's background with
The model class manages loading and refreshing data, setting icons, and handling click events. This class inherits from
TInterfacedObject and must implement the
The item data class defines the storage to hold the title, description, and optional icons for each item in the plug-in's ListView. It inherits from
TInterfacedObject and must implement the
INTAWelcomePageModelItemData interface; it can also optionally implement the
INTAWelcomePageModelItemAdditionalData interface--which the demo plug-in does in order to display a favorite icon.
This interface defines functions and methods for managing data in the ListView:
LoadData: implement this procedure to fill the ListView.
RefreshData: implement this procedure to refresh the ListView.
StopLoading: implement this procedure to allow the IDE to abort a long loading process.
SetOnLoadingFinished: specify the call-back procedure that will get called after the data is loaded.
GetData: this function should return a pointer to an
IInterfaceListof items in the ListView; the demo plug-in does this with the private field,
GetStatusMessage: this function returns a simple status string; often used to report error messages during data loading.
GetIsDataLoaded: a function indicating whether data has finished loading.
GetImageCollection: a function returning a
TImageCollectionof images linked to items in the ListView; the demo plug-in project includes a "resources" data module containing the collection of images.
The demo plug-in shows a list of installed plug-ins in the Delphi IDE; its
LoadData procedure calls another
WelcomePagePluginService to get the list of plug-ins and adds the name and description of each to the ListView.
This interface defines the "getter" and "setter" methods for the title, description, and image index of the items in the ListView:
The demo plug-in sets the title and description to the name of each of the installed plug-ins it finds and if the name of the plug-in starts with "Embarcadero" sets the ImageIndex to a special icon.
This optional interface provides the "getter" and "setter" methods for the ListView items' right-side button icon:
If this interface is not included, the ListView items don't show the additional button. The demo plug-in uses this to display a "favorite" icon--but it's for demonstration purposes only as the list of favorites is not stored.
The company I work for has hundreds of small Delphi projects with overlapping functionality dating back over three decades and originally written in COBOL. The file naming structure they developed dictates that the first two characters of the project name determine the "application module" to which it belongs. All these projects are in the same source folder. So, a useful the IDE plug-in would be a TreeView listing all the projects in a source folder grouped by the first two characters of the project name.
With this goal in mind, I copied the "CaptionFrameDemoPlugin" project to a new folder and modified it to show all projects from a configured source folder in a TreeView; thus,
WP.ProjectTreePlugin was born.
The plug-in provides a simple frame to house the TreeView and implements the standard caption frame created by the
WelcomePagePluginService. Just above the TreeView is a small panel containing a label and a button. The label displays the currently configured source folder and the button allows the user to select a folder. The TreeView is filled upon the construction or after the folder is selected.
The new and interesting parts of this plug-in are storing and retrieving a configuration setting, and loading a project into the IDE when an item is double-clicked in the TreeView.
Welcome Page Configuration Settings
While looking through the
ToolsAPI.WelcomePage unit, I stumbled across a promising global variable:
WelcomePageSettings: INTAWelcomePageSettings. After a little experimentation, I found it can be used by your plug-in to conveniently store configuration data for your Welcome Page plug-in. I used the
IsSettingExists function to check if the source folder has been configured and if so, calls
ReadSetting to get the folder and load the TreeView with projects found therein. When the button is clicked and a new folder is selected,
SaveSetting is called to save the folder.
The configuration data is stored in the Windows registry under the current user's
SOFTWARE\Embarcadero\BDS\22.0\WelcomePage key. All plug-ins using this method of saving configuration data will be placed in the same registry key so make sure your plugin value identifiers are unique--or save them yourself elsewhere.
Loading a Delphi Project
One thing I figured a sample plug-in project would certainly demonstrate is how to load a project into the IDE. Surprisingly, that was something I had to figure out myself. Once again, though, reading through Embarcadero's source gave me what what I needed.
If you search for "OpenProject" in the
ToolsAPI unit, you'll come across the
IOTAActionServices interface which provides the following useful functions:
Near the bottom of that unit is the global variable,
BorlandIDEServices which provides access to all the
IxxxxServices interfaces with an example of how to use it.
Armed with that information, I was able to hook into the
OnDblClick event of the plug-in's TreeView,
tvProjectTree, and open the selected project with the following code:
if Supports(BorlandIDEServices, IOTAActionServices, ModuleServices) then begin (BorlandIDEServices as IOTAActionServices) .OpenProject(TPath.Combine(ProjPath, tvProjectTree.Selected.Text + '.dpr'), True); end;
I got this to work fairly well but since, like I mentioned, there are hundreds of projects, there's a little delay whenever the plug-in is reinstalled or when Delphi starts up and is initializing the Welcome Page plug-ins. The projects are all stored on a fast local SSD so loading time is minimal but it would be a good idea to make this multi-threaded for smoother operation.
Another idea is to use a ListView instead of a TreeView and provide a dropdown above the ListView as the list of "application modules" that filter the list of projects in the ListView. With that approach, it would lend itself more for general use where the drop-down list of folders could be a configurable list of "favorite" folders. That may seem duplicitous with Delphi's project list of marked favorites surfaced to the top but if you work on dozens or hundreds of projects that list soon gets unmanageably large.
I learned a lot while exploring these plug-ins and writing my own--and I had fun doing it! There's a lot more you can do--I hope this introduction gives you a head start for building your own useful IDE Welcome Page Plugins.
Now, I need to find an excuse to dig into another new API, the ToolsAPI.Editor!