Sunday, April 4, 2021

TMemoLines and FileStream

Saving TMemoLines to a Logfile

Some Discoveries About Linebreaks

Summary

A small sample program illustrating this is available for download or cloning at GitHub:

SavingMemoLines

While attempting to save multiple groups of lines from a TMemo component, each new group appended to a log file, I expected each new group of lines saved to begin on a new line. After all, TStrings has a TrailingLineBreak option that should ensure that each group of lines ends with an appropriate Linebreak.

Instead, each new group of lines immediately followed the preceding group without any sort of delimiter, Linebreak or otherwise.

A little examination revealed that the TrailingLinebreak was not being inserted. I set out to discover why. (I was not successful in my search!)

What I Tried to Do

Sometimes during development I use a TMemo to record events, either system generated or manually entered by me. Then, I'd like to save those memo lines to a logfile for future reference. I have a small component that does all of this and also has a display option that will display the current log file in a separate window.

What I wanted to do was:

  • Assume we have a TMemo object available
  • Assume we have a full path and file name available to receive the TMemoLines content.
  • Assume we have a procedure that appends the TMemoLines to the end of the log file.
  • TStrings has a TrailingLineBreak option that when True (the default) should ensure that each series of lines ends with a line break.
  • Because each series of lines should end with a line break, when multiple insertions of lines takes place, each new group of lines should begin at the left margin.

It looks something like this:

What Actually Happened

What actually happens is for multiple insertions of lines from the memo, the first line of insertions following the first begins immediately following the end of the first insertion, thus running the two lines together.

One way to avoid this is to ensure that lines in the memo always end with a trailing line break. This means that if you're entering lines manually, you must remember to insert the trailing line break yourself. This might be acceptable unless the lines you're saving are generated by software that doesn't include the trailing line break. Overall, this is not a completely reliable solution and one that seems kind of specious given the fact that TStrings includes a property especially for that purpose.

Why It Happened

I wasn't able to actually pinpoint the reason by examining the Delphi VCL source code. It seems to be related to the fact that the TStrings of the TMemo are really TMemoLines, descended from TStrings and in the process the Text property is overridden to a method that does not examine the TrailingLinebreak option.

If somebody can figure out what happened to the Text property, I'd be interested to hear the explanation.

My Workaround

My workaround is fairly simple.

  • Set up a TStringList as a work area
  • Use MyStringList.Assign(TMemo.Lines) to place the TMemolines in the work StringList.
  • Continue from there using the work StringList, which results in the Text property honoring the TrailingLineBreak option.

Conclusion

Still kind of aggravating, but the workaround is easy to accomplish and given the infrequent use of most log files doesn't result in a lot of unnecessary overhead.

And so, on to the next problem.

Thursday, January 7, 2021

A Delphi Windows Explorer Context Menu Extension to Create Dated Folders

ShellExtDatedFolder

A Delphi Windows Explorer Context Menu Extension to Create Dated Folders

Genesis

Talking with a colleague he expressed a need for a way to easily create new folders that are named using today's date. I responded that it should be a piece of cake. I was very wrong. While it is possible to create new folders in a Windows Shell with but a single entry or two in the System Registry, you can't control the date format easily because of differences in localization. You really need some code to achieve your ends.

That started me on the path to learning about Windows Context Menu Extensions, that are implemented using COM. That, of course, meant I had to learn something about COM. The whole process was lengthy, frustrating, but also rewarding. The results of my explorations can be found in the associated GitHub project (see link below.)

Obtaining the Source Code

The project source code can be downloaded as a .zip file or cloned as a Git project from ShellExtDatedFolder. (GitHub)

Objectives

Here's what I hoped to accomplish. Most of this is a learning path, something I really enjoy doing.

  1. Learn how to develop COM Servers (which is what MS Shell Extensions are)
  2. Learn the specific requirements for various Explorer plugins
  3. Study and dissect the Marco Cantu and Embarcadero examples
  4. Learn a little about the C++ implementation of this
  5. Develop a Windows Explorer Context Menu Extension that creates a new folder named using the current system date. This is a convenience and accuracy improvement for the user who needs to repeatedly create such folders as a means of organizing quantities of individual files by date.

First Steps

  1. Establish repo.
  2. Move existing Cantu and Embarcadro examples into the Repo.
  3. Restructure the project libraries to facilitate easy study and analysis.
  4. Perform code analysis. Code analysis consists of two major activities:
    • Read existing code and research all unknown APIs, structures and related Microsoft documentation.
    • Comment the existing code. Commenting code strengthens understanding and cements what was learned from research. By commenting, I mean, almost every line and in the case of complex operations, paragraphs of comments associated with the methods, classes or other parts of the code.
  5. Using what was learned during the code analysis, implement a Context Menu that creates a folder that includes a name with the current date.

