views:

445

answers:

6
+1  A: 

One way I can think of is to create helper class under implementation

type
  TLinkLabelHelper = class helper for TLinkLabel
  public
    procedure Add(const aBGColor: TColor; const S: string);
  end;

procedure TLinkLabelHelper.Add(const aBGColor: TColor; const S: string);
begin
  Color := aBGColor;
  Caption := S;
end;

Then, I create a public

procedure AfterConstruction; override;

procedure Form_A.AfterConstruction;
begin
  inherited;
  LinkLabel1.Add(Self.Color, 'Hello World');
end;

Hope this works.

KS
No, it won't work, as a themed background can be something different than a single colour-filled area. The page control in the question for example has a gradient background. The correct solution needs to make the link label not paint its own background / make it transparent, so that the parent background shines through.
mghie
mghie is correct. I don't want to change the background *color*, I want the parent control's background *theme* behind the label text.
Nat
A: 

My advice: use simple TLabel. TLabel has a property named Transparent - this is what you need. Set your TLabels cursor to crHandPoint (AFAIR this is the link cursor), set font to blue underline, and write OnClick event handler, that will open web browser to navigate to the pointed url. You can even have one default event handler.

procedure OnClickOnMyLinkTLabels(Sender : TObject);
var
  Address : string;
begin
  if NOT (Sender is TLabel) then Exit;
  Address := (Sender as TLabel).Caption;
  ShellExecute(self.WindowHandle,'open',PChar(Address),nil,nil, SW_SHOWNORMAL);
end;

Edit:

If you do not want to have address in your caption, you can use Tag property to retrieve address and set caption to whatever you want:

procedure OnClickOnMyLinkTLabels(Sender : TObject);
var
  Address : string;
begin
  if NOT (Sender is TLabel) then Exit;
  Address :=  GetAddresByTag( (Sender as TLabel).Tag );
  ShellExecute(self.WindowHandle,'open',PChar(Address),nil,nil, SW_SHOWNORMAL);
end;

How you will implement GetAddresByTag is your choice. The most simple one is use an array of strings:

//in your form defintion
   private
     FAddresses : array of string;

function GetAddresByTag(id : integer): string;
begin
  if (i<Low(FAddresses)) OR (I> High(FAddresses)) then
    raise EXception.Create('wrong id sent!');
  Result:= FAddresses[id];
end;
smok1
:) Great idea. But I would like to have the link in the middle of the text. I'll clarify my example a little to show what I mean.
Nat
@Nat: answer extended
smok1
@smok1: ummm... so you are proposing that I manually position the labels in amongst the text at design time? I am setting the text at runtime, and could be a verity of different strings with links in them (not web addresses, not that it matters).
Nat
@smok1: oh, and the form is resizable, so the text (and links) have to flow... Not easily doable with lots of TLabels on your form.
Nat
No. You should use Tag property of those controls. This is almost everything you have to do in designtime. The rest can be done in a runtime. You can even have and XML file and read appropriate address when needed.
smok1
Oh, and if you do not want to use Tag property, you can even use Name property of a control... So there are a lot of possible ways to get the address when you have an instance of TLabel - what you choose is your decision.
smok1
Ah, I see, there is some confusion here... Take a look at my new example. I would like to have multiple clickable areas/links in *one* label, not have multiple labels with multiple links.
Nat
Hmmm... I think OnMouseDown event has some X and Y coordinates, so you could use them to detect in which link user has clicked. But this is going to be a bit hardcore coding...
smok1
Indeed... I would need to draw the label myself, including changing the font style to underlined for each link, manage wrapping, hit test the mouse clicks etc. Not a trivial bit of coding. Thanks for your suggestions though. :)
Nat
Or create your own control consisting of an array of TLabel...
smok1
Yes, you could...
Nat
+4  A: 

Normally I hate it when people offer a third-party component as an answer, but I'll mention the TMS THTMLabel as an alternative for what you want to do. It has the Transparent property of the TLabel, and allows you to use HTML as the caption, and so you can do multiple links as per your example.

_J_
Thanks for that. Had seen that component before, but don't have any dollars to spend at the moment. The budget has been depleted by the purchase of Delphi 2010. :)
Nat
+3  A: 

The csParentBackground and csOpaque styles both require cooperation from other parts of the control's code. Merely setting them wouldn't have much effect; if it did, then the control would probably have a public Transparent property already.

You can look at TCustomLabel.Paint to see how it respects the csOpaque style. It checks for that style by reading its Transparent property before it paints its background:

