Problem putting date from wxCalendarCtrl into wxTextCtrl Topic is solved

Are you writing your own components and need help with how to set them up or have questions about the components you are deriving from ? Ask them here.
Calypso
In need of some credit
In need of some credit
Posts: 7
Joined: Thu Jun 01, 2006 3:41 am
Location: Sydney, Australia

Problem putting date from wxCalendarCtrl into wxTextCtrl

Post by Calypso »

Greetings to you,

I have been struggling with a problem for the better part of two weeks and thought it was time to reach out for some help in this forum. In the words of another (also mentioned in this forum) I am in the forest and can't see the wood for the trees. I have some experience in programming in C++ but I am at a point where I think the solution is very simple but as I have been looking at it so long and so closely at it, I can't see it.

Desired outcome:
In a program I am developing, I have a dialog box which essentially accepts data and writes the data to a text file. The data in one of the fields can be be either a date (self explanatory), an event (eg 5,000 km) or a time period (eg 7 days). The type of data to be inserted depends on the choice from an adjacent combobox.

Proposed solution:
For this I have derived a control (DatePickerTextCtrl) based on wxTextCtrl (no validators) with a variable (isTypeDate - bool). If this variable is true, a single left mouse click in the textctrl should bring up a calendarctrl, if false it should behave like a normal wxTextCtrl. Single clicking a date on the calendarctrl should update the textctrl while a double click should do likewise but also close the calendarctrl. The behaviour is similar to DatePickerCtrl except it does not close the dialog on a single click.

The Coding problem:
During the past two weeks I have had varying levels of success but ultimately had all types of runtime errors ranging from memory leaks, trying to access illegal areas of memory and debug crashes leading to error locations in window.h, event.h etc. These errors have pushed me far beyond my debugging ability. The last few days I have gone back to basics but cannot get the currently selected date in the calendarctrl into the textctrl field. I have built a small test program based on the wxwidgets "minimal.cpp" sample which I am attaching to this topic together with my DatePickerTextCtrl code.

Specific issues:
  • 1. Specifically, my current error shows up in the function InsertDate. For some reason, the textctrl pointer is no longer pointing to the correct location and so the text field is not being populated. I have made various attempts at finding the window, but this has also failed.
  • 2. Also, I am having trouble getting my head around how to close the calendarctrl. (Not shown in the code are my attempts at using wxDialog in lieu of the wxFrame for the calendarctrl).
The code for the DatePickerTextCtrl is shown below.

Class Definition (DatePickerTextCtrl.h):

Code: Select all

/////////////////////////////////////////////////////////////////////////////
// Name:        DatePickerTextCtrl.h
// Purpose:     wxCalendarCtrl in a dialog box linked to a wxTextCtrl
// Author:      F. Vidmar
// Modified by:
// Created:     29.07.10
// RCS-ID:      $Id: DatePickerTextCtrl.cpp nnnnn 2010-07-29 12:00:00Z FV $
// Copyright:   (c) F. Vidmar
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#ifndef _DatePickerTextCtrl_H_
#define _DatePickerTextCtrl_H_

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

// for all others, include the necessary headers
#ifndef WX_PRECOMP
    #include "wx/frame.h"
	#include "wx/dialog.h"
    #include "wx/sizer.h"
    #include "wx/textctrl.h"
#endif

#include "wx/calctrl.h"
#include <wx/dynarray.h>
#include <wx/arrimpl.cpp> // this is a magic incantation which must be done!
#include <wx/combobox.h>
#include <wx/memory.h>

#if wxUSE_DATEPICKCTRL
    #include "wx/datectrl.h"
    #if wxUSE_DATEPICKCTRL_GENERIC
        #include "wx/generic/datectrl.h"
    #endif // wxUSE_DATEPICKCTRL_GENERIC
#endif // wxUSE_DATEPICKCTRL

#ifdef wxHAS_NATIVE_CALENDARCTRL
    #include "wx/generic/calctrlg.h"
#endif