The ShellExtDatedFolder Project Group

  • All projects in the project group are Delphi projects, produced with RAD Studio Enterprise 10.4.
  • All code is heavily commented. This enables the code to be used as a learning tool.
  • All code is contained in this project group. There is one project in the project group:

ZNewFolder Project

Downloading the project code will permit examination of the actual code along with the code comments. The code comments will clarify much of what may be ambiguous here.

Overview

The project builds a COM .dll that populates a Shell Context Menu Extension with a single additional menu item, Zilch Dated Folder. The menu item is only displayed when a right mouse click is made in a folder background or folder tree folder. When the user selects the item, a new folder is created within the selected folder named Zilch-yyyy-mm-dd where yyyy-mm-dd is replaced by the current system date.

Implementation

A standard, lightweight COM object was created using the appropriate Delphi Wizard. The TZNewFolderExt CoClass was created by the Wizard. Microsoft Shell Extensions for Context Menus require the implementation of two Interfaces.

IShellExtInit (Interface)

IShellExtInit has a single method, Initialize. Initialize is called by the Shell after the CoClass is instantiated. It passes three values that can be utilized by the CoClass during later methods:

pidlFolder
is a PItemIDList that contains information about the folder container that was right-clicked.
lpdobj
is a pointer to the interface IDataObject that contains a list of all objects in the folder that was right-clicked.
hkeyProgID
is a Registry Key Handle HKEY to the program.

Of these, only pidlFolder is of current interest. From it is extracted the full file path to the folder that will receive the newly created folder. This information is saved as a string in a CoClass property.

IContextMenu (Interface)

IContextMenu has three methods:

QueryContextMenu
is used to specify the desired menu item position and text.
GetCommandString
is optional. It is used to specify status bar help text and canonical names of verbs in Windows XP and earlier versions of Windows.
InvokeCommand
is invoked when the user actually chooses (clicks) the menu item it supports.

Registration

An additional class, TZNewFolderExtFactory, was also added that descends from TComObjectFactory. This permits the extension of the UpdateRegistry procedure to include three additional System Registry keys to support Context Menu Extensions.

Installation

In all cases, installation may require elevated authorization. Running as Administrator, either when using the cmd window or the installation file should be sufficient to achieve correct installation.

Installation can be manually achieved by copying the ZNewFolder.dll to an appropriate folder and then using RegSvr32.exe (a Microsoft program) to register the COM server. The registration process handles all necessary System Registry modifications. RegSvr32 can also be used to uninstall the Com server using the -u switch.

Also included is an installation file created using DeployMaster, an inexpensive installation package builder available from the DeployMaster website. Deploymaster also provides an uninstall function.

References

I found a number of useful references while working on this. Here are some I found useful.

Component Object Model (COM)
Microsoft Documentation
The Component Object Model documentation portal page. If you have time and are trying to learn about COM a quick read of this documentation (all pages) might be what you need. It's long, and assumes knowledge of C++, so it's not easy reading.
Developing COM-Based Applications Index
Embarcadero DocWiki
The COM development portal page. Provides information specific to Delphi. This is also long but since it has information devoted to Delphi it may prove easier to digest.
The Complete Idiot's Guide to Writing Shell Extensions - Index
Idiot's Guide
An index of all the articles in the Idiot's Guide Since the Idiot's Guide series has become quite large, here is an index of all the articles that gives a quick blurb about each one, so you can quickly find the articles that interest you.
Unfortunately, this suffers from two problems:
  • The content is somewhat dated, although it still contains much valuable information.
  • It is not Delphi-centric. It is written for C++ programmers so if your strictly interested in Delphi this is probably not your cup of tea.
Creating Shortcut Menu Handlers
Microsoft Documentation
A significant and lengthy discussion of Shortcut Menu Handlers. Difficult to understand without a lot of background understanding, but very important to do so.
Shortcut menu handlers, also known as context menu handlers or verb handlers, are a type of file type handler. Like all such handlers, they are in-process Component Object Model (COM) objects implemented as DLLs.
Writing a Windows Shell Extension
Marco Cantù
Delphi Product Manager Marco Cantù's blog post. Also links to a video. This focuses on file operations and COM rather than Menu Shortcuts. Still contains lots of general information useful to understand Shell development.

