Combined date and time picker Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
rudolfninja
Earned some good credits
Earned some good credits
Posts: 107
Joined: Tue Aug 28, 2018 1:02 pm
Location: Belarus

Combined date and time picker

Post by rudolfninja »

Hi all,
I need DateTimePicker control, something like this one on the screenshot, but with one text field for time values and one combobox for choosing AM/PM value.
Image
I haven't find this control in wxWidgets as well as some custom solutions. So the only way to meet my needs is to write this control by myself. I understand, that I need to combine wxCalendarCtrl, wxTextCtrl and wxComboBox in one class, but I've got some questions here:
1) What class should I use as a base class of my control?
2) How to draw all the controls in one frame?
3) How to make it possible to collapse/expand my control?

I've got only assumptions regrading the answers, so I'd like to get answers from others.

Thanks.
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: Combined date and time picker

Post by PB »

1. I would suggest wxPanel.
2. What drawing? You will use standard controls which draw themselves.
3. I guess you need to switch between the control sets and hide/display the appropriate controls as needed while taking care of changing the best size for the panel.

I assume that you are aware that there are many countries that do not subscribe to am/pm notation and use 24 hrs format.
rudolfninja
Earned some good credits
Earned some good credits
Posts: 107
Joined: Tue Aug 28, 2018 1:02 pm
Location: Belarus

Re: Combined date and time picker

Post by rudolfninja »

I started to implement in based on wxPanel. It wasn't difficult to place needed controls on it.
How can I now make this panel collapse and expand? I need the panel starts in collapsed state so only string with chosen date and time is shown (see calendar.png in attachments). And when I click on calendar icon (on the right of text control) it should expand.
calendar.png
calendar.png (4.24 KiB) Viewed 4620 times
Currently I've got only pane with needed controls and for some reason background is transparent:
now.png
now.png (4.62 KiB) Viewed 4620 times
So at the moment I've got two questions:
1) How can I collapse/expand my control to make it look on collapsed state as it shown on "calendar.png" attachment and on expanded state as it looks now?
2) How can I make specific background color?

Code: Select all

#pragma once

#include <wx/wxprec.h>
#include <wx/calctrl.h>
#include <wx/textctrl.h>

class wxCustomDateTimePicker : public wxPanel
{
public:
    wxCustomDateTimePicker();
    explicit wxCustomDateTimePicker(wxWindow* parent);
    virtual ~wxCustomDateTimePicker();

    bool Create(wxWindow *parent,
        wxWindowID winid = wxID_ANY,
        const wxPoint& pos = wxDefaultPosition,
        const wxSize& size = wxDefaultSize,
        long style = wxTAB_TRAVERSAL | wxNO_BORDER,
        const wxString& name = wxPanelNameStr);

private:
    wxCalendarCtrl* m_calendar;
    wxTextCtrl* m_timeText;
};
And cpp file:

Code: Select all

#include "stdafx.h"
#include "wxCustomDateTimePicker.h"
#include <wx/statline.h>

wxCustomDateTimePicker::wxCustomDateTimePicker(wxWindow* parent)
{
    Create(parent);
}

wxCustomDateTimePicker::wxCustomDateTimePicker(){}

bool wxCustomDateTimePicker::Create(wxWindow *parent, wxWindowID winid /*= wxID_ANY*/, const wxPoint& pos /*= wxDefaultPosition*/, const wxSize& size /*= wxDefaultSize*/, long style /*= wxTAB_TRAVERSAL | wxNO_BORDER*/, const wxString& name /*= wxPanelNameStr*/)
{
    bool res = wxPanel::Create(parent, winid, pos, size, style, name);

    SetBackgroundColour(parent->GetBackgroundColour());
    SetBackgroundStyle(wxBG_STYLE_PAINT);
    SetForegroundColour(parent->GetForegroundColour());

    m_calendar = new wxCalendarCtrl(this, wxID_ANY, wxDefaultDateTime, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER);
    wxPoint pt(m_calendar->GetPosition());
    pt.y += m_calendar->GetSize().GetHeight() + 5;
    wxStaticLine* line = new wxStaticLine(this, wxID_ANY, pt, wxSize(m_calendar->GetSize().GetWidth(), 2));
    pt.y += 6;
    m_timeText = new wxTextCtrl(this, wxID_ANY, wxT("12:34:56"), pt);
    return res;
}

