Delphi - issue with drawing over a TDBGrid descendant - delphi

I've wrote a TDBGrid descendant component which is displaying the content of different blob fields for the current row selected, each one in a TJvDBRichEdit. The DrawColumnCell code for displaying the content of the blob fields:
procedure TMyDBGrid.DrawColumnCell(const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState);
var iPos,iNr,iTop:Integer;
begin
// RBA 01/04/2013 12:10
if not (csDesigning in ComponentState) then
if FDisplayBlobAsRTF then //property to show the blob fields or not
begin
if (gdSelected in State) then
begin
if Column.Field.isblob then
begin
ipos := 0;
iNr := 0;
while (iPos <= Column.Field.DataSet.FieldCount-1)and(Column.Field.DataSet.Fields[ipos].FieldName <> Column.Field.FieldName ) do
begin
if Column.Field.DataSet.Fields[ipos].IsBlob then
inc(iNr);
inc(iPos);
end;
with TJvDBRichEdit(FRichEditList[inr]) do//FRichEditList - TObjectList which contains all the TJvDBRichEdit components, and release them on destroy
begin
Left := Rect.Left + Self.Left + 1;
iTop := Rect.Top + Self.Top + 1;
if iTop + Height > Self.Height then
iTop := iTop - Height;
Top := iTop;
Width := Rect.BottomRight.X - Rect.left;
Visible := true;
end;
end;
end
end;
inherited;
end;
overrided LayoutChanged routine
procedure TMyDBGrid.LayoutChanged;
var iPos : Integer;
dtDataset : TDataSet;
jvDbRichEdit : TJvDBRichEdit;
begin
if not (csDesigning in ComponentState) then
if FDisplayBlobAsRTF then
begin
dtDataset := GetDataset;//get the dataset assigned to the grid
if dtDataset <> nil then
if dtDataset.State <> dsInactive then
begin
FRichEditList.Clear;
for iPos := 0 to dtDataset.FieldCount-1 do
if dtDataset.Fields[iPos].IsBlob then
begin
jvDbRichEdit := TJvDBRichEdit.Create(nil);
jvDbRichEdit.Parent := Self;
jvDbRichEdit.DataSource := Self.DataSource;
jvDbRichEdit.DataField := dtDataset.Fields[iPos].FieldName;
jvDbRichEdit.Visible := false;
FRichEditList.Add(jvDbRichEdit);
end;
end;
end;
end;
the problem appears when I make a scroll on the last displayed grid row :
How can I redraw the grid behind? I've tried with repainting the hole grid, but it is flickering.
LE: this is not intended to modify the content in the richedtis, only to display them.

Related

Delphi: Menu Hint bug

