Am working an a small app in Delphi7 but am puzzles on how to use a combination of 3 check boxes
Example: if check.box1 and checkbox2 checked run system application with these prefs
or if checkbox1 only checked run system application with these prefs or if checkbox2 only checked run with these prefs
It sounds like you're having trouble getting started writing what you need, which is basically a series of if..then..else flow-control statements based on compound boolean expressions. You probably want a structure like the one below. I'm not going to show you the entire thing, because it's best for you to work it out for yourself, but this should give you the idea:
if (CheckBox1.Checked) and (CheckBox2.Checked) and not (CheckBox3.Checked) then
begin
// do one thing
end
else
begin
if (CheckBox1.Checked) and not (CheckBox2.Checked) and not (CheckBox3.Checked) then
begin
// do another thing
end
else
// etc
end;
If you're familiar with enumerated types, you might do better to declare one and use that to derive the selected preferences and then act upon them. The point of doing that is twofold: to increase the clarity of your code (at the expense of your code being longer, of course); and to separate the logic for calculating which preference the user has selected from the code which acts upon it.
Something like
type
TRunPreference = (rpNone, rpOne, rpTwo, rpThree, rpFour,
rpFive, rpSix, rpSeven, rpEight);
// rpOne..rpEight are for the 8 permutations of three booleans
// and the rpNone is to provide for initialising the variable
// with something which isn't one of the desired values
var
RunPreference : TRunPreference;
procedure TForm1.Button1Click(Sender: TObject);
begin
RunPreference := rpNone;
if (CheckBox1.Checked) and (CheckBox2.Checked) and not (CheckBox3.Checked) then
begin
RunPreference := rpOne;
end
else
begin
if (CheckBox1.Checked) and not (CheckBox2.Checked) and not (CheckBox3.Checked) then
begin
RunPreference := rpTwo;
end
else
; //
end;
case RunPreference of
rpNone :
; // do nothing
rpOne :
begin
// Act on rpOne
end;
rpTwo :
begin
// Act on rpTwo
end;
// etc
end; { Case of RunPreference }
end;
Inspired by MartynA's solution, and because I prefer compact code, here's how I would do it:
// For the three (sets of?) preferences, define bit values, and
// a variable to hold the combination of the selected preferences:
const
PrefA=1; PrefB=2; PrefC=4;
var
prefs: byte;
procedure TForm12.Button2Click(Sender: TObject);
begin
// whether a preference should be active or not becomes a simple `OR` function
// based on each checkbox's state
prefs := 0;
if CheckBox1.Checked then prefs := prefs or PrefA;
if CheckBox2.Checked then prefs := prefs or PrefB;
if CheckBox3.Checked then prefs := prefs or PrefC;
// then use a `case` statement to run according the preference combination
// The values like `PrefA or PrefC` can be used instead of numeric values,
// since they can be resolved at compile time.
// Whether they improve readability or not is ... (disputable)
case prefs of
0:
ShowMessage('Running without preferences');
PrefA:
ShowMessage('Running with PrefA alone');
PrefB:
ShowMessage('Running with PrefB alone');
PrefA or PrefB:
ShowMessage('Running with PrefA and PrefB');
PrefC:
ShowMessage('Running with PrefC alone');
PrefA or PrefC:
ShowMessage('Running with PrefA and PrefC');
PrefB or PrefC:
ShowMessage('Running with PrefB and PrefC');
PrefA or PrefB or PrefC:
ShowMessage('Running with PrefA and PrefB and PrefC');
end;
end;
If you need to check in your code whether a preference is active or not, you can do it like:
if (prefs and PrefB) then
// do whatever requires PrefB to be selected
Related
I have the following:
TDirection = (dirNorth, dirEast, dirSouth, dirWest);
TDirections = set of TDirection;
In a seperate class I have it declared as a property:
property Directions: TDirections read FDirections write FDirections;
What I want is to be able to treat them as if they were Booleans, so for example if dirNorth was True then it would be 1, if False it would be 0.
I am trying to imagine it as (1,0,0,0)
I think to check if a direction is True, I could use:
var
IsTrue: Boolean;
begin
IsTrue := (DirNorth in Directions);
Not sure if the above is correct or not, but then my other problem is how to change one of the directions to True or False?
I have now reached one of my confusion states :(
This is the last thing I tried to set the value but I am getting Illegal Expression (in Lazarus).
Directions(TDirection(DirNorth)) := True;
Directions is a set of elements of type TDirection.
To see if it contains dirNorth, do dirNorth in Directions. The result of using the in operator is a boolean; dirNorth in Directions is true iff the set Directions contains the element dirNorth.
To make sure dirNorth is included in Directions, do Directions := Directions + [dirNorth].
To make sure dirNorth is not included in Directions, do Directions := Directions - [dirNorth].
To set Directions to a particular value, simply assign: Directions := [dirNorth, dirSouth].
Formally, + computes the union of two sets; - computes the set difference of two sets. * computes the intersection of the two operands.
You also have the nice Include and Exclude functions: Include(Directions, dirNorth) does the same thing as Directions := Directions + [dirNorth]; Exclude(Directions, dirNorth) does the same thing as Directions := Directions - [dirNorth].
For example, if
type
TAnimal = (aDog, aCat, aRat, aRabbit);
TAnimalSet = set of TAnimal;
const
MyAnimals = [aDog, aRat, aRabbit];
YourAnimals = [aDog, aCat];
then
aDog in MyAnimals = true;
aCat in MyAnimals = false;
aRat in YourAnimals = false;
aCat in YourAnimals = true;
MyAnimals + YourAnimals = [aDog, aRat, aRabbit, aCat];
MyAnimals - YourAnimals = [aRat, aRabbit];
MyAnimals * YourAnimals = [aDog];
Implicit in my answer is the fact that the Delphi set type is modelled after the mathematical set. For more information about the Delphi set type, please refer to the official documentation.
You may add an item to a set by doing like this:
Include(Directions, dirNorth);
To remove it from the set:
Exclude(Diretions, dirNorth);
The help states that the result is the same as using the plus operator, but the code is more efficient.
Based on this helper, which doesn't work for properties, I created this one (requires XE6) - it can be used for variables and properties:
TGridOptionsHelper = record helper for TGridOptions
public
/// <summary>Sets a set element based on a Boolean value</summary>
/// <example>
/// with MyGrid do Options:= Options.SetOption(goEditing, False);
/// MyVariable.SetOption(goEditing, True);
/// </example>
function SetOption(GridOption: TGridOption; const Value: Boolean): TGridOptions;
end;
function TGridOptionsHelper.SetOption(
GridOption: TGridOption; const Value: Boolean): TGridOptions;
begin
if Value then Include(Self, GridOption) else Exclude(Self, GridOption);
Result:= Self;
end;
I am trying to get Unicode font glyph ranges (Delphi 6):
var GS:PGlyphSet;
GSSize:LongWord;
rng:TWCRange;
begin
GSSize := GetFontUnicodeRanges(Canvas.Handle, nil);
GetMem(Pointer(GS), GSSize);
try
GS.cbThis:=GSSize;
GS.flAccel:=0;
GS.cGlyphsSupported:=0;
GS.cRanges:=0;
if GetFontUnicodeRanges(Canvas.Handle, GS)<>0 then begin
for i:=0 to GS.cRanges-1 do begin
rng := GS.ranges[i];
The strange thing is that Length(GS.ranges) is 1, but GS.cRanges is 309 and when I try to access the second range GS.ranges[1] I get, of course, a range check error. Before I turned range checking on it has worked in some magical way.
Types for reference (from Windows module):
PWCRange = ^TWCRange;
{$EXTERNALSYM tagWCRANGE}
tagWCRANGE = packed record
wcLow: WCHAR;
cGlyphs: SHORT;
end;
TWCRange = tagWCRANGE;
PGlyphSet = ^TGlyphSet;
{$EXTERNALSYM tagGLYPHSET}
tagGLYPHSET = packed record
cbThis: DWORD;
flAccel: DWORD;
cGlyphsSupported: DWORD;
cRanges: DWORD;
ranges: array[0..0] of TWCRange;
end;
TGlyphSet = tagGLYPHSET;
This struct makes use of the so-called struct hack:
http://c-faq.com/struct/structhack.html
http://tonywearme.wordpress.com/2011/07/26/c-struct-hack/
The ranges member is a variable length array, placed inline in the struct. But you cannot actually encode that in a static C type. That's why you call the function to find out how much memory to allocate, and then heap allocate the struct. If you allocated it on the stack, or using SizeOf(...) then the struct would be too small.
The simplest thing to do is to disable range checking for the code that accesses ranges. Although the type declaration says that only 0 is a valid index for ranges, in fact 0..cRanges-1 are valid.
If you don't want to disable range checking for the relevant code, then take a pointer the element 0, and then use pointer arithmetic in your loop.
var
rng: PWCRange;
....
rng := #GS.ranges[0];
for i:=0 to GS.cRanges-1 do begin
// use rng^
inc(rng);
end;
This is, in my view, the cleanest way to write code for sequential access. For random access, and with range checking in force, you'd be compelled to declare some extra types to defeat range checking:
type
TWCRangeArray = array [0..(MaxInt div SizeOf(TWCRange))-1] of TWCRange;
PWCRangeArray = ^TWCRangeArray;
And then use type casting to access individual elements:
rng := PWCRangeArray(#GS.ranges)[i];
Say i have a string
'SomeName'
and wanted the values return in a case statement. Can this bedone? Can strings be used in a case statement like so
Case 'SomeName' of
'bobby' : 2;
'tommy' :19;
'somename' :4000;
else
showmessage('Error');
end;
In Jcl library you have the StrIndex function StrIndex(Index, Array Of String) which works like this:
Case StrIndex('SomeName', ['bobby', 'tommy', 'somename']) of
0: ..code.. ;//bobby
1: ..code..;//tommy
2: ..code..;//somename
else
ShowMessage('error');
end.
The Delphi Case Statement only supports ordinal types. So you cannot use strings directly.
But exist another options like
build a function which returns a Integer (hash) based on a string
using generics and anonymous methods ( A generic case for strings)
using a function which receive an array of strings (Making a case for Strings, the sane way)
and so on.
#Daniel's answer pointed me in the right direction, but it took me a while to notice the "Jcl Library" part and the comments about the standard versions.
In [at least] XE2 and later, you can use:
Case IndexStr('somename', ['bobby', 'tommy', 'somename', 'george']) of
0: ..code..; // bobby
1: ..code..; // tommy
2: ..code..; // somename
-1: ShowMessage('Not Present'); // not present in array
else
ShowMessage('Default Option'); // present, but not handled above
end;
This version is case-sensitive, so if the first argument was 'SomeName' it would take the not present in array path. Use IndexText for case-insensitive comparison.
For older Delphi versions, use AnsiIndexStr or AnsiIndexText, respectively.
Kudos to #Daniel, #The_Fox, and #afrazier for most of the components of this answer.
Works on D7 and Delphi Seattle,
uses StrUtils (D7) system.Ansistring (Delphi Seattle)
case AnsiIndexStr(tipo, ['E','R'] ) of
0: result := 'yes';
1: result := 'no';
end;
I used AnsiStringIndex and works, but if you can convert to number without problems:
try
number := StrToInt(yourstring);
except
number := 0;
end;
try this it uses System.StrUtils
procedure TForm3.Button1Click(Sender: TObject);
const
cCaseStrings : array [0..4] of String = ('zero', 'one', 'two', 'three', 'four');
var
LCaseKey : String;
begin
LCaseKey := 'one';
case IndexStr(LCaseKey, cCaseStrings) of
0: ShowMessage('0');
1: ShowMessage('1');
2: ShowMessage('2');
else ShowMessage('-1');
end;
end;
I have a class defined as:
TfraFrame = class(TFrame);
then I have several subclasses all inherting from this, such as:
TfraUsers = class(TfraFrame);
TfraGroups = class(TfraFrame);
TfraMenus = class(TfraFrame);
In my main form i have declared variables as:
var
fraUsers: TfraUsers;
fraGroups: TfraGroups;
fraMenus: TfraMenus;
Now my question is, I would like to have one single function to control the generation of the instances of each class. There should only be once instance of each class, but i only want to create the instances if the user requires them.
I would like to pass the variable like this:
procedure ShowFrame(Frame: TfraFrame)
begin
if Frame = nil then
begin
Frame := TfraFrame.Create(self);
Frame.Init(Panel1);
end;
Frame.Show;
end;
and call it like this;
ShowFrame(fraUsers);
I was expecting it to create a instance of TfraUsers (because that is what fraUsers is declared as), however, i suspect it may be creating an instance of fraFrame.
Is there a way to create an instance of the type that the variable was declared as?
Yep, you need to tell showFrame which class to instantiate:
add
type
tfraFrameClass = class of TfraFrame;
just after the declaration of tfraFrame. And change showFrame to:
function ShowFrame(Frame: TfraFrame; FrameClass: TfraFrameClass): TfraFrame;
begin
if Frame = nil then
begin
Result := FrameClass.Create(self);
Result.Init(Panel1);
end
else
Result := Frame;
Result.Show;
end;
Note that you cannot pass Frame as a var parameter as the compiler will insist on declared and actual types of the passed parameter to be exactly the same.
Update
Updated example to show frame when it was already assigned.
Update
I neglected to mention that assigning the result of the Create call in the OP's code for the showFrame procedure wouldn't work. If it wasn't declared as var, the assignment would not go beyond the scope of the showFrame procedure, the value of the variable passed into showFrame would not be changed. Declaring it as a var would not work either as mentioned above. The solution is to do what #Andreas suggests: use an untyped pointer; or make it a function as I did. Of course (grin) I prefer mine as that preserves type safety just a little bit better.
Also, of course in my example the intention was to have the result of the showFrame function assigned to the appropriate frame variable, like so:
fraUsers := showFrame(fraUsers, TfraUsers);
Update
Or, as Sertac pointed out, you can still use procedure with a var parameter when you do a cast on the variable you pass to showForm.
procedure showFrame(var Frame: TfraFrame; FrameClasse: TfraFrameClass);
begin
if Frame = nil then
begin
Frame := FrameClass.Create(self);
Frame.Init(Panel1);
end;
Frame.Show;
end;
and call it as:
showFrame(TfraFrame(fraUsers), TFraUsers);
I suggest that you make your frame variables properties
property fraUsers: TfraUsers read GetfraUsers write ffraUsers;
..
function GetFraUsers: TFraUsers;
begin
if fFraUsers = nil then
fFraUsers := TfraUsers.Create(...);
Result := ffraUsers;
end;
Then
procedure ShowFrame(Frame: TfraFrame)
begin
Frame.Init(Panel1);
Frame.Show;
end;
procedure form1.Button1Click(...)
begin
ShowFrame(fraUsers); // creates the frame if it does not exist
end;
I have an integer field in a ClientDataSet and I need to compare to some values, something like this:
I can use const
const
mvValue1 = 1;
mvValue2 = 2;
if ClientDataSet_Field.AsInteger = mvValue1 then
or enums
TMyValues = (mvValue1 = 1, mvValue2 = 2);
if ClientDataSet_Field.AsInteger = Integer(mvValue1) then
or class const
TMyValue = class
const
Value1 = 1;
Value2 = 2;
end;
if ClientDataSet_Field.AsInteger = TMyValues.Value1 then
I like the class const approach but it seems that is not the delphi way, So I want to know what do you think
Declaration:
type
TMyValues = class
type TMyEnum = (myValue1, myValue2, myValue3, myValue4);
const MyStrVals: array [TMyEnum] of string =
('One', 'Two', 'Three', 'Four');
const MyIntVals: array [TMyEnum] of integer =
(1, 2, 3, 4);
end;
Usage:
if ClientDataSet_Field.AsInteger = TMyValues.MyIntVals[myValue1] then
A cast would generally be my last choice.
I wouldn't say that class consts are not the Delphi way. It's just they have been introduced to Delphi quite recently, and a lot of books and articles you'll find on the internet were written before their introduction, and thus you won't see them widely used. Many Delphi developers (I'd say the majority) will have started using Delphi before they were made available, and thus they're not the first thing that one thinks about.
One thing to consider is backwards compatibility - class constants are relatively new to Delphi so if your code has to be sharable with previous versions than they are out.
I typically use enumerated types, with the difference from yours is that my first enumeration is usually an 'undefined' item to represent NULL or 0 in an int field.
TmyValues = (myvUndefined, myvDescription1, myvDescription2)
if ClientDataSet_Field.AsInteger = Ord(myvDescription1) then...
To use a little bit of Jim McKeeth's answer - if you need to display to the user a text viewable version, or if you need to convert their selected text into the enumerated type, then an array comes in handy in conjuction with the type:
const MYVALS: array [TmyValues ] of string = ('', 'Description1', 'Description2');
You can then have utility functions to set/get the enumerated type to/from a string:
Function MyValString(const pMyVal:TmyValues):string;
begin
result := MYVALS[Ord(pMyVal)];
end;
Function StringToMyVal(const pMyVal:String):TMyValues;
var i:Integer;
begin
result := myvUndefined;
for i := Low(MYVALS) to High(MYVALS) do
begin
if SameText(pMyVal, MYVALS[i]) then
begin
result := TMyValues(i);
break;
end;
end;
end;
Continuing on... you can have scatter routine to set a combo/list box:
Procedure SetList(const DestList:TStrings);
begin
DestList.Clear;
for i := Low(MYVALS) to High(MYVALS) do
begin
DestList.Insert(MYVALS[i]);
end;
end;
In code: SetList(Combo1.Items) or SetList(ListBox1.Items)..
Then if you are seeing the pattern here... useful utility functions surrounding your enumeration, then you add everything to it's own class and put this class into it's own unit named MyValueEnumeration or whaterver. You end up with all the code surrounding this enumeration in one place and keep adding the utility functions as you need them. If you keep the unit clean - don't mix in other unrelated functionality then it will stay very handy for all projects related to that enumeration.
You'll see more patterns as time goes and you use the same functionality over and over again and you'll build a better mousetrap again.
When using constants I recommend assigning the type when the data type is a numeric float.
Delphi and other languages will not always evaluate values correctly if the types do not match...
TMyValue = class
const
// will not compare correctly to float values.
Value1 = 1; // true constant can be used to supply any data type value
Value2 = 2; // but should only be compared to similar data type
// will not compare correctly to a single or double.
Value3 = 3.3; // default is extended in debugger
// will not compare correctly to a single or extended.
Value1d : double = Value1; // 1.0
Value2d : double = Value2; // 2.0
end;
Compared float values in if () and while () statements should be compared to values of the same data type, so it is best to define a temporary or global variable of the float type used for any comparison statements (=<>).
When compared to the same float data type this format is more reliable for comparison operators in any programming language, not just in Delphi, but in any programming language where the defined float types vary from variable to constant.
Once you assign a type, Delphi will not allow you to use the variable to feed another constant, so true constants are good to feed any related data type, but not for comparison in loops and if statements, unless they are assigned and compared to integer values.
***Note: Casting a value from one float type to another may alter the stored value from what you entered for comparison purposes, so verify with a unit test that loops when doing this.
It is unfortunate that Delphi doesn't allow an enumeration format like...
TController : Integer = (NoController = 0, ncpod = 1, nextwave = 2);
or enforce the type name for access to the enumeration values.
or allow a class constant to be used as a parameter default in a call like...
function getControllerName( Controller : TController = TController.NoController) : string;
However, a more guarded approach that provides both types of access would be to place the enumeration inside a class.
TController = class
//const
//NoController : Integer = 1;
//ncpod : Integer = 2;
//nextwave : Integer = 3;
type
Option = (NoController = 0, ncpod = 1, nextwave = 2);
public
Class function Name( Controller : Option = NoController) : string; static;
end;
implementation
class function TController.Name( Controller : Option = NoController) : string;
begin
Result := 'CNC';
if (Controller = Option.nextwave) then
Result := Result + ' Piranha'
else if (Controller = Option.ncpod) then
Result := Result + ' Shark';
Result := Result + ' Control Panel';
end;
This approach will effectively isolate the values, provide the static approach and allow access to the values using a for () loop.
The access to the values from a floating function would be like this...
using TControllerUnit;
function getName( Controller : TController.Option = TController.Option.NoController) : string;
implementation
function getName( Controller : TController.Option = TController.Option.NoController) : string;
begin
Result := 'CNC';
if (Controller = TController.Option.nextwave) then
Result := Result + ' Piranha'
else if (Controller = TController.Option.ncpod) then
Result := Result + ' Shark';
Result := Result + ' Control Panel';
end;
so many options! :-) i prefer enums and routinely use them as you describe. one of the parts i like is that i can use them with a "for" loop. i do use class constants as well but prefer enums (even private enums) depending on what i'm trying to achieve.
TMyType=class
private const // d2007 & later i think
iMaxItems=1; // d2007 & later i think
private type // d2007 & later i think
TMyValues = (mvValue1 = 1, mvValue2 = 2); // d2007 & later i think
private
public
end;
An option you haven't thought of is to use a lookup table in the database and then you can check against the string in the database.
eg.
Select value, Description from tbl_values inner join tbl_lookup_values where tbl_values.Value = tbl_lookup_values.value
if ClientDataSet_Field.AsString = 'ValueIwant' then