Apr 192019
 

An URL label is a TLabel on a form which displays some blue, underlined text which, when clicked with the mouse, opens a web page.

This kind of labels is nothing new in Delphi, I remember using them even back in the 1990s, but they always were a pain in the lower back to create:

  • Write the URL into the caption.
  • Set the font color to blue
  • Set the font to underlined
  • Set the cursor to crHandPoint
  • Write an OnClick event handler that opens the desired URL

Lots of boring and repetitive work and on top of that a Windows API call which I kept forgetting.
This led me to simply not use them.

Alternatively there were specialized components which you could use, so you simply dropped a TUrlLabel control on a form, filled in the caption and be done. There were quite a lot of these, and every time I wanted to use them I stopped, thought about it and didn’t. Install a package into the IDE and add another 3rd party dependency to the program just to have such a simple component? Nah!

Today, I came across these labels again on the GExperts About- and Feedback forms. I had to change some of them, and being a lazy basterd™, I decided to make this process easier once and for all. And not by adding a package / unit with a custom component but writing an overloaded pair of helper procedures which I call once and which does everything for me:

procedure TLabel_MakeUrlLabel(_lbl: TLabel); overload;
procedure TLabel_MakeUrlLabel(_lbl: TLabel; const _URL: string; _SetCaption: Boolean = False); overload;

They are used by simply calling them in the form’s constructor or in FormCreate passing a TLabel as parameter:

constructor TMyForm.Create(Owner: TComponent);
begin
  inherited;

  // if the caption already contains the URL to open:
  TLabel_MakeUrlLabel(l_SomeUrlLabel);

  // if it contains some descriptive text instead:
  TLabel_MakeUrlLabel(l_AnotherUrlLabel, 'https://blog.dummzeuch.de');

  // or if the caption should just be set in this code:
  TLabel_MakeUrlLabel(l_YetAnotherUrlLabel, 'https://blog.dummzeuch.de', True);
end;

Everything is taken care of, no changes to the label’s properties and no OnClick event handler necessary.

Here is how it is implemented:

type
  TUrlLabelHandler = class(TComponent)
  private
    FLbl: TLabel;
    FUrl: string;
    procedure HandleOnClick(_Sender: TObject);
  public
    constructor Create(_lbl: TLabel; const _URL: string); reintroduce;
  end;

constructor TUrlLabelHandler.Create(_lbl: TLabel; const _URL: string);
begin
  inherited Create(_lbl);
  FLbl := _lbl;
  FUrl := _URL;
  FLbl.OnClick := HandleOnClick;
  FLbl.Font.Style := FLbl.Font.Style + [fsUnderline];
  FLbl.Font.Color := clBlue;
  FLbl.Cursor := crHandPoint;
  if (FLbl.hint = '') and (Menus.StripHotkey(FLbl.Caption) <> FUrl) then begin
    FLbl.hint := FUrl;
    FLbl.ShowHint := True;
  end;
end;

procedure TUrlLabelHandler.HandleOnClick(_Sender: TObject);
begin
  ShellExecute(Application.Handle, 'open', PChar(FUrl), nil, nil, SW_SHOWNORMAL);
end;

procedure TLabel_MakeUrlLabel(_lbl: TLabel);
begin
  TLabel_MakeUrlLabel(_lbl, Menus.StripHotkey(_lbl.Caption), False);
end;

procedure TLabel_MakeUrlLabel(_lbl: TLabel; const _URL: string; _SetCaption: Boolean = False);
begin
  if _SetCaption then
    _lbl.Caption := _URL;
  TUrlLabelHandler.Create(_lbl, _URL);
end;

Not rocket science either. All it does is setting some properties and then it uses a simple trick to attach an OnClick event handler to the label: It creates a specialized TComponent descendant which sets the label as its owner and assigns one of its methods as the OnClick event handler. Since the component is owned by the label, it will take care of freeing it, so no further action required and no memory leak possible. The OnClick event then executes the WinApi call I mentioned above and which I kept forgetting:

  ShellExecute(Application.Handle, 'open', PChar(FUrl), nil, nil, SW_SHOWNORMAL);

I like neat tricks like those: Call and forget.

For your (or actually my own) convenience I have added it to the unit u_dzVclUtils of my dzlib. Feel free to use it.

If you would like to comment on this, go to this post in the international Delphi Praxis forum.

 Posted by on 2019-04-19 at 19:35