Recent

Author Topic: SpinEdit behaviour  (Read 12262 times)

Kaller

  • Jr. Member
  • **
  • Posts: 73
SpinEdit behaviour
« on: December 29, 2015, 09:39:37 am »
Hi
I have found something I don't understand with the SpinEdit control.
Entering digits into the control fires the change event and updates the Value field which is fine.
Entering text (eg pasting) is possible even though it is a numerical control, and I can live with that too.
In fact what happens is that a text character immediately stops the change events, they only fire when some integer is represented properly. That is reasonable too, the user can see those characters and
they can be given a validation message at some stage and go fix it. Or I can. WYSIWYG.

But the problem occurs when removing digits from the value showing.
The value changes accordingly, until there is only one digit left showing.
Removing this last digit does not change the value, it keeps the value of that digit even though that digit has been deleted. It seems to break the WYSIWYG principle.

The same thing happens when I enter a value like 456, and then delete it by say Control A control X, or by the mouse cut, or keyboard or whatever. The value 4 will be left in the Value field but there is no visual indication.  So the field appears to be empty when it is not.  I am not sure how to detect the empty display so my update routine will not think there is a digit to save.

The removal of digits always triggers a change event,  but the change event signifies only the visual removal. If I have a solitary 7 in the field say, and delete it by any means, the change event happens, but the Value field still stays at 7.

Lazarus 1.4.4 r49931 FPC 2.6.4 i386-win32-win32/win64




« Last Edit: December 29, 2015, 09:49:03 am by Kaller »

Bart

  • Hero Member
  • *****
  • Posts: 5564
    • Bart en Mariska's Webstek
Re: SpinEdit behaviour
« Reply #1 on: December 29, 2015, 05:57:24 pm »
Do you know how Delphi behaves I these scenario's?

In the case of having either no text in the control, or a non-valid text (e.g. "123b5"), what should TSpinEdit.Value be in that case?
Whenever you leave the control, a valid Value will be enforced.

If you want an input where user can only type/paste integer/floats, use a TMaskedit.
You can see in the maskedit unit how hard it is to get total control over the user's input.

Bart

Kaller

  • Jr. Member
  • **
  • Posts: 73
Re: SpinEdit behaviour
« Reply #2 on: January 01, 2016, 10:12:09 pm »
Quote
Do you know how Delphi behaves I these scenario's?
No, sorry.

Quote
You can see in the maskedit unit how hard it is to get total control over the user's input.

I am paying attention to details, and details do matter, but please do not think that I have any interest in micro-managing the input. This is not a question about control, my question is about feedback. I am worried that the WYSIWYG principle is not being respected and opening the system to errors.   It appears to me that the spinedit is trying to micromanage already, and I think it gets it wrong.

Quote
Whenever you leave the control, a valid Value will be enforced.

That depends on what you mean by validation. I mean if I enter 7 and you store 8 and tell me you stored 9, then that is not an issue of type validity. It is an issue of construct validity.

Quote
In the case of having either no text in the control, or a non-valid text (e.g. "123b5"), what should TSpinEdit.Value be in that case?

What happens at present is quite simple really, it just ignores invalid characters and any following characters or digits.  In the case you mentioned, "123b5", then that interprets down to 123, which is not wonderful, but is plainly a decision somebody has made who designed it, but I can live with that.  But if the data pasted was "b52bomber say, and the value was "42", then the internal value gets reset to "4" and the user and programmer are none the wiser.  The fact that that value "4" is type valid, is not a solution, it is the problem.  You are right though, when you leave the control the "4" is displayed in the control.

What should the control do? IMHO since Delphi behaviour needs to be respected (if indeed the control is faithful) then we need an invalid input flag or property, and/or some validation event that fires even before the change event, and provides the input as string. This would allow different ways to interpret a string containing digits, or outright rejection, or just detect the flag if the user leaves the control in an invalid state and institute alternative fallback behaviours.
« Last Edit: January 02, 2016, 01:15:05 am by Kaller »

Bart

  • Hero Member
  • *****
  • Posts: 5564
    • Bart en Mariska's Webstek
Re: SpinEdit behaviour
« Reply #3 on: January 01, 2016, 11:19:12 pm »
The spinedit has been designed s a "required" field, which imposes semantics on the control.

I have no idea what that means.

Possible solution to your problem:
Make the spinedit ReadOnly, user input is only possible via the spin controls, and text will always be a valid number.