Important Caveats

Bitness

If you are running a 32-bit Shell, you must register and use the 32-bit version of the Com server. On a 64-bit system you must register and use the 64-bit version.

The DeployMaster installation program contains both versions and will correctly choose and install the correct version after examining the bitness of the installation system.

Target Systems

The existing code has only been tested on a 64-bit Windows 10 Pro system, version 2009, released October 2020 and code-named 20H2. The installation program will also allow installation on Windows 7 and windows 8 machines, both 32-bit and 64-bit, as well as a variety of Windows servers, but since the code has never been tested on those configurations it is not known if it will function correctly.

Licensing

The code is licensed under the MIT license, a copy of which is included in the project and also in the source code. Copyright ©2020 –2021 by Milan Vydareny.

Friday, December 4, 2020

Delphi Integer Types

Delphi Integer Types

Exploring Dephi Integer types

Overview

I counted 21 different Integer types generally available in Delphi. If you add to this the Pointer and Currency types (that seem like integers but are not the same as integer types) the number becomes 23. Additionally, there are at least three platforms available to the developer (Win-32, Win-64 and Linux-64) not to mention others making a somewhat daunting array of choices facing the developer. Actually, it's not that daunting and once you get matters sorted out in your head, it's a pretty well-organized and logical system. But, since I've been meaning to explore RTTI (Runtime Type Information) for some time, it was a good excuse to write some simple code to explore Integer Types using RTTI.

As a framework, Ordinals are one of Delphi's Simple Types. A Taxonomy of Delphi Types can be found at About Data Types (Delphi)

Simple types consist of Ordinal Types and Real Types. Ordinal Types include Integer Types but also Character, Boolean, Enumerated and Subrange Types. This blog is concerned with Integers with a few comments tossed in about Real (in the form of Currency) and Pointer Types.

Methodology

I started by writing a short Delphi Console application that displays some of the RTTI properties for Delphi's Integers. Next, I consolidated the output from running the application three times, once as a Windows-32 app, once as a Windows-64 app, and once as a Linux-64 app.

You can download the project from GitHub:

RTTIIntegers

I recommend downloading the project and then opening the Platform Types.pdf file in the Output folder. It will be much easier to read than the thumbnail that I'm showing here.

Platform Types

Discussion

Refer to the RAD Studio Docwiki Simple Types (Delphi).

According to the Embarcadero DocWiki, Integer types can be platform-dependent or platform-independent.

Platform-dependent integers

These are shown in the salmon-colored area of the spreadsheet. Their properties change from platform to platform as you might expect from their classification.

Platform-independent integers

These are shown in the blue-colored area. As you can see, their properties remain the same across all three of the platforms.

Non-conforming integers

These are shown in the green-colored area. Although they serve largely as integers, they are not considered integer types. They are included here as a matter of interest and completeness

Platform-independent Integers

The largest part of the chart is taken by the Platform-independent Integers. Under most circumstances, you will use Integers that are Platform-independent. Further, the most common choice is likely to be the Integer type. The fundamental types are a result of two characteristics:

  • The range or length of the Integer. Integers can be 1, 2, 4 or 8 bytes in length.
  • The signedness of the Integer. Signed Integers can represent both positive and negative numbers; Unsigned Integers are only capable of representing positive values.

In fact, there are only two fundamental types for each of the four range values; one for signed and one for unsigned.

ShortInt and Byte
represent signed and unsigned Integers of one byte in length respectively.
SmallInt and Word
are the signed and unsigned types of two bytes length.
Integer and Cardinal
are four bytes in length and designate signed and unsigned values respectively.
Int64 and UInt64
handle valaues eight bytes in length, signed and unsigned.

Each of these fundamental types has a number of aliases which may be used in place of the fundamental type. There are undoubtedly other aliases I haven't mentioned, and additionally any developer is free to define his own aliases local to his application, although doing so should be approached with caution so as to avoid unnecessary clutter and confusion.

Platform-dependent Integers

According to Marco Cantù’s Object Pascal Handbook,1 the Integer types

NativeInt and NativeUInt
adapt to the Pointer size. Hence, if you need an Integer that adapts to the Pointer characteristics, these are the choice to make. In other words, NativeInt and NativeUInt adapt to Pointers on the Native Platform.
LongInt and LongUint
are used when it is necessary to map to native platform APIs. Since mapping to API functions is not frequently encountered, you will probably not find yourself using these types a great deal.
Pointer and Currency
A Pointer is a Main Storage addressing mechanism, thus you would expect it to change size depending on platform. In fact, it does just that, being 32 bits on 32-bit platforms and 64 bits on 64-bit platforms. Also note that RTTI handles Pointers differently from Integers. The TypeKind value for Pointer is different from all other types illustrated.

