Monday, March 23, 2026

Self portraits




I was playing around with the Gemini app on my phone a few weeks ago, taking various pictures of me and passing them through AI filters with different styles. The surrealistic picture is quite interesting - the original was from somewhere in the 1990s or early 2000s.



This day in blog history:

Blog #Date TitleTags
12223/03/2008ClarificationProgramming, Literature
81723/03/2015Breaking radio silenceERP, DBA
93423/03/2016Composing a boleroHealth, MIDI
111523/03/2018Egged tales (more stories from 40 years ago)Personal, Habonim, 1978
120523/03/2019Wake up callHealth
130123/03/2020Days of CoronaDBA, Israel, Grandfather, John Le Carre, Covid-19
148323/03/2022My first year as a Londoner, part 3 - "The cellar"Personal, 1974
173523/03/2024Looking for his tribe - revisionsComputers, Youtube, Home recording

Sunday, March 22, 2026

The no-show bug

I discovered that in the great migration project, there are 354 units of which I have handled 268. Of these, maybe five are non-visual units and maybe one hundred are modal dialog forms. The rest - 168 so far - are forms defined as mdi child. The major problems that I have had to face so far were converting the database components1 and maintaining table indices2.

Until I came across unit Manage150. This is a regular mdi child form that displays data and there is very little to distinguish between it and many other units. I converted the few database components, fixed the code and update the main program's menu to invoke this unit when needed. I ran the program, clicked on the main menu ... and nothing happened. The form did not appear, despite the fact that it appeared in the list of active windows. I checked that everything had been defined correctly but still the unit would not appear. I call this the 'no-show' bug, and it happened with another form yesterday morning. What does CoPilot have to say about this?

🌟 Why the form was invisible even though it existed In an MDI application, the lifecycle of a child form is different from a normal form: ✔ OnCreate fires before the form has a real window handle ✔ OnCreate fires before the form has a size ✔ OnCreate fires before the form is inserted into the MDI client area If anything in OnCreate causes: - autosizing - layout recalculation - dataset activation that triggers a grid resize - DPI scaling - BiDi adjustments - or even just a control trying to read its parent’s size …then the form can end up with: - Width = 0 - Height = 0 - Left/Top = off‑screen - ClientRect = empty And an MDI child with zero size is visible to Windows, but not visible to you. That’s why it appeared in the Window menu but not on screen.

The first part of the solution is to define an OnShow event handler for the form and move most of the code from OnCreate to OnShow. This may be sufficient to cause the form to show, but in the case of Manage150, the form was still not displaying. What eventually causes the form to show are the following four lines in OnShow:

left:= 8; top:= 8; width:= 480; // according to the hard coded width height:= 320; // ditto

Once those lines have been added to OnShow then the form will display. And strangely, removing those four lines make no difference once the form has been displayed once; somehow something has been fixed internally to prevent the bug.

🌟 Why adding an empty OnShow fixed everything OnShow fires after the form has: - a real window handle - a real parent - a real size - a completed MDI layout pass Even an empty OnShow forces Delphi to perform the final layout cycle. That’s why: - adding OnShow made the form appear - moving the dataset opening back to OnCreate didn’t break it anymore - removing the dimension code didn’t break it either Once the form had a proper layout cycle, everything stabilized.

So if I have another case of a form not showing, I will know what to do.

Internal links
[1] 2087
[2] 2088



This day in blog history:

Blog #Date TitleTags
16322/03/2009Left/right hemisperes of the brainPsychology, The brain
69122/03/2014Research questionnaire / 4DBA
173422/03/2024Introducing the KoboKindle, Kobo
191322/03/2025My (compulsory) army service - part fiveIsrael, Army service

Friday, March 20, 2026

Gallup Happiness report 2025

As in previous years1, the Gallup happiness report has been released, and once again Israel is in the top ten, being ranked 8th. As I wrote then, One thing is clear: maybe we were happy, but not in 2023! I suspect that the 2024 report will show a sharp decline in Israel's happiness. Then I was referring to the proposed ruin of the judicial system. The events of Oct 7 were six months in the future

I seem to have missed reading about last year's index, which is just as well, as apparently Israel was ranked 21st, but we have returned to the top ten. I quote from the Globes article:

In Israel, according to Anat Panti, a happiness policy researcher in the Science, Technology and Society Program at Bar-Ilan University, it is mainly a sense of community that contributes to happiness. "The deep sources of Israeli resilience: family, community, faith, a sense of belonging and strong social ties - still manage to keep large parts of society well above the global average," said Panti. She says that one of the particularly striking figures this year is that, broken down by age group, Israelis under the age of 25 are ranked as the happiest age group within the Israeli population and in third place in the world. "The fact that Israel still ranks eighth in the world, and in particular that young Israelis rank third, is indicative of the strengths of the population in Israel relative to other countries," she said. However, in other indicators examined, there was a deterioration in Israeli responses. In the indicators about worry, sadness and anger - negative emotions experienced by the population - Israel jumped from 119th place before the war to 39th. In the Corruption Perceptions Index, it fell to 107th place, compared with 80th place in 2021.

"The rise in the indicators about concern and anger and the erosion of public trust make it clear that resilience is not immunity," added Panti. According to Panti, the survey on which the current ranking is based was conducted in July 2025, after the campaign against Iran but before the release of the hostages from Gaza. In her assessment, the level of negative feelings in the Israeli public would have decreased if the survey had been conducted after October 2025.

As I concluded a few years ago, if we're so happy here then life really must be tough elsewhere.

