Link to home
Start Free TrialLog in
Avatar of GrahamDLovell
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.
Avatar of jimyX
jimyX

> TDateTimePicker field is open, and a user-defined cancel button is clicked...

Which Cancel Button? Would you elaborate a little more, please?
Avatar of GrahamDLovell

ASKER

The "Cancel Button" is just a TButton, with an OnClick event. The following code is an example.

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.

Open in new window


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:
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.

Open in new window

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

Open in new window

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".

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 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.
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:
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.

Open in new window


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

Open in new window

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!
ASKER CERTIFIED SOLUTION
Avatar of jimyX
jimyX

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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.