Altering ITEMINDEX of TComboBox does not trigger it's OnChange event - delphi

When programmatically changing the value of ItemIndex of a TComboBox component in Delphi, one would expect for the corresponding OnChange event to get triggered.
Afterall, the visible value of the ComboBox get's changed as a result. Strangely it does not. Same behavior in Delphi6, Delphi 2010 and Delphi XE7.
Is there any reason behind this behavior or it's just a pending bug?

From documentation:
Occurs when the user changes the text displayed in the edit region.
Write an OnChange event handler to take specific action immediately after the user edits the text in the edit region or selects an item from the list. The Text property gives the new value in the edit region.
Note: OnChange only occurs in response to user actions. Changing the Text property programmatically does not trigger an OnChange event.
Since there is no editing done, this means that programmatically changing the ItemIndex does not trigger the OnChange event.

As others have answered, it is as designed. You can, however, achieve the functionality you are missing by overriding the SetItemIndex() procedure as follows:
type
TComboBox = class(Vcl.StdCtrls.TComboBox)
procedure SetItemIndex(const Value: Integer); override;
end;
TForm3 = class(TForm)
...
implementation
procedure TComboBox.SetItemIndex(const Value: Integer);
begin
inherited;
if Assigned(OnSelect) then
OnSelect(self);
end;
As you see I activate the OnSelect event instead of OnChange, because OnSelect is the one fired when you select an item from the dropdown list. You can, if you like, just as well use the OnChange event instead.

That is designed behavior. OnChange event is triggered only by user actions and not programatically.
OnChange Event
Occurs when the user changes the text displayed in the edit region.
Write an OnChange event handler to take specific action immediately
after the user edits the text in the edit region or selects an item
from the list. The Text property gives the new value in the edit
region.
Note: OnChange only occurs in response to user actions.
Changing the Text property programmatically does not trigger an
OnChange event.

Related

TListbox - OnChangeEvent does not fire reliable on multiselect

I'm using XE7 and FMX.
My problem is that the OnChange-Event for TListbox works not correctly with multiselect (it does not fire everytime when a change occured).
Steps to reproduce:
Create a FMX-application and add a TListbox to his form.
Add items to this listbox.
Set 'MultiSelectStyle' of Listbox to 'default'.
Handle the OnChangeEvent (set a breakpoint or do something ...).
Now run the project and click on the first Item -> OnChange is executed.
But if first item is clicked again the OnChange will not be executed.
Can tell me somebody how I can fix this, so that everytime when a the selection changes I get a event.
From documentation FMX.ListBox.TCustomListBox.OnChange:
Occurs when the selection in the list box changes.
Write an OnChange event handler to take specific action immediately after a new item or items are selected in the list box.
Use the OnClick or OnItemClick event instead to monitor when the user reselects the same selection. And keep the OnChange handler for all new selections.

Combo box - typing selection then clicking out of focus - doesn't select the typed item

I'm having an issue with the combo box. I have an event handler for OnClick which refreshes data based on what item was selected. The problem is when this scenario occurs:
Drop-down the combo box to list the various options
Type on the keyboard to find a matching item
Combo box changes this selection and calls the OnClick event
My screen refreshes due to this selection / event
Click somewhere outside of the combo box to take the focus away from it
Combo box goes back to the previous selection, even though OnClick was already called
Even though Combo box changed back to prior selection, OnClick isn't called again
After this, Combo Box shows different value than what my data actually represents
So when you open a combo box, and type a few letters on the keyboard to find the item in the drop-down list, OnClick is fired which refreshes my screen. But when you click somewhere outside the combo box (thus taking the focus away from it), the combo box changes back to whatever value was previously selected, instead of what I had typed. And at the same time, the OnClick event isn't fired, so the combo box shows the incorrect value compared to what I had loaded on the screen.
How do I make the combo box stay on the selected item in this scenario of typing the item on the keyboard?
In my code, I deal with this using the OnCloseUp event. Well, in fact I'm using a sub-classed combo for my drop-down lists and they override both the Change and CloseUp methods:
procedure TMyDropDownList.Change;
begin
RespondToChange;
inherited;
end;
procedure TMyDropDownList.CloseUp;
begin
RespondToChange;
inherited;
end;
The RespondToChange method reacts to the new ItemIndex value. If it is expensive to react to every single change whilst the combo is dropped down, then you might consider omitting the call to RespondToChange from the Change method.
You could use OnExit to make the entry with the keyboard jive with the Index on the ComboBox; where VarS is assigned OnChange and is the answer you would like to keep:
procedure TForm1.ComboBox1Exit(Sender: TObject);
begin
{ Windows keyboard select bug, force use of selected }
ComboBox1.ItemIndex := ComboBox1.Items.IndexOf(VarS);
end;
I would call this a bug in the ComboBox design.

TEdit onclick select all?