// ----------------------------------------------------------------------------
// private classes
// ----------------------------------------------------------------------------

class myComboBox : public wxComboBox 
{ 
public: 
    myComboBox() { } 

    myComboBox(wxWindow *parent, 
                wxWindowID winid = wxID_ANY, 
                const wxString& value = wxEmptyString, 
                const wxPoint& pos = wxDefaultPosition, 
                const wxSize& size = wxDefaultSize, 
                int n = 0, 
                const wxArrayString choices = NULL, 
                long style = 0, 
                const wxValidator& validator = wxDefaultValidator, 
                const wxString& name = wxComboBoxNameStr) 
        : wxComboBox( parent, winid, value, pos, size, choices, style, validator, name ) 
    { SetSelection(n); } ;

    virtual ~myComboBox() {}; 

protected: 

private: 

}; 

// Define a control type based on wxTextCtrl
class DatePickerTextCtrl : public wxTextCtrl
{

public:

	DatePickerTextCtrl( wxWindow* parent, 
						int id, 
						const wxString title = wxEmptyString, 
						const wxPoint& pos = wxDefaultPosition, 
						const wxSize& size = wxDefaultSize, 
						long style = 0,
						const wxValidator& validator = wxDefaultValidator,
						const wxString idname = wxTextCtrlNameStr,
						const wxString fieldtype = wxT("Text"));

	virtual ~DatePickerTextCtrl() { };

	// event handlers (these functions should _not_ be virtual)
	void SetTextCtrlType(wxString strType);		// "Date" or other
	bool GetTextCtrlType();

	// Event management for calendar control
	void OnCalendarChange(wxCalendarEvent& event);
	void OnCalendarDClick(wxCalendarEvent& event);
	void OnShowCalendar(wxMouseEvent& event);

	void OnDateUpdate(wxCalendarEvent& event);

	wxCalendarCtrl* m_calCtrl;
	DatePickerTextCtrl* m_txtCtrl;

	wxDateTime GetSelectedDate() {return selectedDate;};
	void SetTextCtrlId(wxWindowID id) {txtCtrlId = id; };


	void InsertDate();
	void SetDate(wxDateTime today) { selectedDate = today; };

private:
	bool isTypeDate;
	wxFrame* thisDialog;			// Frame control
	wxDateTime today, selectedDate, currentDate;
	wxWindowID txtCtrlId;

};

// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------

// IDs for the controls and the menu commands
enum
{
    // menu items
		ID_PICKDATE = 4000,
		ID_CALFRAMECTRL,
		ID_CALCTRL
};

#endif // _DatePickerTextCtrl_H_

Constructor (DatePickerTextCtrl.cpp):

Code: Select all

/////////////////////////////////////////////////////////////////////////////
// Name:        DatePickerTextCtrl.cpp
// Purpose:     wxCalendarCtrl in a dialog box linked to a wxTextCtrl
// Author:      F. Vidmar
// Modified by:
// Created:     29.07.10
// RCS-ID:      $Id: DatePickerTextCtrl.cpp nnnnn 2010-07-29 12:00:00Z FV $
// Copyright:   (c) F. Vidmar
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
	#pragma hdrstop
#endif

#ifndef WX_PRECOMP
	#include "wx/wx.h"
#endif

#include <wx/event.h>
#include <wx/utils.h>
#include "wx/textctrl.h"
#include <wx/memory.h>

#if wxUSE_DATEPICKCTRL

	#include "wx/datectrl.h"

	// use this version if we're explicitly requested to do it or if it's the only
	// one we have
	#if !defined(wxHAS_NATIVE_DATEPICKCTRL) || \
		    (defined(wxUSE_DATEPICKCTRL_GENERIC) && wxUSE_DATEPICKCTRL_GENERIC)

		#ifndef WX_PRECOMP
			#include "wx/dialog.h"
		#endif

		#ifdef wxHAS_NATIVE_DATEPICKCTRL
			// this header is not included from wx/datectrl.h if we have a native
			// version, but we do need it here
			#include "wx/generic/datectrl.h"
		#else
			// we need to define _WX_DEFINE_DATE_EVENTS_ before including wx/dateevt.h to
			// define the event types we use if we're the only date picker control version
			// being compiled -- otherwise it's defined in the native version implementation
			#define _WX_DEFINE_DATE_EVENTS_
		#endif

		#include "wx/dateevt.h"
		#include "wx/calctrl.h"

	#endif // wxUSE_DATEPICKCTRL_GENERIC

