Praise the Masked Edit Control!

Markus Wankus wrote:

First off - my hat(s) off to everyone involved with the Masked Edit
Control. It is pretty sweet.

(On behalf of myself, Jeff, Werner, and several others who
helped build it-- Thanks!)

Of, course I wouldn't be emailing if I didn't have a question.
I am writing a database app which dynamically generates entry
boxes using masked edit controls. The problem I have is when
editing existing values from the database - it appears as
if the masked edit controls are always in "overwrite-mode".
That is - I fill the control with the text from the
database using .SetValue(), but clicking in the middle of the
edit control and typing does not insert text, it overwrites
what is there.

There are a lot of settings available for these things, so I
was wondering if there is one I am not seeing that changes
this behaviour.

I am using a simple mask of "X"*10 (for instance), and a
format code of
'_'.

The answer at the moment, I'm afraid, is no.
wxMaskedTextCtrl is designed as a fixed-position,
fixed-width control, with the ability to place fixed
position mask characters in the control and have input
go either up to or around them. Because of this, it
did not make sense to have it "insert" characters,
as this would violate the spirit of the fixed position mask.

Consider a mask of "(###) ###-####"; if you typed in
"1234567890", you get the value "(123) 456-7890" If you
then place the cursor in the middle of this value and start
typing, there is no place for the substring of to the right
of the cursor to go, and in some places in the string, such a
shift would move the mask characters, which is a no-no.

Instead, the character typed is matched against the allowable
characters in that position, and if kosher, that character
replaces the one in the current value (ie. overwrites.)
If the cursor is at a mask position, it automatically
moves to the next editable position and then validates
the keystroke against the legal characters at that position.

In hindsight, the control *could* keep the "input string"
and the value string separate, and insert into the input
string, then re-"paste" the input into the control, and
allow it to insert iff the value was still valid, but we
didn't do this in the current design.

I also suppose it *could* look at the candidate resultant
value after the right-shift of the substring, and if it
still satisfied the mask positions and the character types
for the template then allow the insert, but this would be
a lot over additional overhead in the processing because
it would have to reexamine every character to the right
of the cursor on every keystroke, and so I wouldn't count
on this any time soon...

But I'm still a little unclear on exactly what the
masked control is doing for you...

As you've specified it, ie. a mask of "X"*10 with
a format code of '_', makes little sense to me; this
means "a control of fixed input length 10, allowing
all characters, allowing spaces too. (redundant,
I think... :slight_smile: Is this what you want? Are you trying
to just limit input an arbitrary input string to
10 characters? Surely you don't need wxMaskedTextCtrl
for that... a simple EVT_CHAR handler for a wxTextCtrl,
looking at the length of the (stripped) string + 1 and
not calling event.Skip() if it exceeds the length
desired would accomplish this.

What am I missing?

/Will Sadkin
Parlance Corporation
www.nameconnector.com

The answer at the moment, I'm afraid, is no. wxMaskedTextCtrl is designed as a fixed-position,
fixed-width control, with the ability to place fixed position mask characters in the control and have input go either up to or around them. Because of this, it did not make sense to have it "insert" characters,
as this would violate the spirit of the fixed position mask.

···

On Thu, 10 Jul 2003 19:36:05 -0400, Will Sadkin <wsadkin@nameconnector.com> wrote:

The answer at the moment, I'm afraid, is no. wxMaskedTextCtrl is designed as a fixed-position,
fixed-width control, with the ability to place fixed position mask characters in the control and have input go either up to or around them. Because of this, it did not make sense to have it "insert" characters,
as this would violate the spirit of the fixed position mask.

···

On Thu, 10 Jul 2003 19:36:05 -0400, Will Sadkin <wsadkin@nameconnector.com> wrote:

The answer at the moment, I'm afraid, is no. wxMaskedTextCtrl is designed as a fixed-position,
fixed-width control, with the ability to place fixed position mask characters in the control and have input go either up to or around them. Because of this, it did not make sense to have it "insert" characters,
as this would violate the spirit of the fixed position mask.

···

On Thu, 10 Jul 2003 19:36:05 -0400, Will Sadkin <wsadkin@nameconnector.com> wrote:

Sorry for the duplicate posts - I am having a little trouble posting after drinking all evening it seems...

The answer at the moment, I'm afraid, is no. wxMaskedTextCtrl is designed as a fixed-position,
fixed-width control, with the ability to place fixed position mask characters in the control and have input go either up to or around them. Because of this, it did not make sense to have it "insert" characters,
as this would violate the spirit of the fixed position mask.

But I'm still a little unclear on exactly what the masked control is doing for you...

As you've specified it, ie. a mask of "X"*10 with a format code of '_', makes little sense to me; this means "a control of fixed input length 10, allowing all characters, allowing spaces too. (redundant, I think... :slight_smile: Is this what you want? Are you trying to just limit input an arbitrary input string to 10 characters? Surely you don't need wxMaskedTextCtrl for that... a simple EVT_CHAR handler for a wxTextCtrl, looking at the length of the (stripped) string + 1 and not calling event.Skip() if it exceeds the length desired would accomplish this.

What am I missing?

Actually - nothing. What you are saying makes perfect sense. I had simply implemented a nice and neutral interface that happened to use masked text controls by default. I think that qualifies as a serious overzealous use of them. ;o) Now that I think about it - from a design standpoint it makes perfect sense (sorry you had to type all that up to get it through my thick skull). If I was designing it I would do exactly the same thing (when I put on my special hindsight glasses...).

