Jump to content

David Schwartz

Members
  • Content Count

    1190
  • Joined

  • Last visited

  • Days Won

    24

Everything posted by David Schwartz

  1. I have a situation where I've got a list of names and a list of places. I want to create a grid that displays a color in each cell that represents some data, sort of like a heat map. Think something like "how many people named Hans visited this place in Seattle during this date range?" So the gist of it is that you go through each combination of x (eg, name) and y (eg, place) then set the color of the corresponding cell to some normalized value. (this is not about the colors or even what's in the cells) There's a first pass goes through each (x,y) pair and does some queries to collect data, then saves it. In my mind, this is kind of like a stringlist with name=value pairs, where each 'name' is an (x,y) pair and the 'value' is an object (eg., another stringlist) that conains relevant data found for that specific pair. The next pass processes the data and extracts relevant numbers, then assigns something like a color value (in this case). There will be ways of filtering the data to offer different possible viewing models, which is why I read all the data first, then process it separately. (eliminates the need to re-issue the same queries over and over.) Each time the filter is changed, the data is re-analyzed and the results displayed. Possible ways I see of organizing this data include: (1) array of array (2) list of lists (3) a dictionary that uses each (x,y) pair as the key and saves the data as the value. Then it would cycle over them and split the pair to get the (col,row) for accessing a grid cell. (4) something else (?) I'd rather not have to do everything with a nested loop, and there's not much to do once the color has been determined other than use it to set the color of a corresponding grid cell. It's just a heat map that you look at. The user is MAINLY interested with what's in the SERIES of heat maps that are created over time so you can see how the data corresponding to the same (x,y) pairs changes over time. So what's of most interest is ... it's a stack (ie, LIST) of 2D heatmaps. Which I guess makes it a 3D data organization. What's the simplest way to organize and use the heat map data? (1), (2), or (3)? or maybe you have a (4) in mind?
  2. David Schwartz

    gridlines in listboxes

    A listbox is really easy to use. A stringgrid is not a simple replacement for a listbox.
  3. David Schwartz

    TJSONObject.Format bug

    It seems if you have quoted strings that contain things like "... 100% satisfaction ..." or "... a 10% discount..." in an input string, the call to doc.Format here: doc := TJSONObject.ParseJSONValue(the_resp) as TJSONObject; JSON_mmo.Text := doc.Format(DEF_INDENT); // <----- chokes on the "% <space> <next char>" sequence saying it's not a valid format. Is there a way to escape these % chars or do something that doesn't require deleting them?
  4. David Schwartz

    TJSONObject.Format bug

    Ok, I figured it out. Thanks!
  5. David Schwartz

    TJSONObject.Format bug

    EDIT: Actually, it's implemented in DW.REST.Json.Helpers. I cannot find any other reference to Tidy in the code base that I just pulled down from github. TJsonHelper = class helper for TJson public class function FileToObject<T: class, constructor>(out AObject: T; const AFileName: string; AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]): Boolean; static; class procedure SaveToFile(const AObject: TObject; const AFileName: string); static; /// <summary> /// Replacement functions for TJson.Format, which does not format correctly /// </summary> /// <remarks> /// Overload that takes a string parameter has been modified so that it should also work for /// JSON which is not generated by TJsonValue.ToString /// Please use this function with CAUTION!! It still may not cater for all situations, and is still not perfect /// </remarks> class function Tidy(const AJsonValue: TJsonValue; const AIndentSize: Integer = 2): string; overload; static; class function Tidy(const AJson: string; const AIndentSize: Integer = 2): string; overload; static; end; . . . // Now based on: https://pastebin.com/Juks92Y2 (if the link still exists), by Lars Fosdal class function TJsonHelper.Tidy(const AJson: string; const AIndentSize: Integer = 2): string; const cEOL = #13#10; var LChar: Char; LIsInString: boolean; LIsEscape: boolean; LIsHandled: boolean; LIndent: Integer; begin Result := ''; LIndent := 0; LIsInString := False; LIsEscape := False; for LChar in AJson do begin if not LIsInString then begin LIsHandled := False; if (LChar = '{') or (LChar = '[') then begin Inc(LIndent); Result := Result + LChar + cEOL + StringOfChar(' ', LIndent * AIndentSize); LIsHandled := True; end else if LChar = ',' then begin Result := Result + LChar + cEOL + StringOfChar(' ', LIndent * AIndentSize); LIsHandled := True; end else if (LChar = '}') or (LChar = ']') then begin Dec(LIndent); Result := Result + cEOL + StringOfChar(' ', LIndent * AIndentSize) + LChar; LIsHandled := True; end; if not LIsHandled and not LChar.IsWhiteSpace then Result := Result + LChar; end else Result := Result + LChar; if not LIsEscape and (LChar = '"') then LIsInString := not LIsInString; LIsEscape := (LChar = '\') and not LIsEscape; end; end; end.
  6. David Schwartz

    JSON parsing question

    I'm dealing with an API that returns its data in a big JSON object that can vary from one request to another because the data being returned is somewhat dynamic. (Some is always there, and some is not.) It's structured generally like this: { section1: {...}, section2: {...}, section4: {...}, section7: {...}, . . . } Some sections consist of sub-objects (ie, {name : {...}} things) and some consist of array of JSON objects (ie, { name : [ ... ] } things) where the members are also JSON objects. Most examples I've seen approach this from the perspective that you know exactly what all of the 'name' values are for each object. In this case, I know there are a bunch of sections and sub-sections, but I don't necessarily know what all of the 'name' parts are. Also, you can infer from the above that there are sections 3, 5, and 6 that are not present in the results of this particular request. A section might not be there until you see it show up sometime; then it's like, "Oh, look at that ... something I haven't see before!" That is, it's not like the server in this case always returns, say, 15 sections, whether some are empty or not. Also, the sections may not always show up in the same order. What's the best way to parse this sort of thing? Part of what I'm not clear about is that I can ask for a Count on the number of things in an Array then iterate over the array, but what about the parts of a list -- like the sections named above? In my mind, I'd approach it using a Case statement, where I'd iterate through the list and drop each section name into the Case. If there's a match, then it would processed. An Else clause would handle any that aren't known yet, mostly for documentation purposes. (I'm ignoring the fact that Pascal's Case statements are still stuck in the 1970's and don't accept strings like most other contemporary languages today...) I'm working with Delphi's built-in JSON Objects Framework, although I could be persuated to use something else if it's noticeably simpler. None of the examples in the docwiki make much sense because they assume you already know the structure and names of everything being sent, as well as the order in some cases. What I *DO* know is the section names of particular interest, and within those sections there are specific elements I know by name that I want to extract the values from. Some of them are in secton sub-objects and some are in arrays of objects in a section. I also know which of these sections contain an array of objects and which are just sub-objects. Or said another way, I know that section values either look like a record or class (as opposed to a single-element array), or they're an array of records or classes.
  7. David Schwartz

    TJSONObject.Format bug

    Yes, I know that. Here's the problem: implementation {$R *.dfm} uses StrUtils, Math, ShellApi, DW.REST.Json.Helpers; The file is in the same folder as the other project files. I even included the unit in the project. So there's no reason the compiler shouldn't be able to find this method. ------------------- JSON_mmo.Text := Tidy( google_srch.TheResponse ); [dcc32 Error] Main_form.pas(533): E2003 Undeclared identifier: 'Tidy' JSON_mmo.Text := TJSON.Tidy( google_srch.TheResponse ); [dcc32 Error] Main_form.pas(533): E2003 Undeclared identifier: 'TJSON' JSON_mmo.Text := JO.Tidy( google_srch.TheResponse ); [dcc32 Error] Main_form.pas(533): E2003 Undeclared identifier: 'Tidy' -------------------- FWIW, I get red squigglies on a numerous classes and methods that the compiler things are not defined, but it compiles just fine and runs. (This is D10.4.2.)
  8. David Schwartz

    TJSONObject.Format bug

    This looks great, except I can't get the compiler to find the Tidy method: var JO := TJSONObject.ParseJSONValue(google_srch.TheResponse) as TJSONObject; JSON_mmo.Text := JO.Tidy( google_srch.TheResponse ); [dcc32 Error] Main_form.pas(533): E2003 Undeclared identifier: 'Tidy' I have the unit in the Uses clause and even included it in the project.
  9. David Schwartz

    TJSONObject.Format bug

    I dunno why it would barf either, except when I went through the raw response text with an editor and replaced all '%' characters with ' pct' the problem disappeared. Maybe it's actually using the normal Format method there? (I have not dug into it.)
  10. I've recently encountered some web apps that use "bubbles" (rectangles, circles, etc.) that you drop onto a canvas that are associated with some specific logic. You can "zoom-in" and perform more detailed design of those things that goes far beyond just editing values in an Object Inspector. (eg., a whole web page editor with access to templates, libraries of various media, etc., for creating the landing page.) Then you can connect them to one or more other bubbles to represent a flow. It's roughtly similar to how the LiveBindings Designer works, only these are much higher-level operations with access to a larger variety of object editors. The few I've seen all seem to be using a very similar (if not the same) UI framework. The approach they're taking represents a rather high-level of graphical programming. They're all very application-specific, but allow non-technical users to build fairly complex apps that don't require a single line of programming. They're used to design apps that implement logic supported on a given SaaS site. For example, one lets you create a web landing page with several fields on it. If one of them is a phone#, then when the user clicks the Submit button, you go to a bubble that says, "If there's a phone# given, then send it to this email or message#". It also logs the submission, adds the data to a mailing list, and does some other stuff. This logic is all built-in, although it works with Zapier to let you add logic to unrelated stuff if you want. We still don't have a view of our apps from inside the IDE that shows the forms and flow between them on a big canvas in the same way that DB designers have had for DB layouts for at least 15 years now, let alone some way to build a UI like this within Delphi. TMS has their Diagram Studio, although it's rather limited. There's also Mitov's OpenWire framework. I don't know what these web-based frameworks are, but they seem to be gaining in popularity. Is anybody doing this with Delphi (or even C#)? Or are we going to have to abandon this ship to get access to UI/UX trends that web devs seem to be leap-frogging over us and providing to customers?
  11. David Schwartz

    Out parameter is read before set

    Yes, exactly! Most of these I was able to solve, although some took quite a lot of head scratching.
  12. David Schwartz

    Out parameter is read before set

    no, not that specifically. But I know of a lot of things C# does have that I'd love to see supported in Delphi. 😉
  13. I'm curious about something that I've run into a few times, and I've never figured out how to solve it nicely. I'm wondering if there are any good solutions. Basically, I have a couple of things derived from TDataset: TOracleDataset and TPgQuery. Both of them are essentially TQuery variants. The problem is, I guess there are some shortcomings with the design of TQuery such that nobody ever derives query components from it. Or maybe it's that TQuery is too restrictive for their needs. Regardless, I want to be able to pass a parameter to a function that takes instances of either one of these. The problem is, they both have a SQL property that's not in the TDataset class they inherit from (along with some other things). procedure Form1.Func1( aQry : <<what_goes_here?>> ); I want to be able to call Func1( aOracleQry ) as well as Func1(aPgQry). But since TDataset doesn't have an SQL property, if I use that as the parameter type, I have to use code like this: procedure Form1.Proc1( aQry : TDataset ); begin . . . aQry.Close; if (aQry is TOracleDataset) then TOracleDataset(aQry).SQL.Text := 'select * from xyz' else TPgQuery(aQry).SQL.Text := 'select * from xyz'; aQry.Open; . . . end; I don't want the function to have to know what kinds of datasets might be passed. That's what inheritance and polymorphism are supposed to handle. What I want to simply say is something like this: procedure Form1.Proc1( aQry : TSomething ); begin . . . aQry.Close; aQry.SQL.Text := 'select * from xyz'; aQry.Open; . . . end; The actual variables themselves DO have an SQL : Strings property, but their common ancestor class does not, and I have no way of inserting a class in between the two. This is just one example; I've encountered the same thing in different contexts. I've just never figured out a good way to address it. Any ideas?
  14. David Schwartz

    Out parameter is read before set

    Am I the only person who treats out params as "write-only" vars? I get that the compiler doesn't always handle them properly when you abuse them. IRL code, I use them when I need to return more than one value from a method. Saying you can pass in an initial value to an out param is like saying you can pass in a default return value (result) to a function -- and there's probably a way to do that where the compiler won't complain about it either. You can also call a method with no parameters at all and the compiler won't complain. And even when you do that, it's possible to pass in values for those parameters! This is to maintain backward compatibility with ancient syntactic crap in very very old versions of TurboPascal that probably have their roots in even older compilers (USCD Pascal, perhaps?). This is what Pascal Analyzer was created for -- to show you crappy code expressions that, while legal, should not be used. I seem to recall that PA's list of stuff is around 3x bigger than Delphi's. PA does for Delphi as what lint does for 'c'. Speaking of which ... who remembers the ads in programming mags by the guys who published a sooper-dooper version of lint (maybe lint++), under a heading like, "Can you find the bug?"
  15. David Schwartz

    swagger help needed

    Just FYI, I'm not "still on" any particular version of swagger. The project I _was_ on had swagger info published that's generated from Microsoft tools that are in widespread use, and that code itself reports that it was V2.0. I have sampled a bunch of swagger specs I've found around the internet, and I've found very few that are V3.0. I don't know who produces these or how, but if most are being generated by MS tools, then consider it's MS themselves that's responsible for this disparity. Certainly nothing I'm connected to. (I'm not on that project any more, and anyway they had told me not to even use Delphi. So at this point it's just a curiosity.) If MS isn't supporting V3.0, then I'm not sure who's going to use it if it's really that hard to parse.
  16. You might want to look at TMS WebCore running on VSC.
  17. David Schwartz

    swagger help needed

    The API I was originally working with is internal, but I've tried reading a number of ones I've found around the internet. There are tons of them. I'm not understanding why java is needed to build something to consume JSON text, build some internal tables, then spit out Delphi classes. From playing with the earlier referenced code, the trickiest part lies in two areas: 1) recognizing isomorphic structures (ie, classes with the exact same data members) and combining them into a single definition; and 2) if you allow selecting only certain APIs or groups, then optionally being able to emit only dependent variable and class definitions and skip everything else. The first step is needed to avoid having a bunch of identical classes defined just because the Swagger code defined them and didn't name them, but the parser gave them all unique default names; or they were named in the Swagger text and all were given different unique names for no particular reason. (I saw a situation recently where a common error return structure generated over 50 identical classes where each one had been given a different name. It's completely unnecessary, and simply requires that the structure of these be recognized as identical and a common base class generated that's used for all references. Worst-case, it would be used as the base class for child classes derived from it. I'd lean towards leaving them all referring to the common base class, then maybe add derived classes to distinguise different error returns, or whatever makes sense in the context. A flag to give you a choice might be a nice option.) There's also the matter of how the emitted classes are ordered. The original code sorted them alphabetically, which might not work for Delphi. If you remove the sorting, then they are emitted in the order they're encountered in the Swagger spec, which may not translate into proper ordering for Delphi either. So internally, it needs to have a way of flagging dependencies and using that to emit code in "layers", or at least in an order where dependent types, vars and classes get defined before they're referenced.
  18. David Schwartz

    swagger help needed

    I don't even use Java, so this isn't much help for me. This thing from my original post: https://github.com/marcelojaloto/SwagDoc and related updates already gets us into the ballpark. The main problem is that it assumes the use of the MVC framework here: https://github.com/danieleteti/delphimvcframework Instead of calling the mvc framework, it needs callbacks or another way to handle data as its parsed. I guess JSON can be a little ambiguous in some cases, which makes it hard to parse?
  19. David Schwartz

    Download Images from a URL

    The FNC components are intended to save you the most EXPENSIVE time -- YOUR PROGRAMMING TIME. If you don't value your time, then don't bother using them. You could also look at the source code and see what they're calling "under the hood" to investigate if there may be a more efficient approach on one platform or another. There may well be. But so what? In order to get a set of components that work and act the same on all platforms, they've needed to implement certain abstractions that are necessarily going to have a cost. That said, the time of the data transfers in this instance will, in most cases, swamp out the abstraction logic, unless they're doing unnecessary data copies. The truth is, most people do not bother to optimize their images in the first place. So you're looking in the wrong place for large speed improvements! Do you have any idea how much bandwidth is wasted by people who take 16 MPixel images then send them to their friends to look at in a 1" x 1" box on their cell phone? A 16 MP image is like a full HD screen in resolution; the 1" x 1" box on the cell phone is under 10kb for an image that most people couldn't see any difference between the two. You need to optimize the images BEFORE they're downloaded to get the most sigificant improvement in DL speeds! Because there's nothing you can do to move an 8MB image as if it's only 10KB in size.
  20. David Schwartz

    Event Listeners Delphi <-> Web

    This problem reminds me of a system I had to analyze in college for an Information Analysis course that involved a new meal ticketing system being set up in the cafeterias around campus. They wanted people to be able to buy a meal ticket at any of the cafeterias, right there in line. So Joe would buy a ticket, get it scanned, and go in to have lunch; but his buddy Bob would come by just as he was entering and say "Hey, Joe!" and they'd shake hands and the ticket would invisibly move from Joe to Bob. Then Bob would go to another cafeteria and use it again. They knew there was a problem, and some said it was a bug in the software. But that wasn't it. Joe got what he paid for, but Bob got a free meal. This is a very old and common problem involving a state machine that has nothing to do with Delphi, web apps, firing events, QR codes, or how many gaming centers a company may or may not have. And it's not so much a matter of programming as it is recognizing the nature of the problem and the best approach to a solution. First, I suggest you rewrite the description, leaving out all of the implementation details. They may be causing you confusion. Express it as a list of "current_state -> event -> next_state" rules. Second, refresh your knowledge of protocols and state machines, and review "protocol sequence diagrams" and maybe "state transition diagrams". (There are certainly tools that let you do this, but a pencil and paper are probably much quicker.) Third, go read some of the RFCs for email handling on the internet from back in the 70s and 80s. The internet was literally designed to survive a nuclear attack that took out random data centers, and the data packets (eg, email messages) would still find their way to their destination in a reasonable time-frame. (Results may depend on one's definition of "reasonable".) What you're trying to model here isn't much different than how basic email delivery is managed. Data moving at 150 baud on a dial-up line is pretty damn slow by today's standards; but sending out a 64 byte packet (probably overkill for this) will still make it 10 miles across town way faster than any human can move. Which leads us to the forth and final thing to do: write up the Terms and Conditions that apply to all kinds of tickets and how they can be used in this network. Starting with the misnomer that "customer buys a ticket" equals "days of free [access]". That's like saying if you buy a bus pass for a week then you get a week of "free rides". No! If you don't have that pass with you and you're caught, it's going to be even MORE expensive than what you already paid, right? (This was the source of the problems with the meal ticketing system I analyzed. They were selling tickets at the door, but the data was entered manually some hours later, but before the next meal. So the tickets could be used any number of times between when they were purchased and when they got entered into the system. They had to change the rules about when tickets could be purchased, and ensure the data was entered into the system before they could be used.) Terminology is important when it comes to design. In this case, the system might actually support "free passes". And if a center is stuffed to the gills with customers who PAID for tickets, you might want to BLOCK those with "free passes" from coming in, or just during certain times. See the distinction?
  21. David Schwartz

    caFree & ,Free??

    Marco is enumerating a few ways of approaching a common situation. Which you choose is up to you, based in large part on surrounding contextual stuff you might know about in that situation that someone else would not. You're asking something like, "What is the best way to introduce a person to another person in terms of what you say?" Are they two friends of yours? Are they strangers to you? Is one a dignitary? Is one the opposite sex and you have to follow certain rules? Are they possibly antagonistic towards each other (eg, a drug dealer vs. a police officer)? Is one a family member or someone you are concerned about for some reason? Do you have an agenda in introducing them? You seem to be asking for something to say that will work in 100% of situations. It's not possible without sometimes sounding like an idiot. You need to adapt your interactions to the situation at hand, just like you would employ different water management policies in a rain forest as opposed to a desert, although you'd use the same tools in both cases.
  22. David Schwartz

    Learning Delphi

    I just made a list of some stuff off the top of my head. These are all things I run into pretty regularly, so they're not very mysterious. It would be instructive to make a list of all of the different ways the following three common idioms are expressed by different programmers in different projects, and how many variations can be found within the same large project: * Logic that would use a Case with a string discriminant if the language supported it * Ways of mapping Enums or regular consts (both numbers and strings) at run-time to some kind of display string that's more readable, including how they're initialized * Form interactions (we could be talking 2-3 DOZEN here!). Variations would include these idioms for both getting and setting values in the form: -- auto-create vs. on-the-fly creation and destruction -- constructor injection -- requires overloading Create that hides default ctor -- property injection -- method injection -- direct access of members (UGH!) -- forms that manage Add / Edit in the same form vs. one for each -- embedded selection popups on fields: read-only vs. ability to add something new (requires another form) -- field validation (before vs. after exiting the form) -- Hiding / Closing / Freeing the form Actually, I bet a whole book could be written on "Form Interactions in Delphi". It's hard to argue there's a "right way" or even a "best way" to do it, but there certainly are a LOT of ways!
  23. David Schwartz

    caFree & ,Free??

    This is a REALLY BAD APPROACH. I don't care if you use with or not. But you don't create an object then say "with <object> do" and then refer to the object in a Finally to free it AFTER you ALREADY FREED the object inside of the Try block! But you don't know at this point because you already (and invisibly) delegated the form's lifetime to the form itself. BAAAAAAADDDDDD! Here's what you could use instead: // Do NOT override the Create to inject parameters into the form unless they're actually required. with TMyForm.Create(self_or_NIL); try // Forms rarely if ever need Constructor injection. The most common misuse is passing DB-related details into // forms that are designed to only work with one table or one specific query or stored proc. It's hardwired. // It just needs a key to do a lookup. And don't rely on the "current record" in another form! // Instead, use PROPERTY INJECTION to initialize properties in TMyForm here, eg., by passing in a key. // And yes, TMyForm is an OBJECT and you should access things inside of it using PROPERTIES! // The setter for this property could trigger a DB lookup that initializes other fields on the form. UserName := 'Fred Flintstone'; . . . // now show the form, and do NOT delegate lifetime to the form if (ShowModal = mrOK) then // requires setting the Action param to caClose NOT caFree!!! begin // now retrieve values from properties in the form, if needed. // note that you can't do this if you use caFree since it's DESTROYED at this point! // which is why it's a Bad Idea to delegate lifetime to the form. // You're using a with statement here, so you can't set it to nil. And even if you weren't, // there's no way to know that the form killed itself. // Unfortunately, it will work often enough that you'll think the occasional exception is for other reasons. Result := PhoneNum; . . . end; finally // NOW Free the form instance Free; end; This is a nice, solid pattern that will serve you well in ALL form interactions. The only excuse not to use it (and not use PROPERTIES inside of the form) is if you're being LAZY. The use of with can be debated until the cows come home and it boils down to a religious debate; but at the end of the day it's really a personal preference as far as I'm concerned. But the way you've done it is obviously error-prone and not something you'd want to do everywhere (for consistency). See my CodeRage 9 talk for more on this topic: https://www.youtube.com/watch?v=JgOhg2GbydQ
  24. David Schwartz

    Learning Delphi

    Right out of school ... it will depend what they were exposed to in school. They probably understand OOP and OOD, but may not have much experience with strongly-typed languages like C/C++ and Pascal/Delphi. To me, after 40 years, most imperative languages all look the same, except for added things for stuff like vectors, parallelism, and more esoteric stuff that all tend to be language-specific. The latest feature that most languages are adopting is the use of "attributes" on classes and class members. I don't know how Delphi's stack up against any others, but it may be worth discussing. Other than that, what I'd call the "dynamics" are going to be a big part of what can hang them up. For instance, Delphi has added some interesting features to the language like for...in that make using various kinds of containers a lot easier. But there are things missing from Delphi that make it impossible to use some of these newer features at times. For...in is great unless you need to refer to the index or position of the selected item in the list, for example. The lack of a trinary operator means there are lots of if...then...else statements that could be collapsed into a much simpler trinary expression. There's the IfThen method, but it evaluates both arguments before choosing one, which makes it useless when trying to avoid referencing null pointers. Most contemporary languages accomodate both of these things today, so it might seem confusing in that respect. (Delphi may support it in an upcoming release as part of NULL expressions, but a trinary operator could have been introduced in the languge way back that simplified it forever.) The RTL has been extended using class helpers to mirror a lot of stuff commonly found in C# today. Class helpers seem like a hack to me, but they do let you introduce new methods into a class without having to either modify the class itself or subclass it. Unfortunately, they're not inheritable, which seems really strange to me (and probalby newcomers as well). Most contemporary languages also support case statements that take strings as discriminant variables, but not Delphi. This gives rise to maybe a dozen different ways people work-around this missing language feature that's only missing for little more than "religious" reasons. Initialization of arrays, lists, and objects in Delphi is something that isn't often consistently applied within and across projects, so make some clear recommendations. (Good Idea for a Coding Style document.) In particular, if you have a list of Enums, there's a common practice of prefixing them with a couple of letters so you know what class they belong to. This prevents simple use of RTTI to get a string representation of their name to display at run-time. So you need a second array of names and a lookup function for that purpose, although I've seen slightly more elegant ways of doing it that escape my mind at the moment. (To me this sort of variable-name-to-string-name mapping should be a fundamental language feature of every programming language ever written. In practice, few if any offer anything at all.) This stuff can be really confusing to people familiar with what other contemporary languages offer in their place. So I'd say give them a lot of "bread-and-butter" type code to write and do a lot of code reviews to suggest alternate ways of expressing things to expand their understanding of the language and RTL. If you're having them build UI-based apps, try to instill some good coding practices, like avoiding putting logic into Event handler methods. It's so easy to do, right? For example, the presence of an Add and Edit button on a form might lead to two almost identical methods, one of which might initialize stuff from one set of vars while the other will initialize with constants. And to allow a "Cancel" button, you probably need to track whether you're in Add or Edit mode. (Forms that use DB-aware controls can handle this directly.) One thing that I personally find quite ridiculous is the fact that most Delphi programmers don't treat interfacing with forms the same way as other objects. For reasons going back to Delphi 1, Delphi allows the programmer to reach directly into forms and fiddle with local variables and even fields from methods inside of the client forms (which are bona fide objects!) using with them, where nobody in their right mind would do the same thing with normal objects. I see very few Delphi apps that do this consistently and what I'd say is "correctly". Again, I think this gets into more of a "religious" debate, but if there's not a lot of consistency in how it's done in your projects, a new programmer is going to naturally ask "WHY?" and be rightfully confused over something that really has no excuse other than the programmers were lazy. Finally, keep on the lookout for use / abuse of global variables. As far as learning the language itself goes, I'd say any old Delphi book from D4 onwards can be used as a guide for most things, except the screenshots of the IDE. Newer versions have more methods in older (ie, "classic") objects than are found in older books, but what's there is pretty much all still 100% valid. I'd also be tempted to give them some exercises to learn all they can about TStringList. It has always been one of the objects in the RTL that I've used the most over time, and they seem to keep adding new functions to it with every Delphi release. This is what comes to mind in a few minutes. There's probably a lot more.
  25. David Schwartz

    Learning Delphi

    Of course, there's also Marco's latest book. And even stuff like old Delphi 3 and 4 books would work for covering the basics (just don't look at the screen-shots!). Are they new to programming? Or just new to Delphi?
×