if not Transparent then
begin
  Canvas.Brush.Color := Self.Color;
  Canvas.Brush.Style := bsSolid;
  FillRect(ClientRect);
end;

The csParentBackground style has no effect on TCustomLabel because that style only affects windowed controls; TCustomLabel descends from TGraphicControl, not TWinControl.

I don't have TLinkLabel, so I can't look at its source code to find out what it would need to change. If it's a TGraphicControl descendant, then it would need to include code like I showed above from TCustomLabel. If it descends from TWinControl, then I'd adapt code from TCustomStaticText instead. That's a little more complicated; it calls DrawParentBackground in response to the cn_CtlColorStatic notification message. It also doesn't paint itself in Delphi code. The control is a wrapper for the Win32 "static" control type.

TLinkLabel evidently paints its background unconditionally. To fix this, you'll need to override the Paint method. Removing functionality (background-painting, in this case) is hard to do with the traditional way of overriding virtual methods because you won't be able to call the inherited method to get all the text painted. Instead, You'll probably have to copy and paste the base class's implementation and then add the conditional parts in the middle somewhere.

Rob Kennedy
I have been doing a lot of investigation into this over night... By default, setting csParentBackground *does* actually do something. It will call the theme services to draw the backgrounnd in response to WM_ERASEBACKGND (dependant on certain settings). This is actually being done... I think the control is then painting over it, but I need to do more research. I will update the question on Monday (it's the weekend down here in OZ, and I have shows to light!).
Nat
Right. That's what I meant by cooperation from the control — even if `csParentBackground` is set, the control has to cooperate by *not* painting over it.
Rob Kennedy
A: 

If your text is static, then you can still do this using labels. Lay out your entire text block INCLUDING the words you want as links. Set the label as transparent. Next, drop separate label components (also set to transparent) that will be the link. Change the color to clNavy, font style to fsunderline and the cursor to crHand. Then position the label OVER the existing text. Then write a onClick handler for each "link" label to perform your hot link.

While this is not optimal, it does work as long as you don't want to bold the text and are willing to keep the text the same font size. Of course this doesn't work so well if the block is dynamic, as you would have to calculate the position of the link labels in code, which is fairly complicated if you are using wordwrap. If not, you can use the canvas.textwidth and canvas.textheight methods to determine the necessary offset positions for your link labels.

skamradt
Beware: When ClearType or ordinary antialiasing is in effect, the top label won't completely cover the bottom label's text. The bottom label will "bleed through," affecting the color of the top text.
Rob Kennedy
Unfortunatly, in my real world code, the text is wrapping, and it's on a resizable form, so it has to flow... Thanks for your suggestion.
Nat
+5  A: 

Nat, you are nearly there with your changes to the ControlStyle of the TLinkLabel. What you have to do in addition is to make sure that the parent of the standard Windows static control (that's what the TLinkLabel is) handles the WM_CTLCOLORSTATIC message correctly.

The VCL has a nice redirection mechanism to let controls handle messages that are sent as notifications to their parent windows for themselves. Making use of this a completely self-contained transparent link label can be created:

type
  TTransparentLinkLabel = class(TLinkLabel)
  private
    procedure CNCtlColorStatic(var AMsg: TWMCtlColorStatic);
      message CN_CTLCOLORSTATIC;
  public
    constructor Create(AOwner: TComponent); override;
  end;

constructor TTransparentLinkLabel.Create(AOwner: TComponent);
begin
  inherited;
  ControlStyle := ControlStyle - [csOpaque] + [csParentBackground];
end;

procedure TTransparentLinkLabel.CNCtlColorStatic(var AMsg: TWMCtlColorStatic);
begin
  SetBkMode(AMsg.ChildDC, TRANSPARENT);
  AMsg.Result := GetStockObject(NULL_BRUSH);
end;
mghie
+1 looks like a great solution, I see no problems, but can't test as I only have D2006 on the machine I'm traveling with. Have you tested this in RAD2009 or 2010? A screen shot to compare with Nat's?
Argalatyr
Tested with Delphi 2009, screen shot (Windows XP, olive colour scheme) at http://img340.imageshack.us/img340/5849/tranparentlinklabel2.png - for illustration only every second label is a `TTransparentLinkLabel`.
mghie
That's the answer! :) I'd managed to get about 80% there on Friday (see comment to Rob Kennedy's post where I mentioned 'research'). Thank you!
Nat