I think the solution in my particular case is to simply use a wxTextCtrl - like you said for the cases where I do not need masks. I do have phone numbers, etc. - but a lot of the fields are just alphanumeric fields of a certain length. Fortunately I programmed it to take a control name and a list of arguments so it *should* technically be a simple one-line change (he said, as his program spontaneously combusted..)

Thanks for the explanation - I really appreciate the response, Will.

Mark.

···

On Thu, 10 Jul 2003 19:36:05 -0400, Will Sadkin <wsadkin@nameconnector.com> wrote:

Sorry for the duplicate posts - I am having a little trouble posting after drinking all evening it seems...

The answer at the moment, I'm afraid, is no. wxMaskedTextCtrl is designed as a fixed-position,
fixed-width control, with the ability to place fixed position mask characters in the control and have input go either up to or around them. Because of this, it did not make sense to have it "insert" characters,
as this would violate the spirit of the fixed position mask.

But I'm still a little unclear on exactly what the masked control is doing for you...

As you've specified it, ie. a mask of "X"*10 with a format code of '_', makes little sense to me; this means "a control of fixed input length 10, allowing all characters, allowing spaces too. (redundant, I think... :slight_smile: Is this what you want? Are you trying to just limit input an arbitrary input string to 10 characters? Surely you don't need wxMaskedTextCtrl for that... a simple EVT_CHAR handler for a wxTextCtrl, looking at the length of the (stripped) string + 1 and not calling event.Skip() if it exceeds the length desired would accomplish this.

What am I missing?

Actually - nothing. What you are saying makes perfect sense. I had simply implemented a nice and neutral interface that happened to use masked text controls by default. I think that qualifies as a serious overzealous use of them. ;o) Now that I think about it - from a design standpoint it makes perfect sense (sorry you had to type all that up to get it through my thick skull). If I was designing it I would do exactly the same thing (when I put on my special hindsight glasses...).

I think the solution in my particular case is to simply use a wxTextCtrl - like you said for the cases where I do not need masks. I do have phone numbers, etc. - but a lot of the fields are just alphanumeric fields of a certain length. Fortunately I programmed it to take a control name and a list of arguments so it *should* technically be a simple one-line change (he said, as his program spontaneously combusted..)

Thanks for the explanation - I really appreciate the response, Will.

Mark.

···

On Thu, 10 Jul 2003 19:36:05 -0400, Will Sadkin <wsadkin@nameconnector.com> wrote:

--
Thanks,
Mark.

(just noticed I have been posting to the gmane newsgroup but not the list so perhaps noone saw this)

Can anyone explain what this event is? And further - what sort of built-in support for caching is already present in a virtual list control? I am in the midst of implementing my own caching logic and would like to take advantage of anything I can.

Also - on a related note - I can't seem to get wxListCtrl.FindItem() to do anything (I am using a virtual list control). If I have a list control with say 8 columns, what is FindItem trying to match on? The first column's text for each item? All 8 columns concatenated together? Any [row][col] text? I'm a little lost and the docs don't say much. Is it even supposed to work for a virtual list control?

Thanks,
Mark.

(just noticed I have been posting to the gmane newsgroup but not the list so perhaps noone saw this)

Hi all,

I was hoping for a little direction on this one. I have written a program which uses a virtual wxListCtrl to talk to a mySQL database and fill itself with data. I am having some trouble implementing the "virtualness" of this list control.

I know how it all works, and have got it to work for me, but this is the situation. Ideally, I need to do some sort of serverside query to determine the size of the virtual list control first, then I need to make sure the OnGetItemText() method can provide the right data. So, in a perfect world, I would just query the database for it in the OnGetItemText() method. This would be fine, but this method is called hundreds of times when the mouse is moving over the list control. It would be realtime, but obciously not good in the speed department...

