-2

Searching for a Delphi label component with basic format/markup support I came across Delphi Markup Label (MDLabel). As a bonus it supports links. Unfortunately I can't get it working. The component is provided as a single MD_Label.pas file. I've created a component package for it and installed it. I can now select it from the components list, but adding it to a form throws an error:

Control 'MDLabel1' has no parent window."

I traced it down to the call CreateWnd and found some topics for similar problems, but still wasn't able to solve this. Did I do something wrong or is this something that needs to be adjusted because the initial code was written for Delphi 2007 and I'm using XE?

The component is to large to post the whole source code here, but you can download it from the link above. Here's the creation part:

constructor TMDLabel.Create(AOwner: TComponent);
begin
  FInitialized := False; // required for runtime creation of MDLabel
  inherited;
  ControlStyle := [csOpaque, csCaptureMouse, csClickEvents, csSetCaption];

  FLinkFontNormal := TFont.Create;
  FLinkFontNormal.Assign(Font);
  FLinkFontNormal.Color := clBlue;
  FLinkFontNormal.Style := [];

  FLinkFontHover := TFont.Create;
  FLinkFontHover.Assign(Font);
  FLinkFontHover.Color := clRed;
  FLinkFontHover.Style := [fsUnderline];

  Width := 100;
  Height := 13;
  Cursor := crArrow;
  TabStop := False;
  DoubleBuffered := True;
  FTextHeight := 0;

  FAutoSizeWidth := True;
  FAutoSizeHeight := True;
  FTextAlignment := taLeftJustify;
  FCompressSpaces := False;
  FTabWidth := 8;
  FParsingText := False;
  FBuildingLines := False;
  FRebuildLines := False;
  FMaxWidth := 0;

  FLinkFontNormal.OnChange := DoFontChange;
  FLinkFontHover.OnChange := DoFontChange;

  FOnLinkClicked := nil;
  FOnPaintBackground := nil;
  FOnHeightChanged := nil;
  FOnWidthChanged := nil;

  FLines := TList.Create;
  FWords := TList.Create;
  FLinkRCs := TList.Create;

  FMouseDownMove := False;
  FMouseWasDown := False;
  FMouseDownIndex := - 1;
  FInitialized := True;
end;

procedure TMDLabel.CreateWnd;
begin
  inherited CreateWnd;
{$IFNDEF UNICODE}
  if (inherited Caption <> '') and (FCaptionUTF8 = '') then CaptionUTF8 := inherited Caption;
{$ENDIF}
end;

Full source: http://pastebin.com/sxYvpqTy

As a side note: If you feel that there's a better component that supports formating text within labels, please feel free to share as a comment (TJvHTLabel and TJvMarkupLabel are not good).