On other quotidian matters, yesterday was a strange day. There was an air raid alarm at about 11 am, so the dog and I went into the security room (my wife was in Bet Shemesh; that's another story). When we told that we could leave, I discovered that I couldn't open the door so I was stuck in the security room. Both our external doors are locked so it would be hard from someone outside to come and release me. Fortunately my daughter has a key to our house and as she was due to visit our side of the kibbutz shortly, she came and released me.

The weather was sunny in the morning, but by 4pm when I took the dog for a walk, there was a strong cold wind blowing. As we were coming home, sporadic drops of rain fell on us. Half an hour later there was a thunderstorm, complete with torrential rain and lightning. 12.9 mm fell. At about 23:30, we had an hour of continuous air raid warnings.

Internal links
[1] 1736



This day in blog history:

Blog #Date TitleTags
56220/03/2013Another Holy Grail achieved: sending email from a separate threadProgramming, Delphi, Email, Threads
68920/03/2014DBA mentoring period commencedDBA
138320/03/2021New song, E Dorian? B minor?Song writing, Music theory, Home recording
191120/03/2025The Hampstead murders (fiction)Police procedurals

Thursday, March 19, 2026

Two methods for creating pivot tables with Firebird 2.5

In the OP's management program, there are a couple of forms that display pivot tables as shown below - this is supposed to represent how many meetings each therapist had each month.

Therapist123456789101112
John41361211148 1
Judy121110987654321

I create this table in the following manner: first there would be a query that gets the raw data from the database (qRawData), then that data would be transferred to what was a clientdataset and is now a TFDMemTable that would be built with 13 columnns (one for the name, twelve for the months).

qRawData.sql statement: select therapists.name, extract (month from meetings.perfdate) as monthnum, count (meetings.id) as meet from therapists inner join meetings on meetings.therapist = therapists.id where meetings.activity = :p1 and meetings.perfdate between :p2 and :p3 group by 1, 2 order by 1, 2 [code] with qYearData do begin fielddefs.add ('therapist', ftWideString, 24, false); for m:= 1 to 12 do fielddefs.add (inttostr (m), ftInteger, 0, false); createdataset; for m:= 1 to 12 do fieldbyname (inttostr (m)).DisplayWidth:= 6; open end; with qRawData do begin close; parambyname ('p1').asinteger:= activity; parambyname ('p2').asdate:= encodedate (year, 1, 1); parambyname ('p3').asdate:= encodedate (year, 12, 31); open; while not eof do begin if fieldbyname ('name').asstring <> thername then with qYearData do begin if thername <> '' then Post; thername:= qRawData.fieldbyname ('name').asstring; append; fieldbyname ('therapist').asstring:= thername; end; amonth:= fieldbyname ('monthnum').asinteger mod 12; if amonth = 0 then amonth:= 12; qYearData.fieldbyname (inttostr (amonth)).asinteger:= fieldbyname ('meet').asinteger; next end; close end;

I wondered whether there was a better way of doing this. CoPilot suggested moving all the pivot code into the SQL query - this means that the code as a whole will be faster and there's no need for all the data transfer, although at the cost of a more complicated query, as follows

select therapists.name, sum (case when extract (month from m.perfdate) = 1 then 1 else 0 end) as m01, sum (case when extract (month from m.perfdate) = 2 then 1 else 0 end) as m02, sum (case when extract (month from m.perfdate) = 3 then 1 else 0 end) as m03, sum (case when extract (month from m.perfdate) = 4 then 1 else 0 end) as m04, sum (case when extract (month from m.perfdate) = 5 then 1 else 0 end) as m05, sum (case when extract (month from m.perfdate) = 6 then 1 else 0 end) as m06, sum (case when extract (month from m.perfdate) = 7 then 1 else 0 end) as m07, sum (case when extract (month from m.perfdate) = 8 then 1 else 0 end) as m08, sum (case when extract (month from m.perfdate) = 9 then 1 else 0 end) as m09, sum (case when extract (month from m.perfdate) = 10 then 1 else 0 end) as m010, sum (case when extract (month from m.perfdate) = 11 then 1 else 0 end) as m011, sum (case when extract (month from m.perfdate) = 12 then 1 else 0 end) as m012 from therapists inner join meetings m on m.therapist = therapists.id where m.activity = :p1 and m.perfdate between :p2 and :p3 group by 1 order by 1

This is the sort of code that I write once and never look at again. As it happens, a few days ago I was also occupied with updating a form with a pivot table, so I'll look at that form again, if I can remember which it was. One small difference between the original code and the new code is when there were no meetings for a therapist in a given month; in this case, the original qRawData would not have a row for that therapist/month combination and so the appropriate cell in the pivot table would have no data. Now in the 'improved' query, a zero will be returned for this therapist/month combination. In order to prevent zeroes being displayed, I use the field's OnGetText method to check whether the value is zero; if so, the field's text will be the empty string, otherwise it will be the number of meetings.

I'm using Firebird 2.5 as the database management system; apparently Firebird 3 has a built-in pivot comand so maybe one day I won't have to write such shenanigans.



This day in blog history:

Blog #Date TitleTags
56119/03/2013Motorbikes (2)Motorbikes
93219/03/2016Purchasing sound equipmentMusical instruments
111419/03/2018Nothing much to write aboutVenice, Commissario Brunetti
130019/03/20201300 blogs and still countingMeta-blogging
148119/03/2022My first year as a Londoner, part 2 - "The Bayit"Personal, Habonim, 1975, 1974
191019/03/2025On the wrong foot (song)Song writing, Home recording

Wednesday, March 18, 2026

Analysing my lunch

The health app on my phone (from my health fund) records my daily steps; I noticed yesterday that I can also photograph meals and get points for doing so.

So today I photographed my lunch via the app; I could see that the app scanned the photo three or four times, and then to my surprise told me that on the plate there was 125 g quinoa, chicken, broccoli and sauce. I don't recall the weights of the other items except for the quinoa. Now I can't restore that dialog and I also can't see how I can get the photograph itself (maybe next time I'll photograph outside of the app and inside). But I can see how much of each macronutrient there was: 1.1 portions of vegetables (and I thought that there was less broccoli than usual), 0.3 portions of fruit (I wonder where that came from), 106 g carbohydrates, 56 g protein and 20 g fats. The quinoa is responsible for 97 g carbohydrate, 21 g protein and 9 g fat, whereas the chicken contributed 31 g protein and 3 g fat. The broccoli contributed 5 g carbohydrate and 2 g protein, whereas the sauce had 5 g carbohydrate, 2 g protein and 8 g fat. Althogether 845 calories, 36% of my daily requirements (that means 2,347 calories that seems a bit high). I'm impressed that the quinoa was identified properly and not mistaken for cous cous or the Israeli invention p'titim (literally 'flakes' as in snowflakes) which are also cous cous - semolina. They look quite similar from a distance.

Isn't AI wonderful? I'll try and photograph my meagre supper (a piece of 'white' pizza left over from yesterday). It will be interesting to see what the AI makes of my breakfast; I suspect that not all of the ingredients will be visible. 



This day in blog history:

Blog #Date TitleTags
56018/03/2013Pictures from a balcony (4)Personal
129918/03/2020Strange daysHealth, Israel, Personal, Covid-19, BCC

Monday, March 16, 2026

Continuing the migration, supplemental

Today I will write about something that I meant to include in the previous post but forgot: when does one need a TFDMemTable (hereinafter, TMT)? After all, one can define a TFDQuery, include an SQL query to retrieve data from the database, connect the query to a datasource and thence to a grid: the data will be displayed. Also, in a simple 'edit' form, there's no need for a TMT.

I have identified two cases when a TMT is required:

  1. If the grid that displays the data has an OnTitleClick event - this allows the user to sort the data.
  2. If the form allows the addition or deletion of data.
According to CoPilot, a TFDQuery doesn't have indexes whereas a TMT does. So when I migrate units with a grid, I first check to see whether the grid has an OnTitleClick event; if so, I know that I need to a TMT. Incidentally, there is a new flag that has to be set in the grid's options - OnTitleClick. This flag didn't exist in Delphi 7, and I came across it in one of the first units that I migrated. I was clicking on the title bar but the event did not fire ... because this new flag was not set.

If the grid does not have an OnTitleClick event, I check whether the form has a qGetOne query - this means that data has to be added - or a qDelete query. In the case of qGetOne, previously I used to iterate over the fields of the query and add them one by one to the clientdataset. A TMT has a new method, AppendRecord, that allows for the data to be added in one statement (never mind that beneath the hood, the data is still being added field by field). I have used this everywhere, apart from one or two special cases where AppendRecord seemed not to be appropriate.

