How to detect ODS_COMBOBOXEDIT in custom styled owner draw TComboBox - delphi

Using Delphi 10.3:
In an owner-drawn TComboBox with Style=csOwnerDrawFixed, I want the owner drawn items in the DropDown list to be different from the static part of the combo. To discriminate between the two cases, I check for odComboBoxEdit in the State parameter, as described here:
How to draw the static part of the combobox
procedure TStylePanel.TargetArrowComboDrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
begin
if (odComboBoxEdit in State) then
begin
// Paint static control
end
else
begin
// Paint item in dropped down list
end;
end;
This works well as long as there's no custom VCL style active. However, with a custom style, this no longer works reliably. Checking the source in Vcl.StdCtrls.pas for TComboBoxStyleHook, it seems to me that the cause is in this combination:
procedure TComboBoxStyleHook.WMPaint(...)
procedure TComboBoxStyleHook.DrawItem(...)
When there's no edit handle (which is the case for csOwnerDrawFixed), DrawItem() assembles a TDrawItemStruct that will never contain ODS_COMBOBOXEDIT, as a result the CN_DRAWITEM handler will never have odComboBoxEdit set.
I could override TComboBoxStyleHook, but I'd need a way to detect if the item is the static item or an item in the list.
As a workaround, I check for Combo.DroppedDown, but that's not the same: even when dropped down, I want the static part to be painted differently than the items in the list.
So the question is, how can I detect (in the custom draw handler or in the style hook) that the custom drawn item is the static area rather than an item in the list?

