How to get selected cell text from TcxGrid? - delphi

I'm using Devexpress TcxGrid and I'm trying to get selected cell text. My TcxGrid is connected to some kind of DataSource - I think it is DataControler.
My goal is to get the text from cells in the entire row and place it in string divided with comas.

If you want values with multiselection and from a TcxGridDbTableView:
In my result i don't have a separation between rows.
function GetSelectedValuesFrmGrid: String;
var
intSelectLoop,
intRowLoop: Integer;
oTableView: TcxGridDbTableView;
strValue: Variant;
oList: TStringList;
begin
Result:= '';
// Kind Of TableView
if <TcxGrid>.ActiveView is TcxGridDbTableView then
begin
oTableView:= <TcxGrid>.ActiveView as TcxGridDbTableView;
oList:= TStringList.Create();
try
for intSelectLoop:= 0 to oTableView.Controller.SelectedRowCount-1 do
begin
for intRowLoop:= 0 to oTableView.Controller.SelectedRows[intSelectLoop].ValueCount-1 do
begin
strValue:= oTableView.Controller.SelectedRows[intSelectLoop].Values[intRowLoop];
// Value can be Null
if VarIsNull(strValue) then
begin
strValue:= '';
end;
oList.Add(strValue);
end;
end;
Result:= oList.CommaText;
finally
oList.Free;
end;
end;
end;

You need the text from all cells in selected rows?
for I := 0 to cxGridDBTableView.Controller.SelectedRowCount -1 do
for J := 0 to cxGridDBTableView.Controller.SelectedRows[I].ValueCount -1 do
SelectedRowStr := SelectedRowStr + VarToStr(cxGrid1DBTableView1.Controller.SelectedRows[I].Values[J]) + ',';
SelectedRowStr := Copy(SelectedRowStr,1,length(SelectedRowStr)-1);

The grid will have a DataControler descendant. You can cycle through the items in the DataController and, depending on how your grid is configured, the items in the DataController can correspond to individual 'columns' shown in your grid. That being said the items in a DataController stay in ther
This code will let you cycle through each column in the grid and build up the string based on the DataController values.
var
i: Integer;
DC: TcxCustomDataController;
s: string;
begin
s := '';
DC := <yourgrid>.DataController;
for i := 0 to <yourgrid>.ColumnCount -1 do begin
s := s + vartostr(DC.Values[DC.FocusedRecordIndex, <yourgrid>.Columns[i].Index]) + ',';
end;
if Length(s) > 0 then
s := Copy(s,1,Length(s)-1);
end;

Related

Retrieve values from database to StringGrid

In connection with my previous question
I've successfully save string values in StringGrid to my database in one column.
Now I want to retrieved the values from database and put it back from StringGrid.
This is what I've tried :
procedure TForm1.BitBtn1Click(Sender: TObject);
var i, iRow, iCol: integer ;
s : string;
Grid2: TStringGrid;
strArray : Array of String;
charArray : Array[0..0] of Char;
begin
getRecords;
with SqlQuery4 do
begin
if RecordCount <> 0 then
begin
names := FieldByName('names').AsString;
s := names;
charArray[0] := '|';
strArray := s.Split(charArray);
Grid2 := Form7.StringGrid1;
i := 0;
iCol:= 0;
iRow:=0;
for iRow := 1 to 19 do // increment rows
begin
for iCol := 0 to 5 do // increment cols / max 5 cols
begin
if iCol = 5 then iCol := 0; // reset column so that it will go on next row
for i := 0 to Length(strArray)-1 do // get string one by one
begin
Grid2.RowCount := Grid2.RowCount + 1; // add row
Grid2.Cells[iCol, iRow] := strArray[i]; // this value always overwrite, how to save this previous data?
end;
end;
end;
end;
end;
end;
It adds row but doesn't get the values in database..
You are missing the First and Next methods for moving in a TDataSet of the query object SqlQuery4
This code could work:
procedure TForm1.BitBtn1Click(Sender: TObject);
var i, iRow, iCol: integer ;
s : string;
Grid2: TStringGrid;
strArray : Array of String;
charArray : Array[0..0] of Char;
begin
getRecords;
with SqlQuery4 do
begin
First; //SqlQuery4 goes to the first row returned by the SQL query
if RecordCount <> 0 then
begin
names := FieldByName('names').AsString;
s := names;
charArray[0] := '|';
strArray := s.Split(charArray);
Grid2 := Form7.StringGrid1;
i := 0;
iCol:= 0;
iRow:=0;
for iRow := 1 to 19 do // increment rows
begin
for iCol := 0 to 5 do // increment cols / max 5 cols
begin
if iCol = 5 then iCol := 0; // reset column so that it will go on next row
for i := 0 to Length(strArray)-1 do // get string one by one
begin
Grid2.RowCount := Grid2.RowCount + 1; // add row
Grid2.Cells[iCol, iRow] := strArray[i]; // this value always overwrite, how to save this previous data?
end;
end;
Next; //SQLQuery goes to the next row available of the data set reurned by the SQL query
end;
end;
end;
end;
The basic way when you open a set of rows (a data set in Delphi) using a SQL query is something like this:
Procedure TForm1.ProcessData;
begin
{
The SQL query may look like:
SELECT *
FROM TABLE
WHERE
COLUMN1 = :PARAMETER1 AND /*Column1 is a VARCHAR */
COLUMN2 = :PARAMETER2 /*Column2 is a Integer*/
}
//If already open, the close it
if Query1.Active then
Query1.Close;
//Load some parameters from some components
Query1.ParamByName('PARAMETER1').AsString := Edit1.Text;
Query1.ParamByName('PARAMETER2').AsInteger := ComboBox1.ItemIndex;
//Open the SQL query
Query1.Open;
Query1.First; //We assure that the we are on the first record
while Not Query1.Eof do //Get into a whiile loop, exit when gets the end of the rows list returned by the Query1 object
begin
{
Your processing code goes here
}
Query1.Next; //Now you advance to the next row to process it, if there is no more rows EOF become TRUE (EOF(stans for End Of File)
end
Query1.Close; //is a good idea to close the query if you not longer need it to save resources
end;

