CheckBox in a DBGrid - delphi

My question is how to set a column in dbgrid in Delphi 7 which will be with a checkbox items.
Thanks in advance.

The easiest and most complete method as tested by me is as follows:
In the private section of your unit, declare a global for retaining grid options. It will be used for restoring after temporary disabling text editing while entering the checkbox column - as this is maybe one of the little errors mentioned by Jordan Borisovin regarding the delphi.about.com article
private
GridOriginalOptions : TDBGridOptions;
In the OnCellClick event, if field is boolean, toggle and post change to database
procedure TForm1.DBGrid1CellClick(Column: TColumn);
begin
if (Column.Field.DataType=ftBoolean) then
begin
Column.Grid.DataSource.DataSet.Edit;
Column.Field.Value:= not Column.Field.AsBoolean;
Column.Grid.DataSource.DataSet.Post;
end;
end;
Drawing the checkbox for boolean fields of the grid
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
const
CtrlState: array[Boolean] of integer = (DFCS_BUTTONCHECK, DFCS_BUTTONCHECK or DFCS_CHECKED) ;
begin
if (Column.Field.DataType=ftBoolean) then
begin
DBGrid1.Canvas.FillRect(Rect) ;
if (VarIsNull(Column.Field.Value)) then
DrawFrameControl(DBGrid1.Canvas.Handle,Rect, DFC_BUTTON, DFCS_BUTTONCHECK or DFCS_INACTIVE)
else
DrawFrameControl(DBGrid1.Canvas.Handle,Rect, DFC_BUTTON, CtrlState[Column.Field.AsBoolean]);
end;
end;
Now the new part, disable cell editing while in the boolean column. On the OnColEnter and OnColExit events:
procedure TForm1.DBGrid1ColEnter(Sender: TObject);
begin
if Self.DBGrid1.SelectedField.DataType = ftBoolean then
begin
Self.GridOriginalOptions := Self.DBGrid1.Options;
Self.DBGrid1.Options := Self.DBGrid1.Options - [dgEditing];
end;
end;
procedure TForm1.DBGrid1ColExit(Sender: TObject);
begin
if Self.DBGrid1.SelectedField.DataType = ftBoolean then
Self.DBGrid1.Options := Self.GridOriginalOptions;
end;
Even more, handle space key for toggling the checkbox
procedure TForm1.DBGrid1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if ((Self.DBGrid1.SelectedField.DataType = ftBoolean) and (key = VK_SPACE)) then
begin
Self.DBGrid1.DataSource.DataSet.Edit;
Self.DBGrid1.SelectedField.Value:= not Self.DBGrid1.SelectedField.AsBoolean;
Self.DBGrid1.DataSource.DataSet.Post;
end;
end;
That's it!

If you're using the TClientDataset+TDatasetProvider+TDataset, you can manipulate the data array variant before it gets to the clientdataset and include an not updatable boolean field.
Once is done, all you need is to draw on the grid using the OnDrawColumnCell event. Here I doesn't used a CheckBox but just a bitmap (when user click it changes to selected/unselected).

Please excuse me for posting this as answer, I don't have the 50 reputation to add comments yet.
Mihai MATEI's answer is very close to the rare (as in really working) solution except for an use case where it bugs.
Whenever the users' first action on the grid is to click on the checkbox, the first click will work but the second will reveal the underlying DBGrid editor.
This happens because the "GridOriginalOptionsmechan" mechanism needs to be initialized.
To do so, just add the following code in the grid's OnEnter event:
procedure TForm1.DBGrid1Enter(Sender: TObject);
begin
DBGrid1ColEnter(Sender);
end;
That's it!

OK
I used this article for my problem. OK But the problem is that it wasn't working how it should. So I change my logic in the code. And implementing it by saving the selected rows from the dbgrid in a list.

Related

FMX.TGrid how to allow user to move columns without messing up the data