#endif // wxUSE_DATEPICKCTRL
#include "DatePickerTextCtrl.h"

DatePickerTextCtrl::DatePickerTextCtrl(wxWindow* parent, 
					wxWindowID id, 
					const wxString title, 
					const wxPoint& pos,
					const wxSize& size, 
					long style,
					const wxValidator& validator, 
					const wxString idname,
					const wxString fieldtype)
			: wxTextCtrl(parent, id, title, pos, size, style, validator, idname)
{
	// Place links to calendar here
#if wxUSE_MEMORY_TRACING
  wxDebugContext::SetCheckpoint();
#endif

	SetTextCtrlType(fieldtype);
	SetTextCtrlId(id);

	m_txtCtrl = this;
	Connect ( wxEVT_LEFT_DOWN, wxMouseEventHandler(DatePickerTextCtrl::OnShowCalendar));
		Connect (ID_CALCTRL, wxEVT_CALENDAR_SEL_CHANGED, 
						wxCalendarEventHandler(DatePickerTextCtrl::OnDateUpdate),
						NULL,
						NULL);
/*
		m_txtCtrl->Connect(wxEVT_CALENDAR_DOUBLECLICKED,
                      wxCalendarEventHandler(DatePickerTextCtrl::OnCalendarDClick),
                      NULL,
					  NULL);
*/

#if wxUSE_MEMORY_TRACING
  // On MSW, Dump() crashes if using wxLogGui,
  // so use wxLogStderr instead.
  wxLog* oldLog = wxLog::SetActiveTarget(new wxLogStderr);

  wxDebugContext::PrintClasses();
  wxDebugContext::Dump();
  wxDebugContext::PrintStatistics();

  // Set back to wxLogGui
//  delete wxLog::SetActiveTarget(oldLog);
#endif
}

void DatePickerTextCtrl::SetTextCtrlType(wxString strType)
{
	if (strType == wxT("Date"))
		isTypeDate = true; 
	else 
		isTypeDate = false;
}

bool DatePickerTextCtrl::GetTextCtrlType()
{
	return isTypeDate;
}

void DatePickerTextCtrl::OnShowCalendar(wxMouseEvent& event)
{
	// Display calendar only if a date is expected
	if (isTypeDate)
	{
#if wxUSE_MEMORY_TRACING
  wxDebugContext::SetCheckpoint();
#endif
  m_calCtrl = NULL;
		wxSize txtctlSize = m_txtCtrl->GetSize();
		wxPoint calPnt = m_txtCtrl->GetScreenPosition();
		calPnt.y += txtctlSize.GetHeight();

		thisDialog = new wxFrame(GetParent(), ID_CALFRAMECTRL, wxT("Calendar"), calPnt, wxDefaultSize, wxSIMPLE_BORDER | wxSTAY_ON_TOP );

		m_calCtrl = new wxCalendarCtrl(thisDialog,
						ID_CALCTRL, 
						wxDateTime::Today(), 
						wxDefaultPosition, 
						wxDefaultSize, 
						wxCAL_SEQUENTIAL_MONTH_SELECTION);

		m_calCtrl->Connect ( wxEVT_CALENDAR_SEL_CHANGED, 
						wxCalendarEventHandler(DatePickerTextCtrl::OnCalendarChange),
						NULL,
						NULL);
/*
		m_calCtrl->Connect(wxEVT_CALENDAR_DOUBLECLICKED,
                      wxCalendarEventHandler(DatePickerTextCtrl::OnCalendarDClick),
                      NULL,
					  NULL);
*/
		// Bring up calendar dialog with todays date or, if valid, with the date in the existing textctrl.

		if (m_txtCtrl->GetValue().IsEmpty())
		{
			currentDate = wxDateTime::Today();
		}
		else
		{
			wxDateTime tempDate;
			tempDate.ParseDate(m_txtCtrl->GetValue());
			if (tempDate.IsValid())
				currentDate = tempDate;
			else
				currentDate = wxDateTime::Today();
		}

		SetDate(currentDate);
		m_txtCtrl->SetValue(selectedDate.Format(wxT("%d-%m-%Y")).c_str());
		thisDialog->Fit();
		thisDialog->Show();
	}
	else
		// Standard wxTextCtrl
		event.Skip();

#if wxUSE_MEMORY_TRACING
  // On MSW, Dump() crashes if using wxLogGui,
  // so use wxLogStderr instead.
//  wxLog* oldLog = wxLog::SetActiveTarget(new wxLogStderr);

  wxDebugContext::PrintClasses();
  wxDebugContext::Dump();
  wxDebugContext::PrintStatistics();

  // Set back to wxLogGui
//  delete wxLog::SetActiveTarget(oldLog);
#endif
}

