?
Solved

CMyListCtrl

Posted on 2003-03-25
33
Medium Priority
?
473 Views
Last Modified: 2013-11-20
Hi.
I've derived CMyListCtrl from CListCtrl because i wanted to edit editems etc
but
i've come up with the following problem, lets assume there are 2 colums in the listview ctrl:
Number | BirthDate
01 | 12/08/1980

How can i be sure that only numbers are typed into the number column and only dates (0-9 & "/") in birthdate column?


Greetz
Jens
0
Comment
Question by:JayBeez
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 22
  • 11
33 Comments
 
LVL 31

Expert Comment

by:Zoppo
ID: 8201138
Hi JayBeez,

you can derive a new class from CEdit, implement there data
validation i.e. with WM_CHAR handler and use this derived
CEdit to create the edit box for edit-item functionality.

ZOPPO
0
 

Author Comment

by:JayBeez
ID: 8201186
Ok, lets say i've made a subclass from CEdit called 'CMyEdit'.  
How can i know in the subclass if we are dealing with an edit label in the number column or a edit label in the date column? Because validation is different on each type.

Thanks for the fast response


0
 
LVL 31

Accepted Solution

by:
Zoppo earned 200 total points
ID: 8201206
You'll have to dedicate this yourself. I would add a functionality
to set the kind of input the CMyEdit is used for, i.e.

class CMyEdit : public CEdit
{
  ...
public:
  enum eDataType
  {
    eDefault,   // a default edit box
    eNumber,    // for numbers
    eDate,      // for dates
  };

private:
  eDataType     m_Type;
public:
  eDataType     GetDataType() { return m_Type; }
  void          SetDataType( eDataType type ) { m_Type = type; }
  ...
};

Then, where you create the edit box you can use SetDataType()
to set the type of data for which the edit box should be used.

And in message handlers for the CMyEdit you can use GetDataType()
to determine the current used type of data to implement
different functionality.

hope this helps,

ZOPPO

0
Get MySQL database support online, now!

At Percona’s web store you can order your MySQL database support needs in minutes. No hassles, no fuss, just pick and click. Pay online with a credit card.

 

Author Comment

by:JayBeez
ID: 8201342
oh, just adding a member variabele to the subclass
never thought it would be that easy :)

So first, i have to check in the CMyListCtrl which kind of column item we are dealing with, and then set the private var. of the subclass correct.
0
 

Author Comment

by:JayBeez
ID: 8201362
oh, just adding a member variabele to the subclass
never thought it would be that easy :)

So first, i have to check in the CMyListCtrl which kind of column item we are dealing with, and then set the private var. of the subclass correct.
0
 

Author Comment

by:JayBeez
ID: 8201377
oh, just adding a member variabele to the subclass
never thought it would be that easy :)

So first, i have to check in the CMyListCtrl which kind of column item we are dealing with, and then set the private var. of the subclass correct.
0
 

Author Comment

by:JayBeez
ID: 8201427
oh, just adding a member variabele to the subclass
never thought it would be that easy :)

So first, i have to check in the CMyListCtrl which kind of column item we are dealing with, and then set the private var. of the subclass correct.
0
 
LVL 31

Expert Comment

by:Zoppo
ID: 8201429
Yes ... that should work ... the only problem there could be
if you then want to use the control with temporary windows MFC
creates with calling functions like GetDlgItem().

So, if you want i.e. to use this CMyEdit in dialogs and access
it like

CMyEdit* pMyEdit = (CMyEdit*)GetDlgItem( IDC_EDIT );

this won't work because the CWnd returned by GetDlgItem() is
not a CMyEdit.

If so you'll have to implement it another way i.e. by holding
the 'state'-flag with the window using GetWindowLong() and SetWindowLong() with GWL_USERDATA.
0
 

Author Comment

by:JayBeez
ID: 8201450
oh, just adding a member variabele to the subclass
never thought it would be that easy :)

So first, i have to check in the CMyListCtrl which kind of column item we are dealing with, and then set the private var. of the subclass correct.
0
 

Author Comment