Updating field in cxGrid acting strange

I have a function to update a cxGrid made with help from answers to Loop through records on a cxgrid and update a field/column
But it is sometimes acting a bit strange. If I open the form with the cxGrid and click the columnheader without doing anything else, the records are updateted OK. But if the 'selectorbar' is moved away from the top, the record marked is not updated.
I am sure it is a property that needs to be changed, but which one.
The variable fSelected is set to False at FormShow and is ther so that the user can unselect records as well.
procedure TfrmContactsSelect.colContactSelectedHeaderClick(Sender: TObject);
var
i: Integer;
Index: Integer;
BookMark : TBookMark;
Contact: variant;
begin
if fMulti = True then
begin
Screen.Cursor := crHourGlass;
fSelected := not fSelected;
BookMark := qryContacts.GetBookmark;
qryContacts.DisableControls;
try
for i := 0 to grdContactsView1.DataController.FilteredRecordCount - 1 do
begin
Index := grdContactsView1.DataController.FilteredRecordIndex[i];
Contact := grdContactsView1.DataController.Values[Index, 4];
if grdContactsView1.DataController.LocateByKey(Contact) then
begin
qryContacts.Edit;
qryContacts.FieldByName('fldcontact_selected').AsBoolean := fSelected;
qryContacts.Post;
end;
end;
finally
qryContacts.EnableControls;
qryContacts.GotoBookmark(BookMark);
qryContacts.FreeBookmark(BookMark);
end;
Screen.Cursor := crDefault;
end;
end;
Delphi XE7, DevExpress 14.2.2, UniDAC 5.5.12 for DB access
Comment:
I have ended up with the following solution based on the answer and input from MartynA
procedure TfrmContactsSelect.colContactSelectedHeaderClick(Sender: TObject);
var
i: Integer;
Index: Integer;
MarkedRecord: variant;
CurrentRecord: variant;
begin
if fMulti = True then
begin
Screen.Cursor := crHourGlass;
fSelected := not fSelected;
Index := grdContactsView1.DataController.FocusedRecordIndex;
MarkedRecord := grdContactsView1.DataController.Values[Index, colContactGuid.ID];
try
for i := 0 to grdContactsView1.DataController.FilteredRecordCount - 1 do
begin
Index := grdContactsView1.DataController.FilteredRecordIndex[i];
CurrentRecord := grdContactsView1.DataController.Values[Index, colContactGuid.ID];
if grdContactsView1.DataController.LocateByKey(CurrentRecord) then
begin
grdContactsView1.DataController.Edit;
grdContactsView1.DataController.SetEditValue(colContactSelected.ID, fSelected, evsText);
grdContactsView1.DataController.Post;
end;
end;
finally
grdContactsView1.DataController.LocateByKey(MarkedRecord);
end;
Screen.Cursor := crDefault;
end;
end;
I can reproduce your problem using the sample project I posted in my answer to your other q.
Try this: Add a TMemo to your form, and inside the 'if grdContactsView1.DataController.LocateByKey(Contact) then' block, write the value of a row-unique datafield and the Selected datafield value to the memo.
Then, what I get when some row other than the top row is selected is that one row is listed twice in the memo, with Selected both false and true, and one of the rows in the filter isn't listed at all, which I think accounts for the behaviour you're seeing. If I then comment out the .Edit .. .Post lines, it correctly lists all the rows in the filter.
So evidently doing the Selected field changes inside a block which iterated the FilteredRecordIndex property of the DBTableView is what's causing the problem.
Personally, I find that it goes a bit against the grain to modify dataset rows in code via a DB-aware control (because you usually end up fighting the DB-awareness of the control), but in this case, it's straightforward to do the processing via the DBTableView of the cxGrid.
procedure TForm1.ProcessFilteredRecords;
var
PrevV,
V : Variant;
i,
Index: Integer;
S : String;
begin
// First, pick up a reference to the current record
// so that we can return to it afterwards
Index := cxGrid1DBTableView1.DataController.FocusedRecordIndex;
PrevV := cxGrid1DBTableView1.DataController.Values[Index, 0];
try
for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do begin
Index := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
V := cxGrid1DBTableView1.DataController.Values[Index, 0];
if cxGrid1DBTableView1.DataController.LocateByKey(V) then begin
cxGrid1DBTableView1.DataController.Edit;
// 2 is the index of my Selected column in the grid
if cxGrid1DBTableView1.DataController.SetEditValue(2, True, evsText) then
Caption := 'OK'
else
Caption := 'Failed';
cxGrid1DBTableView1.DataController.Post;
end;
end;
finally
if cxGrid1DBTableView1.DataController.LocateByKey(PrevV) then
Caption := 'OK'
else
Caption := 'Failed';
end;
end;
Another way to avoid the problem is to change the Selected states in two steps:
Iterate the FilteredRecordIndex to build a list of rows to change - in your case this would be a list of guids
Then, iterate the list of rows and update their Selected states.
Code:
procedure TForm1.ProcessFilteredRecords;
var
V : Variant;
i,
Index: Integer;
BM : TBookMark;
S : String;
TL : TStringList;
begin
Memo1.Lines.Clear;
TL := TStringList.Create;
try
for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do begin
Index := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
V := cxGrid1DBTableView1.DataController.Values[Index, 0];
if cxGrid1DBTableView1.DataController.LocateByKey(V) then begin
if CDS1.FieldByName('Selected').AsBoolean then
S := 'True'
else
S := 'False';
S := CDS1.FieldByName('Name').AsString + ' ' + S;
Memo1.Lines.Add(S);
TL.Add(CDS1.FieldByName('Guid').AsString);
end;
end;
try
BM := CDS1.GetBookMark;
CDS1.DisableControls;
for i := 0 to TL.Count - 1 do begin
if CDS1.Locate('guid', TL[i], []) then begin
CDS1.Edit;
CDS1.FieldByName('Selected').AsBoolean := True;
CDS1.Post;
end
end;
finally
CDS1.EnableControls;
CDS1.GotoBookmark(BM);
CDS1.FreeBookmark(BM);
end;
finally
TL.Free;
end;
end;
Like you, I was expecting that changing a property or two of the cxGrid might avoid the problem without any code, but I haven't been able to find anything which does.

