GrahamDLovell
asked on
Grabbing focus on a control
In a Delphi VCL form, when a TDateTimePicker field is open, and a user-defined cancel button is clicked, all that happens is that the onCloseUp action is processed. The Cancel.OnClick event is not fired.
This means that the user has to press the Cancel button twice to get it to fire.
I have a work-around, so that when the TDateTimePicker field is opened (KeyDown) I change the Cancel button caption to "Stop Processing", and when the onCloseUp action is fired I change it back. This means that the user doesn't realise that the Cancel button is being clicked twice.
A similar problem applies with the TComboBox as well.
Is there a more elegant solution to this problem / software feature.
This means that the user has to press the Cancel button twice to get it to fire.
I have a work-around, so that when the TDateTimePicker field is opened (KeyDown) I change the Cancel button caption to "Stop Processing", and when the onCloseUp action is fired I change it back. This means that the user doesn't realise that the Cancel button is being clicked twice.
A similar problem applies with the TComboBox as well.
Is there a more elegant solution to this problem / software feature.
ASKER
The "Cancel Button" is just a TButton, with an OnClick event. The following code is an example.
After the user open the TDateTimePicker to show the calendar, and then clicks on the Button1, nothing happens, but if the user clicks again, he/she gets the message.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls;
type
TForm1 = class(TForm)
DateTimePicker1: TDateTimePicker;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
MessageDlg('Yes it worked', mtInformation, [mbOK], 0);
end;
end.
After the user open the TDateTimePicker to show the calendar, and then clicks on the Button1, nothing happens, but if the user clicks again, he/she gets the message.
That's by Design. Not sure even if that could be achieved, unless through some hacks.
Not only your button, even the TDateTimePicker parts are not first-time clickable while you view the calender.
i.e. when selecting the day, click on the arrow of the TDateTiimePicker, let the calender show up, try to click on the year (at the TDateTimePicker itself and not at the calender). Clicking twice to get it selected, isn't it?
Alternatively, until someone comes with another idea or hack, you could use TMonthCalender beneath you TDateTimePicker (TDTP) and just hide the dropdown calender of the TDTP.
Which enables you, eventually, to do what you want, except a few minor differences between TDTP Calender. At the end you should evaluate which is better.
Unit:
Not only your button, even the TDateTimePicker parts are not first-time clickable while you view the calender.
i.e. when selecting the day, click on the arrow of the TDateTiimePicker, let the calender show up, try to click on the year (at the TDateTimePicker itself and not at the calender). Clicking twice to get it selected, isn't it?
Alternatively, until someone comes with another idea or hack, you could use TMonthCalender beneath you TDateTimePicker (TDTP) and just hide the dropdown calender of the TDTP.
Which enables you, eventually, to do what you want, except a few minor differences between TDTP Calender. At the end you should evaluate which is better.
Unit:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, Grids, Calendar;
type
TForm1 = class(TForm)
DateTimePicker1: TDateTimePicker;
Msg: TButton;
MonthCalendar1: TMonthCalendar;
DateTimePicker2: TDateTimePicker;
procedure MsgClick(Sender: TObject);
procedure FormClick(Sender: TObject);
procedure DateTimePicker1DropDown(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure MonthCalendar1Click(Sender: TObject);
procedure DateTimePicker1Change(Sender: TObject);
procedure MonthCalendar1Exit(Sender: TObject);
procedure DateTimePicker1Enter(Sender: TObject);
private
{ Private declarations }
flag: Boolean;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.MsgClick(Sender: TObject);
begin
MessageDlg('Yes it worked', mtInformation, [mbOK], 0);
end;
procedure TForm1.DateTimePicker1Change(Sender: TObject);
begin
MonthCalendar1.Date:= DateTimePicker1.Date;
end;
procedure TForm1.DateTimePicker1DropDown(Sender: TObject);
begin
flag:= not flag;
MonthCalendar1.Date:= DateTimePicker1.Date;
MonthCalendar1.Visible:= flag;
DateTimePicker1.Perform(WM_KEYDOWN, VK_F4, 0);
DateTimePicker1.Perform(WM_KEYUP, VK_F4, 0);
if flag then
begin
SendMessage(MonthCalendar1.Handle, WM_SETFOCUS, 0, 0);
end;
end;
procedure TForm1.DateTimePicker1Enter(Sender: TObject);
begin
MonthCalendar1.Visible:= False;
flag:= false;
end;
procedure TForm1.FormClick(Sender: TObject);
begin
if flag then
begin
MonthCalendar1.Visible:= False;
flag:= false;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
flag:= False;
end;
procedure TForm1.MonthCalendar1Click(Sender: TObject);
begin
DateTimePicker1.Date:= MonthCalendar1.Date;
MonthCalendar1.Visible:= False;
flag:= false;
end;
procedure TForm1.MonthCalendar1Exit(Sender: TObject);
begin
MonthCalendar1.Visible:= False;
flag:= false;
end;
end.
Form:object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 252
ClientWidth = 599
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
KeyPreview = True
OldCreateOrder = False
OnClick = FormClick
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object DateTimePicker1: TDateTimePicker
Left = 8
Top = 8
Width = 186
Height = 21
Date = 42100.622453240740000000
Time = 42100.622453240740000000
DoubleBuffered = True
ParentDoubleBuffered = False
TabOrder = 0
OnChange = DateTimePicker1Change
OnDropDown = DateTimePicker1DropDown
OnEnter = DateTimePicker1Enter
end
object Msg: TButton
Left = 376
Top = 164
Width = 75
Height = 25
Caption = 'Msg'
TabOrder = 1
OnClick = MsgClick
end
object MonthCalendar1: TMonthCalendar
Left = 8
Top = 29
Width = 225
Height = 160
Date = 42100.723511527780000000
TabOrder = 2
TabStop = True
Visible = False
OnClick = MonthCalendar1Click
OnExit = MonthCalendar1Exit
end
object DateTimePicker2: TDateTimePicker
Left = 304
Top = 8
Width = 186
Height = 21
Date = 42100.703296377320000000
Time = 42100.703296377320000000
TabOrder = 3
end
end
ASKER
Thanks JimyX for your input and prompt replies.
It is good to know that it is really is a design "feature".
Your fix for the DateTimePicker looks OK, but I am also stuck with a similar problem on the TComboBox, when it is opened. I use that feature multiple times on many forms.
Could another option be to use AppMessage to intervene in the process? Unfortunately, I don't understand enough about the processing of messages in order to investigate a solution along those lines.
It is good to know that it is really is a design "feature".
Your fix for the DateTimePicker looks OK, but I am also stuck with a similar problem on the TComboBox, when it is opened. I use that feature multiple times on many forms.
Could another option be to use AppMessage to intervene in the process? Unfortunately, I don't understand enough about the processing of messages in order to investigate a solution along those lines.
> It is good to know that it is really is a design "feature".
I know that might sound a little weird. But think of when you want to close the Combobox dropped-down List or TDTP Calender by being wary where you are going to click. Some find it useful just to click anywhere, even on a button w/o firing any even, to close that up. Some, apparently including you, do not like it.
I know that might sound a little weird. But think of when you want to close the Combobox dropped-down List or TDTP Calender by being wary where you are going to click. Some find it useful just to click anywhere, even on a button w/o firing any even, to close that up. Some, apparently including you, do not like it.
ASKER
I didn't intend to criticize the design, especially since I do not know all the reasons for the design.
My problem is one for the users, hopefully thousands, most of whom I will never meet, and some of whom will be novices.
If they click on a button and nothing happens, they will not understand, and some might even be cross with me!
I need a workaround, such as that suggested by you, or the one suggested by me, or something similar. I will take time to consider my options.
My problem is one for the users, hopefully thousands, most of whom I will never meet, and some of whom will be novices.
If they click on a button and nothing happens, they will not understand, and some might even be cross with me!
I need a workaround, such as that suggested by you, or the one suggested by me, or something similar. I will take time to consider my options.
I didn't consider that as criticism. And even if it was, of course you have a point, even if it was only you.
Preference is all it gets when programming.
> If they click on a button and nothing happens, they will not understand, and some might even be cross with me!
I do not know if you should worry about that, I doubt you can control all the odds clients always bring up. I wouldn't mind explaining that when asked. That's the default behavior of TDateTimePicker, TCobobox, and many more, all over Win OS, not only in your applications.
> I need a workaround, such as that suggested by you, or the one suggested by me
Workaround for TCombobox!!! This is going to be ugly man.
But anyway, similar to the TDateTimePicker, you can use TListbox as the dropdown list. But this is not going to be very smooth, because of the drop-down delay between dropping until hiding.
Start by either duplicating the combobox items and work normally from the combobox selection (itemindex), when using the entries or have an empty combobox which its text property shows the selected item from the listbox and work over the listbox whenever you need to refer to the itemindex. I prefer duplicating items as the drop-down list height is relative to the listbox height, because of the delay, makes the switching unnoticeable (almost).
You need to make sure that the height of the list box equals the combbox drop-down list height (DropDownCount property).
You can improve by loading the listbox items from the combbox at FormCreate and keep them synchronized as you go.
Unit:
Form:
Preference is all it gets when programming.
> If they click on a button and nothing happens, they will not understand, and some might even be cross with me!
I do not know if you should worry about that, I doubt you can control all the odds clients always bring up. I wouldn't mind explaining that when asked. That's the default behavior of TDateTimePicker, TCobobox, and many more, all over Win OS, not only in your applications.
> I need a workaround, such as that suggested by you, or the one suggested by me
Workaround for TCombobox!!! This is going to be ugly man.
But anyway, similar to the TDateTimePicker, you can use TListbox as the dropdown list. But this is not going to be very smooth, because of the drop-down delay between dropping until hiding.
Start by either duplicating the combobox items and work normally from the combobox selection (itemindex), when using the entries or have an empty combobox which its text property shows the selected item from the listbox and work over the listbox whenever you need to refer to the itemindex. I prefer duplicating items as the drop-down list height is relative to the listbox height, because of the delay, makes the switching unnoticeable (almost).
You need to make sure that the height of the list box equals the combbox drop-down list height (DropDownCount property).
You can improve by loading the listbox items from the combbox at FormCreate and keep them synchronized as you go.
Unit:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
ListBox1: TListBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure ListBox1Click(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);
procedure ComboBox1DropDown(Sender: TObject);
procedure ComboBox1Enter(Sender: TObject);
procedure FormClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ListBox1Exit(Sender: TObject);
private
{ Private declarations }
flag: Boolean;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
MessageDlg('Yes it worked', mtInformation, [mbOK], 0);
end;
procedure TForm1.ComboBox1Change(Sender: TObject);
begin
if ListBox1.Visible then
ListBox1.ItemIndex:= ComboBox1.ItemIndex;
end;
procedure TForm1.ComboBox1DropDown(Sender: TObject);
begin
flag:= not flag;
ListBox1.Visible:= flag;
ComboBox1.Perform(CB_SHOWDROPDOWN, 0, 0);
if flag then
begin
ListBox1.ItemIndex:= ComboBox1.ItemIndex;
SendMessage(ListBox1.Handle, WM_SETFOCUS, 0, 0);
end;
end;
procedure TForm1.ComboBox1Enter(Sender: TObject);
begin
ListBox1.Visible:= False;
flag:= false;
end;
procedure TForm1.FormClick(Sender: TObject);
begin
if flag then
begin
ListBox1.Visible:= False;
flag:= false;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
flag:= False;
end;
procedure TForm1.ListBox1Click(Sender: TObject);
begin
if ListBox1.ItemIndex < 0 then
Exit;
ComboBox1.ItemIndex:= ListBox1.ItemIndex;
ListBox1.Visible:= False;
flag:= false;
end;
procedure TForm1.ListBox1Exit(Sender: TObject);
begin
ListBox1.Visible:= False;
flag:= false;
end;
end.
Form:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 201
ClientWidth = 440
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnClick = FormClick
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object ComboBox1: TComboBox
Left = 97
Top = 8
Width = 193
Height = 21
ItemIndex = 0
TabOrder = 0
Text = 'A'
OnChange = ComboBox1Change
OnDropDown = ComboBox1DropDown
OnEnter = ComboBox1Enter
Items.Strings = (
'A'
'B'
'C'
'D'
'E'
'F'
'G'
'H'
'I'
'J')
end
object Button1: TButton
Left = 16
Top = 8
Width = 75
Height = 25
Caption = 'Msg'
TabOrder = 2
OnClick = Button1Click
end
object ListBox1: TListBox
Left = 97
Top = 30
Width = 193
Height = 107
ExtendedSelect = False
ItemHeight = 13
Items.Strings = (
'A'
'B'
'C'
'D'
'E'
'F'
'G'
'H'
'I'
'J')
TabOrder = 1
Visible = False
OnClick = ListBox1Click
OnExit = ListBox1Exit
end
end
ASKER
I have implemented a button that does nothing, which appears when a TComboBox (or DatePicker) is open, and just allows the user to "stop processing". It is not elegant, but I can live with it.
Obviously, I am worried about user frustrations, and in no case could I explain that "This is the way TDatePicker works!" These are end users I am addressing!
Obviously, I am worried about user frustrations, and in no case could I explain that "This is the way TDatePicker works!" These are end users I am addressing!
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Your example of the Microsoft TimeZone was very helpful. My solution was to ensure that when the ComboBox is dropped down the buttons are not clickable. The same with the DatePicker. Looks pretty standard after all!
Thanks again for your persistence in helping me understand my problem better and in pointing to an effective solution.
Thanks again for your persistence in helping me understand my problem better and in pointing to an effective solution.
Which Cancel Button? Would you elaborate a little more, please?