wxCustomDateTimePicker::~wxCustomDateTimePicker(){}
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: Combined date and time picker

Post by PB »

rudolfninja wrote:Currently I've got only pane with needed controls and for some reason background is transparent:
That's because you have a bug in your code. You call

Code: Select all

SetBackgroundStyle(wxBG_STYLE_PAINT);
but you do not draw the panel background by yourself.
Unless you for some odd reason need custom-drawn panel background, do not call the above. This will most likely fix the issue you asked about in question #2. BTW, I do not think calling SetForegroundColour() on wxPanel does anything, as the panel has only background.

Edited
Regarding question #1, it depends on how the control should behave. I.e., should it really expand or just display a pop-up. If the latter and you do not wish to use a wxDialog, you may take a look at the popup sample or perhaps using wxComboPopup? If it is the former, it would be a bit more complex but should not be difficult, it is basically the same thing wxCollapsiblePane does.
rudolfninja
Earned some good credits
Earned some good credits
Posts: 107
Joined: Tue Aug 28, 2018 1:02 pm
Location: Belarus

Re: Combined date and time picker

Post by rudolfninja »

PB wrote: Regarding question #1, it depends on how the control should behave. I.e., should it really expand or just display a pop-up. If the latter and you do not wish to use a wxDialog, you may take a look at the popup sample or perhaps using wxComboPopup? If it is the former, it would be a bit more complex but should not be difficult, it is basically the same thing wxCollapsiblePane does.
I guess, it should display pop-up on click.
Do you mean this sample? If it so, then I need to inherit my class from wxPopupTransientWindow and place all the control on wxScrolledWindow, don't I?
Second question is solved, thanks.
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: Combined date and time picker

Post by PB »

rudolfninja wrote:Do you mean this sample? If it so, then I need to inherit my class from wxPopupTransientWindow and place all the control on wxScrolledWindow, don't I?
Yes, that's the sample. I meant to run it to see how it behaves and whether this fits your requirements. Perhaps wxComboBox-like control using custom wxComboCtrl with the panel displayed using wxComboPopup would be better or easier to implement...

I believe that you do not need to use wxScrolledWindow if you do not plan on the panel to be scrollable. OTOH, it should do no harm...
rudolfninja
Earned some good credits
Earned some good credits
Posts: 107
Joined: Tue Aug 28, 2018 1:02 pm
Location: Belarus

Re: Combined date and time picker

Post by rudolfninja »

Yes, I don't need to use wxScrolledWindow.
PB wrote: Yes, that's the sample. I meant to run it to see how it behaves and whether this fits your requirements.
The way the sample behaves looks fine and I think that such behavior is suitable for me.
PB wrote: Perhaps wxComboBox-like control using custom wxComboCtrl with the panel displayed using wxComboPopup would be better or easier to implement...
Where can I find an example of this approach or just the result? I don't understand completely final result of such approach.

Thanks.
ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 7481
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: Combined date and time picker

Post by ONEEYEMAN »

Hi,
Just look at the sample itself and how wxComboCtrl class is defined.

Thank you.
rudolfninja
Earned some good credits
Earned some good credits
Posts: 107
Joined: Tue Aug 28, 2018 1:02 pm
Location: Belarus

Re: Combined date and time picker

Post by rudolfninja »

Yes, I've already find how to use this variant and it seems easier to implement and it is more suitable for my needs. I'll try to implement this variant.
Thanks a lot!
rudolfninja
Earned some good credits
Earned some good credits
Posts: 107
Joined: Tue Aug 28, 2018 1:02 pm
Location: Belarus

Re: Combined date and time picker

Post by rudolfninja »

I tried to implement variant with wxComboPopup and it looks very similar to what I want. Now I have the problem with the size of popup pane. I tried different variants to resize it, but all fails.
.h file:

Code: Select all

#pragma once
#include <wx/combo.h>
#include "wx/panel.h"
#include <wx/calctrl.h>

class wxCustomDateTimePickerPopup : public wxPanel, public wxComboPopup
{
public:
    virtual wxWindow *GetControl() { return this; }
    virtual wxString GetStringValue() const { return wxEmptyString; }
    virtual bool Create(wxWindow* parent);

private:
    wxCalendarCtrl* m_calendar;
    wxTextCtrl* m_timeText;
};