Incrementing an INI file's section number

I have an INI file that stores some integers for settings. The section names are stored like this:
[ColorScheme_2]
name=Dark Purple Gradient
BackgroundColor=224
BackgroundBottom=2
BackgroundTop=25
...
[ColorScheme_3]
name=Retro
BackgroundColor=5
BackgroundBottom=21
BackgroundTop=8
...
I need to figure out a way to create new sections, that increment the color scheme number +1 from the highest section number. I have a comboBox that lists out the current colorscheme names, so when a user saves to the INI file, the existing scheme is just overwritten. How can I check the ComboBox text to see if it is an existing section and if not, create a new one with an incremented name? (i.e. from the example code above, ColorScheme_2 and ColorScheme_3 already exist, so the next section to create would be ColorScheme_4).
You can read all sections by using ReadSections method, then iterate returned string list and parse each item in it to store the highest found index value:
uses
IniFiles;
function GetMaxSectionIndex(const AFileName: string): Integer;
var
S: string;
I: Integer;
Index: Integer;
IniFile: TIniFile;
Sections: TStringList;
const
ColorScheme = 'ColorScheme_';
begin
Result := 0;
IniFile := TIniFile.Create(AFileName);
try
Sections := TStringList.Create;
try
IniFile.ReadSections(Sections);
for I := 0 to Sections.Count - 1 do
begin
S := Sections[I];
if Pos(ColorScheme, S) = 1 then
begin
Delete(S, 1, Length(ColorScheme));
if TryStrToInt(S, Index) then
if Index > Result then
Result := Index;
end;
end;
finally
Sections.Free;
end;
finally
IniFile.Free;
end;
end;
And the usage:
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(IntToStr(GetMaxSectionIndex('d:\Config.ini')));
end;
How can I check the ComboBox text to see if it is an existing section and if not, create a new one with an incremented name?
Like this:
const
cPrefix = 'ColorScheme_';
var
Ini: TIniFile;
Sections: TStringList;
SectionName: String;
I, Number, MaxNumber: Integer;
begin
Ini := TIniFile.Create('myfile.ini')
try
SectionName := ComboBox1.Text;
Sections := TStringList.Create;
try
Ini.ReadSections(Sections);
Sections.CaseSensitive := False;
if Sections.IndexOf(SectionName) = -1 then
begin
MaxNumber := 0;
for I := 0 to Sections.Count-1 do
begin
if StartsText(cPrefix, Sections[I]) then
begin
if TryStrToInt(Copy(Sections[I], Length(cPrefix)+1, MaxInt), Number) then
begin
if Number > MaxNumber then
MaxNumber := Number;
end;
end;
end;
SectionName := Format('%s%d', [cPrefix, MaxNumber+1]);
end;
finally
Sections.Free;
end;
// use SectionName as needed...
finally
Ini.Free;
end;
end;