I was able to get it working by adding a stylehook for TComboBox that unconditionally includes ODS_COMBOBOXEDIT. The assumption is that TComboBoxStyleHook.DrawItem is only called by TComboBoxStyleHook.WMPaint when it needs to custom draw the static item, the drop down list is not handled there. There seem to be no unwanted side effects.
type
TComboBoxStyleHookFix = class(TComboBoxStyleHook)
strict protected
procedure DrawItem(Canvas: TCanvas; Index: Integer; const R: TRect; Selected: Boolean); override;
end;
procedure TComboBoxStyleHookFix.DrawItem(Canvas: TCanvas; Index: Integer; const R: TRect; Selected: Boolean);
var
DIS: TDrawItemStruct;
begin
FillChar(DIS, SizeOf(DIS), 0);
DIS.CtlType := ODT_COMBOBOX;
DIS.CtlID := GetDlgCtrlID(Handle);
DIS.itemAction := ODA_DRAWENTIRE;
DIS.hDC := Canvas.Handle;
DIS.hwndItem := Handle;
DIS.rcItem := R;
DIS.itemID := Index;
DIS.itemData := SendMessage(ListHandle, LB_GETITEMDATA, 0, 0);
if (Control is TComboBox) and (TComboBox(Control).Style = csOwnerDrawFixed) then
DIS.itemState := ODS_COMBOBOXEDIT;
if Selected then
DIS.itemState := DIS.itemState or ODS_FOCUS or ODS_SELECTED;
SendMessage(Handle, WM_DRAWITEM, Handle, LPARAM(#DIS));
end;
procedure InitComboStyleHookFix();
begin
TCustomStyleEngine.RegisterStyleHook(TComboBox, TComboBoxStyleHookFix);
end;

Related

Hiding items in TListBox while filtering by String

Short Version: Is there any way to control or modify LisBox items individually? for example set their Visible property to False separately.
I found a TListBoxItem class in Fire Monkey when I was searching, but I don't want to use Fire Monkey and want it in VCL.
Detailed Version:
I tried to filter my ListBox using two TStringList and an Edit, one StringList is global to keep the original list (list_files_global) and another StringList to help filtering procedure (list_files_filter) and my primary list of files is my ListBox (list_files).
I created my global StringList on onCreate event while program is starting to store my original list:
procedure Tfrm_main.FormCreate(Sender: TObject);
Begin
list_files_global := TStringList.Create;
list_files_global.Assign(list_files.Items);
End;
and used Edit's onChange event for filtering:
procedure Tfrm_main.edit_files_filterChange(Sender: TObject);
Var
list_files_filter: TStringList;
i: Integer;
Begin
list_files_filter := TStringList.Create;
list_files_filter.Assign(list_files.Items);
list_files.Clear;
for i := 0 to list_files_filter.Count - 1 do
if pos(edit_files_filter.text, list_files_filter[i]) > 0 then
list_files.Items.Add(list_files_filter[i]);
End;
and for switching off the filter, just recover the list from my global list that I created at first:
list_files.Items := list_files_global;
here so far, everything works just fine, but problem is when I'm trying to edit/rename/delete items from filtered list, for example I change an item:
list_files.Items[i] := '-- Changed Item --';
list will be edited, but when I switch off the filter, the original list will be back and all changes are lost.
so I want to know is there any proper way to solve this problem? Something like hiding items individually or change items visibility, etc... so I can change the filtering algorithm and get rid of all this making extra lists.
I searched the internet and looked into Delphi's help file for a whole day and nothing useful came up.
The items of a VCL listbox, List Box in the API, does not have any visibility property. The only option for not showing an item is to delete it.
You can use the control in virtual mode however, where there are no items at all. You decide what data to keep, what to display. That's LBS_NODATA window style in the API. In VCL, set the style property to lbVirtual.
Extremely simplified example follows.
Let's keep an array of records, one record per virtual item.
type
TListItem = record
FileName: string;
Visible: Boolean;
end;
TListItems = array of TListItem;
You can extend the fields as per your requirements. Visibility is one of the main concerns in the question, I added that. You'd probably add something that represents the original name so that you know what name have been changed, etc..
Have one array per listbox. This example contains one listbox.
var
ListItems: TListItems;
Better make it a field though, this is for demonstration only.
Required units.
uses
ioutils, types;
Some initialization at form creation. Empty the filter edit. Set listbox style accordingly. Fill up some file names. All items will be visible at startup.
procedure TForm1.FormCreate(Sender: TObject);
var
ListFiles: TStringDynArray;
i: Integer;
begin
ListFiles := ioutils.TDirectory.GetFiles(TDirectory.GetCurrentDirectory);
SetLength(ListItems, Length(ListFiles));
for i := 0 to High(ListItems) do begin
ListItems[i].FileName := ListFiles[i];
ListItems[i].Visible := True;
end;
ListBox1.Style := lbVirtual;
ListBox1.Count := Length(ListFiles);
Edit1.Text := '';
end;
In virtual mode the listbox is only interested in the Count property. That will arrange how many items will show, accordingly the scrollable area.
Here's the filter part, this is case sensitive.
procedure TForm1.Edit1Change(Sender: TObject);
var
Text: string;
Cnt: Integer;
i: Integer;
begin
Text := Edit1.Text;
if Text = '' then begin
for i := 0 to High(ListItems) do
ListItems[i].Visible := True;
Cnt := Length(ListItems);
end else begin
Cnt := 0;
for i := 0 to High(ListItems) do begin
ListItems[i].Visible := Pos(Text, ListItems[i].FileName) > 0;
if ListItems[i].Visible then
Inc(Cnt);
end;
end;
ListBox1.Count := Cnt;
end;
The special case in the edit's OnChange is that when the text is empty. Then all items will show. Otherwise code is from the question. Here we also keep the total number of visible items, so that we can update the listbox accordingly.
Now the only interesting part, listbox demands data.
procedure TForm1.ListBox1Data(Control: TWinControl; Index: Integer;
var Data: string);
var
VisibleIndex: Integer;
i: Integer;
begin
VisibleIndex := -1;
for i := 0 to High(ListItems) do begin
if ListItems[i].Visible then
Inc(VisibleIndex);
if VisibleIndex = Index then begin
Data := ListItems[i].FileName;
Break;
end;
end;
end;
What happens here is that the listbox requires an item to show providing its index. We loop through the master list counting visible items to find out which one matches that index, and supply its text.
This is something I often do, but with list views instead of list boxes. The basic principles are the same, though.
I tend to store the individual items as objects, which are reference types in Delphi. And I keep them all in one main unfiltered list, which owns the objects, while I maintain a filtered list (which does not own the objects) for display purposes. Like #Sertac, I combine this with a virtual list view.
To see how this works in practice, create a new VCL application and drop a list view (lvDisplay) and an edit control (eFilter) on the main form:
Notice I have added three columns to the list view control: "Name", "Age", and "Colour". I also make it virtual (OwnerData = True).
Now define the class for the individual data items:
type
TDogInfo = class
Name: string;
Age: Integer;
Color: string;
constructor Create(const AName: string; AAge: Integer; const AColor: string);
function Matches(const AText: string): Boolean;
end;
where
{ TDogInfo }
constructor TDogInfo.Create(const AName: string; AAge: Integer;
const AColor: string);
begin
Name := AName;
Age := AAge;
Color := AColor;
end;
function TDogInfo.Matches(const AText: string): Boolean;
begin
Result := ContainsText(Name, AText) or ContainsText(Age.ToString, AText) or
ContainsText(Color, AText);
end;
And let us create the unfiltered list of dogs:
TForm1 = class(TForm)
eFilter: TEdit;
lvDisplay: TListView;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FList, FFilteredList: TObjectList<TDogInfo>;
public
end;
where
function GetRandomDogName: string;
const
DogNames: array[0..5] of string = ('Buster', 'Fido', 'Pluto', 'Spot', 'Bill', 'Rover');
begin
Result := DogNames[Random(Length(DogNames))];
end;
function GetRandomDogColor: string;
const
DogColors: array[0..2] of string = ('Brown', 'Grey', 'Black');
begin
Result := DogColors[Random(Length(DogColors))];
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
FList := TObjectList<TDogInfo>.Create(True); // Owns the objects
// Populate with sample data
for i := 1 to 1000 do
FList.Add(
TDogInfo.Create(GetRandomDogName, Random(15), GetRandomDogColor)
);
FFilteredList := FList;
lvDisplay.Items.Count := FFilteredList.Count;
lvDisplay.Invalidate;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if FFilteredList <> FList then
FreeAndNil(FFilteredList);
FreeAndNil(FList);
end;
The idea is that the list view control always displays the FFilteredList, which either points to the same object instance as FList, or points to a filtered (or sorted) version of it:
// The list view's OnData event handler
procedure TForm1.lvDisplayData(Sender: TObject; Item: TListItem);
begin
if FFilteredList = nil then
Exit;
if not InRange(Item.Index, 0, FFilteredList.Count - 1) then
Exit;
Item.Caption := FFilteredList[Item.Index].Name;
Item.SubItems.Add(FFilteredList[Item.Index].Age.ToString);
Item.SubItems.Add(FFilteredList[Item.Index].Color);
end;
// The edit control's OnChange handler
procedure TForm1.eFilterChange(Sender: TObject);
var
i: Integer;
begin
if string(eFilter.Text).IsEmpty then // no filter, display all items
begin
if FFilteredList <> FList then
begin
FreeAndNil(FFilteredList);
FFilteredList := FList;
end;
end
else
begin
if (FFilteredList = nil) or (FFilteredList = FList) then
FFilteredList := TObjectList<TDogInfo>.Create(False); // doesn't own the objects
FFilteredList.Clear;
for i := 0 to FList.Count - 1 do
if FList[i].Matches(eFilter.Text) then
FFilteredList.Add(FList[i]);
end;
lvDisplay.Items.Count := FFilteredList.Count;
lvDisplay.Invalidate;
end;
The result:
Notice that there always is only one in-memory object for each dog, so if you rename a dog, the changes will reflect in the list view, filtered or not. (But don't forget to invalidate it!)

Delphi DBGrid alternate row colors for all DBGrids in the project

How can I make all my grids look the same way all over my forms?
I want to implement an alternate row color that must be applied on all grids of my project. Is it possible without adding the same DrawColumnCell event code for every grid?
I want to avoid adding the same code for each of my grids. I have like 30 grids in my project and multiplied by 13 rows of code it just adds a lot of code lines to my project making it "unfriendly".
I am looking for a solution that will only add 13 lines of code to the project, not 390 lines.
My formatting code looks like this (for example):
procedure TDBGrid.DBGrid1DrawColumnCell(Sender: TObject;const Rect: TRect;DataCol: Integer;Column: TColumn;State: TGridDrawState) ;
var
grid : TDBGrid;
row : integer;
begin
grid := sender as TDBGrid;
row := grid.DataSource.DataSet.RecNo;
if Odd(row) then
grid.Canvas.Brush.Color := clSilver
else
grid.Canvas.Brush.Color := clDkGray;
grid.DefaultDrawColumnCell(Rect, DataCol, Column, State) ;
end;
Probably I would need to extend the DBGrid somehow, but I do not know exactly how nor how to look for a solution for this on google
I tried to hack the DBGRid inside each form like this:
type
TDBGrid = class(DBGrids.TDBGrid)
protected
procedure DrawColumnCell(const Rect: TRect; DataCol: Integer;Column: TColumn; State: TGridDrawState); override;
end;
...
procedure TDBGrid.DrawColumnCell(const Rect: TRect; DataCol: Integer;Column: TColumn; State: TGridDrawState) ;
var
grid : TDBGrid;
row : integer;
begin
row := 2;//grid.DataSource.DataSet.RecNo;
if Odd(row) then
Canvas.Brush.Color := clSilver
else
Canvas.Brush.Color := clDkGray;
DefaultDrawColumnCell(Rect, DataCol, Column, State) ;
end;
I can do this but I cannot access the sender, so I can access the dataset and know which record to color and which not (odd and even).
And this is a poor approach anyways since I will have to do it on every form, so it's not really a solution
Any ideas?
Thank you
If you put something like this in your datamodule, and assign it to the OnDrawColumnCell of every DBGrid, it seems to work (see notes that follow):
procedure TDataModule1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
const
RowColors: array[Boolean] of TColor = (clSilver, clDkGray);
var
OddRow: Boolean;
begin
// Safety check, although it really isn't needed; no other control accepts
// this event handler definition, AFAIK, so the only way to call it with the
// wrong Sender type would be to do so in your own code manually. In my own
// code, I'd simply leave out the check and let the exception happen; if I
// was stupid enough to do so, I'd want my hand slapped rudely.
if (Sender is TDBGrid) then
begin
OddRow := Odd(TDBGrid(Sender).DataSource.DataSet.RecNo);
TDBGrid(Sender).Canvas.Brush.Color := RowColors[OddRow];
TDBGrid(Sender).DefaultDrawColumnCell(Rect, DataCol, Column, State);
end;
end;
A couple of notes:
First, you should avoid using TDataSet.RecNo in the first place, because post-BDE datasets don't typically have this value available. Accessing it (particularly on large or query-based datasets) causes a major performance hit to your application. Of course, not using it means that you can't use this solution. A better solution would be to use a handler for the dataset's BeforeScroll or AfterScroll event that toggled a boolean available to this code instead, and use that instead of the test for Odd(RecNo), or if the dataset is only used for displaying in the DBGrid, use the TDataSet.Tag in the AfterScroll event to track the row's odd/even state using
OddRow := Boolean(DataSet.Tag);
DataSet.Tag := Ord(not OddRow);
Add DBGrids to the uses clause of your datamodule, and manually declare the above event in the published section so that it's available to all units that use the datamodule. You can then assign it in the Object Inspector Events tab as usual from those units.
This does not properly handle the TGridDrawState (nor does your initial code). You'll need to add handling for that yourself, as that wasn't what you asked here.
Depending on which color you want for odd and even rows, you may want to reverse the order of the colors in RowColors.
I prefer the repeated typecasts so that it's clear what the code is doing. If it bothers you, you can simply declare a local variable instead:
var
OddRow: Boolean;
Grid: TDBGrid;
begin
if (Sender is TDBGrid) then
begin
Grid := TDBGrid(Sender);
OddRow := Odd(Grid.DataSource.DataSet.RecNo);
...
end;
end;
This works for Delphi XE7
type
TDBGrid=Class(Vcl.DBGrids.TDBGrid)
procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
end;
procedure TDBGrid.WMVScroll(var Message: TWMVScroll);
begin
Self.Invalidate;
inherited;
end;
procedure TForm1. DBGrid1MouseWheel(Sender: TObject; Shift: TShiftState;
WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
begin
if Sender is TDBGrid then
(Sender as TDBGrid).Invalidate;
end;
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
const
MyRowColors : array[Boolean] of TColor = (clLime, clMoneyGreen);
var
RowNo : Integer;
OddRow : Boolean;
S : string;
begin
if Sender is TDBGrid then begin
with (Sender as TDBGrid) do begin
if (gdSelected in State) then begin
// Farbe für die Zelle mit dem Focus
// color of the focused row
Canvas.Brush.Color := clblue;
end
else begin
// count := trunc((Sender as TDBGrid).Height div (Rect.Bottom - Rect.Top));
// RowNo := (Sender as TDBGrid).Height div Rect.Top;
RowNo := Rect.Top div (Rect.Bottom - Rect.Top);
OddRow := Odd(RowNo);
Canvas.Brush.Color := MyRowColors[OddRow];
// Font-Farbe immer schwarz
// font color always black
Canvas.Font.Color := clBlack;
Canvas.FillRect(Rect);
// Denn Text in der Zelle ausgeben
// manualy output the text
if Column.Field <> nil then begin
S := Column.Field.AsString;
Canvas.TextOut(Rect.Left + 2, Rect.Top + 1, S);
// Canvas.TextOut(Rect.Left + 2, Rect.Top + 1, 'Column.Field.AsString');
end;
end;
end
end;
end;

Keep InPlaceEditor highlight when losing focus

Is there a way in Delphi XE2 to preserve the the InPlaceEditor's highlight in a StringGrid when the grid loses focus to another non-modal form?
My current StringGrid options are:
If not, I had hoped to use the code below to preserve a highlight of the current cell after losing focus, but am having some trouble with it leaving cells highlighted when they're no longer the current cell.
Do I need to add an "else" to the code below to change the color back to the original on non-selected cells? Any caveats?
procedure TForm1.sgMultiDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
begin
if (ACol = sgMulti.Col) and (ARow = sgMulti.Row) then
begin
sgMulti.Canvas.Brush.Color := clYellow;
sgMulti.Canvas.FillRect(Rect);
sgMulti.Canvas.TextRect(Rect, Rect.Left, Rect.Top, sgMulti.Cells[ACol, ARow]);
if gdFocused in State then
sgMulti.Canvas.DrawFocusRect(Rect); user
end;
end; { sgMultiDrawCell}
Edit: The screen capture below clarifies how it's behaving today. I want to current cell, when losing focus, to be more clear than the bottom screen capture
If you want to keep the goAlwaysShowEditor option enabled and highlight just the always displayed editor, you need the access to the InplaceEditor property. This needs to subclass your string grid class and change the color of the inplace editor, which is by default TCustomMaskEdit control class. In this code is shown, how to change the color of the inplace editor, depending on when the string grid is focused or not:
type
TStringGrid = class(Grids.TStringGrid)
private
procedure CMEnter(var Message: TCMEnter); message CM_ENTER;
procedure CMExit(var Message: TCMExit); message CM_EXIT;
protected
function CreateEditor: TInplaceEdit; override;
end;
implementation
{ TStringGrid }
procedure TStringGrid.CMEnter(var Message: TCMEnter);
begin
inherited;
if Assigned(InplaceEditor) then
TMaskEdit(InplaceEditor).Color := $0000FFBF;
end;
procedure TStringGrid.CMExit(var Message: TCMExit);
begin
inherited;
if Assigned(InplaceEditor) then
TMaskEdit(InplaceEditor).Color := $0000A6FF;
end;
function TStringGrid.CreateEditor: TInplaceEdit;
begin
Result := inherited;
if Focused then
TMaskEdit(Result).Color := $0000FFBF
else
TMaskEdit(Result).Color := $0000A6FF;
end;
And the result with the focused and unfocused grid state:

Why don't child controls of a TStringGrid work properly?

I am placing checkboxes (TCheckBox) in a string grid (TStringGrid) in the first column. The checkboxes show fine, positioned correctly, and respond to mouse by glowing when hovering over them. When I click them, however, they do not toggle. They react to the click, and highlight, but finally, the actual Checked property does not change. What makes it more puzzling is I don't have any code changing these values once they're there, nor do I even have an OnClick event assigned to these checkboxes. Also, I'm defaulting these checkboxes to be unchecked, but when displayed, they are checked.
The checkboxes are created along with each record which is added to the list, and is referenced inside a record pointer which is assigned to the object in the cell where the checkbox is to be placed.
String grid hack for cell highlighting:
type
THackStringGrid = class(TStringGrid); //used later...
Record containing checkbox:
PImageLink = ^TImageLink;
TImageLink = record
...other stuff...
Checkbox: TCheckbox;
ShowCheckbox: Bool;
end;
Creation/Destruction of checkbox:
function NewImageLink(const AFilename: String): PImageLink;
begin
Result:= New(PImageLink);
...other stuff...
Result.Checkbox:= TCheckbox.Create(nil);
Result.Checkbox.Caption:= '';
end;
procedure DestroyImageLink(AImageLink: PImageLink);
begin
AImageLink.Checkbox.Free;
Dispose(AImageLink);
end;
Adding rows to grid:
//...after clearing grid...
//L = TStringList of original filenames
if L.Count > 0 then
lstFiles.RowCount:= L.Count + 1
else
lstFiles.RowCount:= 2; //in case there are no records
for X := 0 to L.Count - 1 do begin
S:= L[X];
Link:= NewImageLink(S); //also creates checkbox
Link.Checkbox.Parent:= lstFiles;
Link.Checkbox.Visible:= Link.ShowCheckbox;
Link.Checkbox.Checked:= False;
Link.Checkbox.BringToFront;
lstFiles.Objects[0,X+1]:= Pointer(Link);
lstFiles.Cells[1, X+1]:= S;
end;
Grid's OnDrawCell Event Handler:
procedure TfrmMain.lstFilesDrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
Link: PImageLink;
CR: TRect;
begin
if (ARow > 0) and (ACol = 0) then begin
Link:= PImageLink(lstFiles.Objects[0,ARow]); //Get record pointer
CR:= lstFiles.CellRect(0, ARow); //Get cell rect
Link.Checkbox.Width:= Link.Checkbox.Height;
Link.Checkbox.Left:= CR.Left + (CR.Width div 2) - (Link.Checkbox.Width div 2);
Link.Checkbox.Top:= CR.Top;
if not Link.Checkbox.Visible then begin
lstFiles.Canvas.Brush.Color:= lstFiles.Color;
lstFiles.Canvas.Brush.Style:= bsSolid;
lstFiles.Canvas.Pen.Style:= psClear;
lstFiles.Canvas.FillRect(CR);
if lstFiles.Row = ARow then
THackStringGrid(lstFiles).DrawCellHighlight(CR, State, ACol, ARow);
end;
end;
end;
Here's how it looks when clicking...
What could be causing this? It's definitely not changing the Checked property anywhere in my code. There's some strange behavior coming from the checkboxes themselves when placed in a grid.
EDIT
I did a brief test, I placed a regular TCheckBox on the form. Check/unchecks fine. Then, in my form's OnShow event, I changed the Checkbox's Parent to this grid. This time, I get the same behavior, not toggling when clicked. Therefore, it seems that a TCheckBox doesn't react properly when it has another control as its parent. How to overcome this?
TStringGrid's WMCommand handler doesn't allow children controls to handle messages (except for InplaceEdit).
So you can use e.g. an interposed class (based on code by Peter Below) or draw controls by hands, as some people have adviced. Here is the code of the interposed class:
uses
Grids;
type
TStringGrid = class(Grids.TStringGrid)
private
procedure WMCommand(var AMessage: TWMCommand); message WM_COMMAND;
end;
implementation
procedure TStringGrid.WMCommand(var AMessage: TWMCommand);
begin
if EditorMode and (AMessage.Ctl = InplaceEditor.Handle) then
inherited
else
if AMessage.Ctl <> 0 then
begin
AMessage.Result := SendMessage(AMessage.Ctl, CN_COMMAND,
TMessage(AMessage).WParam, TMessage(AMessage).LParam);
end;
end;
In Delphi7 at least I do this:
You need to draw a checkbox on the cell, and keep it in sync with an array of boolean (here fChecked[]) that indicates the state of the checkbox in each row. Then, in the DrawCell part of the TStringGrid:
var
cbstate: integer;
begin
...
if fChecked[Arow] then cbState:=DFCS_CHECKED else cbState:=DFCS_BUTTONCHECK;
DrawFrameControl(StringGrid.canvas.handle, Rect, DFC_BUTTON, cbState);
...
end;
To get the checkbox to respond to the space-bar, use the KeyDown event, and force a repaint:
if (Key = VK_SPACE) And (col=ColWithCheckBox) then begin
fChecked[row]:=not fChecked[row];
StringGrid.Invalidate;
key:=0;
end;
A similar approach is needed for the OnClick method.
Can u use VirtualTreeView in toReportMode (TListView emulating) mode instead of grid ?
Can u use TDBGrid over some in-memory table like NexusDB or TClientDataSet ?
Ugly approach would be presenting checkbox like a letter with a custom font - like WinDings or http://fortawesome.github.com/Font-Awesome
This latter is most easy to implement, yet most ugly to see and most inflexible to maintain - business logic gets intermixed into VCL event handlers

Delphi: switch bands of CoolBar

What is an efficient way to change buttons (bands) in the CoolBar (the red rectangle) while switching among items in the TreeView (the purple rectangle). I want to use one set of buttons for every item in the list view.
Thanks for help and advices!
I'd create the CoolBands I'd need and assign each to the Data pointer of the TTreeNode for which it is to be used. Then in the TreeView's OnChanging handler, I'd "remember" the TreeNode that is currently selected and switch visibility on the CoolBands in the OnChange handler:
procedure TProbeerForm.TreeView1Changing(Sender: TObject; Node: TTreeNode;
var AllowChange: Boolean);
begin
FOldNode := TreeView1.Selected;
end;
procedure TProbeerForm.TreeView1Change(Sender: TObject; Node: TTreeNode);
begin
TCoolBand(FOldNode.Data).Visible := False;
TCoolBand(Node.Data).Visible := True;
end;

Resources