So I have a CheckListBox with 6 Items :
Items.Strings = (
'Banana'
'Apple'
'Pomelo'
'Orange'
'Peach'
'BlueBarry')
If I want to show them then into a ShowMessage dialog, the message printed is.
'anana','pple','omelo','range','each','lueBarry'.
The procedure I use is this.
procedure TForm1.Button1Click(Sender: TObject);
var I : Integer;
begin
for I := 0 to CheckListBox1.Items.Count - 1 do
ShowMessage(CheckListBox1.Items.ValueFromIndex[I]);
end;
Why can't I get the first char from my Item?
Try to insert items in the right way like this
procedure TForm1.Button1Click(Sender: TObject);
begin
CheckListBox1.Items.Add('Banana');
CheckListBox1.Items.Add('Apple');
CheckListBox1.Items.Add('Pomelo');
CheckListBox1.Items.Add('Orange');
CheckListBox1.Items.Add('Peach');
CheckListBox1.Items.Add('BlueBarry');
end;
the result will be:
then...
procedure TForm1.Button2Click(Sender: TObject);
var I : Integer;
begin
for I := 0 to CheckListBox1.Items.Count - 1 do
ShowMessage(CheckListBox1.Items[I]);
end;
You cannot use ValueFromIndex for your porpouse.
TStrings.ValueFromIndex
Return the value part of a string based on it's index.
Declaration
public property TStrings.ValueFromIndex : string
read GetValueFromIndex
write SetValueFromIndex;
Description
ValueFromIndex returns the value part of a string based on the string index. The value part are all characters in the string after the NameValueSeparator character, or all characters if the NameValueSeparator character is not present.
TStrings.NameValueSeparator
Value of the character used to separate name,value pairs
Declaration
public property TStrings.NameValueSeparator : Char
read FNameValueSeparator
write SetNameValueSeparator;
Description
NameValueSeparator is the character used to separate name,value pair. By default, this is the equal sign (=), resulting in Name=Value pairs.
It can be set to a colon for Name : Value pairs.
thake a look at the vcl source :O
function TStrings.GetValueFromIndex(Index: Integer): string;
begin
if Index >= 0 then
Result := Copy(Get(Index), Length(Names[Index]) + 2, MaxInt) else
Result := '';
end;
Related
In cxGrid,I have a column that is boolean (properties : checkbox).
How can I do a footer summary (SUM) of such a column i.e to sum how many records are checked.
Right now, if I set it to SUM, my footer summary displays negative numbers for the items checked.How can I avoid these negative numbers?
edit :
I have found a would be solution on their site with :
procedure TForm1.cxGrid1DBTableView1DataControllerSummaryFooterSummaryItemsSummary(
ASender: TcxDataSummaryItems; Arguments: TcxSummaryEventArguments;
var OutArguments: TcxSummaryEventOutArguments);
var
si: TcxGridDBTableSummaryItem;
begin
si := Arguments.SummaryItem as TcxGridDBTableSummaryItem;
if si.Column = cxGrid1DBTableView1Sonda then
OutArguments.Done := not OutArguments.Value;
end;
However I am getting the error :
Could not convert variant of type (Null) into type (Boolean).
Dont understand this. Field is boolean type (bit).
edit2:
The problem is that sql server by default sets boolean type to NULL.
That is why the conversion error.
You can also just set your grid to calculate that summary using a different field , for example a calculated field where you assign the exact value you want to add each time.
Add a calculated field to your dataset, with the desired value.
MyHiddenField.Value := -1 * YourCheckingField.AsInteger;
Go to the Summaries Tab on the CxGrid dialog, and add a new Summary:
Set the Column property to the Grid Column where you want it to appear
Set the FieldName to your calculated field
And finally set Kind to skSum
It is better to send such questions to DevExpress support team.
You can customize footer:
assign Kind=skNone to footer summary item
use OnGetText event to show what you want
Quick example (shows number of chars in all records as footer value):
procedure TForm54.cxGrid1DBTableView1TcxGridDBDataControllerTcxDataSummaryFooterSummaryItems0GetText(
Sender: TcxDataSummaryItem; const AValue: Variant; AIsFooter: Boolean;
var AText: string);
var i,j: integer;
begin
j := 0;
for i := 0 to cxGrid1DBTableView1.DataController.RecordCount-1 do
j := j + Length(String(cxGrid1DBTableView1.DataController.Values[i, cxGrid1DBTableView1c.Index]));
AText := IntToStr(j);
end;
I think that you can resolve that problem in SQL component. Use typecasting and your cxGrid will work with Integer values.
SELECT CAS(Bit_Column AS int) AS Int_Column
FROM YourTable
You can try this :
procedure TForm1.cxGrid1DBTableView1DataControllerSummaryAfterSummary(
ASender: TcxDataSummary);
var i, j, Imp:integer;
Item: TcxGridDBTableSummaryItem;
begin
DBTableView1.DataController.BeginLocate;
for j:=0 to ASender.FooterSummaryItems.Count-1 do ASender.FooterSummaryValues[j]:=0;
try
for i:=0 to DBTableView1.DataController.RowCount-1 do
begin
if (DBTableView1.DataController.Values[i,cxGrid1DBTableView1Sonda.Index]<>null) then
for j:=0 to ASender.FooterSummaryItems.Count-1 do
begin
Item:=TcxGridDBTableSummaryItem(ASender.FooterSummaryItems[j]);
Imp:= DBTableView1.DataController.Values[i, cxGrid1DBTableView1Sonda.Index];
if (Imp= 1) or ((Imp= 2)
then ASender.FooterSummaryValues[j]:= ASender.FooterSummaryValues[j]+1;
end;
end;
finally
DBTableView1.DataController.EndLocate;
end;
end;
procedure cxGrid1DBTableView1DataControllerSummaryFooterSummaryItemsSummary(
ASender: TcxDataSummaryItems; Arguments: TcxSummaryEventArguments;
var OutArguments: TcxSummaryEventOutArguments);
var
AValue: Variant;
AItem: TcxGridTableSummaryItem;
begin
AItem := TcxGridTableSummaryItem(Arguments.SummaryItem);
// считаем проверенные
if (AItem.Column = tvCompareisCorrect) and (AItem.Kind = skCount) and (AItem.Position = spFooter) then begin
AValue := tvCompare.DataController.Values[ Arguments.RecordIndex, tvCompareisCorrect.Index];
if not VarIsNull(AValue) then
if not VarAsType(AValue, varBoolean) then Dec(OutArguments.CountValue);
end;
Im going through one of my old exam papers, preparing for my finals, and for the love of life, I cant figure out how to do this!
The program was working earlier, but it wasn't sorting the array. Now Im getting an error saying EAccess violation with message: access violation at address 00404BDE
Here's my code (its kind of long, maybe you can help me spot my error) :
private
{ Private declarations }
iCount : Integer;
arrDams : array [1..200] of string;
Procedure List;
procedure Display;
procedure Sort;
procedure Search (sDam : String);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Display; //Display with Numbers
var
k : Integer;
begin
for K := 1 to 200 do
begin
RedOut.Lines.Add (IntToStr(k) + '.) ' + (arrDams[k]));
end;
end;
procedure TForm1.FormCreate(Sender: TObject); // Create
begin
//
end;
procedure TForm1.List; // TextFile to array
var
MyFile : TextFile;
k : Integer;
begin
If FileExists('Dams.txt') <> True
then Application.Terminate;
AssignFile (MyFile, 'Dams.txt');
Reset(MyFile);
For K := 1 to 200 do
begin
Readln(MyFile, arrDams[k])
end;
end;
procedure TForm1.Search(sDam: String); // Search
begin
end;
procedure TForm1.Sort; // Sort;
var
K,L : byte;
sKeep : string;
begin
for k := 1 to iCount -1 do
begin
for l := k + 1 to iCount do
begin
if arrDams[k] > arrDams[L] then
begin
sKeep := arrDams[k];
arrDams[k] := arrDams[L];
arrDams[L] := sKeep
end;
end;
end;
end;
procedure TForm1.btnListClick(Sender: TObject);
begin
List;
Display;
end;
procedure TForm1.btnDisplayClick(Sender: TObject);
begin
display;
sort;
end; //<---------- ERROR OVER HERE!
end.
Theres 3 buttons at the top of the form, namely Show list, Make new textfile with list and Sort list alphabetically. The button Im working on is to sort the list. This question paper says I must make a sort procedure and must be called when the Sort Button is clicked.
Thanks for any advice/help
P.S.
Can you please point me to a link where they explain Selection Sorting in depth - the logic isn't with me on this..
You don't initialize iCount. So it is 0. Therefore iCount-1 is -1. However, you use Byte, an unsigned type, for your loop variable. Now, -1 when interpreted as an unsigned Byte is 255. If you follow this all through it means that you access the array out of bounds. In fact what happens is that the inner loop executes exactly once with a value of l equal to 0 and k equal to 255.
Were you to enable the range checking compiler option, you would have encountered a runtime error as soon as you run off the end of the array.
Presumably you want to initialize iCount to some value. I cannot tell what, but you will know.
Beyond that, stop using unsigned types for loop variables. Replace Byte with Integer.
I have a text file that has, on any given row, data that are expressed both in text format and in numeric format. Something like this:
Dog 5 4 7
How do I write a file reading routine in Delphi that reads this row and assigns the read values into the correct variables ("Dog" into a string variable and "5", "4" and "7" into real or integer variables)?
You can use SplitString from StrUtils to split the string into pieces. And then use StrToInt to convert to integer.
uses
StrUtils;
....
var
Fields: TStringDynArray;
....
Fields := SplitString(Row, ' ');
StrVar := Fields[0];
IntVar1 := StrToInt(Fields[1]);
IntVar2 := StrToInt(Fields[2]);
IntVar3 := StrToInt(Fields[3]);
And obviously substitute StrToFloat if you have floating point values.
Take TJclStringList from Jedi Code Library.
On 1st step you take one list and do .LoadFromFile to split the file to rows.
On second step you iterated through those rows and set secondary stringlist by those lines with space as delimiter. Then you iterate through secondary string list and do what u want.
http://wiki.delphi-jedi.org/wiki/JCL_Help:IJclStringList.Split#string#string#Boolean
Split a string into an array of strings based on a delimiter
https://stackoverflow.com/search?q=%5Bdelphi%5D+string+split
Like that
var slF, slR: IJclStringList; ai: TList<integer>; s: string; i: integer;
action: procedure(const Name: string; Const Data: array of integer);
slF := TJclStringList.Create; slF.LoadFromFile('some.txt');
slR := TJclStringList.Create;
for s in slF do begin
slR.Split(s, ' ', true);
ai := TList<Integer>.Create;
try
for i := 1 to slR.Count - 1 do
ai.Add(StrToInt(slR[i]));
action(slR[0], ai.ToArray);
finally ai.Free; end;
end;
You can use File of TRecord, with TRecord. For example:
type TRecord = packed record
FName : String[30];
Val1: Integer;
Val2: Integer;
Val3: Integer;
end;
And simple procedure:
procedure TMainForm.Button1Click(Sender: TObject);
var
F: file of TRecord;
Rec : TRecord;
begin
AssignFile(F, 'file.dat');
try
Reset(F);
Read(F, Rec);
finally
CloseFile(F);
end;
end;
I have a Form TForm1 having 5 TEdit and 2 TBitBtn.
I also need the Program so that after inputting Numeric Data in Edit1 and Edit2 on BitBtn1Click, Edit1 and Edit2 value will summed and will be displayed in Edit3.
You want to do something like this:
var
val1, val2, sum: Integer;
...
val1 := StrToInt(Edit1.Text);
val2 := StrToInt(Edit2.Text);
sum := val1 + val2;
Edit3.Text := IntToStr(sum);
If you want floating point arithmetic do it like this
var
val1, val2, sum: Double;
...
val1 := StrToFloat(Edit1.Text);
val2 := StrToFloat(Edit2.Text);
sum := val1 + val2;
Edit3.Text := FloatToStr(sum);
To read and set the value of a TEdit control, you simply reference the Text property of the control. The Text property is of type String.
Since Text is a String property, you can treat it in your code as a String variable. You can pass it into a function that expects a String constant:
// Edit1 is the name of the TEdit control
// Display the value in the edit control to the user
ShowMessage(Edit1.Text);
You can assign it to a String variable with a simple assignment:
var
// My string variable
myString: String;
begin
// Edit1 is the Name of the control
myString := Edit1.Text;
end;
To set the value of a TEdit control, you simply assign a string to the Text property. This could be a String constant:
Edit1.Text := 'hello';
Or it could be from a String variable:
Edit1.Text := myString;
Math is done on numeric types, so for arithmetic, you'll need to use a function to convert the string values into numbers.
For Integer arithmetic, you can use StrToInt() or StrToIntDef():
var
myInteger: Integer;
begin
// Convert Edit1.Text string to a number and assign to numeric type for math
// If the value in Edit1.Text cannot be converted, an exception will be raised
myInteger := StrToInt(Edit1.Text);
end;
Using StrToIntDef():
var
myInteger: Integer;
begin
// If Edit1.Text cannot be converted, the default value of 0 will be used
myInteger := StrToIntDef(Edit1.Text, 0);
end;
For floating point arithmetic, use StrToFloat() or StrToFloatDef() instead.
To assign an Integer back to the Text property, you'll need to convert the Integer to a String before you assign it:
var
myInteger: Integer;
begin
myInteger := 12;
Edit1.Text := IntToStr(myInteger);
end;
For floating points, use FloatToStr().
Finally, to put everything together, to get the numeric values of two edit boxes and display the sum in a third edit box, simply do this:
var
// Floating point variables
value1: Real;
value2: Real;
sum: Real;
begin
// Get the values from the edit boxes, converting them to floating point types
value1 := StrToFloat(Edit1.Text);
value2 := StrToFloat(Edit2.Text);
// Sum them
sum := value1 + value2;
// Convert the sum to string and assign back to edit box
Edit3.Text := FloatToStr(sum);
end;
Or in one step:
Edit3.Text := FloatToStr(StrToFloat(Edit1.Text) + StrToFloat(Edit2.Text));
I noticed the following snippet:
so that after inputting Numeric Data in Edit1 and Edit2
If you want to only allow Numeric data, it's a good idea to disallow non numeric data in the edit boxes.
Here's how to do that.
const
TabKey = #9;
Backspace = #8;
Enter = #13;
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if not (Key in ['0'..'9','-',TabKey,Enter,Backspace]) then Key:= #0; //integers
//realnumbers: if not (Key in ['0'..'9','-','e','E','.',TabKey,Enter,Backspace]) then Key:= #0;
end;
If you only have integer data, this will do, if you have scientific numbers, you need to do some testing for the letter e and the decimal point as well to allow for irrational numbers.
Regardless of what you do it's a good idea to check to see the input is a valid number and let the user know.
procedure TForm1.Edit1Change(Sender: TObject);
var
MyEdit: TEdit;
OtherEdit: TEdit;
TryNumber: double;
OtherNumber: double;
Success: boolean;
begin
Success:= true;
if (Sender is TEdit) then begin
MyEdit:= TEdit(Sender);
try
if MyEdit.Text = '' then TryNumber:= 0
else TryNumber:= StrToFloat(MyEdit.Text);
MyEdit.Color:= clWindow; //all is OK make edit standard white.
MyEdit.Hint:= '';
except
MyEdit.Color:= clRed; //Let the user know the output will not compute.
MyEdit.Hint:= MyEdit.Text + ' is not a valid number ';
Success:= false;
end;
end;
if (MyEdit = Edit1) then OtherEdit:= Edit2
else OtherEdit:= Edit1;
try
if OtherText.Text = '' then OtherNumber:= 0
else OtherNumber:= StrToFloat(OtherEdit.Text);
except
Success:= false;
end;
if Success then Edit3.Text:= FloatToStr(TryNumber + OtherNumber);
end;
Note that you can attach this Event to both Edit1 and Edit2, so you do not have to write the code twice. (But I'm sure you already knew that).
(Both edit's share the same event).
Important things to remember
Always use try..except to catch errors so that your program does not break with an error, see: http://www.delphibasics.co.uk/Article.asp?Name=Exceptions
If you have an editbox that only allows numeric data, consider using a maskedit which only allows valid chars, or code your own filter (if it's trivial to do so).
Try and use a single routine for multiple controls, so you don't end up with multiple very similar routines that all do almost the same thing. That way if you change something, you'll only have to change it in one place and it will work in all the controls that make use of that routine.
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;