Jump to content

A.M. Hoornweg

Members
  • Content Count

    447
  • Joined

  • Last visited

  • Days Won

    8

Posts posted by A.M. Hoornweg


  1. I have found the core of the issue.

     

    When a form receives a wm_dpichanged message, TCustomForm.WMDpiChanged is called which scales all components on the form. 

     

    But TCustomGrid does it in a terrible way;  there's this simple loop that modifies the heights of the rows:

     

    [vcl.grids, TCustomGrid.ChangeScale(), line 1735]

        for I := 0 to RowCount -1 do
          RowHeights := MulDiv(RowHeights, M, D);

     

    and in the debugger I see that this loop needs a whopping 72 seconds to complete on my Core i7 notebook (for 86400 rows).

     

    The reason? It appears that the changing of a single row height triggers an InvalidateGrid() plus an update of the scroll bars (which, incidentally, is why my breakpoints kept landing in USER32.SetScrollInfo)

    And all of this happens inside the handling of a wm_dpichanged message, so the message queue is blocked for ages which freezes my desktop. 

     

     

    • Thanks 3

  2. When the freeze happens, all windows on the desktop (including the Delphi IDE) become unresponsive to mouse clicks and keyboard events. I need to press ctrl-alt-del to get into the task manager, only then am I able to set a breakpoint in the Delphi IDE.

     

    I managed to break into the main thread of the program and set a breakpoint there. The breakpoint itself is in GDI32.InternalDeleteDC() and the base of the call stack is in USER32.SetScrollInfo.  Continuing the program then triggers the same breakpoint in an endless loop.

     

    If I simply remove the tDrawGrid from the form then the problem disappears and I can move the form between displays with different DPI without any freezing.  If I set the grid's rowcount to a very low number, there is no problem either. My workaround for now is to temporarily set the rowcount to 1 just before the dpi change.


  3. Hello all,

     

    In a certain application of mine that contains a tDrawGrid, the program hangs if I drag the form between two screens with different resolutions.  The program becomes totally unresponsive and I need to kill it with the task manager.  This crash only happens if the grid contains a large number of rows and columns.  It is compiled with Delphi Rio 10.3.1.

     

    I can work around it by setting the rowcount to a very small number immediately before the DPI change and then restore it afterwards.

     

    Kind regards,

    Arthur


  4. 20 minutes ago, Gary Mugford said:

     

      All applications (the main one, 16, IIRC, applets) goes into T:\DataApp. The apps are with three exceptions, written in Delphi 7. The other three are written in Delphi XE7, the latest licence I own.

      All Config files for BDE and the Network file for BDE goes into T:\Idapi

      All databases go into T:\DataApp\Data.

     

    OK, so you guys use network mappings for just about everything. 

     

    I know for a fact that in our own network some mappings often "hickup" and get disconnected for fractions of a second. This might be related to windows group policies being enforced in the background as suggested in this post https://social.technet.microsoft.com/Forums/en-US/5d9c7f34-5cf3-453a-a40a-3225e7995e9c/network-drive-keeps-loosing-connectivity?forum=win10itpronetworking but I don't know if the solution suggested in that thread actually works or not.  

     

     

    As a workaround, would it be possible for you to ditch the "T:" mapping and instead use UNC names (\\server\sharename)? 

     

     

     

     

     


  5. Class helpers are merely a bunch of procedures and functions grouped together that mimic methods of a class. Each method has an invisible first parameter that passes the object and treats it as if it were "self".

    So literally nothing gets constructed or destructed, they're merely procedures/functions that act on an object. 

     

     

    The advantage of class helpers is that one can extend a class, without actually inheriting from it, and that everything is neatly wrapped inside the namespace of that class.   As an analogy, it's a bit like having  an optional side car attached to a motor cycle, instead of permanently changing the vehicle itself.  The major disadvantage of class helpers is that there can only be one helper "in scope" for your code (so the sequence of units in your "uses" clause suddenly becomes important). 

     

     

     

    (Edit) but of course a class helper can have a method that returns a new instance of the class (or of any other class), but that's not really a constructor.  It would be a factory method.

    • Like 1

  6. On 3/29/2019 at 10:52 AM, Dalija Prasnikar said:

    IS-A and HAS-A rule can help you determine whether inheritance makes sense in a first place.

    If IS-A rule is satisfied, then you have to see whether in that particular case you should use class inheritance or composition. When it comes to creating God objects, they break other OO principles.

     

    I think that the problem with inheritance is mainly that the descendant object may expose unwanted methods of the ancestor.

     

    For example:

     

    A "List" is a sequence of stuff and one can insert, delete and read from any valid position (which also makes sorting possible).  Retrieval does not necessarily imply removal.

    A "Stack" is a list with severe constraints, lacking random access and enforcing LIFO behaviour.  Most implementations also enforce that retrieval is removal (as in PUSH/POP).  A "Queue" is similar, only it enforces FIFO.

     

     

    The problem is the word "constraint". Inheriting from a class in Delphi can extend it, but AFAIK it isn't possible to *hide* methods of an ancestor class, or is it?   It is tempting and dead easy to inherit a tStack or tQueue from a tList,  but in that case one would expose unwanted methods like Insert(), Delete()  and Sort().  

     


  7. Nobody said that Subscriber.OnNotify() needs to be blocking.  In my case the subscriber base class is generic and I've implemented the asynchronicity in the derived classes.  The current subscribers simply queue the information and perform a Postmessage() for asynchronous processing in the main VCL thread. 

     

    The only limitation of the publisher is that it is single threaded by design, so calls to subscribe/unsubscribe & Publish must be done in one thread.

     

     


  8. I'm using this in a publisher/subscriber/event pattern.  The event, the subscriber and the publisher classes are all implemented as generics; my idea was to make the code 100% re-usable for multiple purposes.  By using generics I can "fill in the details" at implementation time.

     

    Anyway; the Publisher publishes the Event to all Subscribers, but of course a subscriber may decide to unsubscribe() during this notification,  thus causing the element to be deleted from the list that was being enumerated during the Publish().   But since this is happening synchronously (single-threaded) and just for one subscriber at a time, it should be 100% safe to simply iterate the list backward and allow it to happen.  And if during the Publish() a new subscriber gets appended to the end of the list, he'll simply miss the notification, which is also perfectly correct behavior (a subscriber shouldn't receive a notification that predates his subscription).  So I think that a backward enumerator will do perfectly for my purpose.


  9. Hi Stefan,

     

    these were just my first trials with generic lists having enumerators, I'm totally unexperienced with that matter .... I have now separated the enumerator from the list by writing a generic class factory for it. No more inheritance needed. It now works like this:

     

    Type

        intList=tList<integer>; 

        Reversed: ReverseList<integer>; // class factory, class function "enum" returns an interface

     

    Var ints:intlist;

          i:integer;

    begin

        ints:=intList.create;

        ints.add(1);

        ints.add(2);

       FOR i IN Reversed.Enum(ints)  Do 

      begin

          ...

      end;

      ints.free;

    end;

     

     

    The reason why I'm experimenting with backward iterators is that it is intended for a list in which elements may get deleted during the enumeration.

    For .. in is simply much more legible than For .. downto IMHO and also less error-prone .

     

     

     

     

    // Interface Part:
    
    TYPE
    iListEnumFactory < T >= INTERFACE
      FUNCTION GetEnumerator: TEnumerator<T>;
    END;
    
    tListEnumReversed<T> = CLASS(TEnumerator<T>)
      PROTECTED
        fowner: tlist<T>;
        fListIndex: integer;
        FUNCTION DoGetCurrent: T; OVERRIDE;
        FUNCTION DoMoveNext: Boolean; OVERRIDE;
      PUBLIC
        CONSTRUCTOR Create(owner: tlist<T>);
      END;
    
    
    // also works fine for tObjectlist<T>
    ReverseList <T>= CLASS(tinterfacedobject, iListEnumFactory<T>)
    PROTECTED 
      fowner: tlist<T>; 
    PUBLIC
      CONSTRUCTOR Create(aOwner: tlist<T>); 
      FUNCTION GetEnumerator: TEnumerator<T>; 
      CLASS FUNCTION Enum(aOwner: tlist<T>): iListEnumFactory<T>; 
    END; 
    
    IMPLEMENTATION
    
    CONSTRUCTOR tListEnumReversed<T>.Create(owner: tlist<T>);
    BEGIN
      INHERITED Create;
      fowner := owner;
      fListIndex := owner.count;
    END;
    
    FUNCTION tListEnumReversed<T>.DoGetCurrent: T;
    BEGIN
      result := fowner[fListIndex];
    END;
    
    FUNCTION tListEnumReversed<T>.DoMoveNext: Boolean;
    BEGIN
      result := fListIndex > 0;
      IF result THEN
        Dec(fListIndex);
    END;
    
    
    CONSTRUCTOR ReverseList<T>.Create(aOwner: tlist<T>);
    BEGIN
      INHERITED Create;
      fowner := aOwner;
    END;
    
    CLASS FUNCTION ReverseList<T>.Enum(aOwner: tlist<T>): iListEnumFactory<T>;
    BEGIN
      result := Create(aOwner);
    END;
    
    FUNCTION ReverseList<T>.GetEnumerator: TEnumerator<T>;
    BEGIN
      result := tListEnumReversed<T>.Create(fowner);
    END;
    
    end.

     


  10. Hello all,

     

    I'm stumbling on an unexpected generics-related problem here (Delphi XE).

    I'm trying to implement a backward-running enumerator for a generic tList<T>.  I believe I did everything correctly but still the enumerator runs forward unless I make a modification that really shouldn't be necessary.

     

    TYPE
     tListEnumReversed<T> = CLASS(tEnumerator<T>)
      PROTECTED
        fowner: tlist<T>;
        fListIndex: integer;
        FUNCTION DoGetCurrent: T; OVERRIDE;
        FUNCTION DoMoveNext: Boolean; OVERRIDE;
      PUBLIC
        CONSTRUCTOR Create(owner: tlist<T>);
      END;
       
      tListReversed<T> = CLASS(tlist<T>)
      PROTECTED
        FUNCTION DoGetEnumerator: tEnumerator<T>; OVERRIDE;
      END;   
       
    
    CONSTRUCTOR tListEnumReversed<T>.Create(owner: tlist<T>);
    BEGIN
      fowner := owner;
      fListIndex := owner.count;
    END;
    
    FUNCTION tListEnumReversed<T>.DoGetCurrent: T;
    BEGIN
      Result := fowner[fListIndex];
    END;
    
    FUNCTION tListEnumReversed<T>.DoMoveNext: Boolean;
    BEGIN
      Result := fListIndex > 0;
      IF Result THEN
        Dec(fListIndex);
    END;
    
    FUNCTION tListReversed<T>.DoGetEnumerator: tEnumerator<T>;
    BEGIN
      Result := tListEnumReversed<T>.Create(Self);
    END;
       
       
       

     

     

    This is the code I used for testing:

     

     

    ...
    Var t:tListReversed<Integer>; 
        i:integer;
      begin
        t:=tListReversed<Integer>.Create;
    	t.Add(1);	 
    	t.Add(2);
    	t.Add(3); 	
      	For i in T do
      	   	   memo1.lines.add(inttostr(i)) 
        t.free;   
      end;

     

     

    The weird thing is, the code runs correctly if I make the following change....

     

    And I have literally no idea why that is the case!  As far as I can see, the base class tEnumerable<T> already has a method GetEnumerator which should call my overridden virtual Method DoGetEnumerator. What am I missing here ???

     

     tListReversed<T> = CLASS(tlist<T>)
      PROTECTED
        FUNCTION DoGetEnumerator: tEnumerator<T>; OVERRIDE;
      PUBLIC
        FUNCTION GetEnumerator: TEnumerator<T>;
      END;
       
       
       ...
    function tListReversed<T>.GetEnumerator: TEnumerator<T>;
    begin
      result:=DoGetEnumerator;
    end;

     


  11. Hello all,

     

    I'm currently using Turbopower Lockbox 2 for AES encryption and I'd like to move on to something that's newer & active.  The caveat is that I need to stay compatible with existing encrypted binary files.

     

    Lockbox2 converts a password into a 128 bit hash (a 16 byte array called tKey128) using a proprietary algorithm. Subsequently, its AES cipher uses this hash for encrypting or decrypting the data.

    I have the old Lockbox2 password hashes ready-made.  Is it possible to use them in a newer library (such as lockbox 3) to decode files that were previously AES encoded using Lockbox 2? 

     

     


  12. I use a freeware tool called "agent ransack" which is brilliant for quickly finding strings in whole directory trees. It also has a regular expressions option and it is lightning fast.  What's especially nice about Agent Ransack is that you can drag&drop the search results onto an editor like Notepad++ and then perform a bulk search&replace inside all opened files.


  13. On 2/22/2019 at 5:43 PM, Uwe Raabe said:

    Not in the Action dialog itself, but in the script.

     

    The iterator sets a variable CompilerVersion in OnBeforeEachIteration:

    
    CompilerVersion = Delphi10Seattle + Iteration - 1;

    and the compiler action sets the Action property in the OnBeforeAction script:

    
    Action.CompilerVersion = CompilerVersion;

     

    That's cool!  I really need to take a closer look at these event functions.  Thanks for teaching me something new!


  14. 3 hours ago, Uwe Raabe said:

    Not for a build server running a FinalBuilder project with an iterator over the required compiler versions. Compiling in own VMs for each compiler version would be a nightmare. Let alone having to run all those VMs synchronously during the build process.

     

    I use Finalbuilder too, but as far as I can see you can't parametrize the Delphi version in the compile action.

    Do you create a separate compile action for each Delphi version?


  15. Never mind, I've found an easier way to find it out.

     

    Look at this page :

     

    http://docwiki.embarcadero.com/Libraries/Rio/en/Help_of_Previous_Versions

     

    This page lets me browse the help file wiki for older Delphi versions (starting with 2010).  I simply navigated to the unit of interest for a specific Delphi version, such as this one:

     

    http://docwiki.embarcadero.com/Libraries/XE7/en/System.AnsiStrings

     

    ... and by simply replacing  the "XE7" in the link for older versions,  I was able to ascertain that function "searchbuf"  first appeared in the Ansistrings unit in Delphi XE4. 

    Problem solved!


  16. Hello all,

     

    Our company uses several Delphi versions and I want my code to compile without warnings on all these versions.

     

    When I compile one of my units with Delphi Rio, the compiler emits a warning that "sysutils.searchbuf" is deprecated and has moved to the "ansistrings" unit.   No problem, but how do I find out in which Delphi version this transition was made?  I'd like to write an appropriate {$IF compilerversion} around my code block to suppress the warning and keep it functional across all Delphi versions.  

     

     

    Kind regards,

    Arthur

     

     

     

     

     


  17. Somehow I totally missed this, but in october 2017 Microsoft did a complete U-turn on deprecating OleDB database connectivity.  They  have even released new OleDB providers for MS Sql Server which will be maintained and updated with the most recent server features.

     

    https://blogs.msdn.microsoft.com/sqlnativeclient/2017/10/06/announcing-the-new-release-of-ole-db-driver-for-sql-server/

    https://docs.microsoft.com/en-us/sql/connect/oledb/oledb-driver-for-sql-server?view=sql-server-2017

     

     

    • Like 2
    • Thanks 2

  18. function GuessDecimalSeparator(const Value: string): Char;
    var i,commacount,dotcount,lastsep:integer; c:char;
    Const DefaultDecimalSeparator='.';
    begin  
     commacount:=0;
     dotcount:=0;
     lastsep:=0;
     
     for i:=1 to length(value) do
     begin   
       c:=value[i];
       if c = '.' then
       begin
         inc(dotcount);
         lastsep:=i;
       end;   
       if c = ',' then
       begin
         inc(commacount);
         lastsep:=i;
       end;        
     end; 
     
    Case (dotcount + commacount) of 
        0: result:=DefaultDecimalSeparator;    //Default             
        1: Result:=Value[lastsep];  //accept the one separator
        ELSE 
            //If a separator occurs more than once, it must be the thousand separator        
            Begin 
               if (commacount > dotcount) then result:='.' //multiple commas 
             else 
               if (dotcount > commacount) then result:=',' //multiple dots 
             else //equal amounts, take last separator 
               result:= Value[lastsep];            
            End;
     end;
    end;

     

    This function uses the simple assumption that a decimal separator should occur at most one time in the string, but thousand separators may occur multiple times.

     

     

    • Thanks 1
×