The TMT comes 'naked' without fields. Here is my methodology for adding them (this may not be optimal). First I check that the query can be opened - it frequently happens that fields are defined automatically as ftString when they should be defined as ftWideString. So I fix all these before I even start with the TMT. When the query is defined properly, I open the 'fielddefs' property of the TMT and begin to add fields. The size of the fields is very important when the field in the query is a ftWideString; the default size is 20 but this should be changed to match the size of the query's field. When all the fields have been defined in the 'fielddefs' property, I then open the TMT's fields editor. This will be empty so I press on 'add all fields' and the fields that I added in the 'fielddefs' property now become persistent fields. I then iterate over the fields in the query, copying their displaynames to the TMT's fields, and finally I delete all the persistent fields of the query. I do this in order to highlight any use in the code of these persistent fields - it has happened that I received bizarre results when debugging and I discovered that these came from usage of the query's persistent fields. As the grid displays data coming from the TMT and not the query, progressing through the TMT gives different data in the persistent fields, whereas the query's persistent fields stay the same.

I wrote about changing column widths in a grid and saving them. I came across a very interesting problem with this: every form has a minimum width that is hard coded. I changed the column widths of one grid so much that the new width was less than the minimum width; as a result, there was a huge space on the left hand side of the grid before data appeared. How could I fix this? I made two additions: I converted the procedure that saves the column widths into a function that returns the total width with an extra 72 pixels for the scroll bar. I then wrote a procedure in the ancestor form that saves this new width. When the form is opened, this new width is compared to the minimum width, and if it is less, then the minimum width becomes this new width. Slightly messy but it works. Part of the problem is that the code for saving and restoring column widths is in a different unit from the ancestor form. Maybe I should move this code to the ancestor form - but not every form descends from this ancestor and maybe I would want to save column widths on such a form (unlikely).



This day in blog history:

Blog #Date TitleTags
11916/03/2008Back to bloggingOffice automation, Meta-blogging
12016/03/2008Chava AlbersteinChava Alberstein
46416/03/2012Rubber duck debuggingProgramming
148016/03/2022My first year as a Londoner, part 1 - being a studentPersonal, 1975, 1974
159316/03/2023I contain multitudesFood science, Non-fiction books

Sunday, March 15, 2026

Continuing the migration

Over the weekend I worked for 15 hours on the continuing migration1 of the OP's management program. I notice that it is often the case that several small units will be converted without any difficulty before one unit comes along that has me stumped and requires much more time to handle. Once I solve such problems, I try to document them so that I won't get caught by a similar problem again, but generally it takes me two instances of a problem before I internalise the solution.

So what have I learnt so far? The major problem is with indexes - every time a FTDMemTable is closed, its indexes get lost. So don't close the table! In order to save having to write the same code over and over again, I added a short procedure to the program's library unit:

Procedure LoadData (aq: TFDQuery; mt: TFDMemTable; idx: integer); begin aq.open; if mt.active then mt.close; mt.copydataset (aq, [coRestart, coAppend]); buildindices (mt); mt.indexname:= 'idx' + inttostr (idx * 2); end;

Of course, once I had this procedure, I had to revise all the units that I had already finished in order to use this code. 

I had a major problem with table indexes again yesterday when I worked on a unit that had a clientdataset (cds) that would be built within the unit, combining data from several queries. Obviously I wasn't initially capable of writing one query that would supply the necessary data so I had to run three queries and combine their results into one cds before displaying. The TFDMemTable works almost the same as a cds, so my original code didn't require much massaging. I had forgotten that I had already come across this problem; solved it and even documented2 it. This is what I meant when I wrote a few paragraphs ago 'generally it takes me two instances of a problem before I internalise the solution'.

The order of writing should be as follows (see manage135):

  1. Define fields, remembering to use ftWideString when necessary.
  2. Add indexes - this can almost be done completely by automatic replacing of code.
  3. Finalisation: createdataset, indexdefs.update then open.

Another problem that I have encountered is with calculated fields, when the calculated field is a string. The inbuilt Delphi CF builder dialog exposes the regular types of fields, including string, but widestring is not amongst the options. I came across this problem last Saturday and even documented it: I apparently solved it then by editing the DFM. I had forgotten this when the same problem occurred yesterday; this time it was solved by a more visible method - by defining the calculated field in the form's create method (see Manage67).

f:= TWideStringField.create (mtReceiptsList); f.fieldname:= 'CashType'; f.DisplayLabel:= 'סוג תשלום'; f.fieldkind:= fkCalculated; f.size:= 20; f.dataset:= mtReceiptsList;

I started off yesterday morning's session by running units that I had already converted, especially those that I converted at the beginning - here and there were minor problems that needed fixing. I have also fixed a few bugs that were in the original program and improved the occasional construct. For example, in many units, the query component that actually retrieves the data from the database would have at the conclusion of its sql statement the clause 'where 1 = 0'. This allows the parameters and fields to be defined without retrieving any data. In the unit's code, I create a stringlist that holds the same sql query but finishes with 'where 1 = 1' - this is so that any dynamic query code can be added without having to worry about whether a 'where' clause has already been added. In the code, I would add this stringlist data to the query line by line; it occurred to me at some stage that it would be much quicker to assign the stringlist's text to the query's text, i.e. aquery.sql.text:= stringlist.text. 