void DatePickerTextCtrl::OnCalendarChange(wxCalendarEvent& event)
{
	selectedDate = event.GetDate();
    static wxDateTime s_dateLast;
    const bool mark = !s_dateLast.IsValid() || selectedDate != s_dateLast;

    s_dateLast = event.GetDate();

	InsertDate();
}

void DatePickerTextCtrl::OnCalendarDClick(wxCalendarEvent& event)
{
	selectedDate = event.GetDate();

	InsertDate();
}

void DatePickerTextCtrl::InsertDate()
{
	wxWindow* ctrl = GetParent()->FindWindow(txtCtrlId);
	wxTextCtrl* dateCtrl = wxDynamicCast(ctrl,wxTextCtrl);
	if (dateCtrl)
		dateCtrl->SetValue(selectedDate.Format(wxT("%d-%m-%Y")).c_str());

//	m_txtCtrl->SetValue(selectedDate.Format(wxT("%d-%m-%Y")).c_str());
}

void DatePickerTextCtrl::OnDateUpdate(wxCalendarEvent& event)
{
	m_txtCtrl->SetValue(selectedDate.Format(wxT("%d-%m-%Y")).c_str());
}
System Details:
Windows XP Professional SP2
WxWidgets 2.9.0
Visual Studio Express C++

I would be greatly appreciative if anyone is in a position to provide some assistance in this problem even if it is simply pointing me in the right direction.

Thanks in anticipation,

Calypso
You do not have the required permissions to view the files attached to this post.
DavidHart
Site Admin
Site Admin
Posts: 4254
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart »

Hi,

One obvious problem with your code is the Connect() statements. You do:

Code: Select all

                m_calCtrl->Connect ( wxEVT_CALENDAR_SEL_CHANGED,       wxCalendarEventHandler(DatePickerTextCtrl::OnCalendarChange),
                                                NULL,
                                                NULL); 
That last NULL must be wrong.

