Link to home
Start Free TrialLog in
Avatar of abulka
abulka

asked on

User pointer in GUI controls

Do GUI controls in Visual C++ have a spare pointer/property that the programmer can use to point to his own object?

In Delphi, there is - and its called a 'tag' property.

It would typically be available for each GUI control.
Avatar of mahno
mahno

Right mouse button click on the control -> Class Wizard -> Member Variables -> Add Variable

mahno
Avatar of Zoppo
Hi abulka,

each control is a windowsm so each control has at least a unique window handle of type HWND.

You can achive this handle for controls using GetDlgItem() passing the parent's window handle and the control's id.

mahno showed a way how this can be done for dialogs or form views in MFC by subclassing the controls ...

In MFC you can also use CWnd::GetDlgItem() to obtain a CWnd-pointer to the control, but this MUST not be stored for later use, because it's a temporary CWnd pointer which later is released in framework's idle time processing. But, you can also get the HWND handle from a CWnd object via CWnd::GetSafeHwnd() and create a 'safe' (none-temporary) CWnd pointer with CWnd::FromHandlePermanent().

hope that helps,

ZOPPO
why are you using MS VC-- ? Use Borland C++ Builder and have all the comfort of Delphi (including Delphi component reuse) _and_ you'll get your tag field.
Hi,

Yes most do. For instance, for each item you insert into a listctrl you can associate a user defined item (DWORD) with it. Normally, the name of the function is SetItemData(...) which accepts as its parameters the item whose data you wish to set and the data item itself.

i.e. ListCtrl.SetItemData(10, 1001);

Which would set the 10th item in the list to have a data item = 1001.

Of course, you're more likely to put something meaningful into here, such as a pointer to a user defined item.
Avatar of abulka

ASKER

graham_k - I do actualy use Delphi / C++ Builder and recognise the ease of that.  I have to understand how to emulate the tag feature for VC++ programmers, too, however.

ScottyDawg - associating data with each element in a list is great - but wouldn't you have to cast/uncast the value into an integer?  Also, could your technique be used for a static text box, edit control or button?

Mahno - this subclassing trick sounds very clean.  Though it seems like you can only add one variable per control, and for a IDC_BUTTON1 the type must be CButton.  So how would I add a property called m_tag to a button which could hold a pointer to any object - or is it up to me to do some casting?

EVERYONE: I also got this technique as a possible solution from a friend - any comments on its validity?:  Apparently you can simulate a "tag" property in VC++ using SetWindowLong (and GetWindowLong) to store a required pointer. SetWindowLong(HWND hWnd, int index, LONG lData) where index = GWL_USERDATA 

thanks so far guys,
-Andy
Avatar of abulka

ASKER

I'd like a more comprehensive soution, that can be used with any GUI control, not just list items - though thanks for that tip anyway.
hi abulka,

I'm not sure what this 'tag' is with Delphi/C++ Builder, but if I understand you correct, I think you want to be able to save some custom data with a control. Is this correct?

If so, there are generally two possibilities:

1. Derive your own class from the control's class, i.e.:
class CMyButton : public CButton
{
....
}
and add any data and functionality you need. Then you only will have to subclass the control with an object of this class. How this can be done is similar to mahno's description, just select the new type (CMyButton) in the 'Variable Type' combo box of the 'Add Member Variable' dialog and make sure the needed files are included. Or you can use CWnd::SubclassWindow() to subclass it directly.
This method has the advantage that you don't have to care about memory allocation/freeing.