CodeX
  • 717
  • 7
  • 23
  • I've never used the Jv labels. But presumably they work. Why would they be "not good", whereas the one that cannot even be dropped on a form is? – Disillusioned Jan 07 '17 at 12:29
  • The problem is not in CreateWnd. The problem is that the control has no parent. – David Heffernan Jan 07 '17 at 12:33
  • I'm not going DL the component. But something that will likely be important in understanding the problem is the component's hierarchy. And then check in the entire hierarchy if and how `SetParent` is overridden. Watch out for any code that may trigger exceptions. – Disillusioned Jan 07 '17 at 12:43
  • 1
    I commend you for putting in some effort. Including steps to reproduce and an error message. (Sadly too many posts don't bother with this bare minimum.) However, your trouble-shooting has much room for improvement. Reading your error message you should first have investigated anything and everything to do with control `Parent`s. That said the constructor's first line is somewhat revealing: `FInitialized := False; // required for runtime creation of MDLabel`. It's hacky and suggests gaps in the author's Delphi knowledge. You'd do well to leave this component alone or just extract useful bits. – Disillusioned Jan 07 '17 at 13:18
  • @CraigYoung JvHTLabel has no WordWrap attribute and doesn't support multi-line text (ironically the Caption attribute comes with a multi-line editor). JvMarkupLabel has multiple bugs including some very obvious like dropping spaces after closing a tag making it unusable. Over the years I learned that many Jvcl components aren't thought-through at all, so I try to avoid them where possible. They provide some additional core feature but at the same time drop other basic functionality, or simply don't cover obvious use cases for the required additional functionality. – CodeX Jan 07 '17 at 13:26
  • @CraigYoung The author of this component might not be the best component developer, but it looks like he invested a lot of thinking, effort and time into it. If you look at the compiled demo you'll see what I mean. It covers everything I could imagine (auto-sizing, transparency, multiple links, styling, tabs, ...). I don't want to ignore that component just because of possible "gaps" in his knowledge regarding component requirements. Again: If you feel like you know a better alternative, please feel free to share. – CodeX Jan 07 '17 at 13:37
  • @CraigYoung The parent attribute isn't touched in the code at all. Please see for yourself, if you don't want to download the zip file: pastebin.com/sxYvpqTy I've already spend several hours on this issue. I'm strugling here, because I have little knowledge on component development and I don't see a way to debug this as this problem occurs at design time. I don't believe the author published this in a non-working state, so there must be some difference between his and my setup. Delphi 2007 vs. XE might be a reason. I came here hoping for help, not because I'm too lazy. – CodeX Jan 07 '17 at 13:56
  • 1
    Cut it down to a minimal repro. If you can't do that consider hiring a programmer or learning the necessary skills yourself. – David Heffernan Jan 07 '17 at 14:24
  • @CodeX I never suggested you're lazy (quite the opposite in fact). (1) My question about the Jv components was bc I consider it inappropriate to shut out options without justification - it hinders further discussion on the matter. (2) Also, please don't misconstrue my comment about the author of the component. I'm sure a lot of effort went into the component and the author certainly would have to have some remarkable skill to achieve what has been done. In fact the skill is precisely what makes me even more wary..... (cont.) – Disillusioned Jan 07 '17 at 14:24
  • In my experience skilled developers can sometimes solve immediate problems very "creatively" (but not in a good way). Those gaps that worry me are likely to lead to subtle bugs and problems that can be hard to notice let alone resolve. (A bit like your experience with the Jv components.) Much of the code should be usable, and you may find it's not as bad as it first appears. But caution is justified. (3) `Parent` may not be touched in the component's unit; but it will come into play in the hierarchy. At a minimum override `SetParent` in the component and check if it's called... (cont) – Disillusioned Jan 07 '17 at 14:35
  • Never forget that in the worst cases you can always use "poor man's debuggers", namely: `ShowMessage` or `Writeln`. But here you might be able to run 2 instances of Delphi. Instance 1 to test the component at design time, and Instance 2 to attach to Instance 1 to try step through at least the component's code. (I don't think I've ever tried that, but worth a shot.) Even if you can't step through code, take a look at `OutputDebugSting`. If you can figure out whether something prevents or changes or delays parent assignment, you're probably half-way there. – Disillusioned Jan 07 '17 at 14:47
  • @CodeX And finally another option is to delete code in the component until it works, and narrow down the problem problem that way. Then restore your last delete and repeat deleting other code. Once you have a minimal amount of code that repro's your problem - [Edit] the question. – Disillusioned Jan 07 '17 at 14:51
  • Thank you for your advices. Before posting the question I already did some `ShowMessage` "debugging". That's how I found out where the error is thrown. But that didn't help, so before taking the whole component apart, I decided it's worth a shot asking here, whether some experienced component developer knows the reason and can explain the problem. This is based on the fact that I believe the code works for the author but not for me, so it can't be _that_ wrong - maybe just some basic pre-XE and XE difference. – CodeX Jan 07 '17 at 15:05
  • @CodeX Where the error happens is one thing. But in your case there was an error message indicating: _where the error happened, parent **should** have been set_. This is why `SetParent` should have been at least a part of your initial investigation. – Disillusioned Jan 08 '17 at 02:14

1 Answers1

2

This error is a very common one for component authors who don't understand how the VCL works internally.

The fact that the error occurs while dropping the component on the Form at design-time means that the component's constructor is doing something it should not be. One of the operations requires the component's Handle to have an allocated HWND, but that is not possible at the time of the error because the component's Parent property has not been assigned yet, or the Parent.Handle does not have an allocated HWND of its own. The Parent is not assigned until after the constructor exits.

So, you need to debug the code and find the offending constructor code that relies on the component's Handle property, and move it out of the constructor. Depending on which code it is, it either belongs in Loaded() or CreateWnd(), or even SetParent(), or it may even need to be disabled completely at design-time (sometimes run-time code should not be executed at design-time or during DFM streaming at all).

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you for your additional details to the discussion in the comments. I reduced the code piece by piece and found out that the method `ParseText` is called indirectly in the creation process. For not changing too much of the original code, I've simply added a `if not Assigned(Parent) then exit;` at the beginning of the method, which solved the problem. – CodeX Jan 11 '17 at 10:27