Feel free to provide a patch offering a "NullValue" that will be used if the text is invalid.
There should then also be a mechanisme to not use the NullValue (and this should be the default situation, to maintain backwards compatibility).

Bart

Kaller

  • Jr. Member
  • **
  • Posts: 73
Re: SpinEdit behaviour
« Reply #4 on: January 02, 2016, 05:29:32 am »
The concept of missing values, nulls and so on is a basic issue for any computing, and making assumptions is what gets people into trouble. The basic idea of a "type" is a set of allowed values and a set of processing methods that are associated with that domain. Data validation simple means enforcing the domain of that type, and restricting processing to the allowed operations. So boolean for example has a set of values True and false, and a set of operations AND OR NOT etc. This type definition is the "meaning" of that dats, the "semantics" of the type.

So the concept of data outside of that type is not an issue for type validation, it is outside of the type. When a data value is not provided, like a missing value in databases, that is additional "semantics". Additional meaning. But there are an infinite range of possible meanings for missing values, such as "not known", "known but not available", "I reserve the right not to tell you",  "not available", "not relevant", "I will put it in later" and so on.

Now in data entry situations it may be a business requirement that a user provide a value for a field. That is what I mean by a required field. The opposite is an optional field, where programmer provides a blank field an the user does not have to enter a value. So a required field is one that cannot be left blank. It is not actually boolean logic either, this is the modal logic of what is "necessary" and "possible", and actually has nothing to do with type validation. It is a separate layer of meta-logic in fact.

Not understanding these issues clearly is what gets things into a muddle, and that is what has happened with the Spin Edit control. The control is set up by default to require a value from the user, it is a bloody mindedness of the control itself, and nothing to do with either type validation or business requirements. So that means you can only use the control in situations that require a value anyhow.

So I am not proposing that it ever provides a null as its value, I am only concerned with the strange behaviour that causes it to pull a digit from its previous value when the user enters invalid input.  I am mostly interested whether anybody else thinks that this particular fallback behaviour is reasonable or not, and what would be the best way to provide some additional functionality so that this particular behaviour can be intercepted.


Bart

  • Hero Member
  • *****
  • Posts: 5564
    • Bart en Mariska's Webstek
Re: SpinEdit behaviour
« Reply #5 on: January 02, 2016, 12:50:27 pm »
From the perspective of whomever came up with spinedit, I can imagine that it was meant to be so, that reading the value was only supposed to be done after the user was done editing.

Reverting to the last known valid value might indeed not be the best option.

With regard to your problem.
I do believe it is your responsibility to verify user input before processing (that is before letting that database handle it), simply because you have choosen an input method that does not do that trick for you. Note that SpinEdit is not a data-aware control, it was never designed to work for that.
And as stated before you can prevent the user from giving wrong input in the control.

B.t.w. AFAIK the user cannot type anything in the control but 0..9 and period or comma.
You may try to prevent pasting/cutting in OnKeyPress event: if Key = ^V or Key = ^X then set Key to #0. (Untested suggestion).

Q: when do you read the value of the spinedit? Inside it's OnChange or just when you want to post something to your dataset?
In the latter case, I can see your problem when the control automatically reverts it's value to the last known one.

Bart

Jurassic Pork

  • Hero Member
  • *****
  • Posts: 1266
Re: SpinEdit behaviour
« Reply #6 on: January 02, 2016, 12:58:00 pm »
and dont't forget : PEBCAK :  Problem Exists Between Chair And Keyboard   ;)
Jurassic computer : Sinclair ZX81 - Zilog Z80A à 3,25 MHz - RAM 1 Ko - ROM 8 Ko

Bart

  • Hero Member
  • *****
  • Posts: 5564
    • Bart en Mariska's Webstek
Re: SpinEdit behaviour
« Reply #7 on: January 02, 2016, 03:21:53 pm »
and dont't forget : PEBCAK :  Problem Exists Between Chair And Keyboard   ;)

Really?
TS has a valid point (although I do not agree with all of his arguments), the described behaviour is indeed a bit odd, and it may very well be improved, but that may then be incompatible with the Greek.

Unfortunately myy D7 comes with TSpinEdit, so I cannot test how that behaves.
One of these day when I have the time, I will try to fire up my old computer (from 2000) and see how D3 Pro behaves (that one did come with TSpinEdit).

I seem to remember that querying Value could even thow exceptions at you when the text was invalid.

Bart