What is the normal way of handling this situation? You need to keep some sort of cache of records somewhere, but how do you know when to update them? It seems this would be a common thing to do, and what the virtual list control was created for. I am obviously missing something.

Currently, I am not using the "virtualness" of the control at all. I do a database query, and store the entire result in a list. Then I set the length of the list control, and my OnGetItemText() just pulls the right value out of the local list of records.

There has got to be some sort of balance between realtime updating of the list control with the database - record by record, and a static local copy that isn't virtual at all.

Any help would be appreciated - pointing me to source or a tutorial would be great.

Thanks,

Mark.

Can anyone explain what this event is? And further - what sort of built- in support for caching is already present in a virtual list control? I am in the midst of implementing my own caching logic and would like to take advantage of anything I can.

OK - I found the rest of this in the wxListEvent docs. A cache hint event basically lets you know what items need to be available to the control with the event.GetCacheFrom() and .GetCacheTo() methods. I personally think these are kind of useless, however, because all they give you is the top item and the end item (plus or minus a few). I can get this from the regular methods, and it is a little too restrictive for say database access. It looks like I am going to have to continue to implement my own caching code which uses a larger cachesize and a little but of smarts.

Also - on a related note - I can't seem to get wxListCtrl.FindItem() to do anything (I am using a virtual list control). If I have a list control with say 8 columns, what is FindItem trying to match on? The first column's text for each item? All 8 columns concatenated together? Any [row][col] text? I'm a little lost and the docs don't say much. Is it even supposed to work for a virtual list control?

I haven't been able to figure this one out yet...

···

On Fri, 11 Jul 2003 00:36:00 -0400, Mark Melvin <markm@digitalwizardry.ca> wrote:

--
Thanks,
Mark.

Hi Mark,

Mark Melvin wrote:

Can anyone explain what this event is? And further - what sort of built- in support for caching is already present in a virtual list control? I am in the midst of implementing my own caching logic and would like to take advantage of anything I can.

OK - I found the rest of this in the wxListEvent docs. A cache hint event basically lets you know what items need to be available to the control with the event.GetCacheFrom() and .GetCacheTo() methods. I personally think these are kind of useless, however, because all they give you is the top item and the end item (plus or minus a few). I can get this from the regular methods, and it is a little too restrictive for say database access. It looks like I am going to have to continue to implement my own caching code which uses a larger cachesize and a little but of smarts.

I guess you found your answers on this, nevertheless here are some code
snippets of what I am currently doing with this based on suggestions
from Robin. I am sure this could be much improved. I am getting my data
from a Firebird DB and create a list (cepageDataCache) with primary key
and name (name as I like to do some search on this - not yet done), then
using OnCacheHint etc to update the listctrl (cepageData).

    def OnCacheHint(self, event):
        cacheFrom = event.GetCacheFrom()
        cacheTo = event.GetCacheTo()
        self.doCacheHint(cacheFrom, cacheTo)

    def doCacheHint(self, cacheFrom, cacheTo):
        self.cepageData = {}
        item = cacheFrom
        while cacheTo >= item:
            self.getData(item, self.cepageDataCache[item])
            item = item +1

    def cuveeAllOnItemSelected(self, event):
        self.currentItem = event.m_itemIndex

    def OnGetItemText(self, item, col):
        if col == 0:
            if not hasattr(self, 'cepageData'):
                self.doCacheHint(item, item)
            if self.cepageData.has_key(item):
                self.colinfo = self.cepageData[item]
            return self.colinfo[0]

        if col == 1:
            return self.colinfo[1]

Would be very interested to see how you deal with this all - I am sure I
could learn something.

Also - on a related note - I can't seem to get wxListCtrl.FindItem() to do anything (I am using a virtual list control). If I have a list control with say 8 columns, what is FindItem trying to match on? The first column's text for each item? All 8 columns concatenated together? Any [row][col] text? I'm a little lost and the docs don't say much. Is it even supposed to work for a virtual list control?

I haven't been able to figure this one out yet...

FindItem on a virtual list doesn't do ANYTHING as the list does not have
any data. One has to do this oneself by searching the cache or the DB.

You might want to search the archive of this group for "virtual", you
might get some more out of the different comments by Robin and others.

···

On Fri, 11 Jul 2003 00:36:00 -0400, Mark Melvin > <markm@digitalwizardry.ca> wrote:

Hi Werner,

Thanks so much for the reply! I have actually got something working quite well indeed. I am using mySQL with Ian Bicking's SQLObject module. This module has a few minor bugs which I have uncovered and fixed in the process but all in all is kinda cool. I have attached some code below in case anyone is interested.

I have also found this list control to be very arcane and obscure. Why can't there be simple, straightforward methods to ScrollToItem() for instance or to SelectItem()? I ended up rolling my own functions which others may find useful:

