Mega Search
23.2 Million


Sign Up

Make a donation  
DBGrid column visible width  
News Group: embarcadero.public.delphi.vcl.components.using

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.

Vote for best question.
Score: 0  # Vote:  0
Date Posted: 5-Jan-2015, at 1:12 PM EST
From: David Keith
 
Re: DBGrid column visible width  
News Group: embarcadero.public.delphi.vcl.components.using
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?).

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 6-Jan-2015, at 3:51 PM EST
From: David Keith