The Currency type is actually a floating point number that behaves like a fixed point number. It always has four decimal places and is included to facilitate monetary calculations with no loss of precision. Currency has a TypeKind value of 4, indicating that it is a floating point number.

Conclusion

Under most circumstances, Integer will probably be your best choice for integer representation. If a different range or signedness is required, a type can be selected from the fundamental types with the required characteristics. Using the real names (not aliases) of the fundamental types will help readability. You will undoubtedly find uses of aliases in code you are called upon to maintain, but if you keep in mind that an alias is nothing more than a different name for something you're used to handling, there should be no problems with understanding.


  1. Cantù, Marco, Object Pascal Handbook—Berlin Edition, Embarcadero Technologies, July, 2015, (PDF) 

Thursday, July 5, 2018

DivvyStats: An Excercise in Architecting and Full Stack Development

My Objectives

Let me begin by saying that this project is a non-revenue project. I haven't been paid for the work. However, I felt that the benefits would justify the effort. Here's what I wanted to accomplish.
  • Architect an entire system, taking the design from conceptualization to the specification of technologies used for implementation.
  • Develop an entire system, e.g. become a "full stack" developer able to start at the beginning and work through to the actual deployment and launch.
  • Learn and develop some proficiency in a number of technologies that were to me, mostly mysterious acronyms before I began to learn and employ them.
I leave it to the reader to decide how well I succeeded in my effort. My personal feeling is that I did rather well given my somewhat inexperienced starting point. Clearly, I have more to learn; but learning more about what you don't know is part of the benefit of the exercise. Learning about things you don't know you don't know is an even greater epiphany!

Foundation Elements

Most systems are built around some fairly known and widely used technologies. This effort was no different. Here's my foundation technology list. All other technologies complimented these:
  • Delphi and its related technologies such as FireDAC, LiveBindings and FireMonkey.
  • Interbase RDBMS along with a couple of user functions (written in Delphi) used to make things a bit easier and more maintainable.)
  • Node.js, your savior or your nemesis depending on your viewpoint.
  • HTML5, an absolute must if you're doing modern web development.
  • CSS3, another "must" for modern web development.
  • JavaScript, because if you use Node it's pretty hard to use anything else. It's ubiquitous despite all of its nay-sayers.
