![]() |
![]() |
![]()
| ![]() |
Inside OP Hints / Fly-over Help / Tooltips / Delphi 2-5
Dropping Hints: Part II A Hint Window Application
In Part I, we began our look at creating hint windows for applications we create in Delphi. In this half, we'll begin with another example of using an independent hint window. We'll also look at an example application (available for download; see end of article for details).
The TWAIHintListBox Component Here's another example of using an independent hint window. The standard Delphi TListBox displays a list of text choices. If the text of any of the items on the list is wider than can be displayed in the list box, you're out of luck. The text gets clipped, so the user never gets to see what may be an important part of the text. Delphi solves this problem in the Object Inspector by allowing the user to resize the sections, and by displaying little hints that contain the whole text over the list item when the mouse pointer is over it. That's what we're going to do programmatically.
We're going to create a TListBox descendant that will display a little hint window over an item whenever the item text is too large to fit in the list box. Figure 1 shows how this would look.
The hint window is created in the constructor and has the standard hint window colors assigned to it. All the important action then takes place in the OnMouseMove event. The event first checks if the HintText property is enabled. If it is, we proceed by getting the text for the item under the mouse cursor and testing if it would fit into the list box's clipping rectangle. If it doesn't, we display the hint window with the full item text covering the list item. The code listing in Figure 2 explains how this is done.
procedure TWAIHintListBox.MouseMove(Shift: TShiftState; X, Y: Integer); var Ind : Integer; rct : TRect; begin if not (HintText) then begin // Item hinting is off. HintWin.ReleaseHandle; inherited; Exit; end; // Get index of item under the mouse pointer. Ind := ItemAtPos(Point(x,y),True); // Check if it's a legal item. if ind = -1 then Exit; // If Item Text is wider, clip the rectangle. if Canvas.textWidth(Items[ind]) > ((Canvas.ClipRect.Right-Canvas.ClipRect.Left)-3) then begin // Get the default item coordinates. rct := ItemRect(ind); // Stretch it to fit the whole item text. Rct.Right := Rct.Left + Canvas.textWidth(Items[ind]) + 9; // Fine tune these values for appearance. Rct.Top := Rct.Top -3; Rct.Bottom := rct.Bottom -1; rct.Left := rct.left -1; // Convert to screen coordinates, so // THintWindow can use them. Rct.TopLeft := ClientToScreen(Rct.TopLeft); Rct.BottomRight := ClientToScreen(rct.BottomRight); // And display. HintWin.ActivateHint(rct,items[ind]); end else HintWin.ReleaseHandle; inherited; end; Figure 2: The OnMouseMove event.
We'll use the CM_MOUSELEAVE message to hide the hint window when the mouse leaves the TListBox, but this won't help us if we move the ListBox's scroll bar. The hint window will stay in its original position even if the original item scrolls out of view. We could trap Windows messages, but it doesn't make sense to provide a message handler function for every conceivable message that might require the hint window to be hidden.
THintWindow provides a function named IsHintMsg where we can subclass the list box's DefaultHandler procedure and test the message using IsHintMsg. DefaultHandler is called after a message has been sent to a window and has gone through all the message handler functions; all unhandled messages get handled here. IsHintMsg tests a message and determines if it's a keyboard, mouse, or application message. If it is, the functions return True.
However, we cannot call the ReleaseHandle method based solely on this result. If the message is WM_MOUSEMOVE and the mouse has been moved inside the rectangle of the current item, the hint window will be hidden and immediately made visible again, producing a flicker effect. Therefore, after the call to IsHintMsg, we also should test that the message is not WM_MOUSEMOVE:
procedure TWAIHintListBox.DefaultHandler(var Message); var MsgT : TMsg; begin MsgT.Message := TMessage(Message).Msg; inherited; if hintwin = nil then Exit; if (TMessage(Message).Msg = WM_VSCROLL) or (HintWin.IsHintMsg(MsgT) and (MsgT.Message <> WM_MOUSEMOVE)) then HintWin.ReleaseHandle end;
Notice, however, that the parameter passed to IsHintMsg is of type TMsg, not TMessage. TMessage is a record describing a message in Windows API style; TMsg is a Delphi-specific message description. So how do you convert from a TMessage to a TMsg? There isn't any real conversion routine, and there doesn't need to be, because the only parameter of the TMsg that IsHintMsg looks at is the message number. All you have to do is assign the Message.Msg to TMsg.Message.
The Global HintWindowClass Variable If you recall, we previously mentioned that the THintInfo record passed to the OnShowHint event contained a field named HintWindowClass. Well, now it can be understood. The standard hint window that Delphi displays is a THintWindow, but a Delphi application is not limited to these dull, yellow, rectangular hints; any descendant of THintWindow can be assigned to the global HintWindowClass variable. This means you can create a subclass of THintWindow that does its own drawing and measuring, and assign it to HintWindowClass so it's automatically used as the hint window. The global HintWindowClass variable is checked every time a hint is to be displayed, but you can assign a different Hint class for an individual hint showing in the OnShowHint event using THintInfo.HintWindowClass.
Subclassing THintWindow THintWindow surfaces many methods that you can subclass to customize a hint window. Let's explore a couple of these subclasses. They're all declared and implemented in the accompanying SubHints.PAS example source code unit (see end of article for download details).
We'll start with a simple example. Let's subclass the Paint method to draw a thin blue line at the top of the hint window. It's not very useful, but illustrates the basics. First we declare the component:
TPaintLineHintWindow = class(THintWindow) public procedure Paint; override; end;
And we implement the Paint procedure:
procedure TPaintLineHintWindow.Paint; begin inherited; // Draw the original text. with Canvas do begin Pen.Width := 2; Pen.Color := clBlue; PenPos := Point(0,0); LineTo(ClipRect.Right,0); end; end;
In the main form's OnCreate event handler, assign TPaintLineHintWindow to the global HintWindowClass variable:
HintWindowClass := TPaintLineHintWindow;
Voilà! Add some controls to the form, and set their ShowHint properties to True. Set the hint text for each control, and run the application. A thin blue line will appear at the top of the hint window.
As you may have realized, to draw the actual hint text, we inherited the Paint method from THintWindow. THintWindow is unyielding about where it paints its text.
Drawing the Hint Text For our next example, we're going to create another hint window class, this time with a cute little circled black question mark at the top left of the text. We'll provide our own text-drawing code, because if we inherit the standard Paint method, the text will invariably appear at the top left of the window and overwrite the question mark. We'll also have to override the CalcHintRect function to take into account the extra area consumed by the question mark.
We'll call this new class TQuestHintWindow. The following is the declaration for it:
TQuestHintWindow = class(THintWindow) public procedure Paint; override; function CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect; override; end;
We've overridden the CalcHintRect function; now it looks like this:
function TQuestHintWindow.CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect; begin // Get the info from the inherited function. Result := inherited CalcHintRect(maxwidth, Trim(AHint), AData); // Ensure height is at least 20 to fit the question mark. if Result.Bottom < 20 then Result.Bottom := 20; // Widen it by 26 to fit the question mark. Result.Right := Result.Right + 26; // Not Required - makes it look better. Result.Bottom := Result.Bottom + 3; end;
This function gets called automatically by the application variable when a hint is about to be shown. It determines the size of the hint window, so any other graphics or items that you might want to place in the window must be accounted for here.
The Paint procedure (see Figure 3) has been completely rewritten, and doesn't call its inherited function. The procedure begins by painting the question mark and its circle on the canvas. Then, the area of the question mark graphic is subtracted from the rectangle that will contain the hint text, and the rectangle is passed to the DrawText API procedure. The DrawText function will draw a string of text into an area bounded by a rectangle, word-wrapping the text if necessary.
procedure TQuestHintWindow.Paint; var fnt : TFont; rct : TRect; begin with Canvas do begin Fnt := Font; Font.Name := 'Arial'; // Everybody has it. Font.Size := 12; Font.Style := [fsBold]; Font.Color := clBlack; Pen.Width := 3; Pen.Color := clGray; Ellipse(2,2,20,20); Pen.Width := 1; Pen.Color := clSilver; Ellipse(2,2,20,20); TextOut(6,2,'?'); Font := fnt; Font.Size := 8; // := fnt; Font.Style := []; Font.Color := clInfoText; Font.Name := 'MS Sans Serif'; Rct := ClientRect; Rct.Left := 26; OffsetRect(Rct,0,4); DrawText(Handle, PChar(Caption), Length(Caption), rct, DT_WORDBREAK); // TextOut(26,5,Caption); end; end; Figure 3: The TQuestHintWindow.Paint procedure.
Getting Artistic But why stop there? Let's really take the THintWindow for a ride and get artistic. The next THintWindow descendant will be named TArtHintWindow. The class and its declaration can be found in Listing One.
As you can see, we've again overridden the Paint and CalcHintRect procedures. This time however, we've also overridden the ActivateHint method and re-implemented the ReleaseHandle procedure. We've overridden the ActivateHint method because we want to reshape our hint window. Windows 95/98/NT supports setting a window's shape by using regions. You define a region using one of the Create???Rgn functions, and apply it to a window using the SetWindowRgn function.
You can create a region of almost any shape by using CreatePolygonRgn; for simplicity's sake, however, we'll go with CreateEllipticRgnIndirect. CreateEllipticRgnIndirect creates a circular region defined to fill a TRect passed to it. We can assign the region in the ActivateHint procedure because it's called every time a hint window is about to be shown. We cannot assign it in the CalcHintRect procedure because there are times that ActivateHint is called without CalcHintRect (especially when the hint window is first created).
Another reason we've overridden ActivateHint is to create the timer. Which timer? Another thing we've added to this class is a graphical ring, bounding the hint window. The timer fires a procedure that changes the color of the ring. This ring gets darker, lighter, darker, lighter, etc. whenever the hint window is visible. To see how this is done, look at the FireTimer procedure in Listing One.
Timers are valuable system resources, so it's best to maintain the timer only when it's needed, i.e. while the hint window is visible. Thus, the timer is created in ActivateHint and is destroyed in ReleaseHandle, which we've had to re-implement because it was originally defined as static.
We've overridden CalcHintRect because if we were to use the original hint rectangle to clip the region, the corners of the hint text may be clipped too. Therefore, CalcHintRect adds a bit to the size of the rectangle to allow for the curves. The real text rectangle is saved for later, so the Paint method can accurately place the text in the center of the window.
The Paint procedure had to be overridden, as we explained earlier, and we again used the DrawText API routine to output the text. This time, we "or'ed in" the value DT_CENTER, so the text is drawn aligned to the center.
A Clickable Hint Previously, we mentioned that a hint window passes all of its clicks to the window below it if it's part of the same application. It does this by replying HTTRANSPARENT to the WM_NCHITTEST message.
A standard window responds to moves in its client area by replying with HTCLIENT, which informs Windows to allow click events for that window. The lparam field of the WM_NCHITTEST message contains the coordinates of the mouse pointer. We can create a clickable area on the hint window by subclassing the WM_NCHITTEST message and testing if the mouse pointer is in a certain area. If it is, we respond with HTCLIENT, which informs Windows to pass click events that happen at that position to the hint window itself.
Our next hint class, named TClickHintWindow, will have a small "x" close button on the top right of the window; you can click on this button to hide the hint window. This of course doesn't have too much use in the standard TApplication hinting mechanism, because you wouldn't be able to move the mouse pointer over to the button without the whole hint window moving to keep up with the cursor. It's useful, however, for independent hint classes that we create and display ourselves at a location of our own choosing.
Because this type of hint would probably be used as a "What this form does" type of hint, and would pop up every time a form gets activated, I've added a procedure named ActivateAtTopLeft, which takes a hint string and performs measuring and displaying of the window at the top left of the screen. Also added is ActivateAtPoint, which will activate the hint window at specified coordinates.
Here's how it's done: We override the CalcHintRect procedure again, and add space to the rectangle for the button. In the overridden Paint procedure, we calculate the rectangle required for the button and we save it into the ButtonRect Private variable. Paint uses the DrawFrameRect API function to draw the image of the button.
We surface the OnMouseDown and OnMouseUp events that are usually hidden in THintWindow to handle the mouse clicks that come along. We also create two procedures to handle these events, MouseDownProc and MouseUpProc, and assign them in the overridden constructor.
When the OnMouseDown event fires, the MouseDownProc uses DrawFrameRect to draw an image of the depressed "x" button. We don't have to test the x-y position to determine if it's in ButtonRect, because the only mouse clicks that will be passed on to the event are those that WM_NCHITTEST allows. For aesthetic reasons, no action is taken until the OnMouseUp event fires, at which time ReleaseHandle is called.
The complete source for TClickHintWindow can be found in Listing Two.
Using the AData Pointer As you look through the TClickHintWindow source, you'll see many references to AData. AData is a parameter of some of the ActivateHint functions. It's a pointer that can be used to send objects to the hint window. The default THintWindow ignores any reference to AData, but subclasses of THintWindow can incorporate this feature to add functionality and customization.
TClickHintWindow has been endowed with the ability to display a TGraphic to the right of hint text. This is done by passing a TGraphic in the AData pointer parameter to the CalcHintRect function, and by calling the ActivateHintData method instead of the usual ActivateHint method. The two specialized activate procedures that we've added also take the AData parameter. We've overridden the CalcHintRect to add space to the resultant rectangle if the AData parameter points to an object of type TGraphic. The width of the graphic is added to the rectangle, and the height of the rectangle is checked to ensure there's enough space for the graphic.
The Paint method offsets the text rectangle and draws the graphic (if one was passed) using the activating routines. How does Paint know if a graphic was passed? Because we've overridden the ActivateHint and ActivateHintData methods. ActivateHint sets a private variable of type TGraphic named graphOb to nil, while ActivateHintData sets graphOb to AData, if AData is an object of type TGraphic.
The Paint method checks the value of graphOb and paints the image if it contains one. Because a hint is always activated before it paints itself, we can be sure the Paint method will receive the proper object. Figure 4 shows how such a hint window might look. The screenshot is from the accompanying source code example.
The AData Pointer and the Application Hint If you recall, another field of the THintInfo record was the HintData member. This is a pointer that is set to nil by default. Indeed, THintWindow has absolutely no use for the HintData pointer. Nevertheless, when the application variable wants to display a hint window, it calls ActivateHintData instead of ActivateHint. THintWindow's ActivateHintData method simply calls ActivateHint and ignores the extra data.
However, if you subclass the THintWindow.ActivateHintData procedure, you can create a hinting system where individual controls are assigned different objects in the OnShowHint event. In the accompanying sample source code, we assign the Forms.Icon property to every THintInfo record. If the selected hint class can handle it, fine; if not, the pointer gets lost in the ActivateHintData procedure, and no harm is done.
There's More THintWindow has other procedures you can override that we'll briefly touch upon here. The CM_TEXTCHANGED message can be overridden to provide custom handling when the Caption property is changed, or when one of the activate events is called. The WM_NCPAINT message is sent by Windows when the window's border has to be repainted. You can override this to provide a custom graphical frame. We briefly touched on the IsHintMsg function before; you can override it to provide a special hint window that will only hide in certain circumstances.
Hint Playground Accompanying this article is the source code of a Delphi application named Hint Playground (HintP.DPR), which implements many of the ideas and methods discussed here (see Figure 5). There are five source files included. Hints.PAS is the main form. On the left side of the form is the control panel. There are several buttons labeled HintWindowClass. Assign one of the THintWindow descendants we created here to the HintWindowClass variable. This hint class will be shown every time a hint is invoked.
Another button, labeled Application Properties, will bring up a different form that will allow you to set some TApplication properties that have to do with the hinting system.
The button Set A Data Image will bring up a dialog box that will allow you to choose the image that will be passed to all hint windows in the OnShowHint event (of course, only TClickHintWindow can take advantage of it). Finally, another button, titled Set Hint Data, will toggle the actual form's Hint property.
On the right side of the form is a group marked Stubborn Labels, which is a bunch of labels that have their HintWindowClass assigned in the OnShowHint event and will display that, regardless of the hint class assigned to HintWindowClass.
In the middle of the form is the TWAIHintListBox component. There is a splitter attached that lets you change the size of the list box at run time to see the effect on individual items. For lack of any other source, the list box populates with the Caption property of any control that has one. (Note: You must first install the component in a design-time package.)
The Split Personality Panel demonstrates using the THintInfo.CursorRect field, and you can check the code in the OnShowHint event. The button labeled Click to give me Multi-line Hint demonstrates how to add word-wrapping hints.
Moving the cursor along the form demonstrates how to add a hint showing position and height. When you click and drag the cursor, a rectangle will appear and another line will be added to the cursor indicating the width and height of that rectangle.
At the bottom of the form is a status bar that displays the TApplication's long hint. Most of the controls on the form will provide the location where you can find their code in the long hint.
At the top left of the screen, you should see a hint window welcoming you to the program. This is a hint window of class TClickHintWindow that's displayed every time a form is activated. You can hide this window by clicking on the small "x" button.
Conclusion The designers of Delphi ignored the Windows API and rebuilt hint functionality from the ground up. They also designed the hinting system to allow near-complete customization. THintWindow was designed ingeniously and allows for easy subclassing. In brief, it's a snap to create a sophisticated hint window in Delphi.
I recommend you study the VCL source code for the THintWindow, or any other VCL control. It's a great source of information and will provide you with insight as to how the VCL was built and how you should go about adding to, and subclassing from it.
The source referenced in this article is available for download.
Motty Adler is a freelance programmer/consultant and President of WAISS Systems. He has been developing software for over five years, and has used Delphi for over two years. He can be reached at mailto:aisssoft@aol.com. WAISS Systems' Web site is http://www.waiss.com/.
Begin Listing One - TArtHintWindow type TArtHintWindow = class(THintWindow) private Rgn: HRGN; fTimer: TTimer; CircColor: TColor; ColDir: Boolean; protected TextRect: TRect; Procedure FireTimer(Sender: TObject); public procedure ActivateHint(Rect: TRect; const AHint: string); override; function CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect; override; procedure Paint; override; procedure ReleaseHandle; constructor Create(AOwner:TComponent); override; end;
implementation
procedure TArtHintWindow.ActivateHint(Rect: TRect; const AHint: string); begin inherited; // Create the region based on the window size. rgn := CreateEllipticRgnIndirect(ClientRect); // Assign the region to the window. SetWindowRgn(Handle, rgn, True); // Create the timer. if fTimer = nil then begin fTimer := TTimer.Create(self); FTimer.OnTimer := FireTimer; FTimer.Interval := 1; end; end;
function TArtHintWindow.CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect; var yOff, xOff : Integer; begin Result := inherited CalcHintRect(Maxwidth, Trim(AHint), AData); // Save this for the paint method. TextRect := Result; // Enlarge rectangle so we have place for the curves. Result.Right := Result.Right + Result.Right div 2 ; Result.Bottom := Result.Bottom + Result.Bottom div 2 ; // Figure out the middle where to put the text. xOff := (Result.Right div 2) - (TextRect.Right div 2); yOff := Result.Bottom div 2 - TextRect.Bottom div 2; OffsetRect(TextRect, xOff, yOff); end;
constructor TArtHintWindow.Create(AOwner: TComponent); begin inherited; fTimer := nil; end;
procedure TArtHintWindow.FireTimer(Sender:TObject); begin // Add or lower the color value. if colDir = false then Inc(circcolor,20) else Dec(circcolor,20); // Change directions. if circcolor > 230 then ColDir := True; if circcolor < 10 then ColDir := False; Canvas.Pen.Color := RGb(circcolor,circcolor,circcolor); Canvas.ellipse(0,0,Width-5,Height-5); end;
procedure TArtHintWindow.Paint; var rct: TRect; begin // Draw the text in middle of the hint window. DrawText(Canvas.Handle, PChar(Caption), Length(Caption), TextRect, DT_WORDBREAK or DT_CENTER); end;
procedure TArtHintWindow.ReleaseHandle; begin // Destroy timer so no unnecessary drawing takes place. FTimer.Free; Ftimer := nil; DestroyHandle; end; End Listing One
Begin Listing Two - TClickHintWindow TClickHintWindow = class(THintWindow) private ButtonRect: TRect; protected GraphOb: TGraphic; procedure WMNCHitTest(var Message: TMessage); Message WM_NCHITTEST; public procedure ActivateHint(Rect: TRect; const AHint: string); override; procedure Paint; override; function CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect; override; procedure ActivateAtTopLeft(AHint: string; AData: Pointer); dynamic; procedure ActivateAtPoint(AHint: string; AData: Pointer; X, Y: Integer); dynamic; procedure ActivateHintData(Rect: TRect; const AHint: string; AData: Pointer); override; procedure MouseDownProc(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure MouseUpProc(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); constructor Create(AOwner:TComponent); override; property OnMouseDown; property OnMouseUp; end;
implementation
procedure TClickHintWindow.ActivateAtPoint(AHint: string; AData: Pointer; X, Y: Integer); var rct: TRect; begin Rct := CalcHintRect(Screen.Width, ahint, AData); OffsetRect(rct, x, y); ActivateHintData(rct, ahint, Adata); end;
procedure TClickHintWindow.ActivateAtTopLeft(AHint: string; AData: Pointer); var rct : TRect; begin Rct := CalcHintRect(Screen.Width, ahint, AData); // We don't have to do anything, because CalcHintRect // returns at 0,0. ActivateHintData(rct, ahint, Adata); end;
procedure TClickHintWindow.ActivateHint(Rect: TRect; const AHint: string); begin GraphOb := nil; inherited; end;
procedure TClickHintWindow.ActivateHintData(Rect: TRect; const AHint: string; AData: Pointer); begin ActivateHint(Rect, AHint); // ActivateHint automatically assigns nil to GraphOb so // if a pointer was passed, we assign it here. if TObject(AData) is TGraphic then begin GraphOb := TGraphic(AData); Invalidate; end; end;
function TClickHintWindow.CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect; var Graph : TGraphic; begin Result := inherited CalcHintRect(Maxwidth, Trim(AHint), AData); // Add space for the Close button. Result.Right := Result.Right + 20; if TObject(aData) is TGraphic then begin Graph := TGraphic(aData); // Add space for the image. Result.Right := Result.Right + Graph.Width + 5; // Ensure image fits in hint window. if Result.Bottom < Graph.Height + 4 then Result.Bottom := Graph.Height + 4; end; end;
constructor TClickHintWindow.Create(AOwner: TComponent); begin inherited; // Assign procedures. OnMouseDown := MouseDownProc; OnMouseUp := MouseUpProc; Color := clInfoBk; end;
procedure TClickHintWindow.MouseDownProc(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin // Draw down button. DrawFrameControl(Canvas.Handle, buttonrect, DFC_CAPTION, DFCS_CAPTIONCLOSE + DFCS_PUSHED); end;
procedure TClickHintWindow.MouseUpProc(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin // Draw up button and hide window. DrawFrameControl(Canvas.Handle, buttonrect, DFC_CAPTION, DFCS_CAPTIONCLOSE); ReleaseHandle; end;
procedure TClickHintWindow.Paint; var txtrct, rct : TRect; begin rct := ClientRect; // Add space for button. rct.Right := rct.Right - 15; OffsetRect(rct,2,2); if GraphOb <> nil then begin // Save text rectangle position. txtrct := CalcHintRect(Screen.Width, Caption, nil); // Make space for graphic. OffsetRect(txtrct,GraphOb.Width+5,0); Canvas.Draw(2,2,GraphOb); end else TxtRct := Rct; // Calculate button rectangle. ButtonRect.Top := 2; ButtonRect.Left := Width - 18; ButtonRect.Bottom := 16; ButtonRect.Right := Width -4; // Draw text and button. DrawText(Canvas.Handle, PChar(Caption), Length(Caption), txtrct,DT_WORDBREAK); DrawFrameControl(Canvas.Handle, buttonrect, DFC_CAPTION, DFCS_CAPTIONCLOSE); end;
procedure TClickHintWindow.WMNCHitTest(var Message: TMessage); var pnt : TPoint; begin // Get coordinates from the Message. pnt := Point(Message.LParamLo, Message.LParamHi); if PtInRect(ButtonRect,pnt) then // It is part of the button. Message.Result := HTCLIENT else // It is elsewhere. Message.Result := HTTRansparent; end; End Listing Two
|
![]() |
|