Jump to content

PeterBelow

Members
  • Content Count

    468
  • Joined

  • Last visited

  • Days Won

    13

Posts posted by PeterBelow


  1. 1 hour ago, PatV said:

    Delphi 11.0 

     

    Hi All,

     

    I would like to have an advice on how can I get this class more useful, I'm annoying with the 'set of  ..' and the property of the set.

     

    Sets have their use but they also have limitations, e. g. they are not supported with generics as far as I know, so a "set of T" does not work. If you could replace the set with a list you could turn the class into a generic base class with the generic T: record parameter for the enumeration that the set is now defined with. Of course a check for "x in set" is much faster than a check for "x in list".

     

    The compiler (11.1) accepts something like

    type
      TWrapper<T: record> = class
      private
        FSet: TList<T>;
      end;
      TEnum = (one, two, three);
      TEnumWrapper = class(TWrapper<TEnum>)
      end;

    But you still cannot apply functions like Ord to T in methods of TWrapper, nor cast it to Byte. The constraint system for generics is too limited here since you cannot specify T as an ordinal type.


  2. 56 minutes ago, Fons N said:

    Hi,

     

    I am trying to import using the clipboard some text which has to be processed. I am not a professional coder, it's just a hobby, but I do use some of my application at work (administration department).

     

    Below is data from a web application. In notepad is looks like this. 

     

    image.png.25c45af0f0566740e56a8e3d381bf211.png

     

     

    You are approaching this from the wrong angle. The data you showed pasted into notepad looks like a semicolon-separated CSV format. To dissect this you can use TStringlist. Something like this:

     

    var
      LText, LLine: TStringlist;
      i: integer;
    begin
      LText := TStringlist.Create;
      try
        LText := Clipboard.AsText;
        // This splits the data into lines
    
        LLine := TStringlist.Create;
        try
          LLine.StrictDelimiter := true;
          LLine.Delimiter := ';';
         
          for i:= 0 to LText.Count - 1 do begin
            LLine.DelimitedText := LText[i];
            if i = 0 then
              ProcessHeaderLine(LLine)
            else
              ProcessDataLine(LLine); 
          end;
        finally
          LLine.Free;
        end;
      finally
        LText.Free;
      end;
    end;
           
         

    Untested, just typed into the post directly.

     

    The two Process routines are something you would write yourself. For each the passed stringlist should hold 5 lines, the column captions for the header and the column values for the data.

    If you really want to replace a non-breaking Unicode space it is a single character with the code #$00A0, not a two-character string. Your hex viewer probably pastes the clipboard content as ANSI text, while Notepad pastes it as Unicode (UTF-16).

     

    • Like 1
    • Thanks 1

  3. 21 hours ago, kvk1989 said:

    hi , how can add file path ?

     

    i want to add file with file path

    like this 

    if combobox.itemindex =0 then

    begin

    firehose loader file with file path

    end;

    thanks

     

    The System.IOUtils unit contains a number of helpers to work with files in general. The TPath record has methods to build a path, dissect a path, find system folders etc.


  4. 10 hours ago, Mike Torrettinni said:

    It's quite common to initialize Result of string function with SetLength(Result, len) and then work with the Result, like:

     

    
    // Switch Upper to lower and lower to Upper case
    function SwitchCase(const aStr: string): string;
    var i: integer;
    begin
      SetLength(Result, Length(aStr)); // without Result := '';
      for i := 1 to aStr.Length do
        if CharInSet(aStr[i], ['a'..'z']) then
          Result[i] := UpCase(aStr[i])
        else
          Result[i] := LoCase(aStr[i]);
    end;

    the Result holds some data, which is overwritten with the code after SetLength. Of course if we initialize Result := ''; before SetLength, then Result is a new string.Is that always unused memory location or is there a danger that we are actually overwriting valid memory block (part of other string, or long array.,..) and can cause issues in the running code?

     

     

    A function return value of type string as well as other compiler-managed types (e.g. dynamic arrays) is actually implemented as an additional Var parameter, so your function would be equivalent to

    procedure SwitchCase(const aStr: string; var Result:string);

    The compiler initializes the string variable passed for Result at the point of call, so it will always be valid. It is not guaranteed to be nil (string.empty), though. The compiler may reuse another hidden local variable used previously in the calling method.

     

    Such things are documented in the Delphi Language guide, see https://docwiki.embarcadero.com/RADStudio/Sydney/en/Program_Control_(Delphi)

    • Thanks 1

  5. 4 hours ago, Ian Branch said:

    Hi Team,
    I need a mechanism/code where a 3 letter Company code is combined with an autoinc number, but, the autoinc number is related to the Company code.  The combined are to fit in a 10 char field.  These are to be individual Company Asset Numbers to go on to QRCode labels and into the table.
    To clarify:
    Let's say there are 3 companies, there will be lots more in practice, with Company codes of ABC, DEF, and GHI.
    I need to be able to generate as/when required, and sequentially, ABC0000001 to ABC9999999, and DEF0000001 to DEF9999999, and GHI0000001 to GHI9999999, etc, on an as needed basis.

    Thoughts/suggestions appreciated.

    Regards & TIA,
    Ian

    Is this for a multi-user application, where several users may request a new code/number combination for the same code concurrently? That may pose a problem unless you can figure out  a way to avoid or at least detect collisions. On a database level sequences (or generators, nomenclature differs between databases) were introduced to solve such issues, but in your case you would need one sequence per code. A unique index on the table storing the code+number values would at least give you a way to detect collisions if you simply use a SELECT max(value) FROM <table> WHERE value like :code+'%' to get the last value used.


  6. 3 hours ago, Henry Olive said:

    I wrote below code ( Delphi 10.3)  and everything is OK mouse wheel works

    but to me, there should not need below code, mouse wheel should work

    without any code. 

    The VCL controls do not implement a default handling of the mouse wheel, unless a Windows control implements it on the API level. TScrollbox is not based on an API control, so you have to code this yourself.  You only have to do this once, though: create a descendant of TScollbox that implements the behaviour you want and the use that. Note that when creating a component you do not use the parent component's events, those are for the component user. Instead you overwrite the virtual or dynamic methods that fire the events.

    • Like 1

  7. 5 hours ago, Henry Olive said:

    Good Day,

    I have a form,  respectively   panel, scrollbox, another panel and some components on the last panel

    ScrollBox's  VertScrollBar  properties as below

    Range = 800

    Visible = True

    Tracking = True

    The scrollbar in the scrollbox works w/o any problem, if i mouse click (up & down)

    but when i try mouse tracking,  scrollbar doesnt move ( doesnt move up or down)

    I want to control the scrollbar with a mouse.

    What is the problem

     

    Hard to say with the info you give us. A scrollbox (VCL) sets its range automatically based on the position and dimensions of the controls it contains. If it is empty or the content is aligned alClient there is nothing to scroll. If all content fits in the client area of the scrollbox there is nothing to scroll either.


  8. 23 hours ago, Mike Torrettinni said:

    That's interesting. From all the stuff that you can sue/get sued in US, also usually about the software the issue is late/non delivery, extra charges, quality... but, I've never heard of a case like this. Do you have any links about such case? What was the actual lawsuit about, what kind of the damage was caused? What country was that?

    I don't know much about the US legal system, other than that it is weird by german standards :classic_dry: and have no actual case I could point to. But in my opinion a known safety problem like the one this discussion is about would be a quality issue. Of course if the client decides to use such an unsafe installation folder and the possible risk is spelled out in the installation guide then it is his problem.

    As far as I'm aware  (and i'm definitely not a lawyer) in Germany software is treated like other products (hardware) now as far as consumer protection legislation and liability is concerned, so damage caused by faulty software could be reason for a (private) lawsuite. In this particular case it would probably boil down to how much knowlege of the risk involved can be reasonably expected of the user installing the software...

    • Thanks 1

  9. 22 hours ago, dummzeuch said:

    Yes, I'm aware of that.

    Are you also aware of the legal repercussions that may have? It's of course OK if you only use the program on your own computers, but if you sell it to clients it opens you up to litigation. If the program is infected on the client's computer and then causes damage there the client could sue you, since the program does not follow accepted safety standards for software. Depends on your countries legislation, of course.


  10. 16 hours ago, dummzeuch said:

    That depends. Our internal programs all do that. They aren't installed under program files, though.

    That requires write access for the user account to the folder the program is in, and that makes it vulnerable to malware attacks.


  11. 17 hours ago, Michael Riley said:

    Thank you. I did a fresh install of Delphi Professional and the only FireDAC source files I see are:

    FireDAC.Phys.pas

    FireDAC.Phys.SQLGenerator.pas

    FireDAC.Phys.SQLPreprocessor.pas

     

    I don't understand why my Delphi 10.4 install has all the FireDAC source files.

    I just checked my Pro installations and both 10.4 and 11.1 only have the three source files you listed. For the other FireDac units only the DCUs are there.

    The latest Feature Matrix lists full FireDac source code as Enterprise /Architect edition only.


  12. 14 hours ago, Attila Kovacs said:

    I have an app which downloads table data's from a server with CleverComponents + OmniThreadLibrary. Data is json, zipped, encoded. All CPU is used for downloading and importing the data into an SQL server.

     

    Here are some results with FastMM 4 (default) vs. 5 using Berlin U2.

     

    32bit FastMM4 Sync done in 138,29 Sec
    32bit FastMM5 Sync done in 106,903 Sec

    64bit FastMM4 Sync done in 144,357 Sec
    64bit FastMM5 Sync done in 107,587 Sec

     

    I was shocked by these numbers so I thought I'm sharing my experience.


     

    What's so shocking about these numbers? :classic_dry:


  13. 22 hours ago, João Bosco said:

    I'm using the "SSH_Pascal" library and creating an SSH connection via delphi.

    After a lot of effort I discovered that it is necessary to have your RSA key and your host in this file called "known_hosts" in windows it is usually at: %USERPROFILE% + .ssh\known_hosts

    To create an RSA key using openSSH via CMD is simple, see the command:

    
    ssh-keyscan -t rsa your host


    There, your RSA key is created.

    The only difficulty I'm having is getting this RSA via cmd and adding it to the file.

    the simplest way through cmd would be: ssh-keyscan -t rsa your host > %USERPROFILE% + .ssh\known_hosts To insert the value into the file.

    But using by delphi is not working.

     

    Redirection is function of the command processor. The easiest way to use it from a Delphi program is to write the commnd line with the redirection to a batch file (.bat or .cmd) and then simply run that via ShellExcecute, IMO.

     

     

     


  14. 16 minutes ago, limelect said:

    @Der schöne Günther 2 lines this is the INI

     

    Crazy as it sounds I gave a full path I got yes

    then I deleted the full path I got yes.

    Well now I am buffeled 

    Is it Delphi that gives me a beautiful day? 

     

    What I think there might have been hidden characters although no reason for it

     

     
     

    If you don't use a full path the program will look for the file in whatever it considers to be the "current directory" at the moment. That can depend on many things, e.g. on how you start the program, and it can even change during the program run, e.g. by using a file dialog. If you do want to keep your sanity (and control of the situation) always use full pathes. A program should use a specific folder for its files, e.g. one created under TPath.GetDocumentsPath or TPath.GetHomePath.


  15. 15 hours ago, Stéphane Wierzbicki said:

    Well, web installation did work. IDE is running but i'm facing issues with GetIt.

     

    GetIt is able to display, filter items but as soon as I click the Install button I'm getting errors (I'm no more at the office now but from memory it was related to network connectivity issue)

     

    I suspect our proxy to be too strict... this is only guessing as Embarcadero isn't providing any info about installing Rad Studio in a "secured" network environment.

     

     

    I had to do that at work (before retirement a couple of years ago) since the web installer was unable towork through our firewall. Just downloading the ISO and running the installer from that worked for me.


  16. 17 hours ago, JDRenk said:

    Thanks, I now have that Com Port and sensor working OK.  Now I have another sensor and Com Port that are totally different.  This one returns a 4 byte float32_t value.  I have the 4 bytes in an array of bytes.  How would I convert that to a decimal string?

    That would be the equivalent of a Delphi Single. Use the good old Move procedure to copy the 4 bytes to a Single variable and pass that to FormatFloat to get a string representation.


  17. 2 minutes ago, chkaufmann said:

    I create a handler where I want to subscribe / unsubscribe consumers:

    
    type
      TBSEvent<T> = reference to procedure(const obj: T);
    
      IBSRemoteMessageController = interface
      ['{1C7ECC50-3CA2-41A0-B230-0E9FE4CF9BE4}']
        procedure Subscribe(AHandler: TBSEvent<IBSRemoteMessage>);
        procedure Unsubscribe(AHandler: TBSEvent<IBSRemoteMessage>);
      end;

    Now in my implementation of IBSRemoteMessageController I keep a list of these events. Unfortunately it looks like when I try to Unsubscribe() I get a different pointer even if I pass the same method.

    The only solution I see here is, that I return a key with the Subscribe() method and then pass this key to Unsubscribe(). Or is there a different solution for this problem?

     

    Regards
    Christian

    If I understand this correctly a "reference to procedure" type is implemented by the compiler as a hidden class with an interface that contains the actual method called when the referenced method is called through this reference. On the point where you pass the reference the compiler creates an instance of the class and uses it to store all captured variables the method refers to. In your case that happens two times, at the point where you call Subscribe and again when you call Unsubscribe, so you get two different instances of the hidden class passed to your methods (actually you get interface references, not object references).

     

    I have no idea whether this will work, but since you can cast an interface reference to an object reference to get the object implementing the interface do that on the aHandler and compare the Classtype of that object with the one obtained from the stored handler in the same manner.


  18. Play with this unit, it seems to do what you want, at least with the kind of input you showed.

     

    unit SqlLogParserU;
    
    interface
    
    uses
      System.Classes, System.Generics.Collections;
    
    type
      TStatementParts = record
        Fieldlist, FromList, WhereClause, OrderAndGrouping: string;
      end;
    
      TColumnDigest = class(TObject)
      strict private
        FName: string;
        FValues: TStringList;
      public
        constructor Create(const AName: string);
        destructor Destroy; override;
        property Name: string read FName;
        property Values: TStringList read FValues;
      end;
    
      TColumnDigestList = class(TObjectDictionary<string, TColumnDigest>)
      end;
    
      TSqlStatementDigest = class(TObject)
      strict private
      const
        CRegEx = '([_\d\w]+)\s*([=<>]+)\s*(''.+''|[\d.+\-]+)';
      var
        FColumns: TColumnDigestList;
        FOccurences: Integer;
        FParts: TStatementParts;
        function GetColumnlist: string;
        function GetStatement: string;
        procedure NormalizeWhereClause;
      strict protected
        function GetReportItem: string;
        property Columns: TColumnDigestList read FColumns;
      public
        constructor Create(const AParts: TStatementParts);
        destructor Destroy; override;
        procedure AddColumnValue(const aColumn, aValue: string);
        procedure AnalyzeWhereclause(const aClause: string);
        procedure IncOccurences;
        property Occurences: Integer read FOccurences;
        property ReportItem: string read GetReportItem;
      end;
    
      TDigestList = class(TObjectDictionary<string, TSqlStatementDigest>)
      end;
    
      TLogSqlParser = class(TObject)
      strict private
        FDigestList: TDigestList;
      strict protected
        procedure AnalyzeAndLogStatement(const aParts: TStatementParts);
        procedure DigestStatement(const aLine: string);
        procedure DissectStatement(const aLine: string; var aParts: TStatementParts);
        procedure HandleOrderAndGrouping(var aParts: TStatementParts);
        function IsSelectStatement(const aLine: string): Boolean;
        function GetReport: string;
        property DigestList: TDigestList read FDigestList;
      public
        constructor Create; reintroduce; virtual;
        destructor Destroy; override;
        procedure Analyze(const aLogText: string); overload;
        procedure Analyze(aText: TStrings); overload;
        property Report: string read GetReport;
      end;
    
    
    implementation
    
    uses
      Sysutils, System.RegularExpressions;
    
    {== TLogSqlParser =====================================================}
    
    constructor TLogSqlParser.Create;
    begin
      inherited Create;
      FDigestList := TDigestList.Create([doOwnsValues]);
    end;
    
    destructor TLogSqlParser.Destroy;
    begin
      FDigestList.Free;
      inherited Destroy;
    end;
    
    procedure TLogSqlParser.Analyze(const aLogText: string);
    var
      LText: TStringList;
    begin
      LText := TStringList.Create();
      try
        LText.Text := aLogText;
        Analyze(LText);
      finally
        LText.Free;
      end;
    end;
    
    procedure TLogSqlParser.Analyze(aText: TStrings);
    var
      I: Integer;
    begin
      DigestList.Clear;
      for I := 0 to aText.Count-1 do
        DigestStatement(aText[I]);
    end;
    
    procedure TLogSqlParser.AnalyzeAndLogStatement(const aParts: TStatementParts);
    var
      LDigest: TSqlStatementDigest;
    begin
      if not Digestlist.TryGetValue(aParts.Fieldlist, LDigest) then begin
        LDigest := TSqlStatementDigest.Create(aParts);
        DigestList.Add(aParts.Fieldlist, LDigest);
      end;
      LDigest.IncOccurences;
      LDigest.AnalyzeWhereclause(aParts.WhereClause);
    end;
    
    procedure TLogSqlParser.DigestStatement(const aLine: string);
    var
      LParts: TStatementParts;
    begin
      if IsSelectStatement(aLine) then begin
        DissectStatement(aLine.Trim.ToLower, LParts);
        if not LParts.Fieldlist.IsEmpty then
          AnalyzeAndLogStatement(LParts);
      end; {if}
    end;
    
    procedure TLogSqlParser.DissectStatement(const aLine: string; var aParts:
        TStatementParts);
    const
      CRegEx =
        'select (.*) from (.*) where (.*)';
    var
      LMatch: TMatch;
    begin
      LMatch := TRegEx.Match(aLine, CRegEx, [roSingleLine]);
      if LMatch.Success then begin
        aParts.Fieldlist := LMatch.Groups[1].Value;
        aParts.FromList := LMatch.Groups[2].Value;
        aParts.WhereClause := LMatch.Groups[3].Value;
        HandleOrderAndGrouping(aParts);
      end {if}
      else
        Finalize(aParts);
    end;
    
    function TLogSqlParser.GetReport: string;
    var
      LReport: TStringList;
      I: Integer;
      LDigest: TSqlStatementDigest;
    begin
      LReport := TStringList.Create();
      for LDigest in DigestList.Values do begin
        LReport.Add(LDigest.ReportItem);
      end; {for}
    
      Result := LReport.Text;
    end;
    
    procedure TLogSqlParser.HandleOrderAndGrouping(var aParts: TStatementParts);
    const
      CGroupBy = ' group by ';
      COrderBy = ' order by ';
      // SQL requires grouping before ordering!
      CBoundaries: array [0..1] of string = (CGroupBy, COrderBy);
    var
      I: Integer;
      LParts: TArray<string>;
      S: string;
    begin
      S:= aParts.WhereClause;
      aParts.OrderAndGrouping := string.empty;
      for I := Low(CBoundaries) to High(CBoundaries) do
        if S.Contains(CBoundaries[I]) then begin
           LParts := S.Split([CBoundaries[I]]);
           aParts.WhereClause := LParts[0];
           aParts.OrderAndGrouping := CBoundaries[I] + LParts[1];
           Break;
        end; {if}
    end;
    
    function TLogSqlParser.IsSelectStatement(const aLine: string): Boolean;
    begin
      Result := aLine.Trim.StartsWith('select ', true);
    end;
    
    {== TSqlStatementDigest ===============================================}
    
    constructor TSqlStatementDigest.Create(const AParts: TStatementParts);
    begin
      inherited Create;
      FParts := AParts;
      NormalizeWhereClause;
      FColumns := TColumnDigestList.Create([doOwnsValues]);
    end;
    
    destructor TSqlStatementDigest.Destroy;
    begin
      FColumns.Free;
      inherited Destroy;
    end;
    
    procedure TSqlStatementDigest.AddColumnValue(const aColumn, aValue: string);
    var
      LColumn: TColumnDigest;
    begin
      if not Columns.TryGetValue(aColumn, LColumn) then begin
        LColumn := TColumnDigest.Create(aColumn);
        Columns.Add(aColumn, LColumn);
      end;
      LColumn.Values.Add(aValue);
    end;
    
    procedure TSqlStatementDigest.AnalyzeWhereclause(const aClause: string);
    var
      LMatch: TMatch;
    begin
      LMatch := TRegEx.Match(aClause, CRegEx);
      while LMatch.Success do begin
         AddColumnValue(LMatch.Groups[1].Value, LMatch.Groups[3].Value);
         LMatch := LMatch.NextMatch;
      end; {while}
    end;
    
    function TSqlStatementDigest.GetColumnlist: string;
    var
      LColumn: TColumnDigest;
      LText: TStringList;
    begin
      LText := TStringList.Create();
      for LColumn in Columns.Values  do
        LText.Add(
          Format('  Column: %s, values: [%s]',[LColumn.Name, LColumn.Values.CommaText]));
      Result := LText.Text;
    end;
    
    function TSqlStatementDigest.GetReportItem: string;
    const
      CMask = 'Sql statement: %s'+SLineBreak+
        '  occurrence: %d'+SLineBreak+
        '%s';
    begin
      Result := Format(CMask,[GetStatement, Occurences, GetColumnlist]);
    end;
    
    function TSqlStatementDigest.GetStatement: string;
    begin
      Result := Format('select %s from %s where %s %s',
        [FParts.Fieldlist, FParts.FromList, FParts.WhereClause, FParts.OrderAndGrouping]);
    end;
    
    procedure TSqlStatementDigest.IncOccurences;
    begin
        Inc(FOccurences);
    end;
    
    procedure TSqlStatementDigest.NormalizeWhereClause;
    const
      CSpace = ' ';
      CSingleQuote = '''';
    var
      LMatch: TMatch;
      S: string;
      LBuilder: TStringbuilder;
      LParts: TArray<string>;
      I: Integer;
    begin
      S := FParts.WhereClause;
      LMatch := TRegEx.Match(S, CRegEx);
      if LMatch.Success then begin
        LBuilder:= TStringbuilder.Create;
        try
          while LMatch.Success do begin
            for I := 1 to 3 do begin
              {1: column name, 2: operator, 3: value}
              LParts := S.Split([LMatch.Groups[I].Value], 2);
              if I < 3 then begin
                LBuilder.Append(LParts[0]);
                LBuilder.Append(LMatch.Groups[I].Value);
              end {if}
              else begin
                if LParts[0].Contains(CSingleQuote) then
                  LBuilder.Append(CSingleQuote + '?' + CSingleQuote)
                else
                  LBuilder.Append('?');
              end; {else}
              LBuilder.Append(CSpace);
              S:= LParts[1];
            end; {for }
    
            LMatch := LMatch.NextMatch;
          end;
          FParts.WhereClause := LBuilder.ToString;
        finally
          LBuilder.Free;
        end;
      end; {if}
    end;
    
    {== TColumnDigest =====================================================}
    
    constructor TColumnDigest.Create(const AName: string);
    begin
      inherited Create;
      FName := AName;
      FValues := TStringList.Create;
      FValues.Duplicates := dupIgnore;
      FValues.Sorted := true;
    end;
    
    destructor TColumnDigest.Destroy;
    begin
      FValues.Free;
      inherited Destroy;
    end;
    
    end.

     

    SqlLogParserU.pas

    • Thanks 1

  19. 7 hours ago, dummzeuch said:

    I remember that for one particular Delphi version there was a hidden Code Insight option to automatically fill in the parameters for a function call.

     

    What I mean by that is, given a function declared as

    
    function bla(FirstParam, SecondParam: integer; out ThirdParam: string): byte;

    when you enter a call to it in the editor

    
    MyVar := bla( 

    and press some hotkey, the function call would be automatically completed as

    
     MyVar := bla(FirstParam, SecondParam, ThirdParam);

    I don't mean the hint on parameters that is still available, but actually adding this text into the editor.

    To enable this, you needed to set a particular registry key.

    This was only possible in one particular Delphi version (2006?) and was removed in the next version.

    If I remember correctly some Borland (Inprise / Codegear ?) employee blogged about it.

     

    Does anybody remember the specifics?

    I don't think that ever existed. If it did it would be completely useless since the actual variables you pass to a function call are rarely (in my case never :classic_dry:) named the same as the parameters in the function declaration.


  20. 18 hours ago, JDRenk said:

    Ok, I have a better handle on this. Since I am getting data back from different sensors via 3 UARTs, I found the best way for all is to read an ANSIString or byte by byte into a buffer.  The data contains other information I need to discard.  So how would I select characters i->j out of k total?     "xxxxxxxxixxxxxxjxxxxxxxxxxk"

    Thanks,  Jeff

    You never told us which Delphi version you are using. If it has a System.Ansistrings unit:  that contains a number of routines to work with ansistrings. Use the AnsiPos function to search for a character or substring; it returns the index of the found item. Use AnsiMidStr to extract a range of characters from an Ansistring (or the good old Copy function, it has an overload for Ansistrings).


  21. 4 hours ago, david_navigator said:

    I have a two sets of datetimes which I need to frequently check to see if the two lists are identical. There isn't the time to iterate through the lists whenever I need this info, so I'm thinking I need to store a value that represents the data.
    I think it's a hash, but this isn't something I've done before and everything I've attempted hasn't worked i.e I've changed some of the datetimes and ended up with the same result.

     

    If it's relevant there are usually about 60 in each set, but it could be between two and a thousand (always an even number) and each is only stored to the minute i.e seconds & mS are always zero.

     

    Can anyone point me in the correct direction please ?

     

    Many thanks

     

    David

    Since you only need to be precise to the minute do not store TDatetimes but multiply the values by 60*24 and truncate the result to give an integer (perhaps an int64, you could reduce the range necessary by subtracting a suitable reference date first, e.g. EncodeDate(2020,1,1)). This gives you the number of minutes from the reference date. Store these into a TList<integer>. To calculate a hash use the list's ToArray method to get an array you can feed to a suitable hash algorithm from the System.Hash unit.


  22. 14 hours ago, AlexQc said:

    The thread is created when the user click a button or when he drag and drop files/directory to the form so yes at that point the TListBox (ListBoxPrograms) is fully created. Since I use a "plain" Delphi TListBox I guess I'm OK? (At least it's working fine here). The thread don't know nothing about the Form (it only receive the TStrings from the form TListBox.Items).

     

    It is working in this case since TListboxStrings.Add (TListboxStrings is the TStrings descendant behind TListbox.Items) does indeed send a message (LB_ADDSTRING) to the control, and the control is visible on the form. But you should not rely on such implementation details when working in a background thread.

    Using Synchronize the pattern would be something like this:

     

    Synchronize(
      procedure
      begin
        Buffer.Add(LCurrentFile);
      end);

    where LCurrentfile is the local variable holding the filename to add to the listbox. The code goes inside your find loop.

    • Like 1
    • Thanks 1

  23. Quote

    I'm back to coding Windows applications and I'm using Delphi 10.4. Everything is working: no memory leak, no race conditions, no deadlocks... I'm pretty sure everything is OK but since it's my first time (seriously) using TThread in a Windows application I want to double check that I didn't do something bad!

    Code simplified for readability:

    I see a few problems with your code.

    In general you cannot access UI controls from a background thread, but you pass ListBoxPrograms.Items as the Buffer parameter to the thread's constructor. This can work if the Add method of ListBoxPrograms.Items is implemented by sending a message to the listbox control, since the OS makes sure a message is delivered in the thread that created the control's window handle. On the other hand the VCL creates a control handle on an as needed basis, and if the ListBoxPrograms.Handle has not been created yet when the thread tries to add the first item the handle would be created in the background thread context, which would not work since this thread has no message loop.

     

    So: Always access UI controls only in a Synchronized or Queued method! You did not show code for the ListFilesDir method, so perhaps you are doing that already.

     

    The second point to investigate is how to safely interrupt the thread's work if it has to be killed before it has completed its work. The TThread Destructor calls Terminate and then waits for the thread to die gracefully (by leaving the Ececute method). For this to work as expected the thread's work loop has to check the Terminated property on each round and exit directly if it is true. Again this is something you would do in ListFilesDir.

     

    Then there is the question of error handling in the thread's work loop. The usual pattern is to wrap the whole body of the Execute method in a try except block and either pass a trapped exception to the main thread for reporting to the user via a Synchronized method (not a queued one!), or use the Windows.MessageBox function to report it directly. That API is thread-safe, while the VCL.Dialogs methods are not.

     

     

    • Thanks 1
×