Jump to content

Lars Fosdal

Administrators
  • Content Count

    3504
  • Joined

  • Last visited

  • Days Won

    115

Everything posted by Lars Fosdal

  1. Just to illustrate the complexity of this in a different language, look at the danes that work with n times 20s and half-20s when above 50 and below 100, and they are not entirely consistent about it either. I am not a native dane, so there may be some mistakes - but this is my understanding of their classic spoken numbering. førr = forty = 40 fem og førr = five and forty = 45 half tres / femti = fifty = 3 x 20 - 10 = 50 seks og halv tres = six and fifty = 6 + (3 x 20 - 10) = 56 seksti / tres = sixty = 3 x 20 = 60 fem og seksti = five and sixty = 65 half firs / søtti = seventy = 4 x 20 - 10 = 70 åtti / firs = eighty = 4 x 20 = 80 half fems / nitti = ninety = 5 x 20 - 10 = 90 syv og halv fems = seven and ninety = 97 hundrede / fems = hundred = 5 x 20 = 100 277 = to hundrede og syv og halv firs = two hundred and seven and seventy = 200 + 7 + 70 Modern Danish does allow saying 55 = "femti fem" instead of "fem og halv tres" - i.e. more like the English spoken "fifty five", but the classic form is widely used. Modern 277 would be "to hundrede og søtti syv", like the English "two hundred and seventy seven".
  2. Lars Fosdal

    How to manage defined list values

    Come to think of it, it could have been nice to be able to enforce the helper implementations with an interface. or even better, be able to reuse an interface implementation for handling attribute naming. Also, this: /SendsLetterToCompilerSanta
  3. Lars Fosdal

    How to manage defined list values

    There is no definitive answer to that as YMMV. We do literally have 100+ types of enums, and we implemented record helpers for each one, simply to ensure that all enums had the same capabilities. AsString, FromString, Name, and in some cases more texts, or biz.logic associated with the enum. We actually did the last push for this model this autumn, to get rid of the last couple of dozens of standalone TypeNameToStr functions. We also introduced app wide translations (Norwegian, Swedish, English) using Sisulizer. It turned out that using attributes for naming was a bit of a challenge, since you can't use a resourcestring as an argument to an attribute - go figure. resourcestring sName = 'Name'; type AttrNameAttribute = class(TCustomAttribute) constructor Create(const aName: String); end; type TSomeType = class private FaName: string; procedure SetaName(const Value: string); public [AttrName(sName)] // <-- [dcc32 Error] : E2026 Constant expression expected property aName: string read FaName write SetaName; end; We ended up setting names explicitly in code instead.
  4. Lars Fosdal

    How to manage defined list values

    How do you associate the string attribute(s) with each enum value?
  5. Lars Fosdal

    How to manage defined list values

    InitHelper initializes the class var arrays for Caption and DevTitle (for the second example using class vars). You only need to call this once for the application, so you could do f.x. it in the unit init section. For your second question, see the implementation of TProjectTypeHelper.Caption. Why the extra class function CaptionOf? A habit of mine, due to the rule of only one class helper for a type in scope at a time.
  6. Lars Fosdal

    How to manage defined list values

    @David Heffernan - Do you scan the rtti attributes once in a class init method/first use, or do you fetch the attributes at each use?
  7. Lars Fosdal

    How to manage defined list values

    For the latter example: begin TProjectType.InitHelper; var prj: TProjectType := ptExternalDev; Writeln(prj.Caption); end; will output External Dev Project
  8. Lars Fosdal

    How to manage defined list values

    Both. See updated post. Let me correct myself... With a record helper, the methods of the helper become part of the helped type. begin TProjectType.InitClass; // only for the second example var prj: TProjectType = ptMain; writeln(prj.Caption); writeln(ptMain.Caption) writeln(TProjectType.CaptionOf(ptMain)) end; all output the same string. Populating a combobox can be done like this for var prj := Low(TProjectType) to High(TProjectType) do ComboBox1.AddItem(prj.Caption, Pointer(Ord(prj))); and later, if you like such ugly hacks 😉 if ComboBox1.ItemIndex >= 0 then begin var selectedprj := TProjectType(Integer(ComboBox1.Items[ComboBox1.ItemIndex])); end;
  9. Lars Fosdal

    How to manage defined list values

    I prefer record helpers, combined with the const arrays. I really wish I could make generic record helpers for arrays, though 😕 type TProjectType = (ptMain, ptSub, ptExternalDev, ptInternalDev); TProjectTypeHelper = record helper for TProjectType private const cProjDefValues: array[TProjectType] of string = ('main_proj', 'sub_proj', 'dev_ext', 'dev_int'); cProjCaptions: array[TProjectType] of string = ('Main Project', 'Sub Project', 'External Dev Project', 'Internal Dev Project'); public class function DefValueOf(const aProjectType: TProjectType): string; static; class function CaptionOf(const aProjectType: TProjectType): string; static; function DefValue: string; function Caption: string; end; implementation { TProjectTypeHelper } function TProjectTypeHelper.Caption: string; begin Result := CaptionOf(Self); end; class function TProjectTypeHelper.CaptionOf(const aProjectType: TProjectType): string; begin Result := cProjCaptions[aProjectType]; end; function TProjectTypeHelper.DefValue: string; begin Result := DefValueOf(Self); end; class function TProjectTypeHelper.DefValueOf(const aProjectType: TProjectType): string; begin Result := cProjDefValues[aProjectType]; end; The class functions could also be case statements - but the array[type] ensures you have values for all. The weak spot here is if you insert a value in the constant list, or reorder values - without doing the same for the strings. Or you can use class vars - which would make it easier to load up different languages at runtime, if so needed. type TProjectType = (ptMain, ptSub, ptExternalDev, ptInternalDev); TProjectTypeHelper = record helper for TProjectType private class var cProjDefValues: array[TProjectType] of string; cProjCaptions: array[TProjectType] of string; public class procedure InitHelper; static; class function DefValueOf(const aProjectType: TProjectType): string; static; class function CaptionOf(const aProjectType: TProjectType): string; static; function DefValue: string; function Caption: string; end; implementation { TProjectTypeHelper } function TProjectTypeHelper.Caption: string; begin Result := CaptionOf(Self); end; class function TProjectTypeHelper.CaptionOf(const aProjectType: TProjectType): string; begin Result := cProjCaptions[aProjectType]; end; function TProjectTypeHelper.DefValue: string; begin Result := DefValueOf(Self); end; class function TProjectTypeHelper.DefValueOf(const aProjectType: TProjectType): string; begin Result := cProjDefValues[aProjectType]; end; class procedure TProjectTypeHelper.InitHelper; begin cProjDefValues[ptMain] := 'main_proj'; cProjCaptions [ptMain] := 'Main Project'; cProjDefValues[ptSub] := 'sub_proj'; cProjCaptions [ptSub] := 'Sub Project'; cProjDefValues[ptExternalDev] := 'dev_ext'; cProjCaptions [ptExternalDev] := 'External Dev Project'; cProjDefValues[ptInternalDev] := 'dev_int'; cProjCaptions [ptInternalDev] := 'Internal Dev Project'; end;
  10. Lars Fosdal

    Delphi 5 to Delphi 10.1

    @Markus Kinzler - What I meant is that in UDP, there is no mechanism the ensures that the packet reaches its destination, nor is there a mechanism that allows the recipient to discover that a packet has gone missing. As you say, all of that would have to be implemented as a protocol on top of UDP. When it comes to the content, I actually don't know if UDP ensures that the content of a delivered package is guaranteed to be intact? I would tend to assume so, but I haven't actually checked.
  11. Lars Fosdal

    Delphi 5 to Delphi 10.1

    UDP is per definition not reliable.
  12. Lars Fosdal

    Firebird 3 and SP in Object Pascal

    We use a couple of C# assemblies - mostly for being able to break down stuff like bar code scan strings into AIs, or other forms of string analysis, validation (checksums) or manipulation. It is a bit cumbersome, tbh, but it is a lot more efficient than trying to do the same stuff in T-SQL.
  13. Lars Fosdal

    Delphi 5 to Delphi 10.1

    Are you using the appropriate code page for your ansistring? Can you give an example of what goes wrong, character-wise?
  14. Lars Fosdal

    Memo scrolling to the bottom ... sometimes

    ah no - dang - I missed that it was Android. Moved the post again.
  15. Lars Fosdal

    Memo scrolling to the bottom ... sometimes

    No worries. I moved it to VCL. What if you try: SendMessage(Memo1.Handle, EM_SCROLLCARET, 0, 0); instead of the goto/scrollto ?
  16. Lars Fosdal

    Memo scrolling to the bottom ... sometimes

    Is there any difference in behaviour if you SetFocus GoToTextBegin ScrollToTop instead? Can you reproduce the problem in a minimal app?
  17. Edit: I rewrote this on my blog: https://larsfosdal.blog/2019/10/31/demystifying-pointers-in-delphi/ Having a general understanding of how memory and addressing works, helps a bit for pointers. Learning assembler gives you that knowledge, but there are simpler ways to think about it. A pointer is a memory location that contains the address to the actual data you want Think of a street with houses. Make a list of the houses and their addresses. This is a list of pointers. Each pointer leads to the actual house it refers to. As you move through the list and follow each pointer, you can visit each house. Street of houses (i.e. your blocks of data, 1Kb each) 10k 11k 12k 13k 14K +------------+------------+------------+------------+------------+ |Apple |Pear | |Banana |Orange | | H1 | H2 | | H3 | H4 | | | | | | | +------------+------------+------------+------------+------------+ Your list of addresses (aka 4 byte pointers) is stored at memory address 100k var ptrlist: array[0..3] of pointer; assuming the list has been initialized with the correct addresses ptrlist[0] 100k contains 10k ptrlist[1] 100k+4 contains 11k ptrlist[2] 100k+8 contains 13k ptrlist[3] 100k+12 contains 14k for var ix := 0 to Length(ptrlist) - 1 do begin here, ptrlist[ix] = 10k,11k,13k,14k, and ptrlist[ix]^ = whatever value that is stored in the respective house the pointer addresses f.x. ptrlist[1] contains 11k (and that value is stored at 100k+4, and ptrlist[1]^ points to 'Pear', i.e. whatever is stored from address 11k Why the empty house? To exemplify that your list of pointers may be a consecutive array or linked list, but the data each pointer points to does not necessarily need to be consecutive. Now, if you address ptrlist[4] - you are out of bounds on the pointer list, and if you are so "lucky" that the address @ptrlist[4] (which is 100k+16) is not inaccesible, then ptrlist[4]^ will point you to whatever random value that pointer contains,, and most likely give you an access violation, or for sure - point you to data you are not meant to visit.
  18. I have a class type that I call a GridSet, and I use RTTI to match object prop names with DB field names (and attributes for overrides) for multi-row datasets, I build a list of property setters that are ordered the way the fields exist in the DB, for all the fields that exist both in the data set and the object and then I simply loop that list row by row when loading the dataset. I also have an attribute driven variation of filling a GridSet that builds the field definitions dynamically from the DataSet - so that I basically can fill the GridSet from any query, and display it in my GridView class, which attaches to a TAdvStringGrid and sets up the grid purely in code with the various event handlers for sorting and filtering. Hence I can show the result of any query in a grid with one line of code - no visual design required. The benefit of declaring the fields in a gridset, is that I can specify nicer titles, max widths, hints, etc.
  19. Consider this pseudo code uses Rest.Json; TDateClass = class private FHasDate: TDateTime; FNoDate: TDateTIme; public constructor Create; property HasDate: TDateTime read FHasDate write FHasDate; property NoDate: TDateTime read FNoDate write FNoDate; end; constructor TDateClass.Create; begin HasDate := Now; NoDate := 0; end; var Json: String; DateClass: TDateClass; begin DateClass := TDateClass.Create; Json := TJson.ObjectToJsonString(Self, [joIgnoreEmptyStrings, joIgnoreEmptyArrays, joDateIsUTC, joDateFormatISO8601]); which results in the Json string looking like { "HasDate":"2019-02-14T06:09:00.000Z", "NoDate":"1899-12-30T00:00:00.000Z", } while the ideal result would be { "HasDate":"2019-02-14T06:09:00.000Z", } Q:Is there a way to make TDateTime properties with a zero value be output as an empty string - and hence be stripped?
  20. I guess we've avoided the problem since we only have used Zulu timestamps. "departureDate":"2019-10-28T06:00:00.000Z", The problem is at the bottom of procedure DecodeISO8601Time in System.DateUtils- That is where the assumption is made that there will always be a time separator if there is a minutes section AHourOffset := StrToInt(LOffsetSign + GetNextDTComp(P, PE, InvOffset, TimeString, 2)); AMinuteOffset:= StrToInt(LOffsetSign + GetNextDTComp(P, PE, '00', STimeSeparator, True, True, InvOffset, TimeString, 2)); Please make a QP.
  21. @Attilla, from the theorist side if a time zome colon is mandatory in a ISO8601 date, then the problem is really that php API, isn't it? While if a colon is optional, the Delphi implementation of the conversion seems lacking, hence a QP would be a starting point. From the more practical side - Is using class based Json conversion out of the question? - Would pre-processing be an option to correct for the format deviation, f.x. by reg.exp searching for the dddd-dd-ddTdd:dd:dd+dddd and injecting the missing colon
  22. Lars Fosdal

    SetFocus issue

    Is it just me, or does it seem to be slightly different behavior for Focus and BringToFront in Windows 10 1809 and later? I've had several experiences with Application modal windows popping up behind the main window, and it has not only been my own Delphi apps. Starting a Delphi App in the IDE for debugging, would leave the app running behind the BDS now and then. I've had MSSQL Server Management Studio 17.x become unusable with a popup locked behind its main window when left running overnight, on several occasions.
  23. Lars Fosdal

    Parsing Google Search Results redux

    I've been wondering about those... Where do I download/install these? Can't see them in GetIt.
  24. Lars Fosdal

    HEIC library

    Sorry, no idea. This was courtesy of Google search, but judging from the description, it should be a reasonably standard plugin for WIC, so the indentifiers should be discoverable through the WIC interfacees.
  25. Lars Fosdal

    HEIC library

    https://www.copytrans.net/copytransheic/ installs a driver for Windows Imaging Component that allows conversion with WIC and is, as far as I can tell, free.
×