class wxCustomDateTimePicker : public wxComboCtrl
{
public:
    wxCustomDateTimePicker(wxWindow* parent);
    ~wxCustomDateTimePicker();

private:
    wxCustomDateTimePickerPopup* m_popup;
};
.cpp file:

Code: Select all

#include "stdafx.h"
#include "wxCustomDateTimePicker.h"
#include <wx/statline.h>
#include <wx/textctrl.h>
#include <wx/sizer.h>

wxCustomDateTimePicker::wxCustomDateTimePicker(wxWindow* parent) :
    wxComboCtrl(parent)
{
    m_popup = new wxCustomDateTimePickerPopup();
    SetPopupControl(m_popup);
}


wxCustomDateTimePicker::~wxCustomDateTimePicker()
{

}

bool wxCustomDateTimePickerPopup::Create(wxWindow* parent)
{
    bool res = wxPanel::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize);

    SetBackgroundColour(parent->GetBackgroundColour());
    SetForegroundColour(parent->GetForegroundColour());

    auto sizer = new wxBoxSizer(wxVERTICAL);

    m_calendar = new wxCalendarCtrl(this, wxID_ANY, wxDefaultDateTime, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER);
    sizer->Add(m_calendar);

    wxPoint pt(m_calendar->GetPosition());
    pt.y += m_calendar->GetSize().GetHeight() + 5;
    wxStaticLine* line = new wxStaticLine(this, wxID_ANY, pt, wxSize(m_calendar->GetSize().GetWidth(), 2));
    sizer->Add(line);
    pt.y += 6;
    m_timeText = new wxTextCtrl(this, wxID_ANY, wxT("12:34:56"), pt);
    sizer->Add(m_timeText);
    SetSizer(sizer);
    sizer->SetSizeHints(this);
    return res;
}
And the result:
now.png
now.png (4.22 KiB) Viewed 4584 times
As you can see, pop up pane is cut off on the left and on the right and has a lot of empty space on the bottom.
1) How can I resize calendar to fit in the size of popup part?
2) How can I resize only popup part?
Thanks.
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: Combined date and time picker

Post by PB »

I think it seems obvious that the calendar cannof fit the current popup and you need to do it the other way around, i.e., fit the popup around the controls. Have you tried something like

Code: Select all

wxSize wxCustomDateTimePickerPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight)
{ 
   return GetSizer()->GetSize();   
}
custompicker.png
custompicker.png (8.72 KiB) Viewed 4570 times
Edit
I noticed that wxTextCtrl seems to not work in the pop-up: it cannot be interracted with. However, if I add wxTimePickerCtrl there it seems to work as expected? Probably a focus issue which may or may not be difficult to work around...

BTW, you probably noticed that absolute positions (and often sizes as well) are irrelevant when using sizers...
rudolfninja
Earned some good credits
Earned some good credits
Posts: 107
Joined: Tue Aug 28, 2018 1:02 pm
Location: Belarus

Re: Combined date and time picker

Post by rudolfninja »

PB wrote:I think it seems obvious that the calendar cannof fit the current popup and you need to do it the other way around, i.e., fit the popup around the controls. Have you tried something like

Code: Select all

wxSize wxCustomDateTimePickerPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight)
{ 
   return GetSizer()->GetSize();   
}
No I haven't tried. Will try it tomorrow. Also I will check your notice about wxTextCtrl.
PB wrote:BTW, you probably noticed that absolute positions (and often sizes as well) are irrelevant when using sizers...
Yes, I know. My first variant was with absolute positions and then I decided to use sizer and forgot remove absolute positions-related code.
Thank you for your help!
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: Combined date and time picker

Post by PB »

The focus issue seems to be fixed by calling UseAltPopupWindow() before calling SetPopupControl(m_popup);

Edit
FWIW, I tried to implement very simple combined date and time picker, similar to that in the original post. Seems to work as expected on Windows 10 (except when I use <Alt+PrtScr> to screenshot, only the pop-up and not the whole frame are captured). Probably the only important thing missing is closing the pop-up after pressing <Enter>.
custompicker.png
custompicker.png (9.37 KiB) Viewed 4537 times
For simplicity sake, all declarations, definitions, and demo are in a single file.