I made a very simple testproject in Delphi 10.2 using FMX.
The setup is simple :
TGrid that is binded to a TClientDataSet (done in the designer).
button that allows the user to open an XML file
This all works fine and the TGrid is populated with all records from the XML File. The XML file is created by another TClientDataSet from an older project.
Now for the problem.
When I move a column to another position all the data is messed up. I do this by just holding down the mouse on a column and than drag it a few columns to the right.
At first it looks fine, but when you start scrolling vertical, it seems that the data is not in the correct columns anymore.
I have the feeling that it only corrects the data in the visual part of the grid, and as soon as you start scrolling the data is not in the correct columns anymore.
Is this a known bug or is there something wrong with my project.
As I said before, there is absolutely no code in this project all is done in the designer. (except for the clientdataset1.LoadFromFile offcourse)
You can try populate your data manually (Grid: TGrid; CDS: TClientDataSet):
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
Col: TColumn;
begin
CDS.Active := True;
for I := 0 to CDS.FieldDefs.Count - 1 do begin
Col := TColumn.Create(Grid);
Grid.AddObject(Col);
Col.Header := CDS.FieldDefs[I].Name;
Col.Tag := I;
end;
Grid.RowCount := CDS.RecordCount;
end;
procedure TForm1.GridGetValue(Sender: TObject; const ACol, ARow: Integer; var Value: TValue);
begin
CDS.First;
CDS.MoveBy(ARow);
Value := CDS.Fields[ACol].Text;
end;
And after this you can use my solution for columns: stackoverflow.com/q/43418528/2292722
This fixed it for me.
I just move the fields that where moved in the grid also in the ClientDataSet and thus far it seems to work.
procedure TForm1.Grid1ColumnMoved(Column: TColumn; FromIndex, ToIndex: Integer);
var
FieldFrom : string;
FieldTo : string;
begin
FieldFrom := Grid1.ColumnByIndex(FromIndex).Header;
FieldTo := Grid1.ColumnByIndex(ToIndex).Header;
ClientDataSet1.FieldByName(FieldFrom).Index := FromIndex;
ClientDataSet1.FieldByName(FieldTo).Index := ToIndex;
end;
But I just wish there was a better way of knowing from the TColumn which fieldname is involved. Seems like the most significant information is missing from this class.

Make a DBGrid go to records based on a letter pressed

Overall idea: mimic for example, pressing the 'c' key on the keyboard while on windows desktop until you get to the 'Chrome' shortcut. Or when using a listbox, pressing the letter of the value you want instead of scrolling through the whole list. I want to be able to do this for a DBGrid, so I could press a letter to jump to the record that the first character of a string of a "name column" matches the key pressed.
This is called incremental search and in the Embarcadero CodeCentral site exist a sample of how create a DBGrid - Incremental search / filter
re your comment to RRUZ answer:
I tried to get it to work without a TEdit and gave up after several hours. I don't want to discourage you, though. I would be interested in such a solution as well. Depending on the exact use case, it might be enough to just catch the entries to the dbgrid and pass them on to an invisible TEdit which then triggers the lookup. (In my case this did not suffice.) You do this by adding a DBGrid1KeyPress method with the following code:
procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
var
msg: TWMKey;
begin
msg.CharCode := Ord(KEY);
PostMessage(Edit1.Handle, WM_CHAR, Ord(key), 0);
end;
You will also have to remove doEditing from the grid's options and possibly switch it to ReadOnly.
A way to do it without having TEdit appearing on screen, is to add the following event snippets. The following code will work on top of the Embarcadero CodeCentral's code that was referred previously by RRUZ:
procedure TForm1.DBGrid1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if Key=VK_BACK then
begin
Edit1.Text := copy(Edit1.Text,1,length(Edit1.Text)-1);
end;
end;
procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
begin
if Key in ['a'..'z'] + ['A'..'Z'] then
begin
Edit1.Text := Edit1.Text + Key;
end;
end;
You can then set the Edit box invisible. All you need is to focus on the list and press keys.

Proper Validation on TPageControl in Delphi

I'm working with Delphi 7 code to ensure that comments are entered on a tab have been saved before users can switch tabs.
The tabs are located on a TPageControl, and this code is triggered OnExit
procedure TfCallerInfo.tsChaplainExit(Sender: TObject);
begin
{ Compare the saved DB value with the text in the comments field }
if (dmMain.qChaplainCOMMENTS.AsString <> dbmChapComments.Text) then
begin
ShowMessage ('Please save the comments before proceeding.');
pcDetail.ActivePage := tsChaplain; // Remain on the Current Page
tsChaplain.SetFocus;
end;
end;
When users click on another tab tsInfoRequest for instance, the validation does trigger, but the Active Page becomes tsInfoRequest instead of remaining tsChaplain.
Any idea what I'm doing wrong?
There's probably a better way to do what you're trying to do. Use the TPageControl.OnPageChanging event instead.
procedure TfCallerInfo.pcDetailPageChanging(Sender: TObject;
NewPage: TTabSheet; var AllowChange: Boolean);
begin
if pc.ActivePage = tsChaplain then
begin
AllowChange := (dmMain.qChaplainCOMMENTS.AsString = dbmChapComments.Text);
if not AllowChange then
ShowMessage(...);
end;
end;
By the way, a better test might be
AllowChange := not dmMain.gChaplainCOMMENTS.Modified;
TField.Modified is set to True when the content of the field is changed when it's DataSet is in dsEdit or dsInsert mode, and set to False when it's state changes back to dsBrowse.

