9

How to display 'x' (close) icon in TBalloonHint?

enter image description here

I want to programmatically display near a control on form a balloon hint that looks like notifications in system tray. If this is not what TBalloonHint can do, what should I use?

Pol
  • 5,064
  • 4
  • 32
  • 51
  • 1
    The close button is added by setting the `TTS_CLOSE` style on the underlying Windows tooltip control. I don't know how you would do that in Delphi however. – Jonathan Potter Oct 08 '14 at 01:06
  • 3
    `TBalloonHint` derives from `TCustomHint`, which wraps a Windows tooltip control, but `TBalloonHint` does not use the `TTS_BALLOON` style, which `TTS_CLOSE` requires. `TBalloonHint` is a custom-drawn tooltip that mimics a balloon tooltip without actually being one, as far as Windows is concerned. You can derive from `TBalloonHint` and override `PaintHint()` to draw your own close button, but it wont act like a button, though. – Remy Lebeau Oct 08 '14 at 01:24

1 Answers1

8

First you need a procedure to show your hint :

uses
  CommCtrl;

// hWnd - control window handle to attach the baloon to.
// Icon - icon index; 0 = none, 1 = info, 2 = warning, 3 = error.
// BackCL - background color or clDefault to use system setting.
// TextCL - text and border colors or clDefault to use system setting.
// Title - tooltip title (bold first line).
// Text - tooltip text.

procedure ShowBalloonTip(hWnd: THandle; Icon: integer; BackCL, TextCL: TColor; Title: pchar; Text: PWideChar);
const
  TOOLTIPS_CLASS = 'tooltips_class32';
  TTS_ALWAYSTIP = $01;
  TTS_NOPREFIX = $02;
  TTS_BALLOON = $40;
  TTF_SUBCLASS = $0010;
  TTF_TRANSPARENT = $0100;
  TTF_CENTERTIP = $0002;
  TTM_ADDTOOL = $0400 + 50;
  TTM_SETTITLE = (WM_USER + 32);
  ICC_WIN95_CLASSES = $000000FF;
type
  TOOLINFO = packed record
    cbSize: integer;
    uFlags: integer;
    hWnd: THandle;
    uId: integer;
    rect: TRect;
    hinst: THandle;
    lpszText: PWideChar;
    lParam: integer;
  end;

var
  hWndTip: THandle;
  ti: TOOLINFO;
begin
  hWndTip := CreateWindow(TOOLTIPS_CLASS, nil, WS_POPUP or TTS_CLOSE or TTS_NOPREFIX or TTS_BALLOON or TTS_ALWAYSTIP, 0, 0, 0, 0, hWnd, 0, HInstance, nil);

  if hWndTip <> 0 then
  begin
    SetWindowPos(hWndTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);

    ti.cbSize := SizeOf(ti);
    ti.uFlags := TTF_CENTERTIP or TTF_TRANSPARENT or TTF_SUBCLASS;
    ti.hWnd := hWnd;
    ti.lpszText := Text;

    Windows.GetClientRect(hWnd, ti.rect);
    if BackCL <> clDefault then
      SendMessage(hWndTip, TTM_SETTIPBKCOLOR, BackCL, 0);

    if TextCL <> clDefault then
      SendMessage(hWndTip, TTM_SETTIPTEXTCOLOR, TextCL, 0);

    SendMessage(hWndTip, TTM_ADDTOOL, 1, integer(@ti));
    SendMessage(hWndTip, TTM_SETTITLE, Icon mod 4, integer(Title));

    //TTM_TRACKACTIVATE => Makes sure you have to close the hint you self
    SendMessage(hWndTip, TTM_TRACKACTIVATE, integer(true), integer(@ti));
  end;
end;

Then call it :

ShowBalloonTip(Button1.Handle, 4, clDefault, clRed, 'Baloon Title', 'Baloon text');

Hint: if you don’t have hWnd (e.g. Speed Buttons or other graphic component) or want to show the baloon elsewhere send TTM_TRACKPOSITION message after TTM_SETTITLE.

