| dsListBox (part 2) |
{
This list box shows hints for items that are too long. OnMouseLeave is
also implemented.
}
{
TdsListBox2 (hint for long strings)
Let's start with a new project, save it into desired folder and declare
some vital things for our list box.
}
type
TdsListBox2 = class(TListBox)
private
ItemOldPos: Integer;
FClickedItem: Integer;
FHintWinVisible: Boolean;
FHintForLongStrings: Boolean;
FOnMouseLeave: TNotifyEvent;
protected
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure DoMouseLeave;
public
HintWin: THintWindow;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure DefaultHandler(var Message); override;
published
property HintForLongStrings: Boolean read FHintForLongStrings write FHintForLongStrings;
property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave;
end;
{
Ancestor for our TdsListBox1 will be TListBox.
We will have a property for hint for long strings, as well a event for mouse
leave. Why other things are there you'll see later on.
Constructor is one thing I almost always override. This project is no exception.
}
constructor TdsListBox2.Create(AOwner: TComponent);
begin
inherited;
FClickedItem := -1;
FHintForLongStrings := true;
HintWin := THintWindow.Create(Self);
HintWin.Color := clInfoBk;
HintWin.Canvas.Font.Color := clInfoText;
end;
{
We create hint window in here and set show hint for long strings to true by
default. FClickedItem is just a helper.
}
destructor TdsListBox2.Destroy;
begin
HintWin.Free;
inherited;
end;
{
As we createded hint window in the constructor, we shold destroy it in the
destructor although it's not neccessary as hint window has it's owner and
will be destroyed when his owner will be destroyed.
For the mouse leave event, we need to do two things. First of all, we have
to write message handler for the CM_MOUSELEAVE.
}
procedure TdsListBox2.CMMouseLeave(var Message: TMessage);
begin
inherited;
HintWin.ReleaseHandle;
FHintWinVisible := false;
DoMouseLeave;
end;
{
It's nice to write the Delphi way, so we will only call DoMouseLeave here.
This procedure is virtual, so anyone can override it. All that it does,
calls event handler if it's assigned.
}
procedure TdsListBox2.DoMouseLeave;
begin
if Assigned(FOnMouseLeave) then FOnMouseLeave(Self);
end;
{
Now the big one. We override MouseMove and in there, we must test if item is
too long and if so, show hint. I won't go into details here as code should
be quite clear.
}
procedure TdsListBox2.MouseMove(Shift: TShiftState; X, Y: Integer);
var
i: Integer;
R: TRect;
begin
inherited;
if FHintForLongStrings then
begin
// Get the index of the item under the mouse pointer
i := ItemAtPos(Point(X,Y), true);
if ItemOldPos <> i then FHintWinVisible := false;
if i = -1 then
begin
HintWin.ReleaseHandle;
FHintWinVisible := false;
Exit;
end;
if i = FClickedItem then
begin
Exit;
end
else
FClickedItem := -1;
// Check if the Item Text is wider then the cliprectangle
if Canvas.TextWidth(Items[i]) > ((Canvas.ClipRect.Right - Canvas.ClipRect.Left) - 3) then
begin
// Get the default item coordinates
R := ItemRect(i);
// Stretch it to fit the whole item text
R.Right := R.Left + Canvas.TextWidth(Items[i]) + 9;
// Fine tune these values for apperance
R.Top := R.Top - 3;
R.Bottom := R.Bottom - 1;
R.Left := R.left - 1;
// now convert to screen coordinates so that THintWindow can use them
R.TopLeft := ClientToScreen(R.TopLeft);
R.BottomRight := ClientToScreen(R.BottomRight);
// And show
if not FHintWinVisible then
begin
HintWin.ActivateHint(R, items[i]);
FHintWinVisible := true;
ItemOldPos := i;
end;
end
else
begin
HintWin.ReleaseHandle;
FHintWinVisible := false;
end;
end;
end;
{
There is one thing we have to override. DefaultHandler. That is the last place
where one could answer to the messages that are sent to the control.
What we want to do here is hide hint window if mouse is over scroll bar.
}
procedure TdsListBox2.DefaultHandler(var Message);
var
MsgT: TMsg;
begin
MsgT.message := TMessage(Message).Msg;
inherited;
if HintWin = nil then Exit;
if (MsgT.message = WM_VScroll) or
(HintWin.IsHintMsg(MsgT) and (MsgT.message <> WM_MouseMove)) then
begin
HintWin.ReleaseHandle;
FHintWinVisible := false;
end;
end;
{
And one more thing. If user selects an item over which hint window is
displayed, we should hide that hint window. For that, we override MouseDown.
}
procedure TdsListBox2.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
inherited;
FClickedItem := ItemIndex;
HintWin.ReleaseHandle;
FHintWinVisible := false;
Screen.ActiveForm.Update; //repaint invalidated region of the active form immediately
end;
{
To test our list box, you could write this into forms OnCreate.
}
procedure TForm1.FormCreate(Sender: TObject);
var
l: TdsListBox2;
begin
l := TdsListBox2.Create(Self);
l.Parent := Self;
l.Left := 50;
l.top := 50;
l.Width := 100;
l.Height := 150;
l.Items.Add('item 0 should be long');
l.Items.Add('item 1');
l.Items.Add('item 2');
l.Items.Add('item 3 should be long one');
l.Items.Add('item 4 is long as well');
l.Items.Add('item 5');
l.Items.Add('item 6');
l.Items.Add('item 7');
l.Items.Add('item 8');
l.Items.Add('item 9');
l.Items.Add('item 10 yet another long item');
l.Items.Add('item 11');
l.Items.Add('item 12');
l.Items.Add('item 13');
l.Items.Add('item 14');
l.Items.Add('item 15 is the last long item');
end;