Bart

  • Hero Member
  • *****
  • Posts: 5564
    • Bart en Mariska's Webstek
Re: SpinEdit behaviour
« Reply #8 on: January 02, 2016, 05:13:17 pm »
Workaround to keep valid text and valid value in TFloatspinEdit:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FloatSpinEdit1Change(Sender: TObject);
  2. var
  3.   S: String;
  4.   {%H-}D: Double;
  5.   Code, SelS: Integer;
  6.   Empty: Boolean;
  7. begin
  8.   //writeln('FloatSpinEdit1Change:');
  9.  
  10.   S := FloatSpinEdit1.Text;
  11.   S := StringReplace(S, DefaultFormatSettings.DecimalSeparator, '.', [rfreplaceAll]);
  12.   Val(S, D, Code);
  13.   //writeln('  Code = ',Code);
  14.   while (Code <> 0) and (S <> '') do
  15.   begin
  16.     if (Code > Length(S)) then Dec(Code);
  17.     if (Code > 0) then
  18.       System.Delete(S, Code, 1);
  19.     Val(S, D, Code);
  20.     //writeln('  Code = ',Code);
  21.   end;
  22.   S := StringReplace(S, '.', DefaultFormatSettings.DecimalSeparator, [rfreplaceAll]);
  23.   if (S = '') then
  24.   begin
  25.     S := FloatSpinEdit1.ValueToStr(FloatSpinEdit1.GetLimitedValue(0));
  26.     Empty := True;
  27.   end
  28.   else
  29.     Empty := False;
  30.  
  31.   if FloatSpinEdit1.Text <> S then
  32.   begin
  33.     //writeln('  Changing Text to: "',S,'"');
  34.     SelS := FloatSpinEdit1.SelStart;
  35.     FloatSpinEdit1.Text := S;
  36.     if not Empty then
  37.       FloatSpinEdit1.SelStart := SelS
  38.     else
  39.       FloatSpinEdit1.SelectAll;
  40.   end
  41.   //else writeln('  No change to Text necessary');
  42.  
  43.   //writeln(Format('OnChange: Value = %.2f, Text = %s',[FloatSpinEdit1.Value,FloatSpinEdit1.Text]));
  44. end;
  45.  

Bart

Kaller

  • Jr. Member
  • **
  • Posts: 73
Re: SpinEdit behaviour
« Reply #9 on: January 04, 2016, 12:07:30 am »
Thanks for that.  There are other ways to feed data to the control, right mouse and paste for one,not to mention virtual keyboards, and anyway they can be mapped by macro key assignments (Lazarus has a whole section devoted to key assignments), or speech to text, its a workaround at best.

On reflection (no pun intended) I think that the SpinEdit as it stands is a bit of an abortion, and long past its use-by date.  I am drawing toward the opinion that it would be easiest to simply modify the standard TEdit with more properties and events. This is a classic encapsulation situation after all, the integer ranged type is encapsulated by methods that handle validations and conversions and events of my own choosing.

The secondary problem is then what to do about the twiddly fiddly up down arrows. They are supposed to be a convenience but they are verging on a gimmick.  So maybe this is the better solution, a seperate data "scroller" that can be associated with an edit box, and one that is a bit more customisable. I found a set of components called Eye Candy that look promising, it has similar issues but it does have a separate scroller with a min max feature that looks very useful.  I might roll my own edit box and attach it to those thumbwheels.

What got me started on this nit picking journey was I wanted to make an "inline" edit box, where <escape> set it back to a default and <enter> immediately updated the value. It is for a settings section of an app where the user changes an occasional setting. There is no real need for a traditional "Form" paradigm which is a database relic really.  I was thinking the user could just change the value and press <enter> and they are done. Basically the same kind of metaphor that a spreadsheet uses.  So if they delete the value in the box ot put garbage in there then it would reject it and repopulate the control with the original value. All of this without losing the focus, why not.
« Last Edit: January 04, 2016, 06:01:48 am by Kaller »

Bart

  • Hero Member
  • *****
  • Posts: 5564
    • Bart en Mariska's Webstek