Code: Select all

#include <wx/wx.h>
#include <wx/calctrl.h>
#include <wx/timectrl.h>
#include <wx/combo.h>
#include <wx/settings.h>

class wxCustomDateTimePicker;

// wxCustomDateTimePickerPopup declaration

class wxCustomDateTimePickerPopup : public wxPanel, public wxComboPopup
{
public:      
    bool Create(wxWindow* parent) wxOVERRIDE;
    
    wxWindow* GetControl() wxOVERRIDE { return this; }    
    wxString GetStringValue() const wxOVERRIDE;        
    wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight) wxOVERRIDE;

    void OnPopup() wxOVERRIDE;
    
    static wxString GetDisplayDateTimeString(const wxDateTime& dateTime);
private:
    wxCalendarCtrl*   m_calendar;    
    wxTimePickerCtrl* m_timePicker;

    wxCustomDateTimePicker* GetCustomDateTimePicker();

    wxDateTime GetDateTime() const;
    void SetDateTime(const wxDateTime& dateTime);
    
    void OnOKButtonClicked(wxCommandEvent&);
};


// wxCustomDateTimePicker declaration

class wxCustomDateTimePicker : public wxComboCtrl
{
public:
    wxCustomDateTimePicker(wxWindow* parent, const wxDateTime& dateTime);

    wxDateTime GetDateTime() const { return m_dateTime; }
    void SetDateTime(const wxDateTime& dateTime);   
private:
    wxDateTime m_dateTime;
    wxCustomDateTimePickerPopup* m_popup;
};


// wxCustomDateTimePickerPopup definition

bool wxCustomDateTimePickerPopup::Create(wxWindow* parent)
{
    if ( !wxPanel::Create(parent, wxID_ANY, wxDefaultPosition,
                          wxDefaultSize, wxBORDER_RAISED | wxTAB_TRAVERSAL) )
    {
       return false;
    }

    SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK));

    wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
    wxBoxSizer* subSizer = new wxBoxSizer(wxHORIZONTAL);

    m_calendar = new wxCalendarCtrl(this, wxID_ANY, wxDefaultDateTime, 
        wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
    mainSizer->Add(m_calendar, wxSizerFlags().Expand().Border(wxALL, FromDIP(2)));
 
    m_timePicker = new wxTimePickerCtrl(this, wxID_ANY);
    subSizer->Add(m_timePicker, wxSizerFlags(3).Expand().Border(wxALL, FromDIP(2)));
    
    wxButton* OKButton = new wxButton(this, wxID_OK);        
    OKButton->SetDefault();
    OKButton->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &wxCustomDateTimePickerPopup::OnOKButtonClicked, this);     
    subSizer->Add(OKButton, wxSizerFlags(2).Expand().Border(wxALL, FromDIP(2)));

    mainSizer->Add(subSizer, wxSizerFlags().Expand());
    SetSizerAndFit(mainSizer);    
    
    return true;
}

wxString wxCustomDateTimePickerPopup::GetStringValue() const
{        
    return GetDisplayDateTimeString(GetDateTime());
}

wxSize wxCustomDateTimePickerPopup::GetAdjustedSize(int WXUNUSED(minWidth), 
                                                    int WXUNUSED(prefHeight), 
                                                    int WXUNUSED(maxHeight))
{ 
    //@fixme: if possible, take into account the method parameters
    return GetSize();       
}

void wxCustomDateTimePickerPopup::OnPopup()
{    
    wxCustomDateTimePicker* picker = GetCustomDateTimePicker();

    if ( picker )    
        SetDateTime(picker->GetDateTime());    
}

wxDateTime wxCustomDateTimePickerPopup::GetDateTime() const
{
    wxCHECK(IsCreated(), wxInvalidDateTime);
    
    wxDateTime dateOnly, timeOnly;

    dateOnly = m_calendar->GetDate();
    wxCHECK(dateOnly.IsValid(), wxInvalidDateTime);

    timeOnly = m_timePicker->GetValue();
    wxCHECK(timeOnly.IsValid(), wxInvalidDateTime);

    return wxDateTime(dateOnly.GetDay(), dateOnly.GetMonth(), dateOnly.GetYear(),
                      timeOnly.GetHour(), timeOnly.GetMinute(), timeOnly.GetSecond());
}