Displaying item when LVItemGetCaption is being called with EasyListView?

I am trying to implement virtual data mode with EasyListview
From the demo :
procedure TForm1.AddItems(Count: Integer);
var
i: Integer;
begin
// Add items to the listview. Actually the items are added to the first
// group. This group is created automatically when the first item is added.
LV.BeginUpdate;
try
for i := 0 to Count - 1 do
LV.Items.AddVirtual;
finally
LV.EndUpdate;
end;
end;
procedure TForm1.LVItemGetCaption(Sender: TCustomEasyListview;
const Item: TEasyItem; Column: Integer; var Caption: WideString);
begin
case Column of
0: Caption := 'Item ' + IntToStr(Item.Index);
1: Caption := 'Detail ' + IntToStr(Item.Index);
end;
end;
If I add some items which are string :
procedure TForm1.AddItems(Count: Integer);
var
i: Integer;
begin
// Add items to the listview. Actually the items are added to the first
// group. This group is created automatically when the first item is added.
LV.BeginUpdate;
try
for i := 0 to Count - 1 do
begin
LV.Items.AddVirtual.Caption := 'DISPLAY ME ' + IntToStr(i);
end;
finally
LV.EndUpdate;
end;
end;
How to get and displaying the stored virtual caption(=string) when LVItemGetCaption is being called?
If I get the caption with Caption := LV.Items.Items[Item.Index].Caption ; then Stack overflow.
You must add your data object to the item. E.g.:
type
TMyData = class
Caption: string;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
item: TEasyItemVirtual;
MyData: TMyData;
begin
EasyListview1.BeginUpdate;
try
for i := 0 to 100 - 1 do
begin
MyData := TMyData.Create;
MyData.Caption := Format('My Item %D',[i]);
item := EasyListview1.Items.AddVirtual;
item.Data := MyData;
end;
finally
EasyListview1.EndUpdate;
end;
end;
procedure TForm1.EasyListview1ItemGetCaption(Sender: TCustomEasyListview; Item: TEasyItem;
Column: Integer; var Caption: WideString);
begin
case Column of
0: Caption := TMyData(Item.Data).Caption;
1: Caption := TMyData(Item.Data).Caption;
end;
end;
And don't forget to free your object:
procedure TForm1.EasyListview1ItemFreeing(Sender: TCustomEasyListview; Item: TEasyItem);
begin
if Assigned(Item.Data) then
Item.Data.Free;
end;
Virtual nodes are ones that don't store their data. They're just views of data you are expected to already have in some other data structure of your program. When the control needs to display a node, it asks your program what text it should use by firing the OnItemGetCaption event.
In fact, it will call the event any time it needs to know the value of the Caption property, so when you try to handle the caption-fetching event by fetching the value of the caption, you trigger infinite recursion.

Stringlist with delimiter as a string?

