How to activate window of other application - windows-10

I have HTTP server application (service application that is ran as desktop application) that runs other application and later one should activate window of yet another application. Computer X contains all applications. When I run http://192.168.16.21:225/command/TTaskType.ttTestTask from Opera web browser from computer X, my WinActivate function works. When I run http://192.168.16.21:225/command/TTaskType.ttTestTask from computer Y, window is not activated. None debug outputs returns errors (I get FLastErrorY printed). I even tried to run HTTP server as admin. What else could I try?
function WinActivate(const AWinTitle: string): boolean;
var
_WindowHandle: HWND;
_KeyboardState: TKeyboardState;
begin
ResetError;
_WindowHandle := FindWindow(nil, PWideChar(AWinTitle));
FLastError := GetLastError;
SetCursorPos(10, 12);
OutputDebugString(PWideChar('FLastError1: ' + IntTostr(FLastError) +
', _WindowHandle: ' + Format('%.8X', [_WindowHandle]) + ' ' + DateTimeToStr(Now)));
if _WindowHandle <> 0 then
begin
//ShowWindow(_WindowHandle, SW_MINIMIZE);
if IsIconic(_WindowHandle) then
begin
ShowWindow(_WindowHandle, SW_RESTORE);
ResetError;
Result := IsIconic(_WindowHandle);
if Result then
Result := WinWaitActive(AWinTitle, 1000);
end
else
Result := SetForegroundWindow(_WindowHandle);
OutputDebugString(PWideChar('FLastErrorX: ' + IntTostr(FLastError) +
', _WindowHandle: ' + Format('%.8X', [_WindowHandle]) + ' ' + DateTimeToStr(Now)));
if not Result then
begin
FLastError := GetLastError;
OutputDebugString(PWideChar('FLastError2: ' + IntTostr(FLastError) +
', _WindowHandle: ' + Format('%.8X', [_WindowHandle])));
// Applications might lock focus, so, hack it around
GetKeyBoardState(_KeyboardState);
ShowWindow(_WindowHandle, SW_SHOWNORMAL);
SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, 0, SPIF_UPDATEINIFILE);
if _KeyboardState[VK_MENU] <> 1 then
keybd_event(VK_MENU, 0, KEYEVENTF_EXTENDEDKEY or 0, 0);
ResetError;
if not SetForegroundWindow(_WindowHandle) then
begin
FLastError := GetLastError;
if not SetForegroundWindow(_WindowHandle) then
begin
FLastError := GetLastError;
OutputDebugString(PWideChar('FLastErrorY: ' + IntTostr(FLastError) +
', _WindowHandle: ' + Format('%.8X', [_WindowHandle]) + ' ' + DateTimeToStr(Now)));
end;
end;
keybd_event(VK_MENU, 0, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP, 0);
OutputDebugString(PWideChar('FLastError3: ' + IntTostr(FLastError) +
', _WindowHandle: ' + Format('%.8X', [_WindowHandle]) + ' ' + DateTimeToStr(Now)));
Result := FLastError = 0;
end;
if not Result then
begin
SetWindowPos(_WindowHandle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOACTIVATE or SWP_NOMOVE);
SetWindowPos(_WindowHandle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOACTIVATE or SWP_NOMOVE);
end;
end;
Result := WinWaitActive(AWinTitle, 1000);
OutputDebugString(PWideChar('Dabar'));
end;

So, I have found out whats wrong. I knew that AutoHotkey haves window activation functionality. I have downloaded its source code and window activation code is well written and commented. So I have build a test application using AutoHotkey and my window was not activated. I found out it was because no there is no active user logged in.

Related

TryStrToDateTime ignores trailing characters in string ("false positive")

