Why is RegSetValueEx only adding half a string? - delphi

Note: I am using Delphi XE2
I am trying to add a registry key. Here is my current code:
function AddRegKey(Key:HKEY;Keyname,Value:String):Boolean;
var
phkResult:HKEY;
begin
if RegOpenKeyEx(Key, PWideChar('SOFTWARE\Microsoft\Windows\CurrentVersion\Run'), 0, KEY_SET_VALUE, phkResult) = ERROR_SUCCESS then
begin
Result := (RegSetValueEx(phkResult, PWideChar(KeyName), 0, REG_SZ, PWideChar(Value), Length(Value)) = ERROR_SUCCESS);
RegCloseKey(phkResult);
end
else Result:=False;
end;
begin
If AddRegKey(HKEY_CURRENT_USER,'TestTest','123456789') Then
MessageBox(0,'Success','Test',0);
end.
Which is resulting in the following:
It is only adding the first four characters
But when I change the "Value" parameter to:
123456789123456789
It then adds:
123456789
So it seems as if it is only adding half of the given value for some reason. How do I go about solving this?
Am I passing the wrong data type for the *lpData parameter in RegSetValueEx?

Read the documentation. The last parameter of RegSetValueEx() is a byte count, but you are passing it a character count instead (and not even the right count, either - you have to include the null terminator). SizeOf(WideChar) is 2, so you are telling RegSetValueEx() to write only half of the String data. You need to fix that, eg:
function AddRegKey(Key:HKEY; Keyname, Value: String): Boolean;
var
phkResult: HKEY;
begin
Result := False;
if RegOpenKeyEx(Key, PChar('SOFTWARE\Microsoft\Windows\CurrentVersion\Run'), 0, KEY_SET_VALUE, phkResult) = ERROR_SUCCESS then
begin
Result := RegSetValueEx(phkResult, PChar(KeyName), 0, REG_SZ, PChar(Value), (Length(Value) + 1) * SizeOf(Char)) = ERROR_SUCCESS;
RegCloseKey(phkResult);
end;
end;
With that said, You might consider using the TRegistry class, which handles these details for you:
uses
..., Registry;
function AddRegKey(Key: HKEY; Keyname, Value: String): Boolean;
var
Reg: TRegistry;
begin
Result := False;
try
Reg := TRegistry.Create(KEY_SET_VALUE);
try
Reg.RootKey := Key;
if Reg.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Run', True) then
try
Reg.WriteString(KeyName, Value);
Result := True;
finally
Reg.CloseKey;
end;
finally
Reg.Free;
end;
except
end;
end;

Related

How to get the string representation of a ShortCut Key including the SHIFTSTATE?

