My version of a Date Picker ctrl

Frank Millman wrote:

Robin Dunn wrote:
>
> I don't remember details of what it can and can't do, but the
> wx.DateTime's Parse* methods are able to handle lots of
> string formats
> in a fairly intelligent way. It might be worth the effort to let
> wx.DateTime do the parsing of the input and then convert to
Python's
> date type for the rest of the functionality. (I assume
> that's what you
> are using anyway, I haven't looked at the code yet.)
>

Thanks for the hint. I had a quick look at the docs, and
indeed it looks
promising.

I will play around with it and report back if I come up with anything
worthwhile.

I had a look, and it seems very flexible.

One feature that I like is wx.DateTime.ParseFormat(). If someone enters
'nn/nn', do they mean dd/mm or mm/dd?
If you use wx.DateTime.ParseDate(str), it makes a decision based on the
locale, which may not do what you want. If you use
wx.DateTime.ParseFormat(str, '%d/%m'), it uses the format, not the
locale, so you can control exactly what it does. I'm not sure if I will
actually use this, but it is good to know that it is there.

the following -

1. Don't try to use 'locale' to control formatting, rather set up your own
user-defined parameter.

2. Use separate parameters for input and display purposes.

I also took the point that you may need to set up preferences for each
user, but this need not form part of a standard control. The control needs
an input parameter containing the desired format, but it is up to the
application to decide where to get the parameter from.

At this stage I do not have a need to input 'time' data, and I do not have
a need for the sophisticated functionality that Karsten mentioned, that
can parse user input into a date/time using a variety of methods,
prompting the user for a decision if necessary.

Where to from here? I can modify my example to incorporate parameters for
input and display purposes, and repost it.

However, I will wait a bit to give others a chance to add their requests,
if any. If any of those features are already part of GNUmed, then perhaps
Karsten can take my example as a base and copy/paste some of GNUmed's
functionality into it.

Of course this presupposes that my example is a good base to start from.
It may not be. Feel free to suggest alternatives. I have re-attached my
example in case anyone missed it the first time.

Any comments welcome.

Frank

datectrl.py (9.44 KB)

···

From my perspective, the most valuable tips I got from Karsten's posts are

1. Don't try to use 'locale' to control formatting, rather set up your own
user-defined parameter.

2. Use separate parameters for input and display purposes.

It may be an idea to

- use user-defined parameter(s!) for input and display/editing of input

- but use the locale to *output* what the control thinks the
  user entered (which may get displayed next to the input
  in, say, a small font in its own space, or as a
  tooltippish thingy)

- it may be useful (regardless of the above) to have a
  dynamic tooltip display the value formatted into a locale
  dependant (month name) string so that no ambiguities *can*
  arise: 3 M�rz 2010 is not ambigous while 3/3/2010 or
  2010-3-3 or whatever all are

I also took the point that you may need to set up preferences for each
user, but this need not form part of a standard control.

I agree.

The control needs
an input parameter containing the desired format

Or a list of (distinct) formats ?

, but it is up to the
application to decide where to get the parameter from.

Yep.

At this stage I do not have a need to input 'time' data, and I do not have
a need for the sophisticated functionality that Karsten mentioned, that
can parse user input into a date/time using a variety of methods,
prompting the user for a decision if necessary.

Where to from here? I can modify my example to incorporate parameters for
input and display purposes, and repost it.

Sure. How about working on it on the wxPython wiki ?

The TextCtrlAutoComplete example may give you more ideas
should any be needed.

Feel free to lift whatever code you want from the GNUmed
control as well !

However, I will wait a bit to give others a chance to add their requests,
if any. If any of those features are already part of GNUmed, then perhaps
Karsten can take my example as a base and copy/paste some of GNUmed's
functionality into it.

Not exactly but once it's in the wiki I am likely to work on
it a bit at some point (say, a night shift and no patients).

Of course this presupposes that my example is a good base to start from.

Sure. I really like the part about the calendar.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import unicode_literals
from __future__ import division
from __future__ import print_function

It doesn't look like those are used, are they ?

import sys
import wx
import wx.calendar
import wx.combo
from datetime import date as dt

class MyDateCtrl(wx.combo.ComboCtrl):
    def __init__(self, parent, size, pos, title):
        wx.combo.ComboCtrl.__init__(self, parent, size=size, pos=pos)
        self.title = title
        self.TextCtrl.Bind(wx.EVT_SET_FOCUS, self.on_got_focus)
        self.TextCtrl.Bind(wx.EVT_KILL_FOCUS, self.on_lost_focus)
        self.TextCtrl.Bind(wx.EVT_CHAR, self.on_char)
        self.nav = False
        self.setup_button()

    def setup_button(self): # copied directly from demo
        # make a custom bitmap showing "..."
        bw, bh = 14, 16
        bmp = wx.EmptyBitmap(bw, bh)
        dc = wx.MemoryDC(bmp)

        # clear to a specific background colour
        bgcolor = wx.Colour(255, 254, 255)
        dc.SetBackground(wx.Brush(bgcolor))
        dc.Clear()

        # draw the label onto the bitmap
        label = "..."

Just to make sure you're aware of it: there's a unicode
codepoint for a nice ellipsis:

u_ellipsis = u'\u2026'

Here's a bunch of others:

u_right_double_angle_quote = u'\u00AB' # <<
u_registered_trademark = u'\u00AE'
u_plus_minus = u'\u00B1'
u_left_double_angle_quote = u'\u00BB' # >>
u_one_quarter = u'\u00BC'
u_one_half = u'\u00BD'
u_three_quarters = u'\u00BE'
u_ellipsis = u'\u2026'
u_left_arrow = u'\u2190' # -->
u_right_arrow = u'\u2192' # <--
u_sum = u'\u2211'
u_corresponds_to = u'\u2258'
u_infinity = u'\u221E'
u_diameter = u'\u2300'
u_checkmark_crossed_out = u'\u237B'
u_frowning_face = u'\u2639'
u_smiling_face = u'\u263a'
u_black_heart = u'\u2665'
u_checkmark_thin = u'\u2713'
u_checkmark_thick = u'\u2714'
u_writing_hand = u'\u270d'
u_pencil_1 = u'\u270e'
u_pencil_2 = u'\u270f'
u_pencil_3 = u'\u2710'
u_latin_cross = u'\u271d'
u_replacement_character = u'\ufffd'

        font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        font.SetWeight(wx.FONTWEIGHT_BOLD)
        dc.SetFont(font)
        tw, th = dc.GetTextExtent(label)
        dc.DrawText(label, (bw-tw)/2, (bw-tw)/2)
        del dc

        # now apply a mask using the bgcolor
        bmp.SetMaskColour(bgcolor)

        # and tell the ComboCtrl to use it
        self.SetButtonBitmaps(bmp, True)

    # Overridden from ComboCtrl, called when the combo button is clicked
    def OnButtonClick(self):
        text_ctrl = self.TextCtrl
        date = text_ctrl.Value
        if date in ('', ' / / '):
            wx_date = wx.DateTime()
        else:
            d, m, y = map(int, date.split('/'))
            wx_date = wx.DateTimeFromDMY(d, m-1, y)
        dlg = MyCalendarDlg(self, wx_date)
        dlg.CentreOnScreen()

Maybe you can calculate the position of the date picker from
which the calendar was invoked and somehow align to that ?
Say, align the top left calendar corner to the bottom left
corner of the picker ? This might add some visual
consistency.

        if dlg.ShowModal() == wx.ID_OK:
            cal_date = dlg.cal.Date
            text_ctrl.Value = '{0:02}/{1:02}/{2:04}'.format(
                cal_date.Day, cal_date.Month+1, cal_date.Year)
            self.nav = True
        dlg.Destroy()
        self.SetFocus()

    # Overridden from ComboCtrl to avoid assert since there is no ComboPopup
    def DoSetPopupControl(self, popup):
        pass

    def on_got_focus(self, evt):
        if self.nav: # user has made a selection, so move on
            self.nav = False
            wx.CallAfter(self.Navigate)
        else:
            text_ctrl = self.TextCtrl
            if text_ctrl.Value == '':
                text_ctrl.Value = ' / / '
            text_ctrl.InsertionPoint = 0
            text_ctrl.SetSelection(-1, -1)
            text_ctrl.pos = 0
        evt.Skip()

    def on_lost_focus(self, evt):
        value = self.Value
        if value == ' / / ': # ) remove these lines
            self.Value = '' # ) if you want a blank
            evt.Skip() # ) entry to default to
            return # ) today's date
        today = dt.today()
        date = value.split('/')
        day = date[0].strip()
        if day == '':
            day = today.day
        else:
            day = int(day)
        month = date[1].strip()
        if month == '':
            month = today.month
        else:
            month = int(month)
        year = date[2].strip()
        if year == '':
            year = today.year
        elif len(year) == 2:
            # assume year is in range (today-75) to (today+25)
            year = int(year) + int(today.year/100)*100
            if year - today.year > 25:
                year -= 100
            elif year - today.year < -75:
                year += 100
        else:
            year = int(year)
        try:
            date = dt(year, month, day)
            self.Value = '{0:02}/{1:02}/{2:04}'.format(day, month, year)
        except ValueError as error:
            dlg = wx.MessageDialog(
                self, str(error), self.title, wx.OK | wx.ICON_INFORMATION)

This str(error) seems mighty optimistic to me.

            dlg.ShowModal()
            dlg.Destroy()
            wx.CallAfter(self.SetFocus)

        self.TextCtrl.SetSelection(0, 0)
        evt.Skip()

    def on_char(self, evt):
        text_ctrl = self.TextCtrl
        code = evt.KeyCode
        if code in (wx.WXK_SPACE, wx.WXK_F4) and not evt.AltDown():
            self.OnButtonClick()
            return
        if code in (wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_HOME, wx.WXK_END):
            if text_ctrl.Selection == (0, 10):
                text_ctrl.SetSelection(0, 0)
            if code == wx.WXK_LEFT:
                if text_ctrl.pos > 0:
                    text_ctrl.pos -= 1
                    if text_ctrl.pos in (2, 5):
                        text_ctrl.pos -= 1
            elif code == wx.WXK_RIGHT:
                if text_ctrl.pos < 10:
                    text_ctrl.pos += 1
                    if text_ctrl.pos in (2, 5):
                        text_ctrl.pos += 1
            elif code == wx.WXK_HOME:
                text_ctrl.pos = 0
            elif code == wx.WXK_END:
                text_ctrl.pos = 10
            text_ctrl.InsertionPoint = text_ctrl.pos
            return
        if code in (wx.WXK_BACK, wx.WXK_DELETE):
            if text_ctrl.Selection == (0, 10):
                text_ctrl.Value = ' / / '
                text_ctrl.SetSelection(0, 0)
            if code == wx.WXK_BACK:
                if text_ctrl.pos == 0:
                    return
                text_ctrl.pos -= 1
                if text_ctrl.pos in (2, 5):
                    text_ctrl.pos -= 1
            elif code == wx.WXK_DELETE:
                if text_ctrl.pos == 10:
                    return
            curr_val = text_ctrl.Value
            text_ctrl.Value = curr_val[:text_ctrl.pos]+' '+curr_val[text_ctrl.pos+1:]
            text_ctrl.InsertionPoint = text_ctrl.pos
            return
        if code in (wx.WXK_TAB, wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER) or code > 255:
            evt.Skip()
            return
        if text_ctrl.pos == 10:
            wx.Bell()
            return
        ch = chr(code)

unichr(evt.GetUnicodeKey()) ?

        if ch not in ('0123456789'):
            wx.Bell()
            return

You may want to verify against a regular expression.

        if text_ctrl.Selection == (0, 10):
            curr_val = ' / / '
        else:
            curr_val = text_ctrl.Value
        text_ctrl.Value = curr_val[:text_ctrl.pos]+ch+curr_val[text_ctrl.pos+1:]
        text_ctrl.pos += 1
        if text_ctrl.pos in (2, 5):
            text_ctrl.pos += 1
        text_ctrl.InsertionPoint = text_ctrl.pos

class MyCalendarDlg(wx.Dialog):
    def __init__(self, parent, date):
        wx.Dialog.__init__(self, parent, title=parent.title)
        panel = wx.Panel(self, -1)

        sizer = wx.BoxSizer(wx.VERTICAL)
        panel.SetSizer(sizer)

        cal = wx.calendar.CalendarCtrl(panel, date=date)

        if sys.platform != 'win32':
            # gtk truncates the year - this fixes it

I seem to remember a display-century type flag on calendar
controls. Would that help ?

            w, h = cal.Size
            cal.Size = (w+25, h)
            cal.MinSize = cal.Size

Karsten

···

On Wed, Jun 30, 2010 at 07:41:54AM +0200, frank@chagford.com wrote:
--
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD 4537 78B9 A9F9 E407 1346

Like so.

Karsten

···

On Wed, Jun 30, 2010 at 01:32:15PM +0200, Karsten Hilbert wrote:

> dlg = MyCalendarDlg(self, wx_date)
> dlg.CentreOnScreen()

Maybe you can calculate the position of the date picker from
which the calendar was invoked and somehow align to that ?
Say, align the top left calendar corner to the bottom left
corner of the picker ? This might add some visual
consistency.

--
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD 4537 78B9 A9F9 E407 1346

Frank Millman wrote:

Where to from here? I can modify my example to incorporate
parameters for input and display purposes, and repost it.

I have started modifying my original example of a Date Picker control, but
it is still in progress. However, I have some questions.

I will also reply to somw of the points raised by Karsten on 30th June. I
almost missed his post, as for some reason I did not receive it via email,
which is how I usually follow the mailing list. However, I did received the
follow-up with the screen-shot attachment, and that made me realise I had
missed something, so I went into Google Groups and found the original
message there. That was lucky, as it included a lot of important points.

We have agreed that there should be separate parameters for input and
display. What should this parameter look like? For now, I am using
'strftime' format, as it is a standard, and very flexible. Whether it is
user-friendly is another matter, but that can be discussed later.

My initial attempt uses the idea that when the control has focus, the date
is displayed in 'input' format, enabling the user to modify it; and when the
control loses focus, the date is re-displayed in 'display' format, so that
he can verify that the result matches his intentions.

For 'display' purposes, I find the format '%a %d %b %Y' quite convenient
(e.g. Wed 07 Jul 2010), as it is explicit, but short enough to fit into the
text control in my example. I use wx.DateTime.Format(format) to perform the
conversion. I do not know whether it translates weekday names and month
names according to locale.

The question of how to allow the user to input the date is more tricky. One
can define various input formats, such as '%m/%d/%Y', or '%d.%m.%y', etc. If
there is an existing date, one can use wx.DateTime.Format(format) to display
it in that form, and one can use wx.DateTime.ParseFormat(format) to try to
convert the user input into a valid wx.DateTime object.
However, it is not user-friendly to enter the date in that format.

As this could involve quite a bit of work, I would like to get some input
first. These are my initial thoughts on a possible approach.

Adding a popup calendar adds a lot of convenience, so I think it is
reasonable to limit the options to the following -

- if the user knows the date they want, they can enter it into the text
control
- otherwise they must open the calendar and make a selection from there

The calendar popup seems to work ok, so the question is how to enter it as
text.

I would like the user to be able to enter it using only the numeric keypad.
I achieved this in my original example, but that was hard-coded for a format
of 'dd/mm/yyyy'. It should be possible, with some effort, to parse the
strftime string, and use that in a similar way. It would suit me, but would
it suit everyone?

Working on this has made me aware of a problem. My approach effectively
results in the user inputting text in 'overwrite' mode. To make this clear,
one should change the caret to a block instead of a vertical line, but I
could not find any way to achieve this. I did find a post from Robin a
couple of years ago saying that this is a function of the native widget, and
is not accessible from wx. Then I did a bit more experimenting, and found
that, on gtk2, if I press the 'Ins' button on the keyboard, the caret
changes to a block, and the control changes into overwrite mode. If I try
this on msw, neither of these things happen. However, even if there is a way
to make it happen, the question is, is there a way to change to 'overwrite'
mode programatically?

Assuming we agree on a way to input a date, the next question is how to
validate it. I can use wx.DateTime.ParseFormat(format). Unfortunately, the
only way to tell if it is valid is to call wx.DateTime.IsValid(), which
returns True or False. In my original example, I extracted the day, month,
and year portion, and used them to convert to a python datetime.date object.
Karsten queried this ('This str(error) seems highly optimistic to me'), but
the reason I did it that way is it actually tells you what is wrong if it
fails, and this can be used to display an error message to the user.

Then the question is *when* to validate it. At present I do this from a
'lost focus' event. This is convenient, but I think that most of us have our
own ideas about when and how to validate individual fields on a form. Is it
ok to leave it like this for now, and leave it for each person to modify to
suit his own requirements?

If I can get some feedback on the above, I can knock up something simple and
get it into the wiki, and we can carry on from there.

All comments welcome.

Thanks

Frank

We have agreed that there should be separate parameters for input and
display. What should this parameter look like? For now, I am using
'strftime' format, as it is a standard, and very flexible. Whether it is
user-friendly is another matter, but that can be discussed later.

You mean you are using strftime to format strings into
objects, right ? Sure, why not.

My initial attempt uses the idea that when the control has focus, the date
is displayed in 'input' format, enabling the user to modify it; and when the
control loses focus, the date is re-displayed in 'display' format, so that
he can verify that the result matches his intentions.

While that is certainly an idea that I like it also *forces*
the user to leave the control in order to verify it
understands what he typed.

Also, when users see a field and go "oh, I need to modify
that" they'll pre-form a mental strategy as to how to
achieve the desired modification. Now, when they enter the
control and it changes the value that strategy will not
match reality anymore and will need to be adjusted. Thus the
user needs to *learn* and *remember* something about the
behaviour of the control. In other words the pre-formed
strategy will need to be formed on something *learned about*
rather than something *deduced from* the currently visible
content of the control -- which is not intuitive :wink:

Maybe the idea to display what the control thinks the user
meant could be presented in a tooltip like pop-above while
the user is editing the control ? When leaving the control it
would get appended to the content inside (). When entering
the control the appended string would be removed again and
the pop-above re-shown.

For 'display' purposes, I find the format '%a %d %b %Y' quite convenient
(e.g. Wed 07 Jul 2010), as it is explicit, but short enough to fit into the
text control in my example.

Sounds good to me.

The question of how to allow the user to input the date is more tricky. One
can define various input formats, such as '%m/%d/%Y', or '%d.%m.%y', etc. If
there is an existing date, one can use wx.DateTime.Format(format) to display
it in that form, and one can use wx.DateTime.ParseFormat(format) to try to
convert the user input into a valid wx.DateTime object.
However, it is not user-friendly to enter the date in that format.

Maybe the range of masked controls in wxpython can give
ideas or even lend code here ?

Adding a popup calendar adds a lot of convenience,

Absolutely. Do keep that. Perhaps re-position as outlined in
my previous post.

so I think it is
reasonable to limit the options to the following -

- if the user knows the date they want, they can enter it into the text
control
- otherwise they must open the calendar and make a selection from there

IOW, if the user *doesn't* know "the date they want" they
will select one from a calendar ?? Why not just insert a
random date then ? Just kidding :slight_smile:

I would like the user to be able to enter it using only the numeric keypad.
I achieved this in my original example, but that was hard-coded for a format
of 'dd/mm/yyyy'. It should be possible, with some effort, to parse the
strftime string, and use that in a similar way. It would suit me, but would
it suit everyone?

I guess that would be useful.

this on msw, neither of these things happen. However, even if there is a way
to make it happen, the question is, is there a way to change to 'overwrite'
mode programatically?

I'm pretty sure there is.

Assuming we agree on a way to input a date, the next question is how to
validate it. I can use wx.DateTime.ParseFormat(format). Unfortunately, the
only way to tell if it is valid is to call wx.DateTime.IsValid(), which
returns True or False. In my original example, I extracted the day, month,
and year portion, and used them to convert to a python datetime.date object.
Karsten queried this ('This str(error) seems highly optimistic to me'), but
the reason I did it that way is it actually tells you what is wrong if it
fails, and this can be used to display an error message to the user.

I was mainly referring to the fact that str() will break
apart in ugly ways as soon as non-7-bit-ascii starts getting
involved.

Then the question is *when* to validate it. At present I do this from a
'lost focus' event. This is convenient, but I think that most of us have our
own ideas about when and how to validate individual fields on a form. Is it
ok to leave it like this for now, and leave it for each person to modify to
suit his own requirements?

Provide a flag .validate_on_lost_focus defaulting to false.
Provide a method "is_valid_timestamp()". Provide some way of
allowing NULL dates.

Maybe provide a flag .color_if_invalid and provide a method
.display_as_valid() using that color (also think of
".color_if_valid" which needs to be derived from the system
at control __init__().

Thanks for spending the time to work on this !

Karsten

···

On Wed, Jul 07, 2010 at 05:08:26PM +0200, Frank Millman wrote:
--
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD 4537 78B9 A9F9 E407 1346

Ok, I have made some more changes and uploaded the result to wxPyWiki -

  http://wiki.wxpython.org/DateEditorAndPicker

I tried to copy/paste the program into the Code Sample section, but for some
reason the line endings were messed up and I could not figure out how to fix
it. So I removed it from there and uploaded the file as an attachment. Hope
that is ok.

I think I have addressed most of Karsten's comments. Those outstanding are
listed under Special Concerns.

Comments welcome.

Frank

Ok, I have made some more changes and uploaded the result to wxPyWiki -

  DateEditorAndPicker - wxPyWiki

Nice. I would add sensible defaults to the __init__ parameters.

If you feel torn between the dilemma of keyword args vs positional args
I suggest not supporting special args (over those required by ComboCtrl)
at all and letting them be set directly:

dc = DateCtrl(...)
dc.allow_null = ...
dc.display_format = ...

If you turn (some of) those into properties you can even verify
the values on setting and throw errors if the values don't fit.

You could then reduce to __init__(self, *args, **kwargs) and pass
those on by super(... *args, **kwargs).

I'll see if I can spend a bit of time on it.

Karsten

···

--
GMX DSL: Internet-, Telefon- und Handy-Flat ab 19,99 EUR/mtl.
Bis zu 150 EUR Startguthaben inklusive! Aktuelle Nachrichten aus Politik, Wirtschaft & Panorama | GMX

My web based date picker does just that, this is important I think
because dd/mm/yyyy mm/dd/yyyy variations, the user may enter a date
that the control understands perfectly well, but the user may not
agree with what the control

http://www.sampartington.co.uk/components/

Try entering 15 ma for example. The user may have meant may or march,
but by giving constant feedback there is less cause for confusion.

Sam

···

On 9 July 2010 22:48, Karsten Hilbert <Karsten.Hilbert@gmx.net> wrote:

On Wed, Jul 07, 2010 at 05:08:26PM +0200, Frank Millman wrote:
Maybe the idea to display what the control thinks the user
meant could be presented in a tooltip like pop-above while
the user is editing the control ? When leaving the control it
would get appended to the content inside (). When entering
the control the appended string would be removed again and
the pop-above re-shown.

Frank Millman wrote:

Ok, I have made some more changes and uploaded the result to
wxPyWiki -

  DateEditorAndPicker - wxPyWiki

I have uploaded a new version of the program, with a tooltip added to the
dropdown button.

A copy is attached here for reference.

Frank

datectrl_1.3.py (13.3 KB)

Hello Frank !

With this code:

> DateEditorAndPicker - wxPyWiki

I am seeing the attached problem. It is due to the
locale specific display_format including an abbreviation for
the day of week and month ...

Karsten

···

--
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD 4537 78B9 A9F9 E407 1346

Karsten Hilbert wrote:

Hello Frank !

With this code:

> > DateEditorAndPicker - wxPyWiki

I am seeing the attached problem. It is due to the
locale specific display_format including an abbreviation for
the day of week and month ...

Hi Karsten

I can get a similar error if I do the following -

    input_format='%a %d %b %Y'
    default_to_today=True

Then by tabbing through the date field, without entering anything, I get the
error.

It happens because the *input* format is designed to accept numeric input
only, so %a and %b are not valid.

As far as I can tell, any valid strftime format is ok for *display* format,
and should not give an error.

Can you confirm exactly what was used for input_format and display_format.

Thanks

Frank

> With this code:
>
> > > DateEditorAndPicker - wxPyWiki
>
> I am seeing the attached problem. It is due to the
> locale specific display_format including an abbreviation for
> the day of week and month ...
>

Hi Karsten

I can get a similar error if I do the following -

    input_format='%a %d %b %Y'
    default_to_today=True

Then by tabbing through the date field, without entering anything, I get the
error.

It happens because the *input* format is designed to accept numeric input
only, so %a and %b are not valid.

All I did was run the code w/o changes.

As far as I can tell, any valid strftime format is ok for *display* format,
and should not give an error.

Can you confirm exactly what was used for input_format and display_format.

The code defaults.

Karsten

···

On Thu, Sep 09, 2010 at 02:21:59PM +0200, Frank Millman wrote:
--
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD 4537 78B9 A9F9 E407 1346

It seems to only happen every so often, particularly when
back-tabbing into the field and quickly, too. Feels timing
related. Maybe it got to do with navigation being quicker
than some of the "wx.CallAfter()" happen ?

Karsten

···

On Thu, Sep 09, 2010 at 02:21:59PM +0200, Frank Millman wrote:

> > > DateEditorAndPicker - wxPyWiki
>
> I am seeing the attached problem. It is due to the
> locale specific display_format including an abbreviation for
> the day of week and month ...
>

Hi Karsten

I can get a similar error if I do the following -

    input_format='%a %d %b %Y'
    default_to_today=True

Then by tabbing through the date field, without entering anything, I get the
error.

It happens because the *input* format is designed to accept numeric input
only, so %a and %b are not valid.

As far as I can tell, any valid strftime format is ok for *display* format,
and should not give an error.

Can you confirm exactly what was used for input_format and display_format.

--
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD 4537 78B9 A9F9 E407 1346

Also, after using the calendar popup, the combo button
appears to stay depressed (but still works).

This is on Debian Testing with wxPython 2.8.10.

Karsten

···

On Thu, Sep 09, 2010 at 06:28:06PM +0200, Karsten Hilbert wrote:

> It happens because the *input* format is designed to accept numeric input
> only, so %a and %b are not valid.

All I did was run the code w/o changes.

--
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD 4537 78B9 A9F9 E407 1346

Maybe this helps.

Karsten

···

On Thu, Sep 09, 2010 at 06:28:06PM +0200, Karsten Hilbert wrote:

> I can get a similar error if I do the following -
>
> input_format='%a %d %b %Y'
> default_to_today=True
>
> Then by tabbing through the date field, without entering anything, I get the
> error.
>
> It happens because the *input* format is designed to accept numeric input
> only, so %a and %b are not valid.

All I did was run the code w/o changes.

--
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD 4537 78B9 A9F9 E407 1346

Karsten Hilbert wrote:

> > It happens because the *input* format is designed to
accept numeric input
> > only, so %a and %b are not valid.
>
> All I did was run the code w/o changes.

Ok, I think I have solved it. There were two issues.

1. The program 'validates' the date by extracting the day/month/year
portions from the current value of the text control, and using them to
construct a wx.DateTime instance. If successful it redisplays the date using
display_format. If unsuccessful it displays an error message.

This assumes that the current value is in input_format. If it happens to be
in display_format, the entire validation falls over.

This is easy to demonstrate. Tab through the screen once, entering a valid
date in the date field. Then, when focus is on Field1, click on Field3. This
triggers the validation, but the date was never converted back to
input_format, so you will see the same error that you were getting.

I fixed it by adding a new variable to keep track of what the current format
is. If it is already in display_format, it does not need validating.

2. I explained in the notes that strategies may vary regarding *when* to
trigger the validation. For simplicity, I triggered it when Field3 gained
focus.

This has a side-effect. If you back-tab from Field3 to Invoice Date, the
value is converted to input_format. If you back-tab again to Field1, the
validation is not triggered, and the format is not changed.

In my live system I use a more sophisticated strategy so it is not a problem
for me. To make my sample program more robust, I changed it so that the
validation is triggered when Field1 *or* Field3 gain focus.

I have attached a revised program. If you can confirm that the problem is
solved, I will upload it to the wiki.

Also, after using the calendar popup, the combo button
appears to stay depressed (but still works).

I see that the wxPython demo has exactly the same problem. If you go to More
Windows/Controls > ComboCtrl, and then click the button on the custom button
bitmap, close the popup, and select one of the other ones, the button
appears to stay depressed. This happens on gtk2, but not on msw. Maybe Robin
can suggest a workaround.

Frank

datectrl_1.4.py (13.4 KB)

1. The program 'validates' the date by extracting the day/month/year
portions from the current value of the text control, and using them to
construct a wx.DateTime instance. If successful it redisplays the date using
display_format. If unsuccessful it displays an error message.

This assumes that the current value is in input_format. If it happens to be
in display_format, the entire validation falls over.

This is easy to demonstrate. Tab through the screen once, entering a valid
date in the date field. Then, when focus is on Field1, click on Field3. This
triggers the validation, but the date was never converted back to
input_format, so you will see the same error that you were getting.

I fixed it by adding a new variable to keep track of what the current format
is. If it is already in display_format, it does not need validating.

Should this not rather be tracked by variable

  self.input_needs_validation

which is set to false after validation and set to true on
both EVT_CHAR and when the calendar popup sets the date ?

In other words, what one would have expected self.is_valid
to do (except with reversed logic).

I have attached a revised program. If you can confirm that the problem is
solved, I will upload it to the wiki.

Works for me.

Karsten

···

On Fri, Sep 10, 2010 at 03:15:15PM +0200, Frank Millman wrote:
--
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD 4537 78B9 A9F9 E407 1346

Karsten Hilbert wrote:

>
> I fixed it by adding a new variable to keep track of what
the current format
> is. If it is already in display_format, it does not need validating.

Should this not rather be tracked by variable

  self.input_needs_validation

which is set to false after validation and set to true on
both EVT_CHAR and when the calendar popup sets the date ?

In other words, what one would have expected self.is_valid
to do (except with reversed logic).

I think I tried that and had a problem. The validation serves two purposes -
perform the validation, and if successful, redisplay the text control using
display_format.

If the control is converted to input_format, and the user simply tabs
without changing anything, the control still has to be converted back to
display_format, even if it does not strictly need validating.

I will sleep on it before I upload anything, and see if I can improve it.

> I have attached a revised program. If you can confirm that
the problem is
> solved, I will upload it to the wiki.

Works for me.

That's great.

Frank

Hi Karsten et al

Here is a revised version of datectrl_1.4 - I have found and fixed a few
more bugs.

I have another question for Robin regarding the customised bitmap button.
The following relates to gkt2 only.

Karsten has already reported that, once the button is selected, its
appearance stays 'selected' after moving off it. I have confirmed that the
demo shows the same behaviour.

I have now noticed another oddity. If you hover the mouse over the button,
it turns a tasteful shade of blue. I am using the default theme installed by
Fedora 10. Other themes may behave differently.

With the demo, if the mouse leaves the button, the background colour reverts
to the original. In my program, I have a tooltip that is displayed if the
mouse stays over the button for > 0.5 sec. If you move the mouse over the
button and then off within 0.5 sec, it behaves the same - it changes to blue
and then reverts. However, if you leave it there long enough for the tooltip
to be displayed, then when moving off, the tooltip disappears, but the
background stays blue!

It is not crucial to fix this, as the effect is cosmetic and will not upset
the average user. Still, it would be nice to fix it if possible.

Any suggestions?

Thanks

Frank

datectrl_1.4.py (13.8 KB)

Hi Karsten et al

Here is a revised version of datectrl_1.4 - I have found and fixed a few
more bugs.

I have another question for Robin regarding the customised bitmap button.
The following relates to gkt2 only.

Karsten has already reported that, once the button is selected, its
appearance stays 'selected' after moving off it. I have confirmed that the
demo shows the same behaviour.

I've been able to reproduce it in your sample on XP too. It looks to me like it is simply that showing the dialog is preventing the ComboCtrl from seeing the EVT_LEFT_UP or something so it doesn't know that it is supposed to redraw the button in the unclicked state. The ComboCtrl should probably do something like capturing the mouse on left-down and handling the EVT_CAPTURE_LOST/_CHANGED events as if it was a left-up.

You can probably work around this by not showing the dialog in OnButtonClick, and capturing the mouse and waiting for either capture-lost or the left-up event and doing it there instead if the mouse is still within the bounds of the button.

I have now noticed another oddity. If you hover the mouse over the button,
it turns a tasteful shade of blue. I am using the default theme installed by
Fedora 10. Other themes may behave differently.

With the demo, if the mouse leaves the button, the background colour reverts
to the original. In my program, I have a tooltip that is displayed if the
mouse stays over the button for> 0.5 sec. If you move the mouse over the
button and then off within 0.5 sec, it behaves the same - it changes to blue
and then reverts. However, if you leave it there long enough for the tooltip
to be displayed, then when moving off, the tooltip disappears, but the
background stays blue!

I'm also able to see it in your sample on XP, so it's not strictly a GTK issue either. When the tip window is shown it becomes the active window (and I think it captures the mouse) so it causes a EVT_LEAVE_WINDOW event for the combo button, and so it is redrawn without the mouse-over state even though the cursor happens to still be within its bounds. You could probably do something in your on_mouse_leave such that it does not call Skip in this case, but then you may need to force it somehow when the tip window is dismissed.

···

On 9/12/10 2:06 AM, Frank Millman wrote:

--
Robin Dunn
Software Craftsman