How to select all text of a TEdit1 whenever user click on it or click to select some text of it
It can be quite dangerous to do anything beyond the default behaviour of the TEdit control. Your users know how the standard Windows controls behave and any deviation from this is likely to cause confusion.
By default the AutoSelect property is set to True.
Determines whether all the text in the edit control is automatically selected when the control gets focus.
Set AutoSelect to select all the text when the edit control gets focus. AutoSelect only applies to single-line edit controls.
Use AutoSelect when the user is more likely to replace the text in the edit control than to append to it.
When this property is True, the entire contents of the edit control are selected when it gets the focus by means of keyboard action. If the control gets the focus by a mouse click then the contents will not all be selected. In that case you simply press CTRL+A to select all. A double click will select the word underneath the mouse. This is all standard behaviour implemented by the underlying Windows control.
If you change the select in response to the OnClick event, as per the currently selected answer, then you will find that it is impossible to move the caret with a mouse click. This is exceedingly counter-intuitive behaviour.
This is a classic example of why you need to be very careful about changing the behaviour of a control from its default. It's simply very easy not to miss a particular use case when testing but when your users get hold of the program, they are sure to find all such wrinkles.
What you could safely do is to call SelectAll from OnDblClick. This would, I believe have no annoying side-effects.
Another option would be to call SelectAll when the focus switched to the edit control, but not every time you click in the control. This might feel a little odd to the user, but I personally think it would be reasonable to take this course of action. If you want to do this you need to handle the OnEnter event of your edit control:
procedure TForm1.Edit1Enter(Sender: TObject);
begin
PostMessage(Edit1.Handle, EM_SETSEL, 0, -1);
end;
How to select all text of a TEdit1 whenever user click on it
Select Edit1 in the VCL editor and double-click on the OnClick event:
procedure TForm13.Edit1Click(Sender: TObject);
begin
Edit1.SelectAll;
end;
You can also link this event to another control like a button.
Select the button, choose and click on the V arrow to select an event you want to link.
Now both Edit1.OnClick and Button1.OnClick link to the same event.
How to select some text of a TEdit1 whenever user click on it:
procedure TForm1.Edit1Click(Sender: TObject);
begin
Edit1.SelStart:= 1;
Edit1.SelLength:= 2;
end;
You must use OnMouseUp;
procedure cxMRUEdit1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button=mbLeft then cxMRUEdit1.SelectAll;
end;

Why doesn't OnUpdate trigger for invisible components [duplicate]

This question already has answers here:
How can I use an action to determine a control's visibility?
(3 answers)
Closed 8 years ago.
When I make a component invisible by setting the connected TAction to invisible, the onupdate event will not trigger anymore. To recreate, do the following.
Create a new VCL forms application
Drop a button, a checkbox and an actionlist on the form.
Create a new action, and connect the button to it.
Write the following code for the actions OnExecute and OnUpdate event:
procedure TForm1.Action1Execute(Sender: TObject);
begin
ShowMessage('Test');
end;
procedure TForm1.Action1Update(Sender: TObject);
begin
TAction(Sender).Enabled := not CheckBox1.Checked;
TAction(Sender).Visible := TAction(Sender).Enabled;
end;
Run the application. The button is visible, and works properly. Check the checkbox, and the button disappears. Uncheck the checkbox. The button doesn't appear. In fact, if you put a breakpoint in Action1Update, you'll never get to it. Why is this, and how do I fix it?
No need to fix this, it works as designed. Only visible controls need to update their state, so only actions whose linked controls are visible are updated. When you hide the button there's no more reason to update the action.
Have the OnUpdate only call a separate routine that does what is required. Then you can call that routine from other places. Action lists were designed for that.
I understand what you're trying to do, and it makes sense that you would want it to work that way. However, here's a workaround for the way it does work.
You can update other controls in an OnUpdate also. You're not limited to updating the control that receives the notification. So, in the action for the control that determines visibility, you can set the visibility of the other controls there. In your case, that's the checkbox:
Create a new action (Action2) and assign it to Checkbox1.
Then in the checkbox action's OnUpdate:
procedure TForm1.Action2Update(Sender: TObject);
begin
Button1.Visible := TAction(Sender).Checked;
end;
Be sure to assign an OnExecute to the checkbox as well. Something as simple as this is fine:
procedure TForm1.Action2Execute(Sender: TObject);
begin
TAction(Sender).Checked := not TAction(Sender).Checked;
end;
To me, this still makes logical sense. You'll be able to look in one spot to see all of the controls whose visibility relies on that checkbox being set.
You can override the InitiateAction method on the form. This will happen whenever the application goes idle, just as on OnUpdate event does for each action.

How do I make the TAB key close a TComboBox without losing the current position?

I have a TComboBox on a form. Its Style property is set to csDropDownList. If I open the dropdown and select an option with my mouse or keyboard and hit ENTER, the dropdown box closes and the ItemIndex property is changed before the KeyPress event handler is fired. If I hit TAB, the dropdown doesn't disappear until after the KeyPress event handler has fired and focus has moved off the control, and the ItemIndex doesn't get updated; it reverts back to whatever had been selected before I opened the list.
If I want TAB to update ItemIndex to whatever's currently selected in the dropdown list, how would I implement it?
Set the Form's KeyPreview property to True.
In the ComboBox OnKeyDown event:
procedure TForm1.ComboBox1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_TAB) then
begin
Key := VK_RETURN;
Perform(WM_NEXTDLGCTL,0,0);
end;
end;
This emulates the return key and then moves focus to the next control.
I believe this is the default behavior, and to change it you might need to subclass the control (or even a class helper), intercept the windows message for the keystroke, then if its a tab send a return to the control and handle the tab yourself.
You should try to trap TAB earlier in the KeyUp event or maybe even earlier in the KeyDown.
When you retrieve your index use this instead of the classical ComboBox->ItemIndex
ComboBox->Items->IndexOf(ComboBox->Text)

Resources