by:JayBeez
ID: 8201466
oh sorry about posting twice.

I'll try it out for sure.

Thanks for the fast help. :)

Cya
Jens
0
 
LVL 31

Expert Comment

by:Zoppo
ID: 8201467
BTW, just a hint ... whenever you refresh this page in your browser
(i.e. with F5) you're asked whether to re-post the form data.
Therefore everytime your last comment is added anew.

It's better to refresh the page with the ' Reload This Question'
link at the top-left of the page.
0
 

Author Comment

by:JayBeez
ID: 8216211
Hi

Still got a question

To remember you of tThe idea: if the users clicks in the list and start editing the label, then he may only enter data which is correct for that certain column. For example only numbers in 'Num' column.

At the making of the columns, can i set for example (like in visual basic) a tag for each column (an help variabele)?
That would be usefull .. then I can check in the OnBeginLabelEdit which type of column we are talking about and adjust my validation to that.

Greetings
Jens
0
 
LVL 31

Expert Comment

by:Zoppo
ID: 8216355
hm ... unfortunateley I don't think you can store any user-data
for columns in a CListCtrl ... maybe you can use the list control's
header control ... it has a function CHeaderCtrl::SetItem()
which takes a HDITEM structure which has a member
'LPARAM lParam' which can store application-dependend data.

Other option would be to keep an array of such flags with the
CMyListCtrl ... you would have to update this array each time
columns are added/removed/moved...

hope that helps,

ZOPPO
0
 

Author Comment

by:JayBeez
ID: 8216488
Which would be the best solution and possible to integrate?
0
 

Author Comment

by:JayBeez
ID: 8216603
I already tried something out, didn't work but was a try ;)

void CDlgM4_1::SetColumnData(UINT CtrlId)
{
  CHeaderCtrl* pHeader;
  pHeader = (CHeaderCtrl*)(GetDlgItem(CtrlId)->GetDlgItem(0)); //Header control is a child window of the list view control and always has the ID 0.

 int iAantal;
 iAantal = pHeader->GetItemCount();

 HDITEM* MyItem;
 MyItem->pszText = "NameColumn";
 MyItem->lParam = 'Text';

 pHeader->SetItem(0, MyItem);
}

It compiles well, but it crashes after creating the structure.  I think it's something like this .. is this already good?


Greetings
Jens
0
 

Author Comment

by:JayBeez
ID: 8216663
sry, i think it works already .. i see the column name has changed . I'll test it out and see if it works correctly.

Shouldn't be HDITEM*, but HDITEM
HDITEM MyItem;
MyItem.mask = HDI_TEXT;
MyItem.pszText = "NameColumn";
MyItem.lParam = 'Text';

pHeader->SetItem(0, &MyItem);


Thanks for the fast help :) :)
0
 

Author Comment

by:JayBeez
ID: 8216727
still a little question ...

this here:
 CHeaderCtrl* pHeader;

do i have to delete pHeader when i'm at the end of the function, or does MFC do that?
0
 

Author Comment

by:JayBeez
ID: 8216847
and another question ..
'LPARAM lParam' .. i can set lParam = 'NUM';
everything works fine, he accepts and runs well

But in my 'CListCtrl' EditSubLabel(nRow, nCol):
...
HDITEM MyItem;
MyItem.mask = HDI_TEXT;
pHeader->GetItem(nCol, &MyItem); //Krijg het item-info door
CString res;
res = MyItem.lParam;
...

He puts some weird ascii code into the CString.  I don't know this 'LPARAM' data type, how can i set it correctly and get it correctly?

Greetings
Jens
0
 
LVL 31

Expert Comment

by:Zoppo
ID: 8216949
Hi again,

don't delete the pHeader ... it's a temporary window objects
handled by MFC and will be deleted automatically next time the
application is idle.

Then I think you can simply set the 'mask' of the HDITEM to
only modify the 'lParam' , i.e.


// I would use a enum for the edit-type, i.e.
enum eEditType
{
 eDefault = 0,
 eNumber,
 eDate,
// ... here you later can add more types, i.e. for
// filenames, email adresses a.s.o
};

