if then else loop problem delphi - delphi

I'm still a beginner and I have been trying to solve this problem by my self but I guess I 'm out of luck. I think it is probably quite simple but here's the deal.
I have 3 checkboxes. Each one writes a specific line in a text file when a button is pressed but if none are selected. I want a message to be displayed. But what happens there is that the message pops out even if one checkbox is checked. Here's the code: (btw, feel free to suggest any other code that would make it easier/clearer)
if cbSCV.Checked then
WriteLn(permFile, 'scv');
if cbMP.Checked then
WriteLn(permFile, 'mp');
if cbBTK.Checked then
WriteLn(permFile, 'btk');
if not (cbBTK.Checked) and not (cbMP.Checked) and not (cbBTK.Checked) then
showmessage('Choose at least 1 option.');

try replacing the if sentence to
if not (cbBTK.Checked) and not (cbMP.Checked) and not (cbSCV.Checked) then
because you are checking the cbBTK.checked value twice

For what it's worth I'd probably reverse the logic and write the troublesome test like this:
if not (cbBTK.Checked or cbMP.Checked or cbSCV.Checked) then

To complement #soid's answer: I'd probably write it like this:
procedure TForm1.CheckIt;
var
Count: Integer;
procedure HandleCheckBox(ACheckBox: TCheckBox; const AID: string);
begin
if ACheckBox.Checked then
begin
WriteLn(permFile, AID);
Inc(Count);
end;
end;
begin
Count := 0;
HandleCheckBox(cbSCV, 'scv');
HandleCheckBox(cbMP, 'mp');
HandleCheckBox(cbBTK, 'btk');
if Count = 0 then
ShowMessage('Choose at least 1 option.');
end;
This is a few more lines but it is IMHO less error prone and more "automatic" if you later need a fourth or fifth checkbox.

I would rewrite it like this:
if cbSCV.Checked then WriteLn(permFile, 'scv');
if cbMP .Checked then WriteLn(permFile, 'mp' );
if cbBTK.Checked then WriteLn(permFile, 'btk');
if not (cbSCV.Checked) and
not (cbMP .Checked) and
not (cbBTK.Checked) then
showmessage('Choose at least 1 option.');
This takes the same number of lines but places the repeated elements together to make it easy to read the whole construct quickly and spot places where you are not following the pattern. Your bug, which we have all had in our code, is easier to see if it is written like this.

Hmmm. For those things I like a set-based approach.
One way is this
type
TEnumSomething = (esSCV, esMP, esBTK);
TSomethingSet = set of TEnumSomething;
{var section}
var
Conj: TSomethingSet;
{code section}
Conj := [];
if cbSCV.checked then
begin
Conj := conj + [esSCV];
WriteLn(permFile, 'scv');
end;
{do this for the other 2 checkboxes}
If Conj = [] then ShowMessage('');
You can also make Conj an form field and make checkboxes
set/unset this on their OnClick event.
Warning: maybe some syntax detail is missing, I'm not on delphi IDE now...

I probably wouldn't rewrite it like this, but hey, this is fun. I'm at work and I don't have Delphi here, so this is just sample code. Generics!
type
TCheckBoxDict: TDictionary<String, TCheckBox>;
var
Dict: TCheckBoxDict;
function HandleCheckBoxes(ADict: TCheckBoxDict) : boolean;
var
Key: String;
CheckBox: TCheckBox;
begin
Result := false;
for Key in ADict.Keys do
if ADict.Items[Key].Checked then
begin
WriteLn(permFile, Key);
Result := true;
end;
end;
begin
Dict := TCheckBoxDict.Create;
Dict.Add('scv', cbSCV);
Dict.Add('mp', cbMP);
Dict.Add('btk', cbBTK);
if not HandleCheckBoxes(Dict) then
ShowMessage('Choose at least one option');
Dict.Destroy;
end;

Related

Assigning the selected items in a TxpComboBox to a variable