void wxCustomDateTimePickerPopup::SetDateTime(const wxDateTime& dateTime)
{
    wxCHECK_RET(IsCreated(), "Call Create() before calling SetDateTime()");
    
    m_calendar->SetDate(dateTime);
    m_timePicker->SetValue(dateTime);
}

wxString wxCustomDateTimePickerPopup::GetDisplayDateTimeString(const wxDateTime& dateTime) 
{
    wxCHECK(dateTime.IsValid(), wxString(_("Invalid date and/or time")));

    // @fixme: change the format string as needed
    return dateTime.Format();
}

wxCustomDateTimePicker* wxCustomDateTimePickerPopup::GetCustomDateTimePicker()
{
    wxCHECK(IsCreated(), NULL);
    
    return dynamic_cast<wxCustomDateTimePicker*>(GetComboCtrl());
}

void wxCustomDateTimePickerPopup::OnOKButtonClicked(wxCommandEvent&)
{
     wxCustomDateTimePicker* picker = GetCustomDateTimePicker();

    if ( picker )        
        picker->SetDateTime(GetDateTime());     
    Dismiss();
}

// wxCustomDateTimePicker definition

wxCustomDateTimePicker::wxCustomDateTimePicker(wxWindow* parent, const wxDateTime& dateTime)
    : wxComboCtrl(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCB_READONLY),
      m_dateTime(dateTime)
{            
    UseAltPopupWindow();    
    m_popup = new wxCustomDateTimePickerPopup();
    SetPopupControl(m_popup);    

    SetDateTime(m_dateTime);
        
    // make the combo control fit the date and time string
    wxSize size = GetMinClientSize();
    const wxString dateTimeStr = wxCustomDateTimePickerPopup::GetDisplayDateTimeString(m_dateTime);
    
    size.SetWidth(GetSizeFromTextSize(GetTextExtent(wxString::Format(" %s ", dateTimeStr))).GetWidth());
    SetMinClientSize(size);
}

void wxCustomDateTimePicker::SetDateTime(const wxDateTime& dateTime) 
{ 
    m_dateTime = dateTime;
    SetValue(wxCustomDateTimePickerPopup::GetDisplayDateTimeString(m_dateTime));
}

class MyFrame : public wxFrame
{
public:
    MyFrame() : wxFrame(NULL, wxID_ANY, "Test")
    {       
        wxPanel* mainPanel = new wxPanel(this);
        wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);

        wxDateTime dateTime = wxDateTime::Now();

        wxCustomDateTimePicker* picker = new wxCustomDateTimePicker(mainPanel, dateTime);        
        mainSizer->Add(picker, wxSizerFlags().Border(wxALL, FromDIP(2)).CenterHorizontal());
            
        mainPanel->SetSizer(mainSizer);         
    }	
};

class MyApp : public wxApp
{
public:	
	bool OnInit()
	{
        (new MyFrame)->Show();
        return true;
	}
}; wxIMPLEMENT_APP(MyApp);
rudolfninja
Earned some good credits
Earned some good credits
Posts: 107
Joined: Tue Aug 28, 2018 1:02 pm
Location: Belarus

Re: Combined date and time picker

Post by rudolfninja »

Thanks a lot for you help.
I think that the topic can be closed as resolved. All the other features of the control I can implement by myself (at least I hope so)
rudolfninja
Earned some good credits
Earned some good credits
Posts: 107
Joined: Tue Aug 28, 2018 1:02 pm
Location: Belarus

Re: Combined date and time picker

Post by rudolfninja »

Unfortunately, one more question here.
I changed calendar type from wxCalendarCtrl to wxCalendarCtrlBase in order to create wxGenericCalendarCtrl. I need SetHighlightColours call to change default highlight colors and this method is only works for wxGenericCalendarCtrl. And now when I popup the control, the calendar is painted very slow so I can see the process of painting. The issues isn't reproduces when using wxCalendarCtrl - only with wxGenericCalendarCtrl.
Is it possible to change highlight colors in wxCalendarCtrl or is it possible to paint wxGenericCalendarCtrl as fast as it is done with wxCalendarCtrl?
Thanks.
Post Reply