I have an attribute called HistoryText in a object that is stored as a string.
I want to show all rows in a grid. I should be able to delete and edit rows in the grid.
The format is:
16.5.2003-$-12:09-$-anna-$-Organization created
2.6.2005-$-13:03-$-jimmy-$-Organization edited
19.12.2005-$-13:33-$-madeleine-$-Organization edited
So each row have 4 fields, date, time, user, and message with a delimiter string as '-$-'.
As the delimiter a string and not a char it cannot be assigned to the stringlists delimiter property.
I have a routine to extract the string to a Stringlist:
procedure ParseDelimited(const aStringList: TStringList; const aOrgList, aDelimiter: string);
var
vDelimiterPos : integer;
vPartialStr : string;
vRemaingTxt : string;
vDelimiterLength : integer;
begin
vDelimiterLength := Length(aDelimiter);
if (AnsiRightStr(aOrgList, Length(aDelimiter)) = aDelimiter) then
vRemaingTxt := aOrgList
else
vRemaingTxt := aOrgList + aDelimiter;
aStringList.BeginUpdate;
aStringList.Clear;
try
while Length(vRemaingTxt) > 0 do
begin
vDelimiterPos := Pos(aDelimiter, vRemaingTxt);
vPartialStr := Copy(vRemaingTxt,0,vDelimiterPos-1);
aStringList.Add(vPartialStr);
vRemaingTxt := Copy(vRemaingTxt,vDelimiterPos+vDelimiterLength,MaxInt);
end;
finally
aStringList.EndUpdate;
end;
end;
and it seems to work fine. My problem is syncing the changes in the StringList back to the original String property ? There are so much historical data with this delimiter so I don't think change it to a TChar is a realistic option.
Update:
A clarification. I think I can manage to convert the String to a StringList with the method above. Then display it in the grid should not be so hard. The problem come when I want to convert the TStringList back to the original String property wih '-$-' as delimiter. I cannot do HistoryText := myStringList.Delimitedtext for example.
Second update:
I have solved it. You all got a +1 for fast answers and really trying to help. In summary how I did it.
Read from Historytext:
MyStringList.Text := Historytext;
Now each row have 3 delimiters of '-$-' and each line is separated by a linefeed as usual.
In a loop parse the Stringlist and show it in the grid. I don't bother about MyStringList anymore.
Let the user delete and edit rows in the grid.
When finished loop by row and columns in the grid and build a new string with the same format as original.
Assign that string to HistoryText.
So shift focus from StringList to the grid made it easier :)
Instead of Delimiter (a char) and DelimitedText, you can also use LineBreak (a string) and Text:
lst := TStringList.Create;
try
lst.LineBreak := '-$-';
lst.Text := '16.5.2003-$-12:09-$-anna-$-Organization created';
Memo1.Lines := lst; // or whatever you do with it
finally
lst.Free;
end;
Ans it works even the other way round.
Wild stab in the dark (it isn't very clear what you are asking):
Work through the grid rows:
For each row:
assign an empty string to a temporary string var
For each column add row/column value plus your delimiter to temporary string var
remove last delimiter from temporary string var (if it is non-empty)
add temporary string var to stringlist
Write stringlist's text property back to your HistoryText
const
Delimiter = '-$-';
var
row: Integer;
col: Integer;
SL: TStringList;
rowString: string;
begin
SL := TStringList.Create;
try
for row := 0 to StringGrid1.RowCount - 1 do begin
rowString := '';
for col := 0 to StringGrid1.ColCount - 1 do begin
rowString := StringGrid1.Cells[col, row] + Delimiter;
end;
if rowString <> '' then begin
rowString := Copy(rowString, 1, Length(rowString) - Length(Delimiter));
end;
SL.Add(rowString);
end;
HistoryText := SL.Text;
finally
SL.Free;
end;
end;
Using Uwe's solution of TStrings' LineBreak property:
var
row: Integer;
col: Integer;
SLRows: TStringList;
SLCols: TStringlist;
begin
SLRows := TStringList.Create;
try
SLCols := TStringList.Create;
try
SLCols.LineBreak := '-$-';
for row := 0 to StringGrid1.RowCount - 1 do begin
SLCols.Clear;
for col := 0 to StringGrid1.ColCount - 1 do begin
SLCols.Add(StringGrid1.Cells[col, row]);
end;
SLRows.Add(SLCols.Text);
end;
HistoryText := SLRows.Text;
finally
SLCols.Free;
end;
finally
SLRows.Free;
end;
end;

Resources