I've been trying to find a way to find the visible/viewable width of a
column that is very wide, based on the underlying field's length.
When the grid is viewed at runtime one of the column's data often runs
off the screen to the right. In order to see the data you have to scroll
to the right. Unfortunately the UI design doesn't fit displaying a
separate memo field.
What I've done is to use TJvBalloonHint from the JEDI project in
conjunction with the TJvDBGrid. Using the grid's OnShowCellHint I call a
custom method that builds the hint text, calculates the display position
for the hint and displays it.
******TJvDBGrid descendant*******
procedure TMyJvDBGrid.ShowGridCellHint(Sender: TObject; Field: TField;
var AHint: String; var ATimeOut: Integer);
begin
FBalloonHint.HintPos(ScreenToClient(Mouse.CursorPos).X,ScreenToClient(Mouse.CursorPos).Y);
end;
********************************
function GetTextWidth(const Text: UnicodeString; AFont: TFont): Integer;
var
bmp: Vcl.Graphics.TBitmap;
begin
bmp := Vcl.Graphics.TBitmap.Create;
try
bmp.Canvas.Font := AFont;
Result := bmp.Canvas.TextWidth(Text);
finally
FreeAndNil(bmp);
end;
end;
******TJvBalloonHint descendant*******
procedure TMyJvBalloonHint.HintPos(X, Y: Integer);
var
Cell: TGridCoord;
ActRec: Integer;
r: TRect;
Grid: TIniSectionDBGrid;
sTitle: UnicodeString;
begin
Grid := TMyJvDBGrid(Self.Owner);
// correlates pixel location of the mouse
// cursor to the row & column in the grid
Cell := Grid.MouseCoord(X, Y);
if dgIndicator in Grid.Options then
// indicator column counts as a column
Dec(Cell.X);
if dgTitles in Grid.Options then
// titles counts as a row
Dec(Cell.Y);
// is the grid connected to a dataset via a TDataSource object?
if Grid.DataLink.Active and (Cell.X >= 0) and (Cell.Y >= 0) then
begin
// preserve the active record
ActRec := Grid.DataLink.ActiveRecord;
try
// set active record to the row under the mouse cursor
Grid.DataLink.ActiveRecord := Cell.Y;
// set hint to the field value under the mouse cursor
Hint := Grid.Columns[Cell.X].Field.AsString;
// set hint title to the name of the column under the mouse cursor
sTitle := Grid.Columns[Cell.X].Field.FieldName;
if CellChanged(Cell.X,Cell.Y) then
if GetTextWidth(Hint,Grid.Font) > Grid.Width then
begin
r.TopLeft := Point(mouse.CursorPos.X,Mouse.CursorPos.Y);
r.BottomRight := Point(mouse.CursorPos.X,Mouse.CursorPos.Y);
Grid.BalloonHint.ActivateHintRect(r,sTitle,Hint,0,ikNone);
end;
finally
Grid.DataLink.ActiveRecord := ActRec;
end;
end;
end;
function TMyJvBalloonHint.CellChanged(const X, Y: Integer): Boolean;
var
Grid: TMyJvDBGrid;
begin
// persists cell position in order to determine if the
// mouse cursor position has changed to another cell
Result := False;
if (X <> FX) or (Y <> FY) then
begin
Grid := TMyJvDBGrid(Self.Owner);
if Grid.BalloonHint.Active then
Grid.BalloonHint.CancelHint;
Result := True;
if Assigned(FOnShowHint) and FShowHint then
FOnShowHint(Self);
FX := X;
FY := Y;
end;
end;
procedure TMyJvBalloonHint.SetHint(AValue: UnicodeString);
var
i,n: Integer;
chars: TSysCharSet;
begin
FHint := '';
chars := [];
if Length(TextWrapChars.Chars) > 0 then
begin
for i := 0 to Pred(Length(TextWrapChars.Chars)) do
for n := 1 to Length(TextWrapChars[i]) do
if TextWrapChars[i] <> #0 then
Include(chars,TextWrapChars[i]);
FHint := WrapText(AValue, #13#10, chars, TextWrapWidth);
end
else
FHint := AValue;
end;
**************************************
This code only displays a hint - with the text of the field wrapped so
that it is visible in it's entirety - if the field text is longer than
the display width of the entire grid.
What I want to do is display the hint only if the field text is greater
in length than the displayed/visible width of the column. But I can't
find a way to measure the displayed/visible width of a column. In other
words, if a column is wider than it's displayed width, I'd like to know
what the width of the displayed/visible part of the column is. Then I
can measure the width of the text in the underlying field and determine
if the text is chopped off on the right or left side of the grid.
The above code displays the hint at the cursor position. I'd like to
display the hint at the bottom of the visible part of the cell, in the
center of the visible part of the cell, no matter where the cursor is
laterally within the cell rect.
Thanks for any assistance.
On 1/5/2015 4:12 PM, David Keith wrote:
> I've been trying to find a way to find the visible/viewable width of a
> column that is very wide, based on the underlying field's length.
>
> When the grid is viewed at runtime one of the column's data often runs
> off the screen to the right. In order to see the data you have to scroll
> to the right. Unfortunately the UI design doesn't fit displaying a
> separate memo field.
>
> What I've done is to use TJvBalloonHint from the JEDI project in
> conjunction with the TJvDBGrid. Using the grid's OnShowCellHint I call a
> custom method that builds the hint text, calculates the display position
> for the hint and displays it.
>
>
> ******TJvDBGrid descendant*******
> procedure TMyJvDBGrid.ShowGridCellHint(Sender: TObject; Field: TField;
> var AHint: String; var ATimeOut: Integer);
> begin
...
> end;
>
> ********************************
>
> function GetTextWidth(const Text: UnicodeString; AFont: TFont): Integer;
> var
> bmp: Vcl.Graphics.TBitmap;
> begin
...
> end;
>
> ******TJvBalloonHint descendant*******
....
>
> function TMyJvBalloonHint.CellChanged(const X, Y: Integer): Boolean;
> var
> Grid: TMyJvDBGrid;
> begin
...
> end;
>
> procedure TMyJvBalloonHint.SetHint(AValue: UnicodeString);
> var
> i,n: Integer;
> chars: TSysCharSet;
> begin
...
> end;
>
> **************************************
>
> This code only displays a hint - with the text of the field wrapped so
> that it is visible in it's entirety - if the field text is longer than
> the display width of the entire grid.
>
> What I want to do is display the hint only if the field text is greater
> in length than the displayed/visible width of the column. But I can't
> find a way to measure the displayed/visible width of a column. In other
> words, if a column is wider than it's displayed width, I'd like to know
> what the width of the displayed/visible part of the column is. Then I
> can measure the width of the text in the underlying field and determine
> if the text is chopped off on the right or left side of the grid.
>
> The above code displays the hint at the cursor position. I'd like to
> display the hint at the bottom of the visible part of the cell, in the
> center of the visible part of the cell, no matter where the cursor is
> laterally within the cell rect.
This isn't perfect, but it's fairly close to answering both questions.
Since I subclassed TDBGrid I have access to the protected members
including 'LeftCol'. Using the grid's 'ClientWidth' property and an
iteration over the columns I was able to roughly calculate the starting
position of the 'chopped off' column and it's displayed/visible width
using this method:
function ColumnIsChopped(Grid: TIniSectionDBGrid; const ColNum: Integer;
out ColumnDisplayWidth, ColumnLeftPos: Integer):
Boolean;
var
i: Integer;
begin
if ColNum > Pred(Grid.Columns.Count) then
Exit;
// the whole enchilada...
ColumnDisplayWidth := Grid.ClientWidth;
if ColNum <> Grid.LeftCol then
begin
// start iteration & measurements with the left most displayed
column in grid
i := Grid.LeftCol;
while i < ColNum do
begin
// subtract width of column from overall grid client (displayed)
// width
ColumnDisplayWidth := ColumnDisplayWidth - Grid.Columns[i].Width;
inc(i);
end;
end;
// determine the starting position in pixels of the provided column
ColumnLeftPos := Grid.ClientWidth - ColumnDisplayWidth;
// if remaining display width is less than the text width of text in
// column, assume that the column text display is chopped off on the
// right
Result := ColumnDisplayWidth <=
GetTextWidth(Grid.Columns[ColNum].Field.AsString,Grid.Font);
end;
In preparation for displaying the hint I call the ColumnIsChopped method
to determine the following:
) Is the column under the mouse cursor getting chopped?
) What is the approximate left position in pixels of the current
column?
) What is the displayed/visible width of the column under the cursor?
) Is the width of the text in the column greater than the
displayed/visible width of the column?
procedure TIniSectionDBGrid.TIniSectionDBGridHint.HintPos(Position: TPoint);
var
Cell: TGridCoord;
ActRec,colDisplayWidth,iLeft,iLeftPos: Integer;
r: TRect;
Grid: TIniSectionDBGrid;
sTitle: UnicodeString;
begin
Grid := TIniSectionDBGrid(Self.Owner);
// correlates pixel location of the mouse
// cursor to the row & column in the grid
Cell := Grid.MouseCoord(Position.X, Position.Y);
if dgIndicator in Grid.Options then
// indicator column counts as a column
Dec(Cell.X);
if dgTitles in Grid.Options then
// titles counts as a row
Dec(Cell.Y);
// is the grid connected to a dataset via a TDataSource object?
if Grid.DataLink.Active and (Cell.X >= 0) and (Cell.Y >= 0) then
begin
// preserve the active record
ActRec := Grid.DataLink.ActiveRecord;
try
// set active record to the row under the mouse cursor
Grid.DataLink.ActiveRecord := Cell.Y;
if CellChanged(Cell.X,Cell.Y) then
if ColumnIsChopped(Grid,Cell.X,colDisplayWidth,iLeft) then
begin
// calc x position for hint
iLeftPos := iLeft + Round(colDisplayWidth / 2);
// set hint to the field value under the mouse cursor
Hint := Grid.Columns[Cell.X].Field.AsString;
// set hint title to the name of the column under the mouse
// cursor
sTitle := Grid.Columns[Cell.X].Field.FieldName;
r.TopLeft := Point(iLeftPos,Mouse.CursorPos.Y);
r.BottomRight := Point(iLeftPos,Mouse.CursorPos.Y);
Grid.BalloonHint.ActivateHintRect(r,sTitle,Hint,0,ikNone);
end;
finally
Grid.DataLink.ActiveRecord := ActRec;
end;
end;
end;
Now all that is left is to figure out how to position the hint at the
bottom of the cell or the top of the cell depending on the cell's
vertical orientation in the grid and the corresponding hint orientation
in relation to the cell (above or below?).