Years ago I wrote some code in this program that would save column widths within a grid (each field within a grid will open at its maximum width, but frequently the data doesn't require such a wide field so the user can change the column widths), but I had never succeeded in restoring the widths, so I had abandoned this. After a quick consultation with CoPilot - who serves as my 'language lawyer' - I changed slightly both the saving and restoring code, but more importantly moved the restoration code to where it would have an effect. This should be after the call to LoadData (see above): at this stage the grid 'knows' what data it is displaying and has adjusted the column widths accordingly. Yesterday I added this functionality to a unit that didn't have it previously (in fact, only one unit had it) and discovered that it is very simple to add.

I reckon that I've completed over two thirds of the migration, including almost all of the units that receive user input and update the database. The remaining units are all reports, most of which are structually identical, so it's just a matter of plowing on and shortly the migration will be completed. Then will come the testing....

Internal links
[1] 2084
[2] 2082



This day in blog history:

Blog #Date TitleTags
46315/03/2012Sequencing "Lost" / 2Van der Graaf Generator, Peter Hammill, Home recording
68815/03/2014Boy, was I wrong - programming naivetyProgramming, Delphi, ClientDataSet
138115/03/2021A year of Covid-19Covid-19
159215/03/2023Goodbye, Dilbert (at least for the time being)Personal
173115/03/2024Jasmine Myra (2)Jasmine Myra

Saturday, March 07, 2026

Pollo Cacciatore

My last blog1 and the silence since may have given the impression that life wasn't going well with me, but that's not true. I have been keeping well and working many hours on migrating the OP's management ERP system to Delphi 12, and as the OP said to me yesterday, "I should be thankful for the war" as it has allowed me the time to work on this project. Naturally, my day job has been very slow: I barely worked on Sunday and Monday and although the pace picked up slightly during the week, I also had time to program. And unfortunately I also had time for migraines: one woke me in the middle of the night a few days ago (this is the first time that I have ever awoken with a migraine) and one took out a few hours of Thursday.

As with the Prolog interpreter project2, I don't want to fill this blog with daily updates of how the conversion process is going. I've identified some key points that I have to be aware of when migrating although almost every day has me coming across a new problem. I spent all day yesterday debugging the 'DoDockets' form that for the users is probably the most used form of all. There is still one problem to be solved, but I'm fairly sure that I know what the solution is. Once this is out of the way, I hope to get the rest of the input-receiving forms finished today and tomorrow, leaving all the report forms. Whilst there are many such forms, most are basically the same, so migrating these should be fast and problem free.

This is the blog entry that I intended to write a week ago before real life intervened. The title, pollo cacciatore, is Italian for "Hunter's chicken" and is basically a dish that mixes chicken with a multitude of vegetables. I came across it in the Kate Benedict book series; I thought that I remembered a discussion of this dish but couldn't find one when I looked for it. The cooking has got absolutely nothing to do with the stories themselves but add a certain amount of verisimilitude. Maybe I'll write about them at a later date.

Anyway, when the world was still young and innocent, i.e. last Friday, I made this dish as an experiment. The recipe that I used included browning the chicken pieces in a wide pot; this step has never seemed to have contributed anything to the final taste. I discovered that the pot wasn't big enough to include four chicken thighs and four drumsticks so I had to remove one of each. In order, the vegetables cooked were onion, carrots, celery, mushrooms, bell pepper and parsley. After cooking them for about 15 minutes along with shallots, I added a glass of red wine (this is, after all, an Italian recipe) and then a can of crushed tomatoes (ditto). This stayed on the gas ring for another hour. The result was quite tasty but the chicken did not fall off the bone as it normally does in my cooking.

I considered how the dish could be improved, both in terms of cooking time and the number of portions. It didn't take long for the answer to come: instead of cooking in a pot on the gas, cook in the usual oven tray that can hold 16-18 portions. So yesterday evening, I didn't bother with browning the chicken; I made the vegetable sauce in the same way as the week before, albeit with larger quantities. When the sauce was bubbling away merrily, I placed the chicken pieces in the tray, then poured the sauce on top (with an extra glass of water, just to be sure that there is enough liquid), covered the tray with baking paper then aluminium foil, then into the oven for 2½ hours at 175°C.

I thought that the chicken came out very well, but it didn't seem to absorb much taste from the vegetables and tomato source. The general appraisal was that the results didn't justify all the extra work, so next time it's back to chicken pieces with (uncooked) onions, shallots and mushrooms, along with fig sauce and onion soup. As this food is supposed to feed my wife and I during the coming week, there was plenty left over, and it may be that the taste will improve the longer the chicken is in the sauce.

Internal links
[1] 2085
[2] 2063



This day in blog history:

Blog #Date TitleTags
34107/03/2011Post mortem on the Marketing examMBA, Marketing
55307/03/2013Alvin Lee, RIPObituary
129607/03/2020Thesis dilemmaDBA
147807/03/2022Rifts and drifts (song)Song writing

Sunday, March 01, 2026

The war stopped being remote and impersonal today

At about 2pm today, with no prior warning*, the air raid siren went off. My wife and I were in our security room within a minute; I had just shut the door when there was a terrific boom and a shock wave that seemed to shake the entire building.

Shortly after, we were informed that a ballistic missile had landed in Bet Shemesh and killed 9 people: a direct hit on a synagogue with an air raid shelter below. Nothing can withstand a missile coming from the upper atmosphere with a 500 kg warhead. 

Even though that missile landed 4-5 kilometres away from where we are, we felt it as if it were next door. Unsurprisingly, many houses in the vicinity of where the missile fell have been damaged from the shock waves. 

Bet Shemesh can hardly be considered a strategic target, although it is quite possible that the missile was aimed at an air force base maybe 10 km from where the missile fell, but because of poor guidance (or poor intelligence) the missile missed its target.

Until now, this operation and the 12 day war that preceeded it had seemed remote, impersonal and detached from our day to day existance. It's even been a little fun, having days off from work and social encounters that don't happen too often. But today has changed all that: it has brought the war almost to my doorstep.

* Normally a warning is sent out about ten minutes in advance that a missile launch has been detected, but I don't recall receiving such a warning before this event. Often there's a warning with no siren afterwards because the missile's trajectory has been computed more accurately and it's more clear when the missile is not going to land.

Internal links
[1]  1950

Bringing the Management ERP program to life

Yesterday was a very strange and frustrating day. Inbetween the many times that I spent in our security room, I continued to work on migrating the management ERP program to Delphi 12/Unicode/Win11. I continued in the same vein as Friday, migrating the forms needed to create a minimal version of the program that actually does something. 

By about lunchtime, I had a clean compilation with maybe 30 different units (I haven't counted them) but when I came to run the program, it didn't progress after the splash screen, displaying the error 'Class FMTBCD not known'. I recognised this name as it is one of three units (the others being SqlExpr and Provider) that dbExpress adds to units that use that kind of database component. I hadn't been diligent in deleting these units from the source files, so first I had to do a global search then removed them from about 15 different units. 

After recompiling, I still had this error, so the next step was to look for persistent field variables defined as TFMTBcdField (or similar). There were a small number of such variables, but replacing them was problematic as that definition came from the DFM files. So I added some more replacement rules to the DFM migrator to handle this case. I should point out that sometimes adding what seems to be a simple change to the migrator is actually quite complicated. For example, I had some such fields that had to be redefined as TFloatField, but as the fields in the original DFMs had properties such as 'precision' and 'size' that TFloatField does not have, these lines have to be excluded from the transfer - not so easy. These changes are required because Delphi 12 Community Edition deliberately excludes certain types.

Even after changing the type of these variables, I was still getting the error message. Eventually I tracked it down to one specific query in the data module, an 'invisible' query that has several fields defined as BCDs. Changing these was very difficult but eventually I succeeded. But was that the end of my problems? No - first there was TMemoField then TSmallintField. Fortunately it was easy to overcome these problems by directly editing the appropriate DFM file.

Then when I thought that I had a clean compilation and no run time errors, Windows' Access Control had to stick his ugly head into the process, so I had to spend a frustrating hour or so getting a certificate, saving it into the certificate manager and thence into the Delphi compiler. Fortunately I won't have to do this again, although I may have to copy what is equivalent to a small batch file when I work on another program.

Finally at about 7 pm, after a very long and stressful day, the program compiled and actually ran. I could bring up the list of customers! This may not seem much, but to get this far required fixing and compiling many units and overcoming what can only be described as overhead. Of course, very little on this form works at the moment, but the remaining problems here are relatively easy to fix. 

And once that's done, then I'll have to start the process over again, finding important forms that need to be compiled and then the units that support those forms. The 'DoCustomers' form is used for three different tables and requires 18 different user forms in its 'uses' statement, so completing this form will be an important milestone. Then there is the 'DoDockets' form with about 20 different supporting forms, and the 'DoTables' form with maybe 30 forms. Once these are completed, then 'there only remains' about 40 report forms, but these should be relatively simple to migrate as they are all based on the same template.



This day in blog history:

Blog #Date TitleTags
6701/03/2007Donating bloodHealth, Donating blood, BCC
23601/03/2010More MBAMBA, Economics
33801/03/2011Pre-exam nervesMBA, Randy Newman, Marketing
81501/03/2015Last of the luddites (2)Computers

Saturday, February 28, 2026

How things can change in a minute

Whilst I was walking the dog early this morning, I was planning in my mind what today's blog would be about. After I came home and read my email, my plan was thrown out of the window (or more accurately, out of my mind) as I received an email (actually three, but they were all basically the same) informing me of something. But after breakfast and before I could brush my teeth, I heard a familiar sound. The dog heard this slightly before me because she entered our bedroom - also our security room - before I was totally conscious of what the sound was, as it was competing with a song on the radio and some noise from my phone.

At about 8:15 am, alarms were sounded throughout Israel, to inform us that the long-awaited war1with Iran had begun. The dog was already in place, I closed the door to the security room then closed the secure windows; my wife turned on the TV so that we could hear what was going on. I also turned off the alarm that had been sent to my phone.

So we're back to the situation that we were in eight months' ago2. As I wrote then, At the moment, life is like a hybrid of the early Covid days and the days following October 7: everywhere is quiet, people are mainly at home and only essential services are open (e.g. the supermarkets are open but the train station and post office are not). Of course, today is Shabbat, so most services are closed anyway. There was supposed to be a chamber concert taking place in the kibbutz in another few hours, but I assume that this will be cancelled. And indeed it is: checking the electronic kibbutz noticeboard, I see that a message to this effect was posted ten minutes ago.


What I was going to write about after reading my email is this: Jasmine Myra has recorded a new album that will be released in a few months' time (15 May 2026)! Over the past 12 months, there has been silence from her camp and I seriously wondered whether she was continuing with music. Letters to her website and to her record company's site were not replied.

Quoting from BandCamp, where the album is available for pre-order: Saxophonist and composer Jasmine Myra presents nine beautiful and powerfully grounded compositions that express her ruminations on life, growth, and progression, powered by the artist’s vision of duality. “It’s those bittersweet moments which are heart-breaking but so important. Looking forward and trying to make sense of life,” she says. “Pain is unavoidable, and you’ll have hardship no matter what, but you don’t grow or learn about yourself or the world around you without it. The duality is the growth and coming out the other side. I had the concept from the start.”

Jasmine Myra’s verdant musical vision and talent for instrumental storytelling came to life over five days, with her long-standing ensemble gathering in one room at The Nave studios in Leeds with the addition of a string section – all recorded live.

Myra had crossed paths with Ancient Infinity Orchestra bandleader Ozzy Moysey before she moved from Leeds to London, often attending and playing at the same jam sessions. This made him the perfect choice to conduct the 13-piece band, freeing her up to bring maximum tenderness and elegiac tones to the alto sax lines she’d written. Her own playing sits deliberately within each track, never flying above. Instead, it wraps gently around precision melodies she wrote for strings, piano, flute, guitar, vibraphone, and harp which themselves furl and unfurl gorgeously around tenor sax, double bass, drums, and percussion. Melodies that sparkle like sunlight on water.

The one track that is available for streaming, "Where light settles", is not particularly impressive on first listen, but that's not too surprising. Most of her music is quiet and reflective, responding well to repeated listens. I assume that the album will be available for digital download at some stage, meaning that I will be able to hear it before I receive the physical cd.

Today's original topic will wait till tomorrow (it's not topical so delaying it won't blunt its impact) and what might have been tomorrow's blog will wait until ... whenever.

Internal links
[1] 2080
[2] 1950



This day in blog history:

Blog #Date TitleTags
23528/02/2010Still working even when feeling lousyProgramming, Organisation behaviour, Blood pressure
45728/02/2012Sequencing "Lost"MIDI, Van der Graaf Generator, Peter Hammill, Reason
55228/02/2013Sansa clip+ mp3 playerMP3
172628/02/2024The Dublin Murder Squad, continuedSong writing, Police procedurals
190628/02/2025Emergency room bluesHealth

Friday, February 27, 2026

DFM migrator

Another part of the migration process from Delphi 7/Windows XP/ANSI to Delphi12CE/Windows11/Unicode is the conversion of the definition files (DFM) that each form has; such files contain definitions of the visual elements of a form. Most of the visual components remain as they were, but any database components, primarily queries, have to be converted. I had done a certain amount manually, but this is painstaking work, added to which certain extra definitions have to be changed.

So on Saturday morning I started work on a DFM migrator. At first, this required a simple substitution of TFDQuery for TSQLQuery, but then I discovered more definitions that needed to be changed. Once I had completed (as I thought) the code for TSQLQuery, I thought that I was done, but then I came across a form with the 'trinity' as I call it: a TSqlDataSource connected to a TDataSetProvider connected to a TClientDataSet. There are two major problems handling the trinity: firstly, the components can appear in any order, and secondly some of the required definitions (the SQL statement and the parameters) are in the TSqlDataSource whereas any field definitions (the titles which appear when a field is displayed) are in the TClientDataSet!

How to solve this? I had no idea of where to start, so I turned to my trusty CoPilot who suggested a two pass solution over the DFM file, saving certain data (where?) during the first pass then using the saved data to output the remaining data. This is how some if not most assemblers work as certain data, such as jumps, can only be calculated after the entire file has been read. CP suggested some convoluted data structures in which would be saved the data. By this time (Saturday lunch), I was developing a migraine, not because of the work but because of the changing weather, so I had a sandwich and went to lie down for a few hours.

But I couldn't turn my mind off, and shortly I came up with a much simpler solution to the one that CP suggested: I could build a symbol table in which I would store the component's name, its type and the line in the DFM where its definition starts. This way I wouldn't have to save the text of any component which was already saved in the file.

So when I felt a bit better, I started work on implementing this symbol table. One problem that I had was determining when a component's definition had finished; after all, none of these lines are being sent to the output file. This was easy for the TDataSetProvider as it has only four lines, one of which is the name of the TSqlDataSource, but the other two were more complicated. Eventually I solved this by noting that the penultimate line of both definitions begins with 'Top', so the migrator could 'eat the lines' until the 'Top' line, then read one more line (the 'end' that completes each definition).

Then after all the lines had been read, I could traverse the symbol table, looking for entries with the TClientDataSet type then building from the data the definitions for a TFDQuery component. I'm not 100% sure that I did this properly as there seemed to be all kinds of problems, and I was getting very tired whilst fixing them.

Last night, I revised the DFM migrator, specifically the code that handles TSQLQueries; one file that had ten queries prior to conversion had only one after conversion, so obviously there was some work that needed to be done there! I simplified certain aspects of this, especially the handling of parameters, and after very careful debugging, the TSQLQuery conversion code worked perfectly. Encouraged by this, I will revise the 'trinity conversion': starting anew with a fresh brain will no doubt improve the faulty code that I wrote when over-tired. The problem here appears to be not with my code but with the final 'end' that is written to the DFM before my code can output the built query, so that's an easy fix.

Today I finally fixed the 'trinity' code and I also added code to handle TSimpleDataSets - these should have been easy to fix but the required code was more tricky than I expected - the order of the different sections has to be changed. Now that the migrator is working properly, I probably converted about 30 forms today. Even so, there are some forms that I have to convert manually, as their DFM seems not to be in the correct format, and my migrator would only mangle them more, so much so that the compiler won't be able to read them.

Aside from the DFM migration, the actual program code needs massaging here and there. Last week I wrote1 about indexes; in the end, my original code that called a routine to build the indexes stayed the same, where the actual routine cannibalised the code that I had written for FireDAC. I checked this on the one completed form (Manage38) that I have so far that actually displays data. 

Another construction that requires rewriting - although not very much - involves ad hoc clientdatasets. I build these when data from several queries has to be combined. Thinking about it now, I could use the UNION operator in order to build one query from two unrelated tables, but this is more complicated (in one sense) than saving the results of two or three separate queries into a cliendataset. Original code would be like this

with qShowMailingList do
begin fielddefs.add (field1, ftString, 32, false); fielddefs.add (field2, ftString, 48, false); fielddefs.add (field3, ftString, 24, false); fielddefs.add (field4, ftInteger, 0, false); fielddefs.add (field5, ftString, 8, false); fielddefs.add (field6, ftInteger, 0, false); createdataset; open; for i:= 0 to 3 do begin j:= i * 2; addindex ('idx' + inttostr (j), fieldlist.strings[i], [], '', '', 0); addindex ('idx' + inttostr (j+1), fieldlist.strings[i], [ixDescending], '', '', 0); end; alist:= tstringlist.create; getindexnames (alist); alist.free; close; open; end;

The new code uses the TFDMemTable component instead of the ClientDataSet and looks like this

with qShowMailingList do begin fielddefs.add (field1, ftWideString, 32, false); fielddefs.add (field2, ftWideString, 48, false); fielddefs.add (field3, ftWideString, 24, false); fielddefs.add (field4, ftInteger, 0, false); fielddefs.add (field5, ftString, 8, false); fielddefs.add (field6, ftInteger, 0, false); createdataset; open; for i:= 0 to 3 do begin j:= i * 2; indexdefs.add ('idx' + inttostr (j), fieldlist.strings[i], []); indexdefs.add ('idx' + inttostr (j), fieldlist.strings[i], [ixDescending]); end; close; open; end;

Apart from the change in component, the code is almost the same, the differences being

  1. ftString is replaced by ftWideString, to ensure Unicode
  2. The index definitions are subtlely different and now simpler
  3. The 'getindexnames' hack is no longer required

Internal links
[1] 2079



This day in blog history:

Blog #Date TitleTags
33727/02/2011Michael PalinTV series, Prague, Poland, Michael Palin
45627/02/2012More spooksTV series, MI5
92827/02/2016Chicken breasts in tomato sauceCooking
101327/02/2017What are the benefits of ERP enhancement?ERP, DBA
120327/02/2019Pneumonia againHealth
147627/02/2022More musiciansObituary, King Crimson
158727/02/2023Bone conduction headphones/mp3 playerMP3, Headphones
172527/02/2024The Dublin murder squadKindle, Cormoran Strike, Pedal board, Police procedurals

Thursday, February 26, 2026

Microwave + grill

Our microwave oven plate was not revolving, so I thought that it was time to replace it with a more modern microwave oven. The tendency these days is to have a microwave and a grill combined in one device and so this is what we bought last week ago. The grilling function intrigued me: the oven comes with a grilling rack that of course is made of metal, and I wondered how such a rack could work in a microwave oven, where metal is a strict no-no.

One can use the grill function on its own, which is what I did yesterday when grilling fish for lunch. The grilling element is in the 'roof' of the oven and can just about be seen in the picture on the left. I covered the top of the grilling rack with aluminium foil, both to prevent leakage from the fish (and the plate at the bottom is meant to catch any leakage that escapes the foil) and to help cook the other side of the fish. 

This works reasonably well; when comparing it to cooking the fish in the oven, there doesn't seem to be much difference. I grilled for 14 minutes as opposed to 15 minutes in the oven, but I could just as easily cut a minute from the oven time with little change. I assume that the microwave oven uses less electricity than the oven, but otherwise things are the same. I suspect that because of the aluminium foil, the fish didn't have 'grill lines' on it but that's purely aesthetic and doesn't affect the taste.

I wonder about the possibility of 'microwaving' and grilling simultaneously; obviously this would be without the frame and foil, but in such a scenario, the food would be relatively far from the grilling element. Microwave ovens work by stimulating water molecules in the food to revolve at speed; this causes them to heat up and it is this heat that cooks the food. Such ovens 'cook from the inside out', which is why the outside of microwave cooked food looks uncooked, whereas grilling (and all other kinds of cooking) cook 'from the outside in', which is why the outside layers of something can be hot and the inside cool.

According to a thread on Reddit, After a lot of research and asking and carefully reading the manual, I found that in combi mode the maximum power it uses for microwave is 440 watt (in microwave mode it can reach 1200 watt). So I guess as it is relatively low power and the fact that the included metal rack has insulated legs it is safe to be used in low power. 

So what's the point? I am not convinced by this explanation.



This day in blog history:

Blog #Date TitleTags
45526/02/2012RefactoringProgramming, Delphi
81426/02/2015Assigning numerical valuesDBA
101226/02/2017More thoughts about my new doctoral topicERP, DBA
111326/02/2018DBA newsDBA
147426/02/2022Welcome to the urologistHealth
147526/02/2022The power of regret and a proto-songPsychology, Song writing, Non-fiction books

Monday, February 23, 2026

Trump

There is a huge discrepancy in the way that USA President Trump is regarded in his own country and how he is regarded in Israel.

For many Israelis, he is extremely popular1, albeit unpredictable, although that unpredictability may help his popularity. He is the man who managed to return the last of the Israeli hostages after the 7 October massacre; he also put an end to that war. He is the man who many Israelis believe is about to start another war with Iran and possibly even bring an end to the reign of the Ayatollas. That Iran will attack Israel should USA (and possibly Israel) attack Iran is seen as collateral damage.

Every time I see Trump on Israeli news, I ask my wife whether the average American in the street is even slightly interested in what Trump is doing in foreign lands. The partial answer was shown on tv the other night: his US popularity has declined to about 40% (may be lower) which is very low for an incumbent president.

As Professor Michael Covington (who I follow for computing reasons) writes, I think February 20, 2026, may be remembered as the end of the Trump era in American politics — it may have broken Trump's spell in a way that the election of Biden did not. That is the day the Supreme Court overturned Trump's capriciously imposed tariffs. He is trying to reinstate the tariffs by other legal mechanisms, but that's not the important part.

Trump single handedly cost many Israelis a great deal of money when he began a trade war last year: the S&P 500 share benchmark funds dropped in value overnight and have not regained the momentum that they had before. I had no small amount of money linked to the S&P 500 (retirement funds) and although I switched them to Israeli share funds (that had a phenomenal yield last year), there was some loss. Not everyone was adept as I (and there are probably more people adept than me in this field) and so some probably suffered.

Internal links
[1] 2018



This day in blog history:

Blog #Date TitleTags
15923/02/2009DietCooking, Food science, Diet, Jeff Duntemann
67823/02/2014Carole Bayer Sager - tooCarole Bayer Sager
67923/02/2014DCI Banks on televisionTV series, DCI Banks, Police procedurals
137823/02/2021My room (waiting for wonderland) - a little musical analysisVan der Graaf Generator, Music theory

Saturday, February 21, 2026

Migrating a query form to D12CE

I reckon that yesterday1's serendipitous discovery about the data controls saved about ten hours' work and who knows how much frustration. With not having to worry about data controls, I was able to dive straight in to the migration process. To use a metaphor, migrating an application is like hill climbing; one has to put in a great deal of work to cover maybe 80% of what needs to be done, but once that 80% is done, the rest is relatively easy.

After having obtained what I thought was a stable framework with nothing extra defined, I wanted to migrate a simple query form first: this form displays the number of times each unit has been accessed by which user during a given time period. This form uses the regular (for me) paradigm2 of a page control with two tabs, one in which the user chooses values for parameters and one in which the relevant data is displayed. This form also allows the query parameters to be saved for recall at a later date.

As such, even though I'm theoretically migrating one unit (Manage38, to be precise), I also have to migrate a unit that handles the saved parameters (Manage137) and add the various necessary queries to the data module. Adding the necessary queries is mechanical work, copying the name, sql statement and parameters from the old dbExpress based queries to the new FireDAC queries. There is one very important caveat with this: if the query is saving a Hebrew string (like the name of a person or an activity or whatever), the parameter datatype must be defined as ftWideString; this ensures that the string will be saved in Unicode Hebrew.

I don't think that Manage137 presented any challenges, but Manage38 certainly did. Apart from replacing queries, I also had to reconnect an OnCalcFields handler to one of the queries. The major problem with this unit was the handling of indexes for displaying the data, should the user wish to change by which field the data is sorted, and whether it is sorted in reverse. I had devoted some time to this issue previously3, but there the solution was very specific, using real field names, whereas I wanted something much more generic - the index handling routine will probably be used by 50-100 units! 

As I wrote then,[w]ith a ClientDataSet I could define indexes on the query when a form opens and these indexes would always be available; with FDMemTable, predefined indexes get deleted every time data is copied to the table (a real bug that has been fixed), so I have to use a more dynamic method. Eventually I will turn the 'ChangeIndex' procedure to something more general so it doesn't need to access the actual names of the fields, but for the time being, I am happy to have something halfway efficient working. So instead of defining the indexes at the beginning each unit's code and simply changing them when the user clicks on the grid's header, I needed to create an index after that header click.

This actually required two separate library calls, although one is a subset of the other. The subset call is required when the form is initially displayed so that it can be sorted by the same field as it was the last time the form was opened. The more interesting library call is in the OnTitleClick handler - 'n' is the column number, and 'rev' adds an extra definition should the data be required to be sorted in reverse order.

Procedure ChangeIndex (rev: boolean; column: integer; aquery: TFDQuery); var s, aname: string; idx: TFDIndex; begin s:= aquery.fields[column].FieldName; aname:= 'idx_' + s; idx:= aquery.Indexes.FindIndex (aname); if idx <> nil then aquery.indexes.delete (idx.Index); idx:= aquery.Indexes.Add; idx.name:= aname; idx.fields:= s; if rev then idx.descfields:= s else idx.descfields:= ''; idx.active:= true; aquery.IndexName:= aname; aquery.First; end;

Now that I have this unit defined, I'll start migrating other query forms. It's good that I read my old blogs, because yesterday I had forgotten the corollary to that statement predefined indexes get deleted every time data is copied to the table, namely [this is] a real bug that has been fixed. I'm using a newer version of Delphi and FireDAC, and CoPilot assures me that my original procedure ('BuildIndexes') will work now as it worked then. So I'll restore that code into the current unit and see whether it does indeed work. Assuming that it does, that will save another mechanical translation to watch out for.

What is going to be time consuming is that some 'father' forms have only one 'son' form but one in particular has 20-30 son forms! I'll leave that one for later.

Internal links
[1] 2078
[2] 1400
[3] 2066



This day in blog history:

Blog #Date TitleTags
2521/02/2006Virus ate my bagelsProgramming, Computer virus
81321/02/2015Musicology and harmonyMusic theory

Friday, February 20, 2026

No need for non-data aware components!

About 15 minutes after having written yesterday's blog1 that concluded that I can migrate the Manager ERP program by means of non-data aware components, as Delphi 12 CE is missing the data controls tab in the components palette, I found a very interesting blog that shows code that converts a standard component (a panel) into a data aware component.

I discussed this with CoPilot and we came to the conclusion that I could convert a string grid into what might be called DBGridLite: a read-only grid that does what I want it to do. This mythical component would be data aware, of course. Later on during the day, I thought that I would start by creating my own version of a data aware edit control, starting with TCustomEdit, then adding the Delphi Dabbler code to make the control data aware.

This was very easy, as basically I pasted the dabbler's code into my unit then updated the method names. When I came to compile my unit, the compiler told me that I was missing two units, DB and DBCtrls. So I added these, the component compiled and I added it to the standard component library. I then wanted to write a simple test harness, so I opened a new project, copied to it the FDConnection from the Manager program (this is the one that I am converting) then added a TDQuery and a TDataSource. I started looking at the component palette for my new component, and to my surprise saw the Data Controls tab! In this tab were all the regular components such as TDBEdit and TDBGrid. So I dropped a TDBEdit onto my form, connected it, then ran the program. The TDBEdit shows what it should so. In other words, the data controls do work with D12CE! A DBGrid also works. Here's what CoPilot had to say about this.

Noam, that is fascinating — and it tells us something very important about how Delphi 12 CE behaves internally. What you’ve just discovered isn’t a glitch, and it isn’t magic. It’s Delphi showing its hand a little. Let me unpack what’s really going on, because this is one of those “Delphi olklore” things that only becomes obvious when you stumble into it the way you just did. ⭐ Why the Data Controls suddenly appeared Delphi 12 CE does ship with the runtime database units: - Data.DB - Datasnap.DBClient - Datasnap.Provider - Vcl.DBCtrls - Vcl.DBGrids These are required for backwards compatibility so CE can compile older projects. What CE does not ship with is the design‑time packages: - dcldb.bpl - dcldbx.bpl - dclfiredac.bpl Those packages are what normally make the Data Controls tab appear. But here’s the twist: ✔ When you install a component package that references DB units Delphi must load the DB units to compile your package. And when it loads them… ✔ The IDE “wakes up” the Data Controls palette Because the units are now active in the design‑time environment. This is why: - Your custom DBEdit component required DB and DBCtrls - The IDE loaded those units - The IDE realized “oh, I have DB controls available” - The Data Controls tab appeared - And the controls work perfectly at runtime This is not a loophole — it’s a side effect of how the IDE registers component classes. ⭐ Why TDBEdit and TDBGrid work in CE Because CE does include the runtime VCL DB units. It just hides the design‑time packages unless something triggers them. Your custom component triggered them. Once the IDE loads: - Vcl.DBCtrls - Vcl.DBGrids it automatically registers: - TDBEdit - TDBMemo - TDBGrid - TDBNavigator - etc. These controls are registered by class registration, not by package. So if the units are loaded, the controls appear. ⭐ Is this “allowed”? Yes — because: - You are not bypassing licensing - You are not copying restricted packages - You are not installing Embarcadero’s design‑time DB packages - You are simply compiling your own component that uses DB units CE is designed to allow compiling DB‑aware applications. It only tries to prevent design‑time database tooling (like FDConnection editors, dataset designers, etc.). But the controls themselves are not blocked. ⭐ What this means for your project This is excellent news for you. It means: ✔ You can use TDBEdit, TDBGrid, TDBMemo, etc. in Delphi 12 CE ✔ You can design forms with DB controls ✔ You can compile and run DB‑aware applications ✔ You do NOT need Delphi 10.2 for UI work ✔ You do NOT need to rewrite father/son forms ✔ You do NOT need to build TDBGridLite unless you want to ✔ You do NOT need the 30‑day trial This discovery changes your entire migration plan for the better. ⭐ Why this happened only now Because: - You hadn’t installed any component that referenced DB units - CE keeps DB controls hidden until the DB units are loaded - Your custom DBEdit forced the IDE to load DB and DBCtrls - The IDE then auto‑registered all DB controls This is a known quirk, but almost nobody hits it unless they write custom DB components. You just unlocked the Data Controls tab by accident. ⭐ What you should do next Now that you know DB controls work: ✔ Continue converting dbExpress → FireDAC ✔ Keep using your custom DBEdit if you want ✔ Use TDBGrid and TDBEdit freely ✔ Don’t worry about CE limitations — they don’t affect your use case ✔ You can still build TDBGridLite later if you want a cleaner, modern grid But you no longer need to. Noam, this is genuinely great news. You’ve just saved yourself days of UI rewriting and avoided a lot of unnecessary complexity.

As I mentioned yesteday, in the Manager program there is a great deal of intertwining, one unit calling another, so adding one unit to the new project will actually require me to add two or three units. But once those have been added and debugged, they will be available for other units. In other words, the first steps are very large but once they have been achieved, the rest becomes much simpler.

Let's see how it goes.

Internal links
[1] 2077



This day in blog history:

Blog #Date TitleTags
137620/02/2021Cormoran Strike (3)Cormoran Strike
137720/02/2021Winter comfort foodCooking
172320/02/2024More guitar effectsGuitars, Pedal board

Thursday, February 19, 2026

Delphi 12 Community Edition

I've been working with this version of Delphi (D12CE) on the new computer1 for the past few weeks. The increased speed and large screen make this pleasant. During the week, I completed building the migration program for the 'Manager': 110 tables. Some of these have only a few rows and some have thousands. Some also have very large text fields: these had to be converted to blob fields.

After completion, I ran the program to convert all the tables - a few snags were encountered, mainly due to indexes that depended on values in other tables that had yet to be built. I removed these indexes via the database manager then ran the migrator again. This process took a few runs to iron out all the problems, but eventually the migrator could convert all the tables in one go. This took 18 minutes to do, meaning that had I tried to run this on my laptop, it would take about an hour (I'm not going to bother to attempt this).

Once the migration was out of the way, I could make a start on converting the actual program that accesses the database. This program has about 250 different units, meaning that conversion is going to take quite a time. The majority of units belong to one of two basic types, so once I develop a method for converting each type, the rest of the conversion should be mechanical, only requiring time.

The beginning, of course, is very difficult due to the amount of intertwining between different units. I want to get a very basic version of the program running first - this includes the main screen, the 'about' screen, the splash screen and the datamodule. This latter unit has 30-40 queries that have to be converted from dbExpress to FireDAC - again, this is mechanical and requires only time and patience. I have decided to change my approach: I'll convert a few queries and delete the rest that will be added only later in the conversion process, when needed.

Of course, even in this minimal configuration, there are many errors, basically due to missing units. Whilst trying to figure this out, I noticed that in the component palette, there are no data controls (eg dbedit, dbgrid). Are they missing due to configuration errors, I wondered? As always, CoPilot has the answer:

🧨 The Delphi 12 Community Edition is missing the Data Controls palette This is not your fault. This is not a bug. This is not something you broke. This is Delphi 12 Community Edition’s licensing restriction. ✔ Embarcadero removed the Data Controls palette from CE This includes: - TDBGrid - TDBEdit - TDBNavigator - TDBComboBox - TDBLookupComboBox - TDBMemo - - and all other data‑aware controls They are not included in Delphi 12 CE. This is intentional. It is part of Embarcadero’s starategy to push database developers toward the paid editions. ✔ That’s why you see no “Data Controls” tab It’s not hidden. It’s not disabled. It’s not a configuration issue. It’s simply not included. 🧩 Why you saw them in older versions Delphi XE, XE2, XE7, 10.2, 10.3 CE — all included data controls. Starting with Delphi 11 CE and continuing in Delphi 12 CE: ❌ Data Controls are removed ❌ Database components are restricted ❌ FireDAC is limited ✔ You can still compile database apps ❌ But you cannot design them visually This is why your palette looks incomplete.

CoPilot suggested three options that basically are not feasible and so I won't discuss them here. In case anyone is wondering, apparently it's not possible to copy the source code of the data aware components and recompile, although I do wonder whether this would work if I were to change a few names and insert them into a 'Private' components tab in the palette. 

Fortunately I thought of a fourth option that is definitely feasible whilst involving a certain amount of extra work: I could use non-data aware components! As it happens, there are some 'edit' forms that use non-data aware components, so converting this type of form should be fairly easy. Regarding the forms that show data grids, I suspect that converting them to non-data aware grids will actually simplify them, although at first it's going to be more difficult for me. I suspect that the complications that I described2 a month ago (when not using D12CE) are going to disappear in the new configuration.

As it happens, the one form that I did convert last night, the 'Help' form, already used non-data aware controls. At the moment, I'm not sure why I even touched this form as it's hardly essential and not one that lies in my 'minimal app' list/critical path. I suspect that this form may have given me a subconscious hint that later caused me to consider non-data aware components as the way out of the Delphi 12 CE hole.

This entire program, because of its size, uses a great deal of refactoring; for example, each of the two basic types of form have a predefined abstract ancestor form, where all the common properties are defined once. This means that new forms of these types already inherit a great deal of functionality and I only have to define the specific parts that are individual to each form without having to duplicate code. In the conversion process, these ancestor forms have to be handled first, or at least, very early on in the process.

Regarding D12CE itself, I have a licence for a year. I have read that it is very easy to renew the licence when the time comes; I hope that this is true. 

Internal links
[1] 2065
[2] 2066



This day in blog history:

Blog #Date TitleTags
15819/02/2009Increased productionProgramming, ERP
33619/02/2011One flew over the cuckoo's nestFilms, Literature, Tom Wolfe, Ken Kesey
67619/02/2014Carole Bayer Sager - oneCarole Bayer Sager
67719/02/2014This day in musicCarole Bayer Sager
81219/02/2015Changes in fortune, continuedERP, DBA
111219/02/2018Left joins in PriorityPriority tips
147219/02/2022Finishing "You hold me"Home recording
172219/02/2024Mike Procter, RIPObituary, Milton family