Re: SpinEdit behaviour
« Reply #10 on: January 04, 2016, 11:40:45 am »
From what I understad of your needs: use a TMaskEdit control with an appropriate mask.
Use '0' as the blank char (character after last semicolon in EditMask) and use EditText (not Text) to retrieve the value using StrToInt().
It will handle all unwanted paste/cut etc. for you (I've spent many hours on implementing it), and users will never be able to enter invalid text in the control.
That's the whole purpose of a TMaskEdit.
You could derive derive a control from it that has a Value property (see e.g. the TDateEdit in editbtn unit).
The Value can be calculated in it's TextChanged method at all times.
You can add handers for VK_UP/VK_Down keys to increment and decrement the value etc. etc.
See e.g my TTimeEdit control.

Another solution: if you are on Windows: use a TCustomEdit derived control and set NumbersOnly to True.

Even for a SpinEdit there is an alternative solution: do not use the Value property but use StrToIntDef(SpinEdit.Text, AValue, ADefault). Or, as stated before: set ReadOnly to True, which will it impossible to have invalid text in the control.

There are plenty possibilities, just choose one that fits your needs and your personal preference.

Bart

Bart

  • Hero Member
  • *****
  • Posts: 5564
    • Bart en Mariska's Webstek
Re: SpinEdit behaviour
« Reply #11 on: January 04, 2016, 01:31:47 pm »
I asked the other devels wether we should change this behaviour.
On D3 and D6 Value will be MinValue if Text is invalid.
On D7 GetValue will raise an EConvertError in this case (which we certainly do not want).

Bart

Kaller

  • Jr. Member
  • **
  • Posts: 73
Re: SpinEdit behaviour
« Reply #12 on: January 04, 2016, 11:33:41 pm »
If I use a fixed length format mask to enter variable length numbers  it spoils the aesthetic, and introduces a dead area into the data entry space,  and I am also not a fan of popups either and making an edit box readonly defeats the purpose of having it in the first place. I think I found a simple way to avoid the single digit value appearing:

Add an InitialValue (integer) and an IsEmpty (boolean) property.
The parameters are omitted
because I wrote this as a quick and dirty interceptor to test it.

Code: Pascal  [Select][+][-]
  1. procedure TSpinEdit.KeyDown;
  2. begin
  3. procedure TSpinEdit.Change;
  4.   IsEmpty := false;
  5.   if Text = '' then
  6.     IsEmpty := true;
  7. end;
  8.  
  9. procedure TSpinEdit.DoneEditing;
  10. begin
  11.   if IsEmpty then
  12.     Value := InitialValue;
  13. end;
  14.  

So now if I paste "B52Bomber" into the control, or erase the contents using the mouse say, and either press enter ( I changed the enter and escape behaviour successfully from the keybord)  or leave the control, it reverts to the initial value. :)
« Last Edit: January 05, 2016, 08:19:23 am by Kaller »

mse

  • Sr. Member
  • ****
  • Posts: 286
Re: SpinEdit behaviour
« Reply #13 on: January 05, 2016, 09:40:50 am »
What got me started on this nit picking journey was I wanted to make an "inline" edit box, where <escape> set it back to a default and <enter> immediately updated the value. It is for a settings section of an app where the user changes an occasional setting. There is no real need for a traditional "Form" paradigm which is a database relic really.  I was thinking the user could just change the value and press <enter> and they are done. Basically the same kind of metaphor that a spreadsheet uses.  So if they delete the value in the box ot put garbage in there then it would reject it and repopulate the control with the original value. All of this without losing the focus, why not.
That is approximately the working principle of the MSEgui TDataEdit descendants.
MSEgui has dedicated edit widgets for the base types (TStringEdit, TIntegerEdit, TRealEdit, TDateTimeEdit...) with a value property of the type.
The user can enter any text, validation happens by pressing Enter, by leaving the widget or some more events. Until successful validation the value property stays unchanged.
Pressing Esc restores the original value text.

Bart

  • Hero Member
  • *****
  • Posts: 5564
    • Bart en Mariska's Webstek
Re: SpinEdit behaviour
« Reply #14 on: January 05, 2016, 03:59:10 pm »
Code: Pascal  [Select][+][-]
  1. procedure TSpinEdit.KeyDown;
  2. begin
  3. procedure TSpinEdit.Change;
  4.   IsEmpty := false;
  5.   if Text = '' then
  6.     IsEmpty := true;
  7. end;
  8.  
  9. procedure TSpinEdit.DoneEditing;
  10. begin
  11.   if IsEmpty then
  12.     Value := InitialValue;
  13. end;
  14.  

That won't even compile.
Moreover, this might be behaviour you want right now, but should it be like that always?
And it won't work if i select all text then do right-click->cut/delete.
Where do you initialize InitialValue?

Bart

 

TinyPortal © 2005-2018