I'm not sure how i would capture the row selected by a mouse click and then press a button to delete that selected row in a stringGrid in delphi.
procedure DeleteRow(Grid: TStringGrid; ARow: Integer);
var
i: Integer;
begin
for i := ARow to Grid.RowCount - 2 do
Grid.Rows[i].Assign(Grid.Rows[i + 1]);
Grid.RowCount := Grid.RowCount - 1;
end;
procedure TManageUsersForm.RemoveRowButtonClick(Sender: TObject);
var
Recordposition : integer;
begin
UserStringGrid.Options := UserStringGrid.Options + [goEditing];
UserStringGrid.Options := UserStringGrid.Options + [goRowSelect];
end;
So the first procedure is for deleting a row and the second makes sure when a user clicks a cell the whole row is highlighted not just that 1 cell.
The mouse click is the most important part!!
Thank You :)
The mouse click is not the most important part. Users can select a row either by keyboard or mouse, it doesn't matter, you'd just want to delete the current row. In the case of a mouse click, or otherwise, you can get the current row by Row.
procedure DeleteCurrentRow(Grid: TStringGrid);
var
i: Integer;
begin
for i := Grid.Row to Grid.RowCount - 2 do
Grid.Rows[i].Assign(Grid.Rows[i + 1]);
Grid.RowCount := Grid.RowCount - 1;
end;
Call it like;
DeleteCurrentRow(UserStringGrid);
I imagine the problem you might be having is to work out which grid row the user has clicked on. One way is:
procedure TForm1.StringGrid1Click(Sender: TObject);
var
StringGrid : TStringGrid;
Row : Integer;
GridRect : TGridRect;
begin
// The Sender argument to StringGrid1Click is actually the StringGrid itself,
// and the following "as" cast lets you assign it to the StringGrid local variable
// in a "type-safe" way, and access its properties and methods via the temporary variable
StringGrid := Sender as TStringGrid;
// Now we can retrieve the use selection
GridRect := StringGrid.Selection;
// and hence the related GridRect
// btw, the value returned for Row automatically takes account of
// the number of FixedRows, if any, of the grid
Row := GridRect.Top;
Caption := IntToStr(Row);
{ ...}
end;
See the OLH about TGridRect.
Hopefully the above will be sufficient to get you going - you've obvious already got most of the way yourself. Or, you could try the method suggested in the other answer, which is a little more "direct" but this way might be a bit more instructive as a "how to". Your choice ...
Related
On closequery of the form I have :
if MessageDlg('Close program ?',
mtConfirmation, [mbYes,mbCancel],0) <> mrYes then CanClose := False
else if DataModule2.mytable.State in [dsEdit,dsInsert] then
if MessageDlg('Save changes ?', mtConfirmation,
[mbYes,mbNo],0) = mrYes then DataModule2.mytable.Post;
Is there a way I can highlight (or color) a changed cell in cxgrid when I trigger my onclosequery event ?
I don't need to know what was changed but just to know which cell was changed so the user can see it so he can easily decide weather to save the changes or not.
It is simple to get the cxGrid to draw a cell (or row) highlighted in some way using the
cxGrid1DBTableView1CustomDrawCell event. And by having a flag that indicates that the OnCloseQuery event is in progress, you can restrict its action to inside that event.
Update The code I originally posted with this answer could not successfully mark more than one cell in the current grid row as changed. The updated code below can do this however; note the comments in the two
procedures.
type
TForm1 = class(TForm)
[...]
public
QueryingClose : Boolean;
end;
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
try
QueryingClose := True;
//{cxGrid1.Invalidate{True); Do NOT call Invalidate, because it causes the
// grid's repainting logic to operate in a way which effectively makes it
// impossible to mark more that one cell in the current data row as changed
ShowMessage('Close?');
finally
QueryingClose := False;
end;
end;
procedure TForm1.cxGrid1DBTableView1CustomDrawCell(Sender:
TcxCustomGridTableView; ACanvas: TcxCanvas; AViewInfo:
TcxGridTableDataCellViewInfo; var ADone: Boolean);
var
Field : TField;
MarkCell : Boolean;
S1,
S2 : String;
EC : TcxGridTableEditingController;
begin
if QueryingClose and
(TcxGridDBTableView(Sender).DataController.DataSet.State in[dsEdit, dsInsert]) then begin
Field := TcxGridDBColumn(AViewInfo.Item).DataBinding.Field;
S1 := VarToStr(Field.OldValue);
// When this event is called, the user may be in the middle of editing a cell's contents
// So, the purpose of the following lines is to close the inplace editor being used to do
// this amd post the chamged value back to the TField associated with the cell
EC := TcxGridDBTableView(Sender).Controller.EditingController;
if EC.IsEditing then
EC.HideEdit(True);
S2 := VarToStr(Field.Value);
MarkCell := S1 <> S2;
if MarkCell then
ACanvas.Brush.Color := clLime;
end;
end;
For this to work, your TDataSet-descendant type must support correctly returning the original contents of the fields on their OldValue property; TClientDataSet, which I've used to write/test this code certainly does this but I've no idea what actual TDataSet type you're using.
Hopefully, it should be apparent that you could use these two procedures to
build a list of TFields that have changed values, including the FieldName OldValue, and Value.
I want to make something like that. I have a list in my StringGrid and i want to delete one row by selecting cell and then clicking the button. Then this list should show again in StringGrid without this row. The biggest problem i have with deleting row, i tried whis one procedure but it only deleted row in StringGrid, not in list, i think.
procedure DeleteRow(Grid: TStringGrid; ARow: Integer);
var
i: Integer;
begin
for i := ARow to Grid.RowCount - 2 do
Grid.Rows[i].Assign(Grid.Rows[i + 1]);
Grid.RowCount := Grid.RowCount - 1;
end;
Please someone for help. :)
If you're using a standard VCL TStringGrid (without using the live bindings made available in recent versions), you can use an interposer class to access the protected TCustomGrid.DeleteRow method.
The following code has been tested in Delphi 2007. It uses a simple TStringGrid dropped on a form, with the default columns and cells, and a standard TButton.
The TForm.OnCreate event handler simply populates the grid with some data to make it easier to see the deleted row. The button click event deletes row 1 from the stringgrid every time it's clicked.
Note: The code does no error checking to make sure that there are enough rows. This is a demo application and not an example of production code. Your actual code should check the number of rows available before attempting to delete one.
// Interposer class, named to indicate it's use
type
THackGrid=class(TCustomGrid);
// Populates stringgrid with test data for clarity
procedure TForm1.FormCreate(Sender: TObject);
var
i, j: Integer;
begin
for i := 1 to StringGrid1.ColCount - 1 do
StringGrid1.Cells[i, 0] := Format('Col %d', [i]);
for j := 1 to StringGrid1.RowCount - 1 do
begin
StringGrid1.Cells[0, j] := Format('Row #d', [j]);
for i := 1 to StringGrid1.ColCount - 1 do
begin
StringGrid1.Cells[i, j] := Format('C: %d R: %d', [i, j]);
end;
end;
end;
// Deletes row 1 from the stringgrid every time it's clicked
// See note above for info about lack of error checking code.
procedure TForm1.Button1Click(Sender: TObject);
begin
THackGrid(StringGrid1).DeleteRow(1);
end;
If you're using a more recent version, and have attached the data to the grid using live bindings, you can just delete the row from the underlying data and let the live bindings handle removing the row.
The selected row can be retrieved StringGrid1.selected and the you can call the following procedure.
procedure TUtils.DeleteRow(ARowIndex: Integer; AGrid: TStringGrid);
var
i, j: Integer;
begin
with AGrid do
begin
if (ARowIndex = RowCount) then
RowCount := RowCount - 1
else
begin
for i := ARowIndex to RowCount do
for j := 0 to ColumnCount do
Cells[j, i] := Cells[j, i + 1];
RowCount := RowCount - 1;
end;
end;
end;
I have a DevExpress grid where I would like to add an unbound checkbox to be able to select some of the items.
After the selection is made I press a button and I must loop the grid to get all the selected items.
It has to be a checkbox. I have tried with a multiselectable grid, but the users can't work with that.
I have tried all the samples that I have been able to find on the supportsites, but no luck.
I need the unbound approach since it is a multiuser setup and users have been selecting and deselecting for each other.
My question: does anyone have a working sample that shows how this can be done?
I've done this and it was (is!) pretty ugly! Create the grid view with bound columns and add an unbound checkbox column with a field type of boolean.
Basically I handle the OnCellClick of the grid view. I check if the item clicked is the checkbox column - by finding the first unbound column in the view with a checkbox type. Then I toggle its state.
I've set AutoEdit on the dataset to true but Deleting/Editing/Inserting to false and ImmediateEditor is false. Not exactly sure which of those are important.
I think the hardest thing was trying to fathom out the complex hierarchy of grid and view level objects and working out which levels contained which of the needed bits. I'm sure there's a better way of doing it but what we've got now works and I'm not going to touch it again!
This is lifted from my code but modified slightly and not tested as it stands - it also needs a bit more error checking:
procedure TMyForm.ViewCellClick(Sender: TcxCustomGridTableView;
ACellViewInfo: TcxGridTableDataCellViewInfo; AButton: TMouseButton;
AShift: TShiftState; var AHandled: Boolean);
var
col: TcxGridColumn;
begin
// Manually handle the clicking of the checkbox cell - otherwise it seems
// virtually impossible to get the checked count correct.
col := GetViewCheckColumn(Sender);
if (Sender.Controller.FocusedItem = col) then
begin
ToggleRowSelection(TcxCustomGridTableView(TcxGridSite(Sender).GridView), col);
end;
end;
procedure TMyForm.ToggleRowSelection(AView: TcxCustomGridTableView; ACol: TcxGridColumn);
var
rec: TcxCustomGridRecord;
begin
rec := AView.Controller.FocusedRecord;
if (rec = nil) then exit;
if (rec.Values[ACol.Index] = TcxCheckBoxProperties(ACol.Properties).ValueChecked) then
begin
rec.Values[ACol.Index] := TcxCheckBoxProperties(ACol.Properties).ValueUnchecked;
end
else
begin
rec.Values[ACol.Index] := TcxCheckBoxProperties(ACol.Properties).ValueChecked;
end;
end;
function TMyForm.GetViewCheckColumn(AView: TcxCustomGridView): TcxGridColumn;
var
index: integer;
vw: TcxCustomGridTableView;
item: TcxCustomGridTableItem;
begin
// We're looking for an unbound check box column - we'll return the first
// one found.
Assert(AView <> nil);
result := nil;
if (AView is TcxCustomGridTableView) then
begin
vw := TcxCustomGridTableView(AView);
for index := 0 to vw.ItemCount - 1 do
begin
item := vw.Items[index];
if (item.Properties is TcxCustomCheckBoxProperties) then
begin
if (item is TcxGridDBColumn) then
begin
if (TcxGridDBColumn(item).DataBinding.FieldName = '') then
begin
result := TcxGridColumn(item);
break;
end;
end;
end;
end;
end;
end;
I then extended it by checking for a SPACE bar press in the OnKeyUp of the grid and calling ToggleRowSelection and also similar for a double click on a row.
When iterating through the rows you can test if a row is checked using something like the following:
function TMyForm.GetViewIsRowChecked(AView: TcxCustomGridView; ARecord: TcxCustomGridRecord): boolean;
var
col: TcxGridColumn;
begin
result := False;
col := GetViewCheckColumn(AView);
if ((col <> nil) and (ARecord <> nil)) then
begin
result := (ARecord.Values[col.Index] = TcxCheckBoxProperties(col.Properties).ValueChecked);
end;
end;
I think that's it. I've dug it out of a large grid/view helper unit we've built up over a while. Oh, and it's currently working with Delphi 2010 with DXVCL v2011 vol 1.10.
Hope it helps.
My application uses a TButtonGroup control. I assign to each button an event handler: doClick. By assigning information to each button (Pointer (i)) I can figure out which button was called. This is the code:
procedure TVector_Menu.Synchronize (rows, cols: Int32);
var btn: TGrpButtonItem;
i: Int32;
begin
ButtonGroup.Items.Clear;
Self.Rows := rows;
Self.Cols := cols;
for i := 0 to rows * cols - 1 do
begin
btn := Buttongroup.Items.Add;
btn.Data := Pointer (i);
btn.ImageIndex := i;
btn.OnClick := doClick;
end; // for
Self.ClientHeight := 4 + rows * ButtonGroup.ButtonHeight;
Self.ClientWidth := 22 + cols * ButtonGroup.ButtonWidth;
end; // Synchronize //
procedure TVector_Menu.doClick (Sender: TObject);
var btn: TGrpButtonItem;
i, r, c: Int32;
begin
btn := (Sender as TGrpButtonItem); // ### TButtonGroup
i := Int32 (btn.Data);
get_rc (i, r, c);
if Assigned (FOnClick)
then FOnClick (Sender, #FButton_Matrix [r, c]);
end; // doClick //
When doClick is called I get an Invalid Typecast at the line labeled '###'. The typecast is correct when I use TButtonGroup for btn as well as in the typecast, but this one does not contain a data property and that would not have been of much use anyway.
As a Test I assigned an OnClick event handler to the TButtonGroup control and I noticed that when I click a button, first the button event handler is called and next the TButtonGroup, containing the button, event handler.
Question: is there a way to find out which button of a TButtonGroup was clicked?
Using Delphi XE on Windows 7/64
You get an invalid typecast exception because Sender is in fact the TButtonGroup and is not a TGrpButtonItem. What this means is that you need to use a different event handler for each button if you are going to use TGrpButtonItem.OnClick.
In your situation it is clear that you should use the TButtonGroup.OnButtonClicked event which does provide the button index.
There is a potential pitfall here, however, that you need to make sure you avoid. The documentation states:
Occurs when a button is clicked, if the OnClick event is not present.
In other words the OnButtonClicked event will only fire if you have not assigned an OnClick event handler for either the button group or the button item.
use...
procedure TfrmFattern.ButtonGroup1ButtonClicked(Sender: TObject; Index: Integer);
begin
StatusBar1.SimpleText := IntToStr(Index + 1);
end;
All is in the title.
How can we also make an officehint customisable for each row. Mean when mousemove on a row, display the information of this record (from a db query).
Thanks
You can color individual cells using the CellProperties property of the grid. You can use this to color an entire row:
var
RowIndex: Integer;
ColIndex: Integer;
with MyDBAdvGrid do
begin
// you choose the row index; you may want to iterate all rows to
// color each of them
RowIndex := 2;
// now iterate all (non-fixed, visible) cells in the row and color each cell
for ColIndex := FixedCols to ColCount - 1 do
begin
CellProperties[ColIndex, RowIndex].BrushColor := clYellow;
CellProperties[ColIndex, RowIndex].FontColor := clGreen;
end;
end;
To fill your office hint with record data I would suggest updating it when the user moves the mouse. Use the MouseToCell function to get row and column under the mouse, then use MyDBAdvGrid.AllCells[ColIndex, RowIndex] to access the cell content.
An Alternative to Heinrich answer is to use the OnGetCellColor event.
This can be use like so:
procedure TDBAdvGrid.DBGridGetCellColor(Sender: TObject; ARow,
ACol: Integer; AState: TGridDrawState; ABrush: TBrush; AFont: TFont);
begin
if (your condition) then ABrush.Color := clRed;
end;
Similarly for the hint:
procedure TDBAdvGrid.DBGridGridHint(Sender: TObject; ARow, ACol: Integer;
var hintstr: String);
begin
hintstr := 'your hint text';
end;