I am writing a data import utility to import data into a database application. The data can be retrieved from the clipboard (e.g. from Excel) or a file and is initially stored in a TStringlist. In the next step the user can select the appropriate column separator (tab, comma, etc.). After splitting the data into columns (using the selected separator), each string value (let's call it a cell value) is checked for validity against the applicable database field type.
The issue I'm having is with datetime values (and possibly date and/or time value, but I haven't checked that yet). If the user selects the "wrong" separator, the data is not split and each line contains a single column (which is correct, of course). In such a case, the cell value may contain a string like the one below (I am showing the debug value to show the correct separator, tab in this case):
'04/01/10 00:00'#9'2.50'#9'100'#9'Text value'
When using TryStrToDateTime or StrToDatetime, this string value "passes" because the string is "clipped" (i.e. ignoring the trailing text and returning the correct datetime value of 04/01/10). If I then, at a later stage, pass the cell value (the original string) as a variant to a range comparison function, it obviously fails with an EVariantTypeCastError.
Is there a method (or existing Delphi RTL function) to check for the string value to contain a valid date(time) only (i.e. no trailing text)? Unable to find a function (or function parameter) taking this into account, I have also been thinking about checking string length but my software is used internationally and the datetime format will therefore be varying and may thus have different lengths.
PS: I have added below sample code here as I couldn't add it to my comments.
procedure TForm1.Button1Click(Sender: TObject);
var
lStrVal: string;
lIsDateTime: boolean;
lDateTimeVal: TDateTime;
begin
lStrVal := '01/01/2019 10:00' + chr(9) + '120.00' + chr(9) + 'Some text';
lIsDateTime := TryStrToDateTime(lStrVal, lDateTimeVal);
if lIsDateTime then
messageDlg('String value is a date/time! ' + #13#10 + 'String: ' + lStrVal + #13#10 + 'Date/time is: ' + DateTimeToStr(lDateTimeVal), mtInformation, [mbOK], 0)
else
messageDlg('String value cannot be converted to a date/time!', mtWarning, [mbOK], 0);
end;
It appears that the solution (at least for my application) is to combine TryStrToDateTime and TryStrToDate. To demonstrate, start a new VCL application and drop a ListBox and TButton on the form (leave standard names). Paste the following code in the TButton OnClick handler:
var
lDateVal, lDateTimeVal: TDateTime;
lStrVal: string;
lResult: boolean;
begin
ListBox1.Items.Clear;
lStrVal := '01/01/2019 10:00' + '-120.00' + '-Some text';
ListBox1.Items.Add('String value : ' + lStrVal);
lDateVal := EncodeDate(2000, 1, 1);
lDateTimeVal := EncodeDate(2000, 1, 1) + EncodeTime(1, 0, 0, 0);
lResult := TryStrToDate(lStrVal, lDateVal);
if lResult then
ListBox1.Items.Add(' - TryStrToDate : TRUE')
else
ListBox1.Items.Add(' - TryStrToDate : FALSE');
ListBox1.Items.Add('DateTime value: ' + DateTimeToStr(lDateVal));
lResult := TryStrToDateTime(lStrVal, lDateTimeVal);
if lResult then
ListBox1.Items.Add(' - TryStrToDateTime : TRUE')
else
ListBox1.Items.Add(' - TryStrToDateTime : FALSE');
ListBox1.Items.Add('DateTime value: ' + DateTimeToStr(lDateTimeVal));
// Reset
lDateVal := EncodeDate(2000, 1, 1);
lDateTimeVal := EncodeDate(2000, 1, 1) + EncodeTime(1, 0, 0, 0);
lResult := TryStrToDateTime(lStrVal, lDateTimeVal) and TryStrToDate(lStrVal, lDateVal);
if lResult then
ListBox1.Items.Add(' - Combined : TRUE')
else
ListBox1.Items.Add(' - Combined : FALSE');
ListBox1.Items.Add('DateTime value: ' + DateTimeToStr(lDateTimeVal));
ListBox1.Items.Add('');
lStrVal := '01/01/2019 10:00';
ListBox1.Items.Add('String value : ' + lStrVal);
lResult := TryStrToDateTime(lStrVal, lDateTimeVal) and TryStrToDate(lStrVal, lDateVal);
if lResult then
ListBox1.Items.Add(' - Combined : TRUE')
else
ListBox1.Items.Add(' - Combined : FALSE');
try
lDateTimeVal := VarToDateTime(lStrVal);
lResult := true;
except
lResult := false;
end;
if lResult then
ListBox1.Items.Add(' - Using Variant : TRUE')
else
ListBox1.Items.Add(' - Using Variant : FALSE');
This leaves me with the question if this means TryStrToDateTime is incorrectly implemented in comparison to TryStrToDate? As a minimum there seems to be a contradiction on the "design" of the functions; "string value is a Date" versus "string value begins with a DateTime". Unless I am missing something...

Sequential Multi-Threading

I wanna build Multi-Threaded procedure for the following code, but not able to correctly build it. I tried using Semaphore to sequentially complete the code but it only executed the myStream2.CopyFrom(myStream1, StrToInt64('$' + SL1[(i - 1)])); part which comes under else part. The problem presents is that if we restore those streams into output filestream (mystream2) haphazardly, the file will corrupt.
Can anyone help me please.
for i := 1 to SL1.Count do
begin
if Length(SL1[i - 1]) > 12 then
begin
TempDir := SrcDir + 'temp_' +
IntToHex(Random(2147483647) + Random(2147483647), 8) + '\';
ForceDirectories(TempDir);
for k := 1 to 2 do
begin
tempfile := TempDir + IntToHex((i - 1), 8) + '.tmp' + IntToStr(k);
fs := TFileStream.Create(tempfile, fmCreate or fmShareExclusive);
fs.CopyFrom(myStream1, StrToInt64('$' + DecodeRefStr(SL1[(i - 1)])[k]));
fs.Free;
end;
ExecAndWait(SrcDir + 'process.exe', Method + ' ' + '"' + TempDir +
IntToHex((i - 1), 8) + '.tmp1' + '" "' + TempDir +
IntToHex((i - 1), 8) + '.tmp2' + '" "' + TempDir +
IntToHex((i - 1), 8) + '.tmp4' + '"', TempDir,
True, True);
tempfile := TempDir + IntToHex((i - 1), 8) + '.tmp4';
fs := TFileStream.Create(tempfile, fmOpenRead);
myStream2.CopyFrom(fs, 0);
fs.Free;
TDirectory.Delete(TempDir, True);
end
else
myStream2.CopyFrom(myStream1, StrToInt64('$' + SL1[(i - 1)]));
end;
myStream1.Free;
myStream2.Free;

Inno Setup return only user specified command line switches

The support function GetCmdTail returns all command line parameters passed to Setup or Uninstall as a single string. This produces:
/SL5="$A808E8,550741730,269824,D:\Setup.exe" /DEBUGWND=$6A0ACA /verysilent /suppressmsgboxes /closeapplications /restartapplications /norestart
Is there another function or simple way of just returning the user specified command line switches:
/verysilent /suppressmsgboxes /closeapplications /restartapplications
/norestart
in this particular case, whilst excluding the /DEBUGWND entry and/or any other parameters that have not been user specified?
Since Inno Setup 6.2, ParamCount and ParamStr exclude some of these internal parameters, so the if condition in the below code is not needed.*
Based on a similar code I use to run an elevated installer:
function GetUserCmdTail: string;
var
I: Integer;
S: string;
begin
for I := 1 to ParamCount do
begin
S := ParamStr(I);
{ Filter all internal Inno Setup switches }
if (CompareText(Copy(S, 1, 5), '/SL5=') <> 0) and
(CompareText(Copy(S, 1, 10), '/DEBUGWND=') <> 0) and
(CompareText(Copy(S, 1, 10), '/SPAWNWND=') <> 0) and
(CompareText(Copy(S, 1, 11), '/NOTIFYWND=') <> 0) and
(CompareText(S, '/DETACHEDMSG') <> 0) and
(CompareText(S, '/DebugSpawnServer') <> 0) then
begin
Result := Result + AddQuotes(S) + ' ';
end;
end;
end;

Replacing one character in a string by its index in Delphi?

I've tried the following code without success.
string.remove[index];
string.insert[index];
Can anyone suggest how can I correct this code?
Just do this:
stringVariable[index] := 'A';
You can directly access a string like it was a character array in Delphi:
MyString[Index] := NewChar;
For multiple character deletions and inserts, the System unit provides Delete and Insert:
System.Delete(MyString, Index, NumChars);
System.Insert(NewChars, MyString, Index);
Recent versions of Delphi provide the helper functions to string:
MyString := MyString.Remove(3, 1);
MyString := MyString.Insert(3, NewChars);
Once again, please read the tutorials I've referred you to twice previously. This is a very basic Pascal question that any of them would have answered for you.
Here is another way to do it. Have fun with Delphi
FUNCTION ReplaceCharInPos(CONST Text: String; Ch: Char; P: Integer): String;
VAR Lng: Integer; left, Right: String;
BEGIN
Lng:= Length(Text);
IF Lng = 0 THEN
Result:= ''
ELSE IF (P < 1) OR (P > Lng) THEN
Result:= Text
ELSE IF P = 1 THEN
Result:= Ch + Copy(Text, 2, Lng - 1)
ELSE IF P = Lng THEN
Result:= Copy(Text, 1, Lng - 1) + Ch
ELSE BEGIN
Left:= Copy(Text, 1, P - 1);
Right:= Copy(Text, P + 1, Lng - P);
Result:= Left + Ch + Right;
END;
END;

InnoSetup: don't uninstall changed files

How to tell InnoSetup to not uninstall (text) files which had been changed by the user (== are different from those installed by InnoSetup)?
Or maybe more difficult: when installing a new version over an existing, InnoSetup should ask the user whether to overwrite the changed file, but on a pure uninstall, it should uninstall it without asking.
I recently had a similar problem. This was my solution to detect if a text file (profile) has been changed from the one installed during the last installation run:
Use ISPP (Inno Setup Pre-Processor) to create the list of text files and their hashes at compile time:
[Files]
; ...
#define FindHandle
#define FindResult
#define Mask "Profiles\*.ini"
#sub ProcessFoundFile
#define FileName "Profiles\" + FindGetFileName(FindHandle)
#define FileMd5 GetMd5OfFile(FileName)
Source: {#FileName}; DestDir: {app}\Profiles; Components: profiles; \
Check: ProfileCheck('{#FileMd5}'); AfterInstall: ProfileAfterInstall('{#FileMd5}');
#endsub
#for {FindHandle = FindResult = FindFirst(Mask, 0); FindResult; FindResult = FindNext(FindHandle)} ProcessFoundFile
At the top of the "Code" section I define some useful things:
[Code]
var
PreviousDataCache : tStringList;
function InitializeSetup() : boolean;
begin
// Initialize global variable
PreviousDataCache := tStringList.Create();
result := TRUE;
end;
function BoolToStr( Value : boolean ) : string;
begin
if ( not Value ) then
result := 'false'
else
result := 'true';
end;
In the "Check" event handler I compare the hashes of previous install and current file:
function ProfileCheck( FileMd5 : string ) : boolean;
var
TargetFileName, TargetFileMd5, PreviousFileMd5 : string;
r : integer;
begin
result := FALSE;
TargetFileName := ExpandConstant(CurrentFileName());
Log('Running check procedure for file: ' + TargetFileName);
if not FileExists(TargetFileName) then
begin
Log('Check result: Target file does not exist yet.');
result := TRUE;
exit;
end;
try
TargetFileMd5 := GetMd5OfFile(TargetFileName);
except
TargetFileMd5 := '(error)';
end;
if ( CompareText(TargetFileMd5, FileMd5) = 0 ) then
begin
Log('Check result: Target matches file from setup.');
result := TRUE;
exit;
end;
PreviousFileMd5 := GetPreviousData(ExtractFileName(TargetFileName), '');
if ( PreviousFileMd5 = '' ) then
begin
r := MsgBox(TargetFileName + #10#10 +
'The existing file is different from the one Setup is trying to install. ' +
'It is recommended that you keep the existing file.' + #10#10 +
'Do you want to keep the existing file?', mbConfirmation, MB_YESNO);
result := (r = idNo);
Log('Check result: ' + BoolToStr(result));
end
else if ( CompareText(PreviousFileMd5, TargetFileMd5) <> 0 ) then
begin
r := MsgBox(TargetFileName + #10#10 +
'The existing file has been modified since the last run of Setup. ' +
'It is recommended that you keep the existing file.' + #10#10 +
'Do you want to keep the existing file?', mbConfirmation, MB_YESNO);
result := (r = idNo);
Log('Check result: ' + BoolToStr(result));
end
else
begin
Log('Check result: Existing target has no local modifications.');
result := TRUE;
end;
end;
In the "AfterInstall" event handler I mark the file hash to be stored in
Registry later. Because in my tests the event was triggered even if the file move failed (target file is read-only) I compare the hash again to find out if the file move was successful:
procedure ProfileAfterInstall( FileMd5 : string );
var
TargetFileName, TargetFileMd5 : string;
begin
TargetFileName := ExpandConstant(CurrentFileName());
try
TargetFileMd5 := GetMd5OfFile(TargetFileName);
except
TargetFileMd5 := '(error)';
end;
if ( CompareText(TargetFileMd5, FileMd5) = 0 ) then
begin
Log('Storing hash of installed file: ' + TargetFileName);
PreviousDataCache.Add(ExtractFileName(TargetFileName) + '=' + FileMd5);
end;
end;
procedure RegisterPreviousData( PreviousDataKey : integer );
var
Name, Value : string;
i, n : integer;
begin
for i := 0 to PreviousDataCache.Count-1 do
begin
Value := PreviousDataCache.Strings[i];
n := Pos('=', Value);
if ( n > 0 ) then
begin
Name := Copy(Value, 1, n-1);
Value := Copy(Value, n+1, MaxInt);
SetPreviousData(PreviousDataKey, Name, Value);
end;
end;
end;
Inno can't do this check natively.
To not replace changed files during install, you'll need to use custom [Code] to do a checksum and compare against a known good value that is precomputed or saved from the previous install.
To avoid removing them during uninstall, you'll need to disable Inno's own uninstall for that file and check against the same checksum before removing them, again in [Code].
Note that it's better to keep any files the user can edit outside of the setup to handle this situation better and to correctly adhere to the application guidelines.

Resources