How to hide caret in Delphi TEdit?

I want to remove the caret from a TEdit control in Delphi. I have made the component Enabled := False but the caret still appears.
My question is how to remove the caret from a disabled TEdit control?
I assume that you mean TEdit control.
The solution is HideCaret function, the only problem is where to call it. The 2 event handlers below worked fine for me:
procedure TForm18.Edit1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
HideCaret(Edit1.Handle);
end;
procedure TForm18.Edit1MouseEnter(Sender: TObject);
begin
HideCaret(Edit1.Handle);
end;
Place a TApplicationEventscontrol on the form and in the OnIdle event, hide the caret, as follows. Set the event to nil so it only fires once.
procedure TFormMain.AppEventsIdle(Sender: TObject; var Done: Boolean);
begin
AppEvents.OnIdle := nil;
HideCaret(Memo1.Handle);
end;

TMenuItem-Shortcuts overwrite Shortcuts from Controls (TMemo)

What can I do that shortcuts for menu items don't overwrite those from local controls?
Imagine this simple app in the screenshot. It has one "undo" menu item with the shortcut CTRL+Z (Strg+Z in German) assigned to it. When I edit some text in the memo and press CTRL+Z I assume that the last input in the memo is reverted, but instead the menu item is executed.
This is especially bad in this fictional application because the undo function will now delete my last added "Item 3" which properties I was editing.
CTRL+Z is just an example. Other popular shortcuts cause similar problems (Copy&Paste: CTRL+X/C/V, Select all: CTRL+A).
Mini Demo with menu item with CTRL+Z short-cut http://img31.imageshack.us/img31/9074/ctrlzproblem.png
The VCL is designed to give menu item shortcuts precedence. You can, however, write your item click handler (or action execute handler) to do some special handling when ActiveControl is TCustomEdit (call Undo, etc.)
Edit: I understand you don't like handling all possible special cases in many places in your code (all menu item or action handlers). I'm afraid I can't give you a completely satisfactory answer but perhaps this will help you find a bit more generic solution. Try the following OnShortCut event handler on your form:
procedure TMyForm.FormShortCut(var Msg: TWMKey; var Handled: Boolean);
var
Message: TMessage absolute Msg;
Shift: TShiftState;
begin
Handled := False;
if ActiveControl is TCustomEdit then
begin
Shift := KeyDataToShiftState(Msg.KeyData);
// add more cases if needed
Handled := (Shift = [ssCtrl]) and (Msg.CharCode in [Ord('C'), Ord('X'), Ord('V'), Ord('Z')]);
if Handled then
TCustomEdit(ActiveControl).DefaultHandler(Message);
end
else if ActiveControl is ... then ... // add more cases as needed
end;
You could also override IsShortCut method in a similar way and derive your project's forms from this new TCustomForm descendant.
You probably need an alike solution as below. Yes, feels cumbersome but this is the easiest way I could think of at the time. If only Delphi allowed duck-typing!
{ you need to derive a class supporting this interface
for every distinct control type your UI contains }
IEditOperations = interface(IInterface)
['{C5342AAA-6D62-4654-BF73-B767267CB583}']
function CanCut: boolean;
function CanCopy: boolean;
function CanPaste: boolean;
function CanDelete: boolean;
function CanUndo: boolean;
function CanRedo: boolean;
function CanSelectAll: Boolean;
procedure CutToClipBoard;
procedure Paste;
procedure CopyToClipboard;
procedure Delete;
procedure Undo;
procedure Redo;
procedure SelectAll;
end;
// actions....
procedure TMainDataModule.actEditCutUpdate(Sender: TObject);
var intf: IEditOperations;
begin
if Supports(Screen.ActiveControl, IEditOperations, intf) then
(Sender as TAction).Enabled := intf.CanCut
else
(Sender as TAction).Enabled := False;
end;
procedure TMainDataModule.actEditCutExecute(Sender: TObject);
var intf: IEditOperations;
begin
if Supports(Screen.ActiveControl, IEditOperations, intf) then
intf.CutToClipBoard;
end;
....

Resources