void
CMyListCtrl::SetColumnType( int iColumn, eEditType eType )
{
 CHeaderCtrl* pHeader = GetHeaderCtrl(); // is a member of CListCtrl

 if ( NULL == pHeader )
 {
  // error
  return;
 }

 HDITEM item;
 item.mask = HDI_LPARAM;
 item.lParam = (LPARAM)eType;
 VERIFY( pHeader->SetItem( i, &item ) );
}

eEditType
CMyListCtrl::GetColumnType( int iColumn )
{
 CHeaderCtrl* pHeader = GetHeaderCtrl(); // is a member of CListCtrl

 if ( NULL == pHeader )
 {
  // error
  return;
 }

 HDITEM item;
 item.mask = HDI_LPARAM;
 if ( FALSE == pHeader->GetItem( i, &item ) )
  return eDefault;
 return (eEditType)item.lParam;
}

// then, anywhere after the columns were created you can
// set the individual column's types, i.e.
 ...
 // lets say m_myList is the CMyListCtrl-object attached to the list
 m_myList.SetColumnType( 3, eNumber );
 m_myList.SetColumnType( 5, eDate );
 ...

// and, later, in the OnBeginLabelEdit you can
// simply ask the list with GetColumnType( <the column nr> )
// about its type


About your last comment: You must take care with the types
you use: LPARAM is a long integer type, so a 32-bit integer. A string is an array of 'char' without a fixed length.

It's possible to cast a string to a long, but then not the
string is copied but the pointer to the string ... this is
very dangerous.

ZOPPO
0
 

Author Comment

by:JayBeez
ID: 8216977
Ok, thank you very much ZOPPO.

I really, really appreciate your effort and time. I'm gonna program this one when i'm finished eating :)

Thanks


Greetings
Jens
0
 

Author Comment

by:JayBeez
ID: 8217459
Works great now!!!!


Thanks alot Zoppo :)
0
 

Author Comment

by:JayBeez
ID: 8217561
Me again Zoppo ;)

Nothing to do with the previous problem though :)

I have a function called 'IsFilledInTextADate(strText)' made in the past in a certain class. That function would check if a date is in format dd/mm/yy or dd/mm/yyyy and return true or false if ok.

Now i would like to use that function in another class too by calling IsFilledInTextADate(strText).

I know i can copy paste the code to the other class .. but that's not the intention of C i think :)

How could i do this?

Greetings
Jens
0
 
LVL 31

Expert Comment

by:Zoppo
ID: 8217649
Well, must this function be the member of a class? Does it
access any functions/members of that class?

If not you have two possibilities:
1. make it simply a global function, i.e.:
// declaration in header, i.e. 'global.h"
extern BOOL IsFilledInTextADate( CString strText );

// implementation in .cpp, i.e. 'global.cpp'
BOOL IsFilledInTextADate( CString strText )
{
 ...
}

you can then use the function in any .cpp file which
includes 'global.h'

2. make it a static member function of a class you think it
should belong to, i.e.:
// declaration in header
class CMyClass
{
 ...
 public:
  static BOOL IsFilledInTextADate( CString strText );
 ...
};

// implementation in .cpp
BOOL CMyClass::IsFilledInTextADate( CString strText )
{
 ...
}

// usage in any other .cpp file
#include "myclass.h"
void
CAnyOtherClass::AnyFunction( CString str )
{
 if ( !CMyClass::IsFilledInTextADate( str ) )
  AfxMessageBox( "Not a date!" );
 ...
}

I think often its useful to build logical groups of global
function using this method.

ZOPPO
0
 

Author Comment

by:JayBeez
ID: 8217828
They don't access member variables in the class.

I have several functions in that specific class who sometimes call each other.
The main function calls several functions like:
a function which checks if the string is in "dd/mm/yy" of "dd/mm/yyyy",
another function checks if the month is between 1 and 12, another function checks if the day is between 1 and 31 and then the main function returns if everything is ok or not.

What would be the best thing to do? First or second solution?
0
 