I got the code below from the website ThoughtCo. (Zarko Gajic) - it presents the hint near the mouse pointer when it is in the menu item:
However, it has a bug: when the menu is opened by the keyboard the tooltip appears next to the mouse pointer, regardless of the location on the screen where the mouse pointer is:
I tried to fix the bug by adding the lines that are commented. Now the error is that the hint always appears regardless of whether you click the menu item quickly or not.
How to fix this problem?
procedure TfrmPrincipal.WMMenuSelect(var Msg: TWMMenuSelect);
var
menuItem : TMenuItem;
hSubMenu : HMENU;
hPopupWnd: HWND; // Added
R: TRect; // Added
Pt: TPoint; // Added
begin
inherited;
menuItem := nil;
if (Msg.MenuFlag <> $FFFF) or (Msg.IDItem <> 0) then
begin
if Msg.MenuFlag and MF_POPUP = MF_POPUP then
begin
hSubMenu := GetSubMenu(Msg.Menu, Msg.IDItem);
menuItem := Self.Menu.FindItem(hSubMenu, fkHandle);
end
else
begin
menuItem := Self.Menu.FindItem(Msg.IDItem, fkCommand);
end;
end;
hPopupWnd := FindWindow('#32768', nil); // Added
if hPopupWnd = 0 then Exit; // Added
GetWindowRect(hPopupWnd, R); // Added
GetCursorPos(Pt); // Added
if PtInRect(R, Pt) then // Added
miHint.DoActivateHint(menuItem)
else // Added
miHint.DoActivateHint(nil); // Added
end;
constructor TMenuItemHint.Create(AOwner: TComponent);
begin
inherited;
showTimer := TTimer.Create(self);
showTimer.Interval := Application.HintPause;
hideTimer := TTimer.Create(self);
hideTimer.Interval := Application.HintHidePause;
end;
destructor TMenuItemHint.Destroy;
begin
hideTimer.OnTimer := nil;
showTimer.OnTimer := nil;
self.ReleaseHandle;
inherited;
end;
procedure TMenuItemHint.DoActivateHint(menuItem: TMenuItem);
begin
hideTime(self);
if (menuItem = nil) or (menuItem.Hint = '') then
begin
activeMenuItem := nil;
Exit;
end;
activeMenuItem := menuItem;
showTimer.OnTimer := ShowTime;
hideTimer.OnTimer := HideTime;
end;
procedure TMenuItemHint.HideTime(Sender: TObject);
begin
self.ReleaseHandle;
hideTimer.OnTimer := nil;
end;
procedure TMenuItemHint.ShowTime(Sender: TObject);
var
r : TRect;
wdth : integer;
hght : integer;
begin
if activeMenuItem <> nil then
begin
wdth := Canvas.TextWidth(activeMenuItem.Hint);
hght := Canvas.TextHeight(activeMenuItem.Hint);
r.Left := Mouse.CursorPos.X + 16;
r.Top := Mouse.CursorPos.Y + 16;
r.Right := r.Left + wdth + 6;
r.Bottom := r.Top + hght + 4;
ActivateHint(r,activeMenuItem.Hint);
end;
showTimer.OnTimer := nil;
end;
WM_MENUSELECT tells you whether the menu item is being selected by mouse or keyboard.
If the MF_MOUSESELECT flag is present, use the mouse coordinates provided by GetCursorPos() (or the VCL's TMouse.CursorPos wrapper), or GetMessagePos().
If the flag is not present, use GetMenuItemRect() to get the screen coordinates of the bounding rectangle of the specified menu item, and then use whatever coordinates you want that are within that rectangle (centered, bottom edge, etc).
You should NOT be trying to work with the menu window directly at all, so get rid of your calls to FindWindow(), GetWindowRect(), and PtInRect().

Add DBLookupCombobox to Delphi DBGrid

I'd like to add DBLookupComboboxes to certain columns in a DBGrid. There is a nice article on About.com on how to do this here. The problem is that with a table having many columns, if you select from the DBLookupCombobox in one column and then try to scroll left, the combobox will move left too as shown in the included images. How can the About.com code can be changed to prevent this behavior? A web search showed two others complaining of the exact same problem with no solution. Note that I want to use a DBLookupCombobox to show a name but enter the id, so using a simple picklist will not do.
procedure TForm1.DBGrid1ColExit(Sender: TObject);
begin
if DBGrid1.SelectedField.FieldName = DBLookupComboBox1.DataField then
DBLookupComboBox1.Visible := False
end;
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
if (gdFocused in State) then
begin
if (Column.Field.FieldName = DBLookupComboBox1.DataField) then
with DBLookupComboBox1 do
begin
Left := Rect.Left + DBGrid1.Left + 2;
Top := Rect.Top + DBGrid1.Top + 2;
Width := Rect.Right - Rect.Left;
Width := Rect.Right - Rect.Left;
Height := Rect.Bottom - Rect.Top;
Visible := True;
end;
end
end;
procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
begin
if (key = Chr(9)) then Exit;
if (DBGrid1.SelectedField.FieldName = DBLookupComboBox1.DataField) then
begin
DBLookupComboBox1.SetFocus;
SendMessage(DBLookupComboBox1.Handle, WM_Char, word(Key), 0);
end
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
with DBLookupComboBox1 do
begin
DataSource := DataSource1; // -> AdoTable1 -> DBGrid1
ListSource := DataSource2;
DataField := 'resource_id'; // from AdoTable1 - displayed in the DBGrid
KeyField := 'id';
ListField := 'resource_name; id';
Visible := False;
end;
DataSource2.DataSet := AdoQuery1;
AdoQuery1.Connection := AdoConnection1;
AdoQuery1.SQL.Text := 'SELECT id,resource_name FROM resources';
AdoQuery1.Open;
end;
Here is one solution using a neat hack from François.
type
// Hack to redeclare your TDBGrid here without the the form designer going mad
TDBGrid = class(DBGrids.TDBGrid)
procedure WMHScroll(var Msg: TWMHScroll); message WM_HSCROLL;
end;
TForm1 = class(TForm)
[...]
procedure TDBGrid.WMHScroll(var Msg: TWMHScroll);
begin
if Form1.DBGrid1.SelectedField.FieldName = Form1.DBLookupComboBox1.DataField then begin
case Msg.ScrollCode of
SB_LEFT,SB_LINELEFT,SB_PAGELEFT: begin
Form1.DBGrid1.SelectedIndex := Form1.DBGrid1.SelectedIndex-1;
Form1.DBLookupComboBox1.Visible := False;
end;
SB_RIGHT,SB_LINERIGHT,SB_PAGERIGHT: begin
Form1.DBGrid1.SelectedIndex := Form1.DBGrid1.SelectedIndex+1;
Form1.DBLookupComboBox1.Visible := False;
end;
end;
end;
inherited; // to keep the expected behavior
end;

DBGRID with Row Height variable

I would like to show in a DBGRID as follows:
Imagine "Grid" as follows:
ID - DESCRIPTION
1 - Line 1 of the grid
2 - Line 2 of the grid
3 - Line 3 of the grid
Now, suppose the size of the DESCRIPTION column is changed and no longer appear the words "GRID";
I would like to stay as well DBGRID
ID - DESCRIPTION
1 - Line 1 of the
grid
2 - Line 2 of the
grid
3 - Line 3 of the
grid
is there any possibility that ??
Not what you're asking, but might help... I once used this code to show complete Memo fields in the standard DBGrid:
TMyForm = class(TForm)
...
private
FormMemoRect: TRect;
MemoGrid: TDBGrid;
BMemo: TBitBtn;
...
Procedure TMyForm.FormMemoDeactivate(Sender: TObject);
Begin
(Sender As TForm).Close;
Sender.Free;
End;
Procedure TMyForm.BMemoClick(Sender: TObject);
Var FormMemo: TForm;
Begin
MemoGrid.SetFocus;
FormMemo := TForm.Create(Self);
With TMemo.Create(FormMemo) Do Begin
Parent := FormMemo;
Align := alClient;
ReadOnly := True;
WordWrap := True;
ScrollBars := ssVertical;
Lines.Text := MemoGrid.DataSource.DataSet.Fields[TComponent(Sender).Tag].AsString;
End;
With FormMemo Do Begin
OnDeactivate := FormMemoDeactivate;
Left := FormMemoRect.Left;
Top := FormMemoRect.Top;
Width := Max(FormMemoRect.Right - FormMemoRect.Left, 300);
Height := FormMemoRect.Bottom - FormMemoRect.Top;
BorderStyle := bsNone;
Show;
End;
End;
Procedure TMyForm.GrdMemoDrawColumnCell(Sender: TObject; Const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState);
Begin
If (gdFocused In State) Then Begin
If Column.Field.DataType In [ftBlob, ftMemo] Then Begin
{Desenha botão para visualização do Memo}
FormMemoRect.Left := TWinControl(Sender).ClientToScreen(Rect.TopLeft).X;
FormMemoRect.Right := TWinControl(Sender).ClientToScreen(Rect.BottomRight).X;
FormMemoRect.Top := TWinControl(Sender).ClientToScreen(Rect.BottomRight).Y;
FormMemoRect.Bottom := FormMemoRect.Top + 100;
If Not Assigned(BMemo) Then
BMemo := TBitBtn.Create(Self);
BMemo.Parent := TWinControl(Sender).Parent;
BMemo.Width := 16;
BMemo.Height := 16;
BMemo.Caption := '...';
BMemo.OnClick := BMemoClick;
BMemo.Tag := Column.Field.Index;
BMemo.Left := TWinControl(Sender).Left + Rect.Right - BMemo.Width + 1;
BMemo.Top := TWinControl(Sender).Top + Rect.Top + 2;
MemoGrid := TDBGrid(Sender);
End
Else
FreeAndNil(BMemo);
End;
End;
For Blob/Memo Fields, you may also find it useful to do some custom GetText to show something directly in the Grid:
Procedure TMyForm.DataSetMemoGetText(Sender: TField; var Text: String; DisplayText: Boolean);
Begin
Text := Copy(Sender.AsString, 1, 50);
If Text <> Sender.AsString Then
Text := Text + '...';
End;
This is how the result looks like.
PS: Sorry for non-standard code style.

How can I change the background and font color of a selected item in list view?

I tried to change the colors with the OnCustomDrawItem event but it has no effect.
procedure TForm1.RListCustomDrawItem(Sender: TCustomListView; Item: TListItem;
State: TCustomDrawState; var DefaultDraw: Boolean);
begin
if cdsSelected in State then begin
Sender.Canvas.Brush.Color:=clRed;
Sender.Canvas.Font.Color:=clYellow;
end;
end;
I use the default TListView component with 3 columns an ViewStyle set to vsReport.
The font color only will work as shown in your code.
If you want to change the Background color you will have to Draw the Item and the SubItems on your own and set DefaultDraw to false.
This could look like:
procedure TMyForm.ListView1CustomDrawItem(Sender: TCustomListView; Item: TListItem
; State: TCustomDrawState; var DefaultDraw: Boolean);
var
rt, r: TRect;
s: String;
i: Integer;
c:TCanvas;
// Fit the rect used for TextRect
Procedure PrepareTextRect;
begin
rt := r;
rt.Left := rt.Left + 5;
rt.Top := rt.Top + 1;
end;
begin
c := Sender.Canvas;
if (cdsSelected in State) then
begin
c.Brush.Color := clRed;
c.Font.Color := clYellow;
// will get the rect for Item + Subitems in ViewStyle = vsReport
r := Item.DisplayRect(drBounds);
c.FillRect(r);
// set width to get fitting rt for tfEndEllipsis
r.Right := r.Left + TListView(Sender).Columns[0].Width;
s := Item.Caption;
PrepareTextRect;
c.TextRect(rt, s, [tfSingleLine, tfEndEllipsis]);
if TListView(Sender).ViewStyle = vsReport then
begin // Paint the Subitems if ViewStyle = vsReport
for i := 0 to Item.SubItems.Count - 1 do
begin
r.Left := r.Left + TListView(Sender).Columns.Items[i].Width;
r.Right := r.Left + TListView(Sender).Columns.Items[i + 1].Width;
PrepareTextRect;
s := Item.SubItems[i];
c.TextRect(rt, s, [tfSingleLine, tfEndEllipsis]);
end;
end;
DefaultDraw := false;
end;
end;
If you set ViewStyle to vsList then you are up and running.

TListview is not correctly painted when OnDrawItem event is used

I'm using the OnDrawItem event in the TlistView component to draw the content using custom colors, but when scroll the listview some artifacts appears.
This is the code used.
procedure TForm35.FormCreate(Sender: TObject);
var
i, j : integer;
Item : TListItem;
s : string;
begin
for i:= 0 to 99 do
begin
Item:=ListView1.Items.Add;
for j:= 0 to ListView1.Columns.Count-1 do
begin
s:= Format('Row %d Column %d',[i+1, j+1]);
if j=0 then
Item.Caption :=s
else
Item.SubItems.Add(s);
end;
end;
end;
procedure TForm35.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
Rect: TRect; State: TOwnerDrawState);
var
x, y, i: integer;
begin
if odSelected in State then
begin
TListView(Sender).Canvas.Brush.Color := clYellow;
TListView(Sender).Canvas.Font.Color := clBlack;
end
else
begin
TListView(Sender).Canvas.Brush.Color := clLtGray;
TListView(Sender).Canvas.Font.Color := clGreen;
end;
TListView(Sender).Canvas.FillRect(Rect);
x := 5;
y := (Rect.Bottom - Rect.Top - TListView(Sender).Canvas.TextHeight('Hg')) div 2 + Rect.Top;
TListView(Sender).Canvas.TextOut(x, y, Item.Caption);
for i := 0 to Item.SubItems.Count - 1 do
begin
inc(x, TListView(Sender).Columns[i].Width);
TListView(Sender).Canvas.TextOut(x, y, Item.SubItems[i]);
end;
end;
I tested this code in Delphi 2007 and XE3, but I'm getting the same results. How i can prevent this issue?
Ok. Change X := 5 to X := Rect.Left;
And another solution (may be more accuracy):
uses
Graphics;
//... Form or something else declarations ...
implementation
procedure TForm35.ListView1DrawItem(Sender: TCustomListView; Item: TListItem; Rect: TRect; State: TOwnerDrawState);
var
s: string;
ts: TTextStyle; // Text style (used for drawing)
begin
// inherited;
// Clear target rectangle
// Set Canvas'es Font, Pen, Brush according for Item state
// Get into s variable text value of the Cell.
ts.Alignment := taLeftJustify; // Horz left alignment
ts.Layout := tlCenter; // Vert center alignment
ts.EndEllipsis := True; // End ellipsis (...) If line of text is too long too fit between left and right boundaries
// Other fields see in the Graphics.TTextStyle = packed record
ListView1.Canvas.TextRect(
Rect,
Rect.Left, // Not sure, but there are a small chance of this values equal to zero instead of Rect...
Rect.Top,
s,
ts)
end;
end.
And to prevent some flicking...
...
var
b: TBitmap;
begin
b := TBitmap.Create;
try
b.Widht := Rect.Right - Rect.Left;
b.Height := Rect.Bottom - Rect.Top;
//...
// Draw content on the b.Canvas
//...
ListView.Canvas.Draw(Rect.Left, Rect.Top, b);
finally
b.Free;
end;
end;

Resources