Connect() connects an event-type from one control to a function of the same control or a different one. The last parameter is the eventSink, which must point to a valid instance of the destination control.
(The usual mistake is not to set it, which means it defaults to 'this'. Sometimes that's what you want...)

However, NULL will always be wrong. It means that each event will be sent to a block of memory that's alleged to be a valid instance, but which actually is guaranteed not to be.

Regards,

David
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am

Post by Auria »

DavidHart: not exactly, passing NULL as event sink makes it revert to the default behavior of using "this" (if you don't specify a value for this argument then NULL is passed). But I believe that the event sink is wrong anyway and yes, it needs to be corrected.

Calypso: which version of wxWidgets is this? If you can use 2.9.x, use Bind instead of Connect, since Bind triggers a compilation error if you do such a mistake
"Keyboard not detected. Press F1 to continue"
-- Windows
Calypso
In need of some credit
In need of some credit
Posts: 7
Joined: Thu Jun 01, 2006 3:41 am
Location: Sydney, Australia

Post by Calypso »

Hi,

Thank you David and Auria for your very quick replies to my query. I have only just been able to fully review my test app with the direction you provided.

I must admit that I wasn't fully comfortable with the eventSink parameter and it was one that I was changing while trying to debug the code (it is the first time I am actually using the Connect method for event control). Ultimately, I set it to NULL in the belief that it was equivalent to this which is in accordance with Auria's comments.

However, when I replaced the second NULL with this, the test app works (no memory leaks or other runtime errors), however, I am not totally confident that my code is 100% ok. The change was made to all instances of the Connect statements, as shown below:

Code: Select all

m_calCtrl->Connect (wxEVT_CALENDAR_SEL_CHANGED, 
		wxCalendarEventHandler (DatePickerTextCtrl::OnCalendarChange),
		NULL,
		this);
As I am using wxWidgets 2.9.0, I also tried Auria's suggestion of Bind instead of Connect. This worked as well and in addition I have greater confidence in the code. I will be using this method in future.

Code: Select all

m_calCtrl->Bind(wxEVT_CALENDAR_SEL_CHANGED, 
		&DatePickerTextCtrl::OnCalendarChange,
		this);
Before closing this query, I would like a little more clarification on eventSink. David stated that the eventSink
must point to a valid instance of the destination control
In my mind?? having the code m_calCtrl->Connect ties the event to the destination control, meaning the eventSink parameter is really superfluous and should be this. Is this a correct interpretation? If not, do you have a simple example eg a situation, of how eventSink can be used (ie code not required)?

Thanks,

Ferdy
DavidHart
Site Admin
Site Admin
Posts: 4254
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart »

First, auria is quite right: using ...NULL,NULL) is equivalent to NULL,this); which might not be correct, but it's not stupid.
Before closing this query, I would like a little more clarification on eventSink.
It isn't easy to explain.
The ways I use to avoid most Connect() mistakes are:
1) always explicitly to enter something as the eventSink, rather than relying on the default to be correct (one good thing about Bind() is that it forces you to do so).
2) always ensuring that the eventSink is an instance of the same class as the method specified in the wxFOOeventhandler. In your case that's DatePickerTextCtrl::OnCalendarChange, so the eventSink must be a DatePickerTextCtrl instance.

Writing: m_calCtrl->Connect(a,b,c) is equivalent to: m_calCtrl->Connect(a,b,c, NULL, m_calCtrl);
That's incorrect in your context, as the destination control is meant to be the DatePickerTextCtrl instance 'this'.
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am

Post by Auria »

DavidHart wrote:First, auria is quite right: using ...NULL,NULL) is equivalent to NULL,this); which might not be correct, but it's not stupid.
no, no !! that's the most common source of confusion with reading the statement in the docs that passing NULL takes "this". It's not taking "this" from the calling context, it's taking "this" from inside the call

So

Code: Select all

X->Connect(..., NULL);
is not equivalent to

Code: Select all

X->Connect(..., this);
but to

Code: Select all

X->Connect(..., X);
But since Bind makes it impossible for you to screw it up, better use Bind :)

Basically the event sink parameter is to reach the equivalent of the "delegate" idea found in many languages and libraries. A method delegate is basically a pointer to a method + an object instance to call this method on. The event sink is the latter
"Keyboard not detected. Press F1 to continue"
-- Windows
DavidHart
Site Admin
Site Admin
Posts: 4254
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart »

>using ...NULL,NULL) is equivalent to NULL,this);
no, no !! that's the most common source of confusion with reading the statement in the docs that passing NULL takes "this". It's not taking "this" from the calling context, it's taking "this" from inside the call
Yes, that's what I said later. But my original, incorrect suggestion was that the NULL would stay as NULL, and that's what I was correcting.