# ----
    def ScrollToItem(self, index):
        """Scroll so 'index' is in the middle of the display (where possible)."""
        top_value = max([0, index - self.GetCountPerPage() / 2])
        bottom_value = min([index + self.GetCountPerPage() / 2, self.GetItemCount() - 1])
        self.EnsureVisible(top_value)
        self.EnsureVisible(bottom_value)
# ----
    def SelectAndScrollToItem(self, index):
        """Scroll so 'index' is in the middle of the display (where possible) and highlight it."""
        self.ScrollToItem(index)
        self.SetItemState(index, wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED,
                                 wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED)

Here is how I am implementing my caching. I am actually caching ~3.5 pages of records. My customer requested that I do not fetch data while dragging the thumb, so there is a couple lines of code for this. He also calls it a chiclet - so that is where the stupid name came from... ;o):

# ----
    def OnCacheHint(self, event):
        # If we are dragging the thumb, just return - don't do database queries
        if self.DraggingChiclet:
            return
        # If both the start and end are already contained in the cache, we do not
        # need to update anything
        start, end = event.GetCacheFrom(), event.GetCacheTo()
        if self.IsIndexInCache(start) and self.IsIndexInCache(end):
            return

        # Set the pagesize based on size of control
        self.pageSize = int(self.GetCountPerPage() * 3.5)
        start = max([0, start - self.pageSize]) end = min([self.GetItemCount(), end + self.pageSize])
        self.cacheStartIndex = start
        self.objectCache = [w for w in self.ResultSet[start:end]]
# ----
    def IsIndexInCache(self, index):
        return index >= self.cacheStartIndex and index < (self.cacheStartIndex + len(self.objectCache))

# ---- These next three methods should be overridden for a Virtual ListCtrl

···

On Sat, 12 Jul 2003 13:20:53 +0200, Werner F. Bruhin <werner.bruhin@free.fr> wrote:

Hi Mark,

Would be very interested to see how you deal with this all - I am sure I
could learn something.

----
# ( at a minimum you must provide OnGetItemText() )
    def OnGetItemText(self, item, column):
        if not self.IsIndexInCache(item):
            if column == 0:
                return '<record not fetched>'
            else:
                return ''

        desired_object = self.objectCache[item - self.cacheStartIndex]
        return getattr(desired_object, desired_object._preferredColumnOrder[column].kw['name'])

Some of this may not make sense without seeing the whole class and being familiar with the SQLObject module. If you are interested I can send the whole class. This should be enough to explain the caching I implemented however. It seems to work quite well. 'self.ResultSet' is an iterator over the results of the SELECT query tied to the control, and executes SQL automatically when items are fetched from the list. This is all part of the SQLObject module and is quite useful.

--
Thanks,
Mark.

Mark Melvin wrote:

Hi Mark,

Would be very interested to see how you deal with this all - I am sure I
could learn something.

Hi Werner,

Thanks so much for the reply! I have actually got something working quite well indeed. I am using mySQL with Ian Bicking's SQLObject module. This module has a few minor bugs which I have uncovered and fixed in the process but all in all is kinda cool. I have attached some code below in case anyone is interested.

I have also found this list control to be very arcane and obscure. Why can't there be simple, straightforward methods to ScrollToItem() for instance or to SelectItem()? I ended up rolling my own functions which others may find useful:

The wxPython version of the wxListCtrl has these helper functions already added to it:

     def Select(self, idx, on=1):
         '''[de]select an item'''

     def Focus(self, idx):
         '''Focus and show the given item'''

     def GetFocusedItem(self):
         '''get the currently focused item or -1 if none'''

     def GetFirstSelected(self, *args):
         '''return first selected item, or -1 when none'''

     def GetNextSelected(self, item):
         '''return subsequent selected items, or -1 when no more'''

     def IsSelected(self, idx):
         '''return TRUE if the item is selected'''

     def SetColumnImage(self, col, image):

     def ClearColumnImage(self, col):

     def Append(self, entry):
         '''Append an item to the list control. The entry parameter should be a
            sequence with an item for each column'''

Here is how I am implementing my caching. I am actually caching ~3.5 pages of records. My customer requested that I do not fetch data while dragging the thumb, so there is a couple lines of code for this. He also calls it a chiclet - so that is where the stupid name came from... ;o):

Can you write this up in the wiki? Or how about making a generic mixin class (that expects to be able to call some data aquisition function) that will help others implement this in their own classes?

···

On Sat, 12 Jul 2003 13:20:53 +0200, Werner F. Bruhin > <werner.bruhin@free.fr> wrote:

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!