TMemo.Modifed/OnChange handling is inconsistent and wrong on Assignments via TMemo.Text := 'aString'
Original Reporter info from Mantis: jbthiel
-
Reporter name: JBThiel
Original Reporter info from Mantis: jbthiel
- Reporter name: JBThiel
Description:
Documentation says TMemo.Modified should be reset False on Assign, and only set True from user edits on the control.
However, sometimes it is wrongly going True, and sometimes not going False.
Behavior varies depending on the Current vs New values, and across different widgetsets.
Further, the faulty Modified=True setting is also triggering a call to OnChange event, which cascades the problem.
Doc for OnChange says Modified indicates User Edits, so when Modified=True from Assign your event handler can easily get confused.
This logic problem wreaks havoc with cross platform SQL coupling via manual controls, and possibly also auto DB controls (untested),
because the controls/event handlers can't reliably distinguish the Assigns coming from SQL backend, versus the GUI user edits.
DOC TMemo.Modified:
https://lazarus-ccr.sourceforge.io/docs/lcl/stdctrls/tcustomedit.modified.html
Reset whenever the changed text was stored (saved) by code.
DOC TMemo.OnChange:
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/StdCtrls_TMemo_OnChange.html
Write an OnChange event handler to take specific action whenever the text for the edit control may have changed. Use the Modified property to see if a change actually occurred. The Text property of the edit control will already be updated to reflect any changes. This event provides the first opportunity to respond to modifications that the user types into the edit control.
Steps to reproduce:
Tested several cases on Linux GTK2 and OSX, as follows.
Most testing was on Lazarus 1.4.4; also spot-checked on Laz 2.0.10 and briefly inspected the code, and most/all faults seem still present.
GTK2:
Consider the following 5 Assignment cases
(TMemo.Text := ...), Current to New value:
On GTK2, first four here DO NOT CHANGE Modified (they keep whatever is the current setting, either True/False), and DO NOT TRIGGER OnChange event
'' to ''
'' to 'aa'
'aa' to 'aa'
'aa' to ''
'aa' to 'bb' <<< This one sets Modified = True, and triggers OnChange event (which sees Modified=true)
The documented behavior for Modified is all of these should set Modified := False.
So all 5 cases are apparently wrong, but in two different ways.
(Note: perhaps one could say that '' to '', and 'aa' to 'aa', are not actually "changes", so should not reset Modified.
However that would be an artifact of the implementation, a pointless complication.
The behavior should NOT depend on the current value.
ie. myMemo.Text := 'aa' should NOT skip resetting Modified just because the value already happened to be 'aa'.)
For OnChange, the documentation seems incomplete, so I'm not sure what the correct behavior is.
It says "may have changed". Obviously '' to 'aa' is a change. Should OnChange be called, with Modified=False?
OSX:
I also tested on Lazarus 1.4.4, OSX 10.6.8 (which uses TCarbonMemo instead of GTK2).
Here, all five Assignment cases DO NOT CHANGE Modified, and DO NOT TRIGGER OnChange event.
So this is also apparently wrong, because is not resetting to Modified=False.
Additional information:
Inspecting into the GTK source code, I noticed that gtk_text_buffer_set_text is done in 2 steps.
First the existing text is deleted; then the new text is set.
Those deepest setters emit the actual delete/insert signals, which presumably also trigger OnChange events via callbacks.
Both the GTK lib and Lazarus wrapper functions have tests on conditions like (len > 0), (Current=New), which can skip the whole setting function, or the separate delete/insert steps.
It goes up the class hierarchy, through TCustomEdit, even to TControl. For example, TControl.SetText has an early check:
if GetText = Value then Exit;
which bypasses everything, including resetting Modified.
So there is quite a maze of conditions, and different handling of skipping/setting at multiple levels.
This is why we see various behaviors for the 5 cases, depending on the current/new values and widgetset.
It would be desirable to simplify and regularize all this logic; get clear conformance on when Modifed should be set/reset, and when OnChange event called; and of course it should work identically across all Lazarus widget sets.
As noted above, I tested on Linux GTK2 Lazarus 1.4.4 (mostly) and also 2.0.10-2; and OS X 10.6.8 Lazarus 1.4.4.
So there is a dependency on the extern widget or interface, Linux GTK2 vs OSX Carbon.
Not tested on Windows.
WORKAROUNDS for Linux GTK2 (and cross-platform compatibility) until this is fixed:
Explicitly control Modified as you need, and don't assume the Assignment will reset it.
Plus,
OPTION 1. Do Assignment (Text := ...) in two steps: first to '', then to the target value. This will stop Modified going True on GTK2, thus also not trigger OnChange.
OPTION 2. Reset Modified := False after the assignment.
However, if you have an OnChange event handler, it will still be called (for 'aa' to 'bb'), with Modified=True inside the handler.
Therefore you should also SET NIL the TMemo.OnChange event handler before doing the Assign, then set it back.
Note there are other open bugs possibly related; I haven't fully surveyed these, just mentioning for background. For example:
0032602: TEdit.OnChange event handler is calling twice for same text
0031820: TDBEdit.Modified property don't reset after DataSet.Post
0032630: [patch] Gtk2 TEdit/TMemo: fix text selection, make OnChange event compatible with LCL-Win32
DescriptionTEdit/TMemo controls from Gtk2 widgetset has number of issues making it imposible to write crossplatform code for text manipulation. See tests.
There seems to be a discrepancy at the interface to the external widget code, whether the extern routine affects Modifed itself or not (either directly or via some callback).
Then the Lazarus interface code needs different logic around that.
It would be desirable to get the Lazarus code, and extern widget functions/callbacks around Modifed and OnChange as consistent as possible, up the class hierarchy, as well as across platforms,
(ie. TCustomMemo and TCustomEdit more similar).
The full test suite/matrix to verify proper functioning looks something like:
Above 5 Assignment setters X check Modified goes False starting from both True/False baseline X check OnChange if it should be called? X all widgetsets X similar controls up the class hierarchy like TCustomEdit etc
Mantis conversion info:
- Mantis ID: 38009
- OS: Linux, OSX
- OS Build: OSX 10.6.8
- Platform: x86_64, x86, LCL 1.4.4, 2.0.10
- Version: 2.0.10
- Fixed in revision: r64420 (#d7de0495)