Author Comment

by:JayBeez
ID: 8217849
Well, my class is named 'CCheckDate'.  I used the class to subclass a CEdit field.
But now i can use some code from that class to check date fields in my CMyListCtrl (eDate remember ;)).
0
 
LVL 31

Expert Comment

by:Zoppo
ID: 8217891
Well, that's hard to say ... I think it doesn't make a difference
for the result. It's just how you like to handle it.

Using the static class function offers one feature which
other doesn't: you can specify the function as private, protected
or public ... but, I suspect this doesn't matter for your
need.

Using 1. is generally ok but not really good C++ style ...

I would use 1. but within a namespace, i.e.
// declaration in header, i.e. 'global.h"
namespace myGlobal
{
 extern BOOL IsFilledInTextADate( CString strText );
};

// implementation in .cpp, i.e. 'global.cpp'
BOOL myGlobal::IsFilledInTextADate( CString strText )
{
...
}

// usage anywhere
#include "global.h"

...
void
CAnyClass::AnyFunction( CString str )
{
if ( !myGlobal::IsFilledInTextADate( str ) )
 AfxMessageBox( "Not a date!" );
...
}

IMO that's good C++ style...

ZOPPO
0
 

Author Comment

by:JayBeez
ID: 8218005
ok i'm gonna use 'namespace' .. i don't see the advantage of it though but why not :)

The namespace MyName
{
  //functions here
};

may i add this in the class file? or do i have to make a separate file with the definition and implemention of these functions?
if ( seperate file) then
  how do i add this to project?

Jens
0
 
LVL 31

Expert Comment

by:Zoppo
ID: 8218438
well, in your case the advantage is small ...

AFAIK this technique was intended to avoid name collisions,
i.e. if you plan to program a library or a DLL or anything
else which could be used by other programmers to be built
into their project they would get a lot of compiler errors
if anything in your code has the same name as existing things
in their code.

A good sample is STL:
First STL-Versions didn't use namespaces. So, code like i.e.
 ...
 CListCtrl* list = (CListCtrl*)GetDlgItem(...);
 ...
produced compiler errors due to collision of STL-list and a
variable name (would be the same as writing 'int CWnd = 5').
Later versions of STL are implemented in namespace 'std' so
you could write things like:
 ...
 CListCtrl* list = (CListCtrl*)GetDlgItem(...);
 std::list <int>  intList;
 ...
without collisions.

Now, I don't think may people will call their functions
IsFilledInTextADate() (:o) ... but still it's good style.
IMO programming in a good style prevents a lot of bugs
and very much helps with i.e.:
- searching bug
- implementing new features
- porting
a.s.o

ZOPPO
0
 

Author Comment

by:JayBeez
ID: 8218644
yeah you are right (again :p)

I've put something like this:
#includes
namespace MyUglyName
{
 #define ...
 class CCheckTypeOfDate:public CEdit
 {
  public:
  ...
  public:
   ...
    bool IsTekstEenDatum(CString strDatum);
  ...
  private:
   ....
  };
}
for the class with the functions i want to use in other files too.

then in an other .h file I include that .h file of the previous class and i add the namespace to a testfunction in the .cpp file:

but i receive an error when i want to access a class method of the subclass

if ( MyUglyName::IsTekstEenDatum(strTekst) )
{
 bAntwoord = true;
}

Error:
IsTekstEenDatum' : is not a member of 'MyUglyName'

i don't get it .. i included the .h file + i put the namespace name in front + "::"

Any idea?
0
 
LVL 31

Expert Comment

by:Zoppo
ID: 8223136
well, the problem here is it is still member of a class ...
as you try to use it it must be global:

// in header
namespace MyUglyName
{
 // not in a class
 extern bool IsTekstEenDatum(CString strDatum);
}

namespace MyUglyName
{
 // not in a class
 bool IsTekstEenDatum(CString strDatum)
 {
   // whatever the function does
 }
}

ZOPPO
0
 

Author Comment