***** EDIT *****

This could also be done via a class helper

First create a unit with a Class helper

unit ComponentBaloonHintU;

interface
uses
  Controls, CommCtrl, Graphics;

{$SCOPEDENUMS ON}

type
  TIconKind = (None = TTI_NONE, Info = TTI_INFO, Warning = TTI_WARNING, Error = TTI_ERROR, Info_Large = TTI_INFO_LARGE, Warning_Large = TTI_WARNING_LARGE, Eror_Large = TTI_ERROR_LARGE);
  TComponentBaloonhint = class helper for TWinControl
  public
    procedure ShowBalloonTip(Icon: TIconKind; const Title, Text: string);
  end;

implementation
uses
  Windows;

{ TComponentBaloonhint }

procedure TComponentBaloonhint.ShowBalloonTip(Icon: TIconKind; const Title, Text: string);
var
  hWndTip: THandle;
  ToolInfo: TToolInfo;
  BodyText: pWideChar;
begin
  hWndTip := CreateWindow(TOOLTIPS_CLASS, nil, WS_POPUP or TTS_CLOSE or TTS_NOPREFIX or TTS_BALLOON or TTS_ALWAYSTIP, 0, 0, 0, 0, Handle, 0, HInstance, nil);

  if hWndTip = 0 then
    exit;

  GetMem(BodyText, 2 * 256);

  try
    ToolInfo.cbSize := SizeOf(TToolInfo);
    ToolInfo.uFlags := TTF_CENTERTIP or TTF_TRANSPARENT or TTF_SUBCLASS;
    ToolInfo.hWnd := Handle;
    ToolInfo.lpszText := StringToWideChar(Text, BodyText, 2 * 356);
    SetWindowPos(hWndTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);
    ToolInfo.Rect := GetClientRect;

    SendMessage(hWndTip, TTM_ADDTOOL, 1, integer(@ToolInfo));
    SendMessage(hWndTip, TTM_SETTITLE, integer(Icon), integer(PChar(Title)));
    SendMessage(hWndTip, TTM_TRACKACTIVATE, integer(true), integer(@ToolInfo));
  finally
    FreeMem(BodyText);
  end;
end;

end.

Then call it:

uses
  ComponentBaloonHintU;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Button1.ShowBalloonTip(TIconKind.Eror_Large, 'Baloon Title', 'Baloon text');
end;
Jens Borrisholt
  • 6,174
  • 1
  • 33
  • 67
  • 2
    Very nice! Note that on newer versions of Delphi (I'm using XE5) you shouldn't use these constants and TOOLINFO record as they are not the Unicode versions. The correct ones are defined in CommCtrl now. The integer casts in SendMessage should be changed to wparam and lparam casts as well. – MarkF Oct 08 '14 at 10:53
  • 1
    Thank you for your update @MarkF I was thinking of makeing it a component, and then I could fix the problems you just listed – Jens Borrisholt Oct 08 '14 at 11:46
  • 2
    Works very well. On Windows Vista and newer we can also use following icons: 4 = TTI_INFO_LARGE, 5 = TTI_WARNING_LARGE, 6 = TTI_ERROR_LARGE (http://msdn.microsoft.com/en-us/library/windows/desktop/bb760414(v=vs.85).aspx) – Pol Oct 09 '14 at 00:45
  • 1
    @Pol thank you for your response. I have made an updated version of the tool tip and corrected the things you pointed out. – Jens Borrisholt Oct 09 '14 at 10:35
  • @JensBorrisholt, the second version, the class helper implementation, it shows the balloon hint correctly, but clicking on it won't hide it until you click that little close button. How to make it hide the hint window when the user clicking on the hint window itself (not only the close button)? Thanks. – Edwin Yip Sep 30 '18 at 14:27
  • Comment for the non-class helper version of the code: Only the first character of the title will be displayed, not sure why. But the **solution** is: use the `TOOLINFO` record and constant defines in `Winapi.CommCtrl` – Edwin Yip Apr 28 '19 at 11:59