Category
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.
The Fundamentals
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 INTAWelcomePagePlugin
and INTAWelcomePageContentPluginCreator
interfaces.
INTAWelcomePagePlugin
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).
INTAWelcomePageContentPluginCreator
This interface tells the plug-in system what your plug-in will look like and how to create it by declaring the the functions CreateView
and 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.
WP.BasicDemoPlugin
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.
WP.CaptionFrameDemoPlugin
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.
WP.DemoPlugin
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 WelcomePagePluginService
.
ListView Data
The model class manages loading and refreshing data, setting icons, and handling click events. This class inherits from TInterfacedObject
and must implement the INTAWelcomePagePluginModel
interface.
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.
INTAWelcomePagePluginModel
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 anIInterfaceList
of items in the ListView; the demo plug-in does this with the private field,FData
.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 aTImageCollection
of 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.
INTAWelcomePageModelItemData
This interface defines the "getter" and "setter" methods for the title, description, and image index of the items in the ListView:
GetTitle
/SetTitle
GetDescription
/SetDescription
GetImageIndex
/SetImageIndex
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.
INTAWelcomePageModelItemAdditionalData
This optional interface provides the "getter" and "setter" methods for the ListView items' right-side button icon:
GetAdditionalImageIndex
/SetAdditionalImageIndex
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.
ProjectTreePlugin
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:
OpenProject
OpenFile
ReloadFile
SaveFile
CloseFile
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;
If you'd like to see this entire project, it's been made open source on GitHub--and has a nice screenshot.
Future Ideas
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.
Conclusion
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!
Add new comment