by:JayBeez
ID: 8223369
Ok, that works great now. The wrong thing i've done was putting the global ones still in the class, should have been out of the class indeed! Thanks :)

I've just coded something, everything which was typed into a edit field is moved into a CStringArray.


m_pTextArray->RemoveAll();

for(int i = 0 ; i <acMandaten.GetLineCount() ; i++)
{
 int iLen = acMandaten.LineLength(acMandaten.LineIndex(i));
 acMandaten.GetLine(i, strTmp.GetBuffer(iLen));
 strTmp.ReleaseBuffer(iLen);
 m_pTextArray->Add(strTmp);
}
int iAantal = m_pTextArray->GetSize();
if((m_pTextArray->GetAt(iAantal - 1)).IsEmpty())
{
 m_pTextArray->RemoveAt(iAantal - 1);
}
Declaration:
CMyEdit acMandaten; //my subclass CMyEdit
CStringArray *m_pTextArray;
//This is a pointer to a CStringArray

CStringArray test;
test.RemoveAll();
CDlgMyDlg Dlg;
dlg.m_pTextArray = &test; //here will pointer point to
dlg.DoModal();

// and then showing what is in the CStringArray
for(i=0; i<test.GetSize();i++)
{
 AfTextOut(pDC, 280, nHoogteLijn, (const char *)(test.GetAt(i)));
 nHoogteLijn -= 40;
}


Everything works well, at first sight, but i did a little testing .. and i discovered that if you type in the edit field something little like '1' .. just 1 char. that char. won't be put in the array when you print it to the screen.
if you type in '1a', everything is moved into the array and is ok.

I don't see what the problem could be. Do you know ZOPPO?
(i provided code so you could test it out easily too)

Greetings
Jens
0
 
LVL 31

Expert Comment

by:Zoppo
ID: 8223778
Hm ... as far as I tested it (I only TRACEd out the strings)
I can't see that problem.

Do you use UNICODE?
What does the AfTextOut() function do?

Maybe even the problem is how you display the text ... to find
this out you should even TRACE out the texts too.

Do you call this part with 'AfTextOut' within some OnDraw() or
WM_PAINT message handler? If not it's not ensured that parts
of drawing stuff is overdrawn/erased by other drawing-operations
from your or any other app or the system.

ZOPPO

BTW: I think it's getting time to close this 'easy' fifty-points
question
0
 

Author Comment

by:JayBeez
ID: 8223863
void CModelView::AfTextOut(CDC *pDC, int x, int y, const char *szText, UINT nFormat, int Width)
{
 RECT    TextRect;
 TEXTMETRIC  tm;                  
   
 pDC->GetTextMetrics(&tm);
                   
 TextRect.left = x;                                              
 TextRect.bottom = y;
 TextRect.right = min(x + Width, 2000);
 TextRect.top = y + tm.tmHeight;
 if (nFormat == DT_LEFTCLIPPED)
    pDC->DrawText(szText, -1, &TextRect, DT_LEFT);
 else
    pDC->DrawText(szText, -1, &TextRect, nFormat | DT_NOCLIP);    
}
I call this part in the OnDraw() but i'm gonna test it out with some TRACE statements.

Greets
Jens                      
0

Featured Post

Percona Live Europe 2017 | Sep 25 - 27, 2017

The Percona Live Open Source Database Conference Europe 2017 is the premier event for the diverse and active European open source database community, as well as businesses that develop and use open source database software.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

In this article, I'll describe -- and show pictures of -- some of the significant additions that have been made available to programmers in the MFC Feature Pack for Visual C++ 2008.  These same feature are in the MFC libraries that come with Visual …
Introduction: Finishing the grid – keyboard support for arrow keys to manoeuvre, entering the numbers.  The PreTranslateMessage function is to be used to intercept and respond to keyboard events. Continuing from the fourth article about sudoku. …
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
Do you want to know how to make a graph with Microsoft Access? First, create a query with the data for the chart. Then make a blank form and add a chart control. This video also shows how to change what data is displayed on the graph as well as form…
Suggested Courses
Course of the Month13 days, 13 hours left to enroll

801 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question