I'm attempting to assign all the selected items in a TxpListBox to a TStringList.
My initial thought was to do something like
Function AssignListBoxToList(ComponentName : TxpListBox) : Boolean;
var
slComponentValue : TStringList;
begin
slComponentValue := TStringList.Create;
slComponentValue.Add(ComponentName.Items);
end;
But it throws the following exception Incompatible types: 'String' and 'TString'.
Is there a way to either create a TStringList of TStrings, or is it safe to use String instead of TString in my TxpListBox, and/or am I missing something.
TxpListBox is a TListBox with a modified look to fit in with the Windows XP design aesthetic.
It looks like TxpComboBox.Items might be a TStrings descendent (like the standard TComboBox.Items). If that's the case, something like this should work:
slComponentValue := TStringList.Create;
slComponentValue.Add(ComponentName.Items[ComponentName.ItemIndex]);
Your function won't work as is, though, because it doesn't return slComponentValue.
It's generally not a good idea (without a specific reason to do so) to return an object from a function, because it's not clear where the responsibility lies to free it. I prefer to make that more clear by having a procedure accept an already-created instance of an object instead:
procedure AssignComboBoxToList(ComponentName : TxpComboBox;
ListToFill: TStrings) : Boolean;
begin
Assert(Assigned(ListToFill));
ListToFill.Add(ComponentName.Items[ComponentName.ItemIndex);
end;
You can then use it like this:
slComponentValue := TStringList.Create;
try
AssignComboBoxToList(YourComboBox, slComponentValue);
if slComponentValue.Count > 0 then
// Do whatever with the slComponentValue list
finally
slComponentValue.Free;
end;
However, as you're only dealing with a single string, it might be easier to just use a single string; there's not really a TStringList neededhere:
strResult := YourComboBox.Items[YourComboBox.ItemIndex];
With that being said, TComboBox doesn't support multiple selections; TListBox does, but TComboBox displays a drop down list and allows selecting of a single item, making your question somewhat unclear.

Label color not changing with FindComponent

I have a lot of labels in my form and I have to change the color to all of them, so I thought to use a for loop + the FindComponent method.
procedure TForm1.RadioButton1Click(Sender: TObject);
var i:shortint;
begin
for i:=16 to 27 do
begin
TLabel(FindComponent('Label'+IntToStr(i)).Font.Color:=clYellow);
end;
Label85.Font.Color:=clYellow;
Label104.Font.Color:=clYellow;
end;
I'm using lazarus and I have this kind of error: identifier idents no member "Font" . By the way as you can see Label104.Font.Color:=clYellow; works (for example). How could I solve this?
TLabel(FindComponent('Label'+IntToStr(i)).Font.Color:=clYellow);
should obviously read
TLabel(FindComponent('Label'+IntToStr(i))).Font.Color:=clYellow;
Your code shouldn't even compile, because your parentheses are out of place:
TLabel(FindComponent('Label'+IntToStr(i)).Font.Color:=clYellow);
The closing parenthesis after clYellow should be with the other two after the IntToStr(i)) and before the .Font.
TLabel(FindComponent('Label'+IntToStr(i))).Font.Color:=clYellow;
Your code is pretty risky, though. It makes an assumption that it will find the label (which may fail if the label gets renamed or deleted in the future). You're much safer to check first before using the result of FindComponent:
procedure TForm1.RadioButton1Click(Sender: TObject);
var
i: Integer;
TempComp: TComponent;
begin
for i := 16 to 27 do
begin
TempComp := FindComponent('Label' + IntToStr(i));
if TempComp <> nil then
(TempComp as TLabel).Font.Color:=clYellow;
end;
Label85.Font.Color :=clYellow;
Label104.Font.Color :=clYellow;
end;
(The last two lines are safe, as the compiler will tell you if those labels get renamed or deleted; it can't do so in the TLabel(FindComponent()) case, because it can't tell at compile time which labels you'll be accessing.)

Delphi: How to set field value of a generic using RTTI?

I'd like to fill the field of a generic object at runtime using D2010.
program generic_rtti_1;
{$APPTYPE CONSOLE}
uses
SysUtils, rtti;
type
TMyObject = class
FField1: string;
end;
TGeneric<TElement: class> = class
procedure FillFields(Element: TElement);
end;
procedure TGeneric<TElement>.FillFields(Element: TElement);
var
ctx: TRttiContext;
begin
ctx := TRttiContext.Create();
ctx.GetType(TypeInfo(TElement)).GetField('FField1').
SetValue(#Element, TValue.FromVariant('Some string'));
ctx.Free();
end;
When the line ctx.Free(); is executed, I get an AV at line 21986 in System.pas (function _IntfClear()). This is called from FContextToken := nil in rtti.pas. (In fact, the SetValue-induced AV pops up if I step into SetValue, however if step over it, only the ctx.Free-induced is reported. See below.)
If I remove ctx.Free();, the AV appears when calling SetValue(#Element, TValue.FromVariant('Some string'));. This too at line 21986 in System.pas.
Trying to figure this mess out, I replaced
ctx.GetType(TypeInfo(TElement)).GetField('FField1').
SetValue(#Element, TValue.FromVariant('Field 1 is set'));
with this:
rType := ctx.GetType(TypeInfo(TElement));
rField := rType.GetField('FField1');
Val := TValue.FromVariant('Field 1 is set');
rField.SetValue(#Element, Val);
This time, I got no error, however WriteLn(MyObject.FField1) printed an empty string. (The AV re-appears if I combine SetValue and TValue.FromVariant, i.e. write rField.SetValue(#Element, TValue.FromVariant('Field 1 is set'));.
In order to pinpoint the guilty line, I commented out line by line, replacing the commented code with a compound statement. By accident I forgot to comment out the Val := TValue.FromVariant('Field 1 is set');-line above, which causes the AV to disappear once more (still calling rField.SetValue(#Element, TValue.FromVariant('Field 1 is set'));). (Note that I don't actually use Val in the troublesome call, still the AV disappears.)
I'm kind'a lost at this point.
For sake of completeness, here's how I'd like to use the above code:
var
Generic: TGeneric<TMyObject>;
MyObject: TMyObject;
begin
MyObject := TMyObject.Create();
Generic := TGeneric<TMyObject>.Create();
Generic.FillFields();
WriteLn(MyObject.FField1);
Generic.Free();
MyObject.Free();
ReadLn;
end;
end.
Do anyone know what I'm doing wrong? (Is this even possible? Are there better ways to do this using generics? )
Well, I don't know if this makes sense to you guys, but here's how I solved it. Hard cast to TObject in procedure TGeneric<TElement>.FillFields works like a charm. Like so:
ctx.GetType(TypeInfo(TElement)).GetField('FField1').
SetValue(TObject(Element), TValue.FromVariant('Field 1 is set'));
Hope this is useful to someone else out there.

Need a ComboBox with filtering

I need some type of ComboBox which can load it's items from DB. While I type some text in to it, it should filter it's list, leaving only those items, that have my text somewhere (at the beginning, middle...). Not all my DataSet's have filtering capabilities, so it is not possible to use them. Is there any ready to use components with such abilities? I have tried to search in JVCL, but without luck.
You could try customizing the autocomplete functionality of a regular ComboBox. Loading its items from a DB is easy:
ComboBox1.Items.Clear;
while not Table1.Eof do begin
ComboBox1.Items.AddObject( Table1.FieldByName('Company').AsString,
TObject(Table1.FieldByName('CustNo').AsInteger) );
Table1.Next;
end;
As far as the auto-complete for middle-of-word matching, you might try adapting this code. The functionality that matches at the beginning of the text in the Items is enabled by setting AutoComplete to true, and needs to be turned off before you try writing your own OnChange event handler that does auto-complete. I suggest that you could more safely do the match and selection on the enter key, because attempting to do it on the fly makes things quite hairy, as the code below will show you:
Here's my basic version: Use a regular combobox with onKeyDown, and onChange events, and AutoComplete set to false, use above code to populate it, and these two events
procedure TForm2.ComboBox1Change(Sender: TObject);
var
SearchStr,FullStr: string;
i,retVal,FoundIndex: integer;
ctrl:TComboBox;
begin
if fLastKey=VK_BACK then
exit;
// copy search pattern
ctrl := (Sender as TCombobox);
SearchStr := UpperCase(ctrl.Text);
FoundIndex := -1;
if SearchStr<>'' then
for i := 0 to ctrl.Items.Count-1 do begin
if Pos(SearchStr, UpperCase(ctrl.Items[i]))>0 then
begin
FoundIndex := i;
fsearchkeys := ctrl.Text;
break;
end;
end;
if (FoundIndex>=0) then
begin
retVal := ctrl.Perform(CB_SELECTSTRING, 0, LongInt(PChar(ctrl.Items[FoundIndex]))) ;
if retVal > CB_Err then
begin
ctrl.ItemIndex := retVal;
ctrl.SelStart := Pos(SearchStr,UpperCase(ctrl.Text))+Length(SearchStr)-1;
ctrl.SelLength := (Length(ctrl.Text) - Length(SearchStr));
end; // retVal > CB_Err
end; // lastKey <> VK_BACK
end;
procedure TForm2.ComboBox1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
fLastKey := Key;
end;
Suppose the contents of the list are "David Smith", and "Mark Smithers". You type S and it matches the first letter of the last name, in David Smith. Now it shows David Smith with the "David S" part not selected, and the "mith" part selected (so that the next characters you type will replace the auto completed portion, a standard auto-complete technique). Note that the above code has had to prefix the S you typed with the "David " part you didn't type. If you are a lot more clever than me, you can find a way to remember that the user typed "s" and then, maybe an "m", followed by some more letters, and eventually having typed "Smithe", match Smithers, instead of always David smith. Also note that you can only set the SelStart and SelLength to select a continuous length of a string.
The code I have provided will only work when the list of items never contains any repeated substrings. There are good reasons why the Windows Common Control combobox "autocomplete" functionality only works with prefix matching, and not mid-string matching.
Since anything that would implement mid-string matching should probably draw the part you typed in not-selected, and since that not-selected part would be in mid-string, you would probably need to write your own control from scratch and not rely on the TComboBox base code, and its underlying MS Common Controls combobox functionality.
DevExpress' "TcxExtLookupCombobox" has this capability - and more. Might be overkill though.

How to fast copy from hash to listview?

There is a hash pas file http://gpdelphiunits.googlecode.com/svn-history/r4/trunk/src/GpStringHash.pas
We can create hash and add key - value
Question 1:
We want to know how to iterate key - value and copy data to listview.
Question 2: is there a way to fast copy like assign method to it?
Thank you very much in advance.
Dear gabr, Thank you so much for your immediate reply and your hash file. Is there doc or help files or examples or demo for your code ? Thank you so much again.
Just test, I do not know where i did wrong
Thank you so much. I just used your code but there is the following error prompt. Or I made some mistakes:
procedure TForm8.ab;
var
a: TGpStringHash;
i,j, fr:integer;
k: string;
enlist: TGpStringHashenumerator;
kv: TGpStringHashKV;
begin
a:=TGpStringHash.Create;
kv:=TGpStringHashKV.Create;
enlist:= TGpStringHashenumerator.Create(a);
for j:=1 to 10 do begin
if a.HasKey(inttostr(j)) then begin
fr:=a.ValueOf(inttostr(j));
a.Update(inttostr(j),fr+1);
end
else begin
a.Add(inttostr(j),1);
end;
end;
for i:=0 to a.Count -1 do begin
kv:=enlist.GetCurrent;
memo1.Lines.Add(kv.Key + inttostr(kv.value) );
end;
end; /// Division by Zero ERROr ///FindBucket(const key: string): cardinal;
ANSWER:
You're using enumerator improperly. Don't instantiate it in front and always use MoveNext to move to the next element.
// fill 'a' as above
enlist := TGpStringHashenumerator.Create(a);
while enList.MoveNext do begin
kv:=enlist.GetCurrent;
memo1.Lines.Add(kv.Key + inttostr(kv.value) );
end;
1) Use the latest version. It implements enumerators for all containers.
2) No.
EDIT:
I have committed my internal GpStringHash test app to the repository. It can server as a demo on how to use GpStringHash classes.
To enumerate TGpStringHash you would use
var
hash: TGpStringHash;
kv: TGpStringHashKV;
for kv in hash do
// do something with kv.Key and kv.Value
If you're using an older Delphi without support for enumerators, you can use ForEach method with an external callback method.
procedure TGpStringHash.ForEach(enumerator: TGpStringHashEnumMethod);
var
enum: TGpStringHashEnumerator;
begin
enum := GetEnumerator;
try
while enum.MoveNext do
enumerator(enum.Current);
finally FreeAndNil(enum); end;
end; { TGpStringHash.ForEach }

Resources