In a Delphi 10.4.2 Win32 VCL Application, and based on the question + solution here which provides a way to get the string representation of a Shortcut Key (but presumably with no possibility to also pass a SHIFTSTATE for the Shortcut Key) I wrote this code:
function MyGetSpecialShortcutName(ShortCut: TShortCut): string;
// gets shortcut name for e.g. VK_NUMPAD0 where TMenuItem.Shortcut gets the wrong shortcut name
var
ScanCode: Integer;
KeyName: array[0..255] of Char;
begin
Result := '';
FillChar(KeyName, SizeOf(KeyName), 0);
ScanCode := Winapi.Windows.MapVirtualKey(LoByte(Word(ShortCut)), 0) shl 16;
if ScanCode <> 0 then
begin
if Winapi.Windows.GetKeyNameText(ScanCode, KeyName, Length(KeyName)) <> 0 then
Result := KeyName;
end;
end;
function GetSpecialShortcutNameWithShiftState(const AScanCode: Word; const AShiftState: System.Classes.TShiftState = []): string;
begin
Result := MyGetSpecialShortcutName(Vcl.Menus.ShortCut(AScanCode, AShiftState));
end;
Usage:
Result := GetSpecialShortcutNameWithShiftState(VK_A, [ssCTRL]);
However, the Result is "A" where the expected Result should be "CTRL+A".
How to get the string representation of a ShortCut Key including the SHIFTSTATE?
The OP wants the key names fully localised, but for completeness I first show that the VCL already has a function to obtain a partly unlocalised string, namely, ShortCutToText in the Menus unit:
ShortCutToText(ShortCut(Ord('A'), [ssShift, ssAlt]))
This returns Shift+Alt+A on all systems.
Now, using the Win32 function GetKeyNameText already mentioned in the Q, it is easy to obtain a fully localised shortcut string:
function GetKeyName(AKey: Integer): string;
var
name: array[0..128] of Char;
begin
FillChar(name, SizeOf(name), 0);
GetKeyNameText(MapVirtualKey(AKey, 0) shl 16, #name[0], Length(name));
Result := name;
end;
function ModifierVirtualKey(AModifier: Integer): Integer;
begin
case AModifier of
Ord(ssShift):
Result := VK_SHIFT;
Ord(ssCtrl):
Result := VK_CONTROL;
Ord(ssAlt):
Result := VK_MENU;
else
Result := 0;
end;
end;
function ShortcutToString(AKey: Integer; AShiftState: TShiftState = []): string;
begin
Result := '';
for var Modifier in AShiftState do
begin
var ModifierKey := ModifierVirtualKey(Ord(Modifier));
if ModifierKey <> 0 then
Result := Result + IfThen(not Result.IsEmpty, '+') + GetKeyName(ModifierKey);
end;
Result := Result + IfThen(not Result.IsEmpty, '+') + GetKeyName(AKey);
end;
(Here I use a IfThen overload from StrUtils.)
Now,
ShortcutToString(Ord('A'), [ssShift, ssAlt])
returns SKIFT+ALT+A on my Swedish system. SKIFT is, as you might already have guessed, the Swedish name for the SHIFT key.

Delphi Using Locate function in data source in memory table

I need to check given value is already exists in the memory table, so i have tried following method.
function TForm4.findValueExists(id: Integer): Boolean;
var
state: Boolean;
begin
state := DBGrid1.DataSource.DataSet.Locate('code', id, []);
end;
here is the Search button procedure
procedure TForm4.butSearchClick(Sender: TObject);
var
id: Integer;
Name: String;
Sell: Double;
Qty: Integer;
Amount: Double;
OldQty: Integer;
RecordExist: Boolean;
begin
if txtprocode.Text <> '' then
begin
FDQuery1.Params.ParamByName('ID').Value := txtprocode.Text;
if FDQuery1.Active then
FDQuery1.Close;
FDQuery1.Open();
try
FDQuery1.First;
if not FDQuery1.Eof then
begin
id := FDQuery1.FieldByName('Id').AsInteger;
Name := FDQuery1.FieldByName('name').AsString;
Sell := FDQuery1.FieldByName('selling').AsFloat;
Qty := 1;
Amount := Sell * Qty;
if Form4.findValueExists(id) then
begin
FDMemTable1.Edit;
OldQty := DBGrid1.Fields[3].Value;
FDMemTable1.FieldByName('Qty').AsInteger := (OldQty + 1);
FDMemTable1.FieldByName('amount').AsFloat := (Sell * (OldQty + 1));
FDMemTable1.Post;
end
else
begin
FDMemTable1.InsertRecord([id, Name, Sell, Qty, Amount]);
end;
end;
finally
end;
end;
end;
unfortunately this method always gives me result as 'false'. but physically i can found matched result for given id.
here is UI
Guys i`m new to Delphi language.
You're not returning a value from your FindValueExists function, so you have absolutely no way of knowing if it locates the record or not. If you'd turn on compiler hints and warnings, the compiler would have pointed that fact out to you. (It would have informed you function findValueExists might not return a value, and also that Value assigned to 'state' is never used.)
Change your findValueExists so that it actually returns the result of the Locate call.
function TForm4.findValueExists(id: Integer): Boolean;
begin
Result := DBGrid1.DataSource.DataSet.Locate('code', id, []);
end;

Faster way to split text in Delphi TStringList

I have an app that needs to do heavy text manipulation in a TStringList. Basically i need to split text by a delimiter ; for instance, if i have a singe line with 1000 chars and this delimiter occurs 3 times in this line, then i need to split it in 3 lines. The delimiter can contain more than one char, it can be a tag like '[test]' for example.
I've wrote two functions to do this task with 2 different approaches, but both are slow in big amounts of text (more then 2mbytes usually).
How can i achieve this goal in a faster way ?
Here are both functions, both receive 2 paramaters : 'lines' which is the original tstringlist and 'q' which is the delimiter.
function splitlines(lines : tstringlist; q: string) : integer;
var
s, aux, ant : string;
i,j : integer;
flag : boolean;
m2 : tstringlist;
begin
try
m2 := tstringlist.create;
m2.BeginUpdate;
result := 0;
for i := 0 to lines.count-1 do
begin
s := lines[i];
for j := 1 to length(s) do
begin
flag := lowercase(copy(s,j,length(q))) = lowercase(q);
if flag then
begin
inc(result);
m2.add(aux);
aux := s[j];
end
else
aux := aux + s[j];
end;
m2.add(aux);
aux := '';
end;
m2.EndUpdate;
lines.text := m2.text;
finally
m2.free;
end;
end;
function splitLines2(lines : tstringlist; q: string) : integer;
var
aux, p : string;
i : integer;
flag : boolean;
begin
//maux1 and maux2 are already instanced in the parent class
try
maux2.text := lines.text;
p := '';
i := 0;
flag := false;
maux1.BeginUpdate;
maux2.BeginUpdate;
while (pos(lowercase(q),lowercase(maux2.text)) > 0) and (i < 5000) do
begin
flag := true;
aux := p+copy(maux2.text,1,pos(lowercase(q),lowercase(maux2.text))-1);
maux1.add(aux);
maux2.text := copy(maux2.text,pos(lowercase(q),lowercase(maux2.text)),length(maux2.text));
p := copy(maux2.text,1,1);
maux2.text := copy(maux2.text,2,length(maux2.text));
inc(i);
end;
finally
result := i;
maux1.EndUpdate;
maux2.EndUpdate;
if flag then
begin
maux1.add(p+maux2.text);
lines.text := maux1.text;
end;
end;
end;
I've not tested the speed, but for academic purposes, here's an easy way to split the strings:
myStringList.Text :=
StringReplace(myStringList.Text, myDelimiter, #13#10, [rfReplaceAll]);
// Use [rfReplaceAll, rfIgnoreCase] if you want to ignore case
When you set the Text property of TStringList, it parses on new lines and splits there, so converting to a string, replacing the delimiter with new lines, then assigning it back to the Text property works.
The problems with your code (at least second approach) are
You are constantly using lowecase which is slow if called so many times
If I saw correctly you are copying the whole remaining text back to the original source. This is sure to be extra slow for large strings (eg files)
I have a tokenizer in my library. Its not the fastest or best but it should do (you can get it from Cromis Library, just use the units Cromis.StringUtils and Cromis.Unicode):
type
TTokens = array of ustring;
TTextTokenizer = class
private
FTokens: TTokens;
FDelimiters: array of ustring;
public
constructor Create;
procedure Tokenize(const Text: ustring);
procedure AddDelimiters(const Delimiters: array of ustring);
property Tokens: TTokens read FTokens;
end;
{ TTextTokenizer }
procedure TTextTokenizer.AddDelimiters(const Delimiters: array of ustring);
var
I: Integer;
begin
if Length(Delimiters) > 0 then
begin
SetLength(FDelimiters, Length(Delimiters));
for I := 0 to Length(Delimiters) - 1 do
FDelimiters[I] := Delimiters[I];
end;
end;
constructor TTextTokenizer.Create;
begin
SetLength(FTokens, 0);
SetLength(FDelimiters, 0);
end;
procedure TTextTokenizer.Tokenize(const Text: ustring);
var
I, K: Integer;
Counter: Integer;
NewToken: ustring;
Position: Integer;
CurrToken: ustring;
begin
SetLength(FTokens, 100);
CurrToken := '';
Counter := 0;
for I := 1 to Length(Text) do
begin
CurrToken := CurrToken + Text[I];
for K := 0 to Length(FDelimiters) - 1 do
begin
Position := Pos(FDelimiters[K], CurrToken);
if Position > 0 then
begin
NewToken := Copy(CurrToken, 1, Position - 1);
if NewToken <> '' then
begin
if Counter > Length(FTokens) then
SetLength(FTokens, Length(FTokens) * 2);
FTokens[Counter] := Trim(NewToken);
Inc(Counter)
end;
CurrToken := '';
end;
end;
end;
if CurrToken <> '' then
begin
if Counter > Length(FTokens) then
SetLength(FTokens, Length(FTokens) * 2);
FTokens[Counter] := Trim(CurrToken);
Inc(Counter)
end;
SetLength(FTokens, Counter);
end;
How about just using StrTokens from the JCL library
procedure StrTokens(const S: string; const List: TStrings);
It's open source
http://sourceforge.net/projects/jcl/
As an additional option, you can use regular expressions. Recent versions of Delphi (XE4 and XE5) come with built in regular expression support; older versions can find a free regex library download (zip file) at Regular-Expressions.info.
For the built-in regex support (uses the generic TArray<string>):
var
RegexObj: TRegEx;
SplitArray: TArray<string>;
begin
SplitArray := nil;
try
RegexObj := TRegEx.Create('\[test\]'); // Your sample expression. Replace with q
SplitArray := RegexObj.Split(Lines, 0);
except
on E: ERegularExpressionError do begin
// Syntax error in the regular expression
end;
end;
// Use SplitArray
end;
For using TPerlRegEx in earlier Delphi versions:
var
Regex: TPerlRegEx;
m2: TStringList;
begin
m2 := TStringList.Create;
try
Regex := TPerlRegEx.Create;
try
Regex.RegEx := '\[test\]'; // Using your sample expression - replace with q
Regex.Options := [];
Regex.State := [preNotEmpty];
Regex.Subject := Lines.Text;
Regex.SplitCapture(m2, 0);
finally
Regex.Free;
end;
// Work with m2
finally
m2.Free;
end;
end;
(For those unaware, the \ in the sample expression used are because the [] characters are meaningful in regular expressions and need to be escaped to be used in the regular expression text. Typically, they're not required in the text.)

alternative to check, whether a value is in a set

I have the following code. It looks ugly, if the value equals to one of the following value then do something.
var
Value: Word;
begin
Value := 30000;
if (Value = 30000) or (Value = 40000) or (Value = 1) then
do_something;
end;
I want to refactor the code as follows:
var
Value: Word;
begin
Value := 30000;
if (Value in [1, 30000, 40000]) then // Does not work
do_something;
end;
However, the refactored code does not work. I assume that a valid set in Delphi accepts only elements with type byte. If there any good alternative to refactor my original code (besides using case)?
I think something like this?
case value of
1, 30000, 40000: do_somthing
end;
How about using an open array?
function ValueIn(Value: Integer; const Values: array of Integer): Boolean;
var
I: Integer;
begin
Result := False;
for I := Low(Values) to High(Values) do
if Value = Values[I] then
begin
Result := True;
Break;
end;
end;
Example (pseudo-code):
var
Value: Integer;
begin
Value := ...;
if ValueIn(Value, [30000, 40000, 1]) then
...
end;
There is a class for larger bitsets, see Classes.TBits.
While it won't do constant expressions easily, it can be useful in certain other cases.

Convert a DevExpress TcxFilterOperatorKind to and from a string?

Here is a codesnippet I use to get filtertype operator from a filter in a DevExpress grid:
OperatorKindToStr is used to extract operatorkind from a filter as string and store it in a xml-file.
StrToOperatorKind is used to convert back a string from xml to set an operatorkind in a filter.
const
CUSTFILTER_FILTERITEM = 'FilterItem';
function OperatorKindToStr(const aOperatorKind: TcxFilterOperatorKind): string;
begin
Result := 'foEqual';
case aOperatorKind of
foEqual: Result := 'foEqual';
foNotEqual: Result := 'foNotEqual';
foLess: Result := 'foLess';
foLessEqual: Result := 'foLessEqual';
// Plus a boring list of other constants
end;
function StrToOperatorKind(const aOpKindStr: string): TcxFilterOperatorKind;
begin
Result := foEqual;
if aOpKindStr = 'foNotEqual' then
Result := foNotEqual
else if aOpKindStr = 'foLess' then
Result := foLess
else if aOpKindStr = 'foLessEqual' then
Result := foLessEqual
else if aOpKindStr = 'foGreater' then
Result := foGreater
else if aOpKindStr = 'foGreaterEqual' then
Result := foGreaterEqual
// Plus a boring list of other if-else
end;
procedure UseStrToOperatorKind(const aFilterItem: IXmlDomElement);
begin
if aFilterItem.nodeName = CUSTFILTER_FILTERITEM then
begin // It is an FilterItem
vStr := VarToStr(aFilterItem.getAttribute(CUSTFILTER_COLPROP)); // Get the columnname
vOperatorKind := StrToOperatorKind(aFilterItem.getAttribute(CUSTFILTER_ITEMOPERATOR));
end;
procedure UseOperatorKindToStr(const aFilterItem: TcxCustomFilterCriteriaItem);
var
vStr: String;
begin
if Supports(TcxFilterCriteriaItem(aFilterItem).ItemLink, TcxGridColumn, GridCol) then
vStr := OperatorKindToStr(TcxFilterCriteriaItem(aFilterItem).OperatorKind);
end;
Apparently I want the StrToOperatorKind and OperatorKindToStr to be a bit smarter.
I have tried GetEnumProp method in VCL TypeInfo but it won't work.
So how can I extract the TcxFilterOperatorKind property from a aFilterItem variable to a string and back to a TcxFilterOperatorKind ?
Use the GetEnumName and GetEnumValue duet as Mason pointed out.
And your functions should become much simpler:
function OperatorKindToStr(const aOperatorKind: TcxFilterOperatorKind): string;
begin
Result := GetEnumName(TypeInfo(TcxFilterOperatorKind), Ord(aOperatorKind));
end;
function StrToOperatorKind(const aOpKindStr: string): TcxFilterOperatorKind;
begin
Result := TcxFilterOperatorKind(GetEnumValue(TypeInfo(TcxFilterOperatorKind), aOpKindStr));
end;
GetEnumProp didn't work because it's the wrong function for what you're trying to do. You're close, though. Try GetEnumName and GetEnumValue, which are also in the TypInfo unit.

Resources