-
Content Count
1053 -
Joined
-
Last visited
-
Days Won
23
Everything posted by aehimself
-
A comprehensive guide to Delphi programming language functions and procedures
aehimself replied to pouyafar's topic in VCL
A profile in GitHub created 11, forum account 10 hours ago (1 post, no rating). Only a RAR file is uploaded, based on the screenshots containing binaries. Contact info is a free Yahoo E-mail address, installations are blocked by AV and there's no real description of what FPDelphi is. Online searches show fuel pump related stuff. I really mean no offence and it can be me living under the rocks, but for someone who has no prior knowledge this is way too suspicious to check out. -
For the time being this is a teaser only, sources will be published on GitHub as soon as I finish up the rough edges. Due to the recent success of detecting and interacting with Delphi IDE versions / instances I decided to put together a new BDSLauncher, which allows you to set up rules to automatically decide which version and which instance of Delphi you want to open a Delphi source file with. If started without any parameters, the rule editor main window will show up: Here a list of file masks can be provided. If the file which is about to be open matches any of these, it will be opened in the version selected under. There are situations when a project has to be open for a .pas (.dfm more likely) to appear correctly. To support this, you can set a string which has to be contained in the IDE caption for it to be selected. If this is empty or there was no instances found with this criteria, a new instance will be launched with the parameters you specify (should be the main project .dproj / .dpk). If a parameter is specified (and the file indeed exists) two things can happen. Either a rule will decide which version / instance the selected file should be started in, or if there's none a selector will appear: If more rules would apply to the source file but the specified instance is not found, the last will be selected alphabetically. In case any rule was selected, no window is shown, only the IDE is launched and / or the source file is opened and the launcher will close shortly after. In theory, the new launcher should support all Delphi versions from 6 and up, however the DDE component used (to do the heavy lifting) was only tested with 7 and 10, 10.1, 10.2, 10.4 and 11. Settings are stored in AppData, so each user with access to the same PC can have different rules set up. What is left: Deeper testing after first impressions (especially with more Delphi versions. There are a few which I was forced to miss) Button / installer to change file associations to itself instead of the original BDSLauncher. This could also be used to reset these associations if a Delphi (un)installer messed these up More options (e.g. cmd/powershell/PascalScript scripting) to decide between versions / instances Allow manual rule ordering instead of the current alphabetical Add icons and / or redesign UI to be a bit more user friendly... It's kind of late now here - hence the teaser only - but I wanted to let you guys know that this is coming. Expect a new post with an alpha version in the upcoming days.
-
You are right, it definitely looks cleaner: As a bonus, with the Cancel button present I can remove the code to handle the Esc keypress 🙂
-
Invalid registry key handling (and due to this, an additional version number detection) has been incorporated in TAEDelphiVersions, thank you for pointing this out! As for the masking, which Delphi version are you using? If MatchesMask('C:\My projects\Project name\Project1.dproj', '*\My projects\Project name\*.dproj') Then ShowMessage('Success!') Else ShowMessage('Fail...'); If MatchesMask('C:\My projects\Project name\Project1.dproj', '*\My projects\*.dproj') Then ShowMessage('Success!') Else ShowMessage('Fail...'); If MatchesMask('C:\Temp\My projects\Project name\Project1.dproj', '*\My projects\*.dproj') Then ShowMessage('Success!') Else ShowMessage('Fail...'); On 11.2 all shows "Success" for me.
-
I seriously suck at UI design so tips like this are more than welcome 🙂 Changed the version selector to show up as a list instead and it indeed feels nicer to handle:
-
There were numerous updates since the last post: Auto-detect Delphi version is now a valid option in rules Window sizes are saved and restored when launched again Added extensive logging in case something goes wrong An icon now appears on the taskbar if only version selector window is shown Rules can be rearranged with drag & drop and can be renamed Changed how rules appear in the list, making it a bit more eye-friendly Version selector window can be closed with the ESC key If Shift is pressed while a file is launched, rule checks will be bypassed and the version selector will show up instead A single file can be launched from the rule editor (main) window. The launching process is exactly the same so it can be used to test the rules The logic for rule processing and opening a file by any rules has been moved to the main form, now RuleEngine only stores the rules - as it should I also had the chance to move it to my developer VM and can confirm that the following are working properly: Taking over file extensions do preserve icons and descriptions Project Version detection is working properly: Delphi 7, 10, 10.1, 10.2, 10.4 and 11.2 is detected and can be controlled: I did have an issue when starting a file width Delphi 7 as it kept popping messages up that some packages couldn't be loaded. As any file could be launched from the command line I suspect maybe UAC / other permission issue and I might need to add some more parameters to the CreateProcess I'm currently using. However, after I pressed Yes on all of these the problem simply went away by itself... so I'll not be able to experiment and debug it anymore 😞
-
Some items from the to-do list are completed: File associations are now created by copying all details from the current Delphi association. This means that (in theory) icons and descriptions will stay the same, but launched with AE BDSLauncher. Unfortunately I could not test this because my associations were already messed up by Delphi and have no icons of anything... 😕 If no rule was able to launch the file, the version selector will select the Delphi version based on the contents of the .dproj, .bdsproj or .dof file. If it was created with a version which is not installed, an error message is shown, and the latest version is selected. Rules are now parsed based on an actual order, not alphabetically. For the time being reordering only can be done with the "Move up" or "Move down" option, I'll extend this with the classic drag & drop in the future. Also, Fixed some appearance issues and an AV if the program is started up with no rules Fixed parameter handling. Quotes are now removed if quoted, if unquoted all parameters will be appended to a single file name. Therefore, these two commands will be able to open the file correctly: BDSLauncher "C:\Path to my project\Project1.dproj" and BDSLauncher C:\Path to my project\Project1.dproj I'll probably beautify the detection code to similar of what dzBdsLauncher has as it's a lot more sophisticated than my if-s but at least it was quick to write 🙂 Due to detection is now possible, I'll probably add it as a possible option for a Delphi version in the rules.
-
Source can be obtained at https://github.com/aehimself/BDSLauncher. Some small changes have been made: If no caption filter is set, it means any running instance can be chosen to open the file with. Always use a new instance is now a separate option If there's only one installed version and no running instances are found, no selector will be shown, a new instance is launched automatically File associations can be taken over and given back to the previous Delphi. For the time being this only includes .pas, .dpr, .dproj, .dfm and .groupproj Left to do: Everything mentioned in the first post At the moment all the file associations are taken over with the description "AE BDSLauncher file" and the same icon as the application. In reality it should be separated, like how original Delphi does it Attempting to guess the Delphi version in .dpr and .dproj files based on @dummzeuch's request Feel free to try it out but don't expect it to work perfectly and look nice 🙂
-
That is actually a good idea and wouldn’t be too hard I presume. If the information was found that version will be selected in the combo box by default. I’ll also have to rethink when the selector appears. It might be good for some to launch a new instance for each file and will probably find it very annoying that a window pops up each time a file was double clicked… As a last resort a “catch-all” rule could be set up to hide the popup… Anyway, I’ll have to think this through.
-
I had issues like this when I used a combo box to change the style of the application in the OnChange handler. I suppose it had to do something with the dropdown window still visible. On the other hand don't be surprised, VCL themes are quite unstable. Since upgrading to 11.2 my main application started to throw similar AVs upon closure, but coming from DBGrid's style hook.
-
I am in a bit of a dazzle here. However it wouldn't be a surprise in general, but is it possible that the Delphi IDE doesn't (and never did) handle WM_DDE_INITIATE correctly? In a different post, @Attila Kovacs managed to get DDE functioning properly on a modern IDE, but I realized that Delphi 7 for example doesn't reply to any conversation request with service "bds" and topic "system". Service is usually the executable name, this is confirmed by the registry entry: However it says nothing about a topic. I experimented with sending empty strings, nulls, "system" or "DELPHI32" directly to windows and HWND_BROADCAST without success. According to Microsoft: Basically, broadcasting 0-0 will end up discovering all available DDE services and topics for use. explorer.exe behaves correctly: However Delphi 7 and Delphi 10.4.2 does not reply with anything. Is it possible that the Delphi IDE never handled WM_DDE_INITIATE messages correctly? If yes, what documentation I have to dig up to get DDE details for earlier Delphi IDEs? The code I used is: unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TForm1 = class(TForm) Memo1: TMemo; procedure FormCreate(Sender: TObject); private procedure DdeWndProc(var AMsg: TMessage); public { Public declarations } end; var Form1: TForm1; implementation Type TDDEInfo = Record WindowHandle: HWND; ServiceAtom: Word; TopicAtom: Word; End; PDDEInfo = ^TDDEInfo; {$R *.dfm} Function FindDelphiWindow(inHWND: HWND; inParam: LParam): Boolean; StdCall; Var ppid: Cardinal; title, classname: Array[0..255] Of Char; Begin // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633498(v=vs.85) // Result := True -> Continue evaluation // Result := False -> Do not continue evaluation Result := True; GetWindowThreadProcessID(inHWND, ppid); // if ppid <> 7196 then // Exit; GetWindowText(inHWND, title, 255); GetClassName(inHWND, classname, 255); Form1.Memo1.Lines.Add('PID: ' + ppid.ToString + ', title: "' + title + '", class: "' + classname + '"'); SendMessage(inHWND, WM_DDE_INITIATE, PDDEInfo(inParam)^.WindowHandle, Makelong(PDDEInfo(inParam)^.ServiceAtom, PDDEInfo(inParam)^.TopicAtom)); End; procedure TForm1.DdeWndProc(var AMsg: TMessage); Var service, topic: PChar; begin if Amsg.Msg = WM_DDE_ACK then Begin GetMem(service, 256); GetMem(topic, 256); Try GlobalGetAtomName(AMsg.LParamLo, service, 255); GlobalGetAtomName(AMsg.LParamHi, topic, 255); Memo1.Lines.Add('ACK from ' + AMsg.wParam.ToString + ': service: ' + service + ', topic: ' + topic); Finally FreeMem(service); FreeMem(topic); End; // PostMessage(HWND(AMsg.wParam), WM_DDE_TERMINATE, AMsg., 0); End; end; procedure TForm1.FormCreate(Sender: TObject); Var di: PDDEInfo; Begin New(di); di^.WindowHandle := AllocateHWND(DdeWndProc); Try di^.ServiceAtom := 0; // GlobalAddAtom(PChar('DELPHI32')); di^.TopicAtom := 0; // GlobalAddAtom(PChar('DELPHI32')); Memo1.Lines.BeginUpdate; Try Memo1.Lines.Clear; EnumWindows(@FindDelphiWindow, LParam(di)); Finally Memo1.Lines.EndUpdate; End; // GlobalDeleteAtom(di^.ServiceAtom); // GlobalDeleteAtom(di^.TopicAtom); Finally DeallocateHwnd(di^.WindowHandle); End; end; end.
-
My idea seems to be correct after all, I should add my daughter right next to the rubber duck on my desk. I don't know what happened when I tried to run the code under D7 but if I change every call to the WinApi "A" version in D10.4.2 it simply works... And yes, I can confirm... Delphi 7 supports DDE, needs the service "DELPHI32" and the topic "system". It just has to be ANSI, not Unicode.
-
I know, this is one of the questions in this topic. As far as I understand, a DDE server should reply with one WM_DDE_ACK message for each service / topic pair it supports, but Delphi doesn't seem to do that. Neither 7, neither 10.4.2. As for being Unicode it was a good guess but it doesn't work. Running the same code from D7, or changing GlobalAddAtom to GlobalAddAtomA yields no results. I guess there's only one question. As detection doesn't seem to work, in which documentation / source file I can look for supported DDE service / topic strings for Delphi? Edit: Ummm...
-
Sometimes it's good to take a break from something. I went to play with my daughter and realized... D7 doesn't understand Unicode! Maybe it receives the message but it will see only it's first character... Will have to test this theory later out though.
-
tjsoniterator How to fill TListViewItem using TJsonIterator ?
aehimself replied to wright's topic in Network, Cloud and Web
So, the sample data you mentioned is not a valid JSON document, but one separate JSON object per line as far as I can see. Maybe this is why TJSONTextReader fails - I don't know, I never used it. If the data will always arrive in this format and efficiency / optimization is no concern, you can do something like... Var line: String; jo: TJSONObject; // jv: TJSONValue; Begin For line In TFile.ReadAlltext('C:\[...]\restaurants.json') Do Begin jo := TJSONObject(TJSONObject.ParseJSONValue(line)); If Not Assigned(jo) Then Continue; // invalid JSON code in one line Try // ShowMessage(jo.GetValue('borough').Value); // For jv In jo.GetValue('grades') As TJSONArray Do // Begin // ShowMessage((jv As TJSONObject).GetValue('grade').Value); // ((jv As TJSONObject).GetValue('score') As TJSONNumber).AsInt // End; Finally jo.Free; End; End; End; The bad thing about this logic is that it reads the whole contents of the file in the memory, which will stay there until processing is complete. With 12 Mb this is not an issue, but you might hit a barrier if the data is in the GBs range. In that case you can use a TFileStream, read into a String until sLineBreak (or .Position = .Size) and build each JSON object from this. The processing logic can stay the same. -
I'm building a tool which searches for and opens Delphi source files. Until now execution was a simple ShellExecute call to the .pas file and everything was working just file. Now, as a second Delphi version is introduced I had to change the code to support opening said file in a specific Delphi version. My issue is that a simple call to bds.exe will always launch a new Delphi instance, and I need to re-use the one currently running. Basically I need to mimic the IDEs behavior when double-clicking a .pas file in Explorer. Unfortunately I could not find any documentation about the parameters of BDSLauncher, nor any relevant parameters of Bds.exe which could achieve this. I also built a small program which only lists the parameters it was started with and temporarily replaced BdsLauncher.exe and then bds.exe to see what it received when something is double-clicked, but all I got was the full path of bds.exe. Can anyone point me to the right direction? Thanks!
-
How to open a file in the already running IDE?
aehimself replied to aehimself's topic in Delphi IDE and APIs
@Attila Kovacs Your discovery method is inefficient. DDE_INITIATE is meant to be sent to HWND_BROADCAST and whoever supports it will reply. You also can identify the window who replied, as a WM_DDE_ACK message sent as a reply to DDE_INITIATE contains the DDE server window handle as WParam. Basically... Procedure TAEDDEManager.DiscoveryHandler(Var inMessage: TMessage); Var whandle: HWND; pid: Cardinal; Begin If inMessage.Msg <> WM_DDE_ACK Then Exit; whandle := inMessage.WParam; GetWindowThreadProcessId(whandle, pid); // Do something with the PID and the window handle of the process End; Procedure TAEDDEManager.RefreshServers; Var discoverywnd: HWND; serviceatom, topicatom: Word; msg: TMsg; res: Cardinal; Begin discoverywnd := AllocateHWnd(DiscoveryHandler); Try serviceatom := GlobalAddAtom(PChar(_service)); Try topicatom := GlobalAddAtom(PChar(_topic)); Try SendMessageTimeout(HWND_BROADCAST, WM_DDE_INITIATE, discoverywnd, Makelong(serviceatom, topicatom), SMTO_BLOCK, 1, res); While PeekMessage(msg, discoverywnd, 0, 0, PM_REMOVE) Do Begin TranslateMessage(msg); DispatchMessage(msg); End; Finally GlobalDeleteAtom(topicatom); End; Finally GlobalDeleteAtom(serviceatom); End; Finally DeallocateHWnd(_discoverywnd); End; End; And yes, I'll change my logic to window messages too. After long weeks of troubleshooting discovery still locks up after the fourth run when I'm using the DDE API 🙂 -
Again - you are correct but only if said library is redistributed with a software. For example, libmysql and oci is not allowing this (libmariadb might but I never read their EULA) and Oracle is notorious of shipping faulty software. Therefore, I'd update them at all places even if I have issues in only one software. LibSSL is also a good example from this perspective. If a serious security flaw is found, it's easier to update only 2 files per architecture than scanning your entire drive.
-
I completely agree with you there. However, I dislike redundancy. if you have 3 portable 32 bit and 2 portable 64 bit programs using libmysql.dll to connect to a database I'd find it wasteful and harder to maintain in case you are updating the client library. But as you said - it all comes down to user preferences.
-
ClientDataset.Filter ( Non English Character )
aehimself replied to Henry Olive's topic in Databases
If you don't have a chance to upgrade, you can take a look at Zeos. It handles Unicode filters and should be able to connect to your preferred RDBMS. -
Delphi beta testing a "premium" privilege?
aehimself replied to Brandon Staggs's topic in Tips / Blogs / Tutorials / Videos
-
Making sure I add unique records to text file
aehimself replied to karl Jonson's topic in Algorithms, Data Structures and Class Design
This level of nesting is just awful. Please do not use it, instead refactor your DB structure or modify your query to return the data exactly as you expect it. I'm leaving this here only for you to see what spaghetti you need because of bad DB design. Type TDateOfBirthPhoneNumber = TDictionary<TDateTime, String>; TSurnameAndRest = TObjectDictionary<String, TDateOfBirthPhoneNumber>; TUsernameAndRest = TObjectDictionary<String, TSurnameAndRest>; TUserCache = Class strict private _cache: TUsernameAndRest; public Constructor Create; ReIntroduce; Destructor Destroy; Override; Procedure AddRecord(Const inUserName, inSurName: String; Const inDateOfBirth: TDateTime; Const inPhoneNumber: String); Function ContainsRecord(Const inUserName, inSurName: String; Const inDateOfBirth: TDateTime; Const inPhoneNumber: String): Boolean; End; implementation Procedure TUserCache.AddRecord(Const inUserName, inSurName: String; Const inDateOfBirth: TDateTime; Const inPhoneNumber: String); Begin If Not _cache.ContainsKey(inUserName) Then _cache.Add(inUserName, TSurnameAndRest.Create([doOwnsValues])); If Not _cache[inUserName].ContainsKey(inSurname) Then _cache[inUserName].Add(inSurname, TDateOfBirthPhoneNumber.Create); _cache[inUserName][inSurname].AddOrSetValue(inDateOfBirth, inPhoneNumber); End; Function TUserCache.ContainsRecord(Const inUserName, inSurName: String; Const inDateOfBirth: TDateTime; Const inPhoneNumber: String): Boolean; Begin Result := _cache.ContainsKey(inUserName) And _cache[inUserName].ContainsKey(inSurname) And _cache[inUserName][inSurName].ContainsKey(inDateOfBirth) And (_cache[inUserName][inSurName][inDateOfBirth] = inPhoneNumber); End; Constructor TUserCache.Create; Begin inherited; _cache := TUsernameAndRest.Create([doOwnsValues]); End; Destructor TUserCache.Destroy; Begin FreeAndNil(_cache); inherited; End; For your information, I did not try this. High possibility of AVs! -
Copy nodes from a TTreeView with Delphi 4!
aehimself replied to Magno's topic in RTL and Delphi Object Pascal
A TList would be better choice but I'm not sure D4 has it... that's why I settled with an array. -
There's more than enough trash there already even at a clean installation so it doesn't really count 🙂 Anyway, it's the easiest solution to support all your 32 and 64 bit apps with everything they might need. After all, Microsoft is doing the same with msvcr*.dlls-s when you install the runtime...
-
Sorry about it, then. I really thought these methods are already in 10.2. You can check where an exception is raised in System.JSON in that earlier version and then simply backtrack to see what conditions you need to meet. Unfortunately I don't have a 10.2 installation anymore to check.