2. You can create an instance of an data object on heap and set its pointer as user data  using ::SetWindowLong() like this:
class CMyData
{
.... // implement data/functionality you need here
};
SetDataToControl( CWnd* pControl )
{
 CMyData* pData = new CMyData;
 ::SetWindowLong( pControl->m_hWnd, GWL_USERDATA, (LONG)pData );
}
CMyData* GetControlData( CWnd* pControl )
{
 return (CMyData)::GetWindowLong( pControl->m_hWnd, GWL_USERDATA );
}
(you'll need some error checking also)
The advantage with this that you can decide at runtime if a control needs the data or not, so somehow more effective memory usage, but you'll have to take care that the memory used is freed when the control is destroyed.

hope that helps,

ZOPPO
Avatar of abulka

ASKER

thanks for your comment zoppo - I'm still digesting it.  

Regarding the subclassing technique:

Why, using VC++ Right mouse button click on the control -> Class Wizard -> Member Variables -> Add Variable technique, is there only ONE variable per control allowed, and for a IDC_BUTTON1 why is that type CButton rather than anything I damn well please?

There's only one CWnd-derived variable allowed, and in addition for some controls (i.e. static text or edit control) more additional variables can be added for easier access to the control's value.

if the control is i.e. a button, then class wizard scans all the code of the projects to find CButton derived classes and you can use one of them. To do this, first derive a class from CButton via the 'Add New Class' button in the ClassWizard. In the menu select 'New...', then in the 'New Class' dialog enter a 'Name', i.e. CMyButton and select as 'Base Class' CButton.
After that add a member variable for a button's ID as before. You can now select between CButton and CMyButton as type for the member.

ZOPPO
Avatar of abulka

ASKER

Why all the fuss with the 'CWnd-derived variable' having to point to a CButton or CMyButton type?  In Delphi the tag property that is supplied with each GUI windows control is a pointer to TObject (the mother of all classes) and thus by judicious casting, a programmer can store anything they want in there.

Why does VC++ stuff restrict you so much?  Can't we make a pointer to ANYTHING (like TObject) and then leave it to the programmer to cast and use that property as he/she sees fit?
Avatar of abulka

ASKER

Please respond as an answer & I'll accept tomorrow.
ASKER CERTIFIED SOLUTION
Avatar of Zoppo
Zoppo
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of abulka

ASKER

Zoppo - I understand your comments about the merits of subclassing.  But I still don't understand why in the technique you show, the new property of the new subclassed control MUST BE A CButton or CMyButton type RATHER THAN a generic POINTER TO ANYTHING.  What do I do if I want the new pointer/property to point to a class FRED or class MyInfo?

Am I missing something?  You may have already answered this?  Sorry if my understanding is slow on this very specific (but important) point...
Hi abulka,

Now, the subclassing object must be at least CWnd derived to have subclassing functionality at all. I just suggest to use a CButton derived class for use with buttons because you the inherit the whole functionality of CButton.

>>What do I do if I want the new pointer/property to point to a class FRED or class MyInfo?

Just as I told before:

derive your class (easy with ClassWizard), i.e. CMyButton, and add members as you need like this:

class CMyButton : public CButton
{
....
   FRED m_FRED; // instance used here
   CMyInfo* m_pMyInfo; // pointer used here
// if you need you can add Get/Set functions for these, i.e.
   FRED& GetFred(){ return m_FRED; }
// you should even initialize at least the pointers in the constuctor to NULL, i.e.
   CMyButton():CButton(){ m_pMyInfo = NULL; }
}

Then again use ClassWizard to add a member variable of type CMyButton for a button control, i.e. m_MyButton1 for IDC_BUTTON1.

Now you can do anywhere you need it some calls like:

m_MyButton.GetFRED().DoSomethingWithFred();
m_MyButton.m_pMyInfo = new CMyInfo();

and also standard CButton calls like:

m_MyButton.SetBitmap();
m_MyButton.GetState();

a.s.o.

ok?

ZOPPO
Avatar of abulka

ASKER

Adjusted points to 75
Avatar of abulka

ASKER

Thanks for the great detail.  I see how you subclass the button using
  class CMyButton : public CButton {
}
but then a further issue is how to get it onto the tool palette so as to be able to drop it onto any form any time.  Perhaps I should ask this in another question.

Also, when you say:
> Then again use ClassWizard to add
> a member variable of type
> CMyButton for a button control,
> i.e. m_MyButton1 for IDC_BUTTON1.

I feel like I'm asking the same question again and again and not getting through.  In the above quotation you say "use ClassWizard to add a member variable of type CMyButton".  !!!   I want to use the class wizard to add a member variable NOT OF type CMyButton but of TYPE FRED, but the class wizard doesn't let me - for some reason it is forcing me to add a member variable of type CMyButton.  


Let's check my assumptions...

I am assuming that the classwizard is being used as an alternative and GUI/easy way to subclass a regular CButton.  I am assuming that we are subclassing CButton in order to add a property which points to say, class FRED.  I am assuming that adding a member variable means the same thing as adding a property.  I am assuming a property can point to any class I want.

I am assuming that the code:

class CMyButton : public CButton
{
   ....
   FRED m_FRED; // instance used here

can be generated automatically by the class wizard, and that adding a member variable (using the class wizard) will generate the code
  FRED m_FRED;
for me.  That's why I am confused when I HAVE TO specify CButton but CANNOT specify FRED when using the class wizard.
Hi again,

this phrase 'add a member variable of type ...' means add a member to the class of the dialog or form view. This new member represents one special button. This 'adding' action includes some code added to the dialog's/form view's class which handles default data exchange and subclassing.

To add a member to any class DevStudio only has a very simple dialog where you can enter (but not select) the type, the name and the access (protected, public ...) for a new member. To open this dialog click with right mouse button onto the class in the class list of the workspace and select the menu item 'Add member variable'...

ZOPPO
Avatar of abulka

ASKER

Got it - thanks.