Of course, there were lots of ancillary technologies used to support the effort, among them uniGUI (FMSoft's web application development framework), npm (Node Package Manager), Express (a Node based framework) and Bootstrap (that helps trying to style responsive websites.) For a more complete list and discussion, see the website (link below.)

Major Topic for Development

One of the decisions to make for a project of this kind is what I call "topic." Just what do you choose for development? The choice should be:
  • Complex enough to be challenging and interesting
  • Interesting and relevant to a reasonably sized audience
  • Solvable using available technologies and skill level of the project team (in this case me)
  • Related to something already of interest.
I finally settled on DivvyBikes, Chicago's bike sharing system. Not only does it satisfy my criteria, but it also offers:
  • A large repository of data free for the taking (for any legal purpose)
  • Serious possibilities for logistic complexity
  • The possibility of having an impact on urban life and transportation
  • Personal interest since I'm a Divvy user and to say I'm enthusiastic about it would be an understatement. (I recently received an award from Divvy for being their champion senior rider.)

The Results

The result of this effort consists of two major deliverables:
  • The DivvyStats website, that you can visit here: DivvyStats. Note that the link uses a nonstandard port.
  • The DivvyStats Dashboards, that you can visit here: DivvyStats Dashboards. Note that this link also uses a nonstandard port.
The website provides a lot of detail and imagery about the system, how it is architected, what technologies are employed and screen shots of expected outputs and displays. This includes things like the system overview diagram, a list of technologies with links for more information, and an E/R Diagram for the database used by the system.

The dashboards are a uniGUI application. The various displays available are explained in greater detail by the website page "Dashboards" accessible from the System Architecture dropdown of the main menu.

Effort

The entire effort occupied about 600 hours. Unfortunately, I did not track "learning curve" separately from application of technology. In some cases, much of the time spent was actually trying to learn a new technology in order to apply it effectively. I developed a half-dozen or more separate, small projects along the way to test various ideas before actually implementing something in the final product. Here are the major time investments, from most time-intensive to least:
  • The maintenance and administrative program, written using Delphi Tokyo, FireMonkey and LiveBindings. Also included were new experiences accessing Divvy APIs, using the Zip components to unzip files and then process the .csv files that came as a part of the zipped download. Along the way I developed a crude ORM and gained some practice in using some of the more common design patterns. Data cleansing was also a bit of a time-consuming effort as the Divvy data covers several years, and the files generated by Divvy early on differ in some ways from files in later years. No external files were used. The Divvy data was downloaded into RAM, unzipped, and the .csv files converted to my mini ORM entirely without writing to external media. This enabled me to achieve about a 700 row/second insertion rate on the Interbase database installed on another server locally, accessed via a gigabit LAN connection. Overall, lots of details occupied time with this program.
  • Next most time consuming was the uniGUI application, a totally new technology for me. While there is some documentation for uniGUI that is helpful, it still could use greater detail and more comprehensive content. Much of what there is to learn must be learned by reading the code and samples, and by reading and learning about the Sencha components that uniGUI deploys.
  • The actual website was next, but since the website is basically a static site, not much effort was required beyond what I had already acquired from other efforts. One of the more interesting pieces of the website was the sending of email used by the "Contact Me" page.
  • Data analysis and research followed website development time-wise. This involved determining data content, examining relationships and generally trying to clean up a few things that were ambiguous.
  • The monitor service (a Windows service program that periodically takes a snapshot of the entire Divvy system) was next. Mostly this was simply learning the most efficient way to update the database. You download 574 records and pop them into the database every X number of minutes. Some editing is required along with data conversion. (I store everything UTC, the only reliable way to store timestamps, but Divvy provides data in local format that of course suffers from all the deficiencies of daylight savings.)
  • A few other minor time chunks were taken up by architecting, database design and administration, deployment, graphics (using Adobe Illustrator) and version control (Atlassian and GitHub.)

Conclusion

Building a complete "system" is amazingly time-consuming and requires a broad base of technology familiarity. It has given me a much greater understanding of just what it takes to bring an idea from concept to Minimum Viable Product.

My suggestion is to go through the website and try the uniGUI application. I hope it provides you with a greater appreciation for the depth and variety of tasks it takes to realize a vision using IT resources.

Good luck with your own efforts!

Wednesday, April 11, 2018

MeWe: A Place for Delphi and Pascal Developers?

MeWe—The Next-Gen Social Network

First, some background on MeWe. It has actually been around for some time. Here's a link to a March, 2016 article that provides the genesis information:


MeWe Right Now

MeWe said it had 200,000 members when it launched out of Beta in 2016. When I installed the MeWe app on my Android phone, today April 11, 2018, the PlayStore showed 100,000 downloads and indicated the app was the number 2 trending social app.

So MeWe does have some traction. With all of the recent brouhaha over Facebook's cavalier treatment of its users' information and recent attempts to censor content, MeWe reports a 4,000% membership increase and a 5,000% week-to-week of invited contacts from existing members. All of this while at the same time being ad-free! Here's an article about all of this:


What Makes MeWe Different and Attractive to Some Users?

MeWe "resembles" Facebook in many ways, but it does have some very distinctive differences. Their biggest selling points seem to be:
  • Privacy. They don't collect and sell your data. Plus you have extensive control over who can see the data you post.
  • Cost. MeWe is free. Further, it has NO advertising of any kind. (To see how MeWe makes money, see the link below.)
  • Lack of Censorship. MeWe doesn't censor your content. You are expected to refrain from using the platform for illegal purposes and if reported the content will be deleted. However, they don't "check up" on you for this purpose. Also, Group owners may censor their own groups. In the large, however, MeWe is a very open and unrestrained platform for the exchange of information.
More information about MeWe is available at


A Delphi and Pascal Developers Group

All of this sounds pretty appealing to me and I got to wondering if MeWe would be a good place for a Delphi and Pascal Developers Group. Since the platform is free for anyone, the best way to test this idea seemed to be to simply start a group and see if people find it valuable. So that's what I've done.

Here's the link to join the MeWe Delphi and Pascal Developers Group:


If you don't already have a MeWe account you'll have to sign up, but it's quick and easy to do so, and you don't even have to give them your contact list! 

So you can take this as your official invitation to give MeWe a try and also see if a Delphi and Pascal Developers group is a good place for you to exchange information with other developers.

Right now, no approvals are required to join the group. You'll be automatically subscribed to the group as soon as you attempt to join. You will probably want to adjust your own settings for the group to suit your preferences, but that's pretty easy to do. The group does have some rules that I thought would be appropriate:
  • Posting must be in English. 
  • Vendor promotions welcomed if they are relevant to Delphi or Pascal. Spam will not be tolerated. This also means that excessively repeating the same posting is discouraged. 
  • Also welcomed are general IT postings of interest to the community but not necessarily directly positioned for Delphi or Pascal.
It goes without saying that personal attacks or other kinds of rude or anti-social behavior will also be dealt with severely.

Hope to see you giving this a try. I'm very interested in seeing how MeWe fares in the world of giants like Facebook and Twitter.

Sunday, March 25, 2018

Part 4 of 4—View—Using Delphi LiveBindings With TObject and TObjectList

Source Code

The source code in the form of a Delphi Project Group is available at:
The project was developed using Delphi RAD Studio Tokyo (10.2.2)

The README file contains additional useful information.

VIEW Details

This is the simplest part of the demo. This is in keeping with our objectives of making the VIEW as trivial a possible to facilitate testing. It also results in a very clean separation of concerns, effectively removing all data manipulations from the VIEW. The use of LiveBindings further isolates the VIEW from other parts of the application.

Create and Destroy

The FormCreate and FormDestroy events are used to create and free the MODEL. The pointer to the MODEL is saved as a property of the form.

Binding the UI Objects to the MODEL

LiveBindings is used to connect the data elements of the MODEL to the UI objects seen by the User. The Visual LiveBindings used to accomplish this appear in the following illustration. There are no other bindings or code fragments used to display the data. The entire VIEW is handled by LiveBindings without needing any other code. (A full-sized PDF of this diagram is also to be found in the Docs folder of the Github project or can be downloaded here.)

Delphi LiveBindings Demo Visual LiveBindings Screen Shot
Delphi LiveBindings Demo Visual LiveBindings
Several things should be observed in order to achieve this result:
  • Work from the VIEW, not the MODEL. In other words, be sure you have VULBO.pas, the Form, selected in the IDE. Then invoke "Bind Visually."
  • Be sure you have specified MDULBO (your Model Unit Name) in the VIEW (VULBO) Uses clause. This will enable the Visual Designer to access the necessary TBindSource instances.
  • The Items in the Branch TListView have been customized by setting the ItemAppearance property to DynamicAppearance and adding the Item.Text1, Item.Text2 and Item.Text3 fields. This discussion does not address how that is accomplished and I assume you already know how to do that or you can find a way to learn about it.
  • For bindings that bind to ObjectList<T> properties, be sure to connect the TListView Synch property with the Asterisk (*) item of the TListBindSourceAdapter. In the diagram above, these are named MDLBO.absEmployees and MDLBO.absBranches respectively. This will permit scrolling operations on the lists to take place correctly. The remaining item, MDLBO.absCorp is not a list; it is a single object. So it cannot be synched.

Conclusion

The discussions and techniques presented here should enable a developer to utilize LiveBindings effectively with both Objects and ObjectLists for real-world development. Of course, there are endless variations to the patterns shown, but regardless of the details of any particular implementation, the fundamental principles used to manage LiveBindings with Objects and ObjectLists remain; developers who understand the fundamentals will be able to expand and adapt the fundamentals to the particular needs of their application.

If you made it this far, thanks for reading and (I hope) understanding! Part 3 is especially daunting because of the number of things that must be managed and coordinated, all of which is compounded by the severely inadequate and confusing naming conventions (if they can be called conventions at all) of LiveBindings.

When all is said and done, it is pretty cool. Enjoy!


Using Delphi LiveBindings With TObject and TObjectList<T>—Part 3 of 4—Model Details<==Previous

Part 3 of 4—Model Details—Using Delphi LiveBindings With TObject and TObjectList

Source Code

The source code in the form of a Delphi Project Group is available at:
The project was developed using Delphi RAD Studio Tokyo (10.2.2)

The README file contains additional useful information.

MODEL Details

The MODEL functions as the main mechanism to manipulate the data for the application. In the case of an application that makes use of SQL or other persistent databases, we would expect to find FireDAC components managed by this class. In the present case, we are not using any kind of database that FireDAC supports, but never-the-less our MODEL will have data objects to manage. 

The Model Is a Data Module

The file MDULBO.pas specifies the MODEL part of the system. The model is a class named TMDLBO and descends from a TDataModule class. This permits the dropping of components on the MODEL design surface.

Instantiation

Normal Delphi instantiation has been disabled for this class. As a singleton class, a different mechanism was used that hides the customary Create constructor and provides instead a function named MDLBOGet that checks to see if the class has already been instantiated. If not yet instantiated, the class is instantiated and a pointer retained for future requests. In any case, the pointer for the instantiated class is returned to the caller.

The GDLBOGet routine invokes the Create constructor as needed. During the execution of the constructor, the database class TCorp is instantiated. As we described earlier, this results in a chain of constructor calls to the various classes of the database; the final result is an instantiation of the database data along with the instantiation of the MODEL (this class.)

Pay special attention to the TMDLBO.Create constructor. When the MODEL is instantiated, a call is made to this constructor. As a singleton, this occurs only once. Note that the constructor first calls the constructor for the database classes, to instantiate the entire database class structure prior to processing anything for the MODEL class. Following this it calls the inherited Create for itself that completes the instantiation of the MODEL.


It is important that this order be maintained because the OnCreate events of the TAdapterBindSource components must be able to access the already-instantiated database classes. Since the TAdapterBindSource components are created by the inherited Create call, instantiating the database data classes prior to the call to inherited Create satisfies this requirement.

LiveBinding Objects

LiveBinding Objects and ObjectLists behave similarly to the bindings for database objects. We note that the database components connect to a TAdapterBindSource just as do Objects and ObjectLists, but that is the extent of our involvement with database components in this discussion. We are only considering Objects and ObjectLists at this time, so any further discussion will not include references to database components (FireDAC, dbExpress, etc.)

In our current application we have three data sources to consider:
  • TCorp is a single object.
  • TBranch occurs multiple times and is managed by a TObjectList<TBranch>; property of TCorp.
  • TEmployee occurs multiple times and is managed by a TObjectList<TEmployee> property of its parent TBranch.
We customarily use a TDataGeneratorAdapter to create arbitrary data for use at design time. A corresponding TAdapterBindSource is needed for each generator to allow us to use LiveBindings to connect the data sources to other components, typically display components. With that in mind, the MDULBO.pas file that contains the MDLBO class appears as follows:

MDLBO Data Module from MDULBO.pas
MDLBO Data Module from MDULBO.pas
Values for the TCorp display components are generated by the dgCorp component. Values for the TBranch display components are generated by the dgBranches component. Finally, values for the TEmployee display components are generated by the dgEmployees component. Each of the data generator components has a corresponding TAdapterBindSource component that connects to its corresponding data generator. To force the TAdapterBindSource components to be a property of the data module, you must drop them on the form from the Tool Pallet and connect them each to the proper TDataGenerator component. If you use Visual Live Bindings to automatically create the TAdapterBindSource components, they will likely end up on the user interface Form—the VIEW. We want them as a part of the MODEL, not the VIEW, to achieve a separation of concerns. Hence, they should be manually placed to achieve our desired result.

In general, Objects and ObjectLists are connected to LiveBindings as illustrated in the following diagram (a full-sized PDF of this diagram is also to be found in the Docs folder of the Github project or can be downloaded here):

Live Bindings Using Objects diagram showing object relationships.
LiveBindings Using Objects
So far we have connected the data generators to their respective TAdapterBindSource components. Data generators are a special case, and the component contains both the generator and the TDataGeneratorAdapter needed to complete the configuration. This is the case illustrated by the rose colored combination in the diagram. Objects (goldenrod) and ObjectLists (blue) are different. The developer must provide both the data source (an Object or an ObjectList) and the developer must provide a TObjectBindSourceAdapter (goldenrod) or TListBindSourceAdapter (blue) as appropriate.

Only one of the paths to the TAdapterBindSource can be active at any given time; we begin by using the TDataGenerator to assist the design effort. But now, at run time, we are faced with replacing the design setup by the actual run-time data sources, either from a TObjectBindSourceAdapter or a TListBindSourceAdapter. This is where the Type Aliases defined in the DBLBO.pas file help. We can easily refer to CorpObjectBSWrapper, or BranchListBSWrapper, or EmployeeListBSWrapper and be confident of referencing the correct kind of components. The actual substitution of data sources (from TDataGenerator to TObject or TObjectList) takes place in the OnCreateAdapter event handler for each of the TAdapterBindSource components in the application.


In the following example, be sure to understand the different classes involved. Prior mention has been made of the draconian naming failures of LiveBindings. Refer to the previous diagram and the Type Aliases mentioned so that you understand what types are being instantiated. Examine the absCorpCreateAdapter event handler code in order to follow the following steps:


  1. You must first create a TObjectBindSourceAdapter for the TCorp instance. See the diagram above. This object behaves just like a component, but cannot be dropped on a form because of its generic nature. The code uses the Type Alias to create the object and save it as a property of the form, just as though it had been created at design time. Notice that during the create process, you must identify the specific object you're connecting, in this case the Corp instance of the TCorp class. The fact that it's a TCorp instance is inherent in the Type Alias, where TCorp is specified.
  2. After the CorpWrapper is created, it must be passed back to the TAdapterBindSource event so that creation of the TAdapterBIndSource can be completed.
These same two steps must be performed for each data source we want to connect to LiveBindings. There is an OnCreate event handler for each of the three data sources we are working with. Note that for Branches and Employees we specify the TObjectList properties as the data source rather than just a single object.

You should now also understand why instantiation of the actual data objects must precede the creation of the LiveBinding objects themselves; the data objects are referenced by the LiveBinding objects and hence must be available at the time the OnCreate event handler fires.

Handling Scrolling, Deleting and Inserting

We now consider how to handle child ListView displays when the parent list's current item changes. A change in the currently selected Branch occurs when the item focus changes. This can be caused by
  • Clicking on another list item or moving to another item using the TNavigator component.
  • Inserting a new Branch. The focus will change to the newly inserted Branch.
  • Deleting a Branch. The focus will change to one of the remaining Branches or, if the list has become empty (zero Branches) will not have any currently focused entry.
The TListBindSourceAdapter has an extensive set of event handlers that are used for handling these conditions. In our case, the particular component we are interested in is identified by the Type Alias BranchListBSWrapper. When we create this object, we must also provide method pointers to the object so that the needed events are handled. In addition, we must provide the actual event handler code that will be executed when the event fires. First, take a look at the event handler code. Note that the signature is a requirement of BranchListBSWrapper.

Two things must take place when the Branch TListView Item focus changes:

  1. We must identify the current TListView object, that contains the TObjectList<TEmployee> property for the currently selected Branch.
  2. We must update the Employee wrapper to point to the TObjectList<TEmployee> property identified in the first step.
Note that the special case of an empty Branch list results in setting the Employee wrapper to a nil value. (No items are displayed.)

The first step is satisfied by locating the TBranch object using an expression with two casts to "dig down" into the Branch TBindSourceAdapter that contains a pointer to the current entry.

The second step updates the EmployeeWrapper (saved as a property of the TMDLBO class) with a pointer to the current Employee TObjectList<Employee> extracted from the Branch obtained in the first step. The SetList method is used to do this. It changes the pointer to the data that will be displayed by the TListView.

Finally, we reposition the Employee List selection to the first item and set the EmployeeWrapper to Active to display the Employee List contents.

There are some additional considerations when a master/detail relationship between two ObjectLists exists. When the focus of the master changes, LiveBindings must be updated to reflect the new detail list that is related to the master. This is shown by the following CreateAdapter event for our Branches list. After the BranchListBSWrapper has been created, three Event Handlers are specified so that synchronization will take place when the Branch Item focus changes. In all other respects, this OnCreate Event is identical to the one previously described, except, of course, it refers to objects appropriate to Branches.



This completes the examination of the MODEL code. There are quite a few "moving parts" to the entire solution:

  • Data classes
  • Data generators
  • LiveBindings components
  • Objects to be displayed by LiveBindings
  • ObjectLists to be displayed by LiveBindings
  • Considerations about creation order
  • Creation of LiveBindings objects at run time and linking them into other LiveBindings components along with specifying needed event handlers
  • Handling TListView scrolling in cases of master/detail relationships
  • Event handler code to implement desired behaviors when TListView entries scroll, are created or deleted
The number of lines of code required to achieve this is not particularly large (except for the actual data objects in your application, which will vary depending on the complexity of your data.) It does require that you "get organized" and perhaps develop a check list of required steps to ensure that you include all the necessary code.

Next we consider the final (and much simpler) piece of the puzzle, the VIEW.

Using Delphi LiveBindings With TObject and TObjectList<T>—Part 2 of 4—Database Details<==Previous
Next==>Using Delphi LiveBindings With TObject and TObjectList<T>—Part 4 of 4—View

FireMonkey String Grid Responsive Columns

FireMonkey String Grid Responsive Columns Code to Cause Column Widths to Change When String Grids Are Resized Overview I have a FireMonke...