Link to home
Start Free TrialLog in
Avatar of PMH4514
PMH4514

asked on

Help writing "helper class" for UI Controls.

This question relates to accessing UI controls from a within a class that isn't derived from a prop sheet or dialog.

edit: Using VC++ 6.0

I've written a class CUIControlManager that has a method defined as:

UIToggleGroup(int* p_aID, BOOL a_bVISIBLE, BOOL a_bENABLE)
    int iIndex = 0;
    while (p_aID[iIndex] != -1)
    {
        CWnd* pControl = GetDlgItem(p_aID[iIndex]);
 //.. etc..


p_aID is a static array of control ID values. I basically want any property sheet classes throughout my app to be able to call this "helper" in order to facilitate all of my UI control operations. (my prop sheet classes have static arrays defined which basically model groups of controls. I can then pass a pointer to an array to this method to turn on/off enable/disable groups..

Anyway - this line:
    CWnd* pControl = GetDlgItem(a_ControlID);

crashes.
edit: the crash is an assertion failure:
      ASSERT(::IsWindow(m_hWnd));
inside WINOCC.CPP


I think this is because my CUIControlManager class perhaps isn't derived from an appropriate class? Do I have to derive it from a PropertySheet or CDialog? What if I don't want to do that, is there another paramater I can pass in or grab within the method in order to use the GetDlgItem(int nID,  HWND *pHWnd) interface instead of GetDlgItem(int nID) ?

I think the problem is that this method has no idea what window to go to to find the specified control ID?  I'm still very new to this, so I think I have the concept down, but the implementation is alluding me.  I thought about adding a 4th paramater to UIToggleGroup that took the ID of the Dialog that contains the control, but if that's a viable approach, how would I turn the ID of the dialog into an HWND pointer for GetDlgItem() ?

thanks
-Paul


Avatar of Member_2_1001466
Member_2_1001466

When is this helper function called the first time? The error seems to be related with not having created the controls yet which is true for the constructor. Dialogs (and property pages) have OnInitDialog () to do some initializing before displaying but when the control windows have be created.
Avatar of PMH4514

ASKER

within my InitDialog method.

BOOL CVideoPage::OnInitDialog()
{
    CPropertyPage::OnInitDialog();

    // a bunch of other stuff.
   
    if ( ERROR_SUCCESS != ( dwReturn = m_pSBehavior->Initialize( m_pView ) ) )

that Initialize call triggers some other calls, ultimately a few levels down where a thread is launched, within which HideUI() is called for the first time.  So, it's after CPropertyPage::OnInitDialog() has been called

The CVideoPage constructor looks like this:
CVideoPage::CVideoPage() : CPropertyPage(CScopePage::IDD)

--------

but I'd like to use it anywhere (anywhere after controls have been created via. InitDialog that is.) I guess the gist of this is, knowing the ID of a property sheet and a Control, how can I successfully call GetDlgItem which requires the handle to a window? Get a handle from an ID.  or am I completely mis-understanding this??

thanks




Avatar of PMH4514

ASKER

whoops. the constructor looks like:
CVideoPage::CVideoPage() : CPropertyPage(CVideoPage::IDD)
Two problems might be the reason:
MFC classes are not thread safe: a map from windows handles (HWND) to MFC classes is kept in thread local storage. From a HWND you can get the control variable CWnd* using FromHandlePermanent (). To get this to work you need to store the HWND instead of the control IDs of your controls in the array.

Or GetDlgItem can return a temporary object (when FromHandlePermanent would return NULL) which won't react like you want.

I guess the best solution would be to have the enabling done inside the property page class. It can be triggered from the helper function and latter can provide the controls to disable/enable but the actual function should sit inside the class. And associate control variables to each of those controls you want to enable/disable. (Perhaps this will resolve your problem already?)
Avatar of PMH4514

ASKER

well I did have this working when the method was within the property page class, but I had hoped to abstract it into its own class that any of my non prop-sheet or dialog derived classes can call, so that I don't have to have multiple copies of what amounts to the same code in each class. Maybe I'll all together abstracting this wrong. I basically have one property sheet that I'm primarilly concerned with. But there is a lot going on within this, different classes (non visual) which are controlling different parts of the system each have a visual representation within a given area on the single property sheet. I could write one class that models this property sheet, but that to me seems improper because I'd rather have classes be able to control their own UI devices, regardless that they are all within the same property sheet. (did that make sense?)

I do sorta like the idea of having an array of handles, that way I could pass a pointer to that array to my helper class and it wouldn't need to know about the parent window and it's being in a seperate thread would make no difference. right?  How would I re-write this:

static int RecordControlIDArray[] = { IDC_BUTTON_RECORD, IDC_BUTTON_STOP_RECORD, -1 };

to store the handles to those controls instead?

Avatar of PMH4514

ASKER

or.. just thinking outloud.. maybe each of my non-visual functional classes, rather than controlling it's UI elements itself, perhaps I could create a UI class.. so if I have CVideoCapture, I could also have CVideoCaptureUI - which itself would be derived from CPropertySheet pointing to the single prop sheet that encompasses the entire system?

CVideoCaptureUI::CVideoCaptureUI() : CPropertyPage(CMainProp::IDD)

or something to that effect.. thoughts?
Avatar of PMH4514

ASKER

oh and can you expand on this please:
>>And associate control variables to each of those controls you want to enable/disable. (Perhaps this will resolve your problem already?)
In a dialog or propery sheet you can create control variable using the class wizard. (Member variables tab) Select an ID and create a variable. Be sure to select control and not value as category. If I want to change control parameters like window text or color I normally use a control variable. And if I understand the help to FromHandlePermanent correctly it is the only way to get the correct control CWnd and not a temporay one.
The abstraction you want to do could be done using a base class you derive all your property sheets from that way the function is part of the class but has not to be written for each class seperatly:
CPropertySheet -> CYourUIPropertySheet -> CVideoCapture

Would this work too?
Avatar of PMH4514

ASKER

oh, I see what you mean. Actually I already do have member variables for all of my controls.  Are you saying that if I had an array of those member variables, I could pass a pointer to that array to my helper class and enable/disable those controls from my non dialog class?
Avatar of PMH4514

ASKER

>>CPropertySheet -> CYourUIPropertySheet -> CVideoCapture
>>Would this work too?

hmm.. that's a good idea. Will this allow me to have one physical property sheet, for the sake of argument, break it into 4 quadrants, each quadrant showing feedback based on functionality happening within entirely seperate classes?
Avatar of PMH4514

ASKER

is it a valid statement that any functional classes that have any kind of UI representation should be derived from CPropertySheet or CDialog?
>>hmm.. that's a good idea. Will this allow me to have one physical property sheet, for the sake of argument, break it into 4 quadrants, >>each quadrant showing feedback based on functionality happening within entirely seperate classes?

No. It allows you to have different classes have the same functionality (functions) without having to write them for eah one seperatly. In your case you should create an array of the controls in the constructor/OnInitDialog which will be controlled in this base class function. (The array needs to be a member var of the base class I guess).
>> Actually I already do have member variables for all of my controls.  Are you saying that if I had an array of those member variables, I >>could pass a pointer to that array to my helper class and enable/disable those controls from my non dialog class?

As long as those control variables are not accessed from a seperate thread it should be OK. Generally you should not change the GUI from another thread. Either race conditions or deadlocks will happen; If you don't synchronize you will have race conditions otherwise possible deadlocks. A better way is to send a message from the thread to the main window. In the handler you then change the UI from within the main thread.
>> is it a valid statement that any functional classes that have any kind of UI representation should be derived from CPropertySheet or   >> CDialog?

I don't see why. The problem you are facing is more related to the fact that you want to access members of those classes outside. For windows sake these members are normally not encapsulated with all the problems arising from it: it is not always clear where they are changed.

And there are more UI interface classes beside the controls used within a dialog or a property sheet: CFormView, CToolBar, CStatusBar and more?
Avatar of PMH4514

ASKER

>>No. It allows you to have different classes have the same functionality (functions) without having to write them for eah one seperatly.
yes, I understand that part. basic inheritance.

>>In your case you should create an array of the controls in the constructor/OnInitDialog
yeah, see I think this is what I am (I guess wrongly) trying to get away from. OnInitDialog would imply that I've derived my class from CPropertyPage (right?) I've been thinking that I could have a class that is NOT derived from CPropertyPage or CDialog  that itself contains the array of control ID's that it cares about. If that was a poor assumption, I was then thinking that multiple classes could be derived from the same visible property page.  In a nutshell I want to keep functional areas as seperated and stand-alone as possible with their own classes, while keeping in mind that their visual representations are running physically in different areas of the same window at the same time.

>>As long as those control variables are not accessed from a seperate thread it should be OK
yeah, they would be.

>> A better way is to send a message from the thread to the main window. In the handler you then change the UI from within the main thread.
I'm beginning to think this is what I'll need to do.  So you think all GUI related code should exist in a single CPropertySheet instance, regardless how unrelated the functionality the various elements represent actually are? Seperate well modeled classes can exist to drive this functionality and only send messages to this main property page to do all UI updates?

That would make sense to me, but from a purely OO standpoint I would prefer modules control thier own UI elements..

>>For windows sake these members are normally not encapsulated with all the problems arising from it: it is not always clear where they are changed.

yeah, I guess it's that kind of thinking that's new to me being so new to C++ and windows programming. I guess encapsulating "The UI" as a single entity, regardless the fact that independant elements within "The UI" represent such disparate functional areas would be a valid design decision in light of the windows issues. right?

>>And there are more UI interface classes beside the controls used within a dialog or a property sheet: CFormView, CToolBar, CStatusBar
yeah, but in my case all I'm concerned with is a single property sheet with alot of buttons, sliders, radio groups, progress indicators and such.

hmmm...

You can have several dialogs embedded in a dialog as childs for example. It is something I am doing in my current project. So different functionality resides in different windows (if those are dialogs, tab controls, property sheets I don't care). Look at different apps a lot of them group their functionality into different kind of windows. Some have dockable tools which might float other have popup dialogs. That part you can choose to your needs (and to your knowlegde, whichever is the more limiting fact).

Only if threads come into the game you need to seperate them from the GUI. A thread does its work but should not alter the GUI directly unless you make sure that the main thread is not touching these variables/controls. Espiacally framegrabbers and video drivers use the latter approach. The thread acquiring the images is directly writing to the window (HWND) on the main dialog for speed reasons but the main thread doesn't touch it anymore. If you mix access to the GUI from a thread and the main thread care has to be taken that no variables (like controls) are touched simultaniously. And the effect from synchronisation together with thread switching penalty could make the advantage of using a thread turn into a disadvantage.

If you have time look at the very useful articles on
http://www.flounder.com (MVP tips)
to get some insights on how windows/MFC works and what pitfalls are ahead of you.

Avatar of PMH4514

ASKER

>>A thread does its work but should not alter the GUI directly unless you make sure that the main thread is not touching these variables/controls

Ok that I hadn't thought of, it makes sense. But for the sake of argument, assume I know and can ensure through whatever locking mechanisms are appropriate, that the main thread doesn't touch the UI variables/controls that the worker thread updates . My worker thread is a class that is not derived from a dialog or property sheet.  How would I update a control on a property sheet from within the thread knowing only it's ID? or is that just a can of worms I shouldn't be going near? This is a threaded app, there is framegrabbing going on in a thread, which uses DirextX features to paint a large image on screen. There is external hardware which controls various movements and a thread is constantly polling that for changes and then updates the UI accordingly.. the video capture now is another thread, which is constantly buffering and writing out these captures frames, and it needs to update the record/stop/pause/save progress indicators on the screen as well. All of this is on the same Property Sheet.

Anyway, if there is an actual code answer for enabling a control from within a non visual class, I'd love to see it, but otherwise this has side-tracked to a very good design discussion.
:)
Avatar of PMH4514

ASKER

wow.. I'll be spending alot of time reading those essays on founder.com :)

thanks
ASKER CERTIFIED SOLUTION
Avatar of Member_2_1001466
Member_2_1001466

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
>> wow.. I'll be spending alot of time reading those essays on founder.com :)

found them 1.5 years ago but still reading them from time to time.
Avatar of PMH4514

ASKER

>>A simple message like UWM_VIDEO_ENABLE_CONTROLS or UWM_SCOPE_ENABLE_CONTROLS are then handled inside their respective >>dialogs/property sheets. Like that you can change the controls state from any part of the program in a known manner.

yup, that makes total sense. to this point I've acta
Avatar of PMH4514

ASKER

whoops.. I meant to end that last statement after "sense."