简体   繁体   中英

DBGrid column visible width

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: TMyJvDBGrid;
   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.

1st Q): 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.

2nd Q): 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.

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:

  1. ) Is the column under the mouse cursor getting chopped?
  2. ) What is the approximate left position in pixels of the current column?
  3. ) What is the displayed/visible width of the column under the cursor?
  4. ) 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?).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM