Solved

Dynamically adding pages in a Wizard(using PropertySheet)

Posted on 2001-08-13
30
543 Views
Last Modified: 2013-11-20
I like to add pages (steps )in a user created Wizard (using CPropertySheet)dynamically.i.e, a particular page will be coming when the user clicks "Next"...until he clicks "Finish".So that particular page will be coming as many times as the user clicks next...as if adding nodes to a chain...the no. of nodes is not predefined and depends on the user.No prior input about the no. of nodes is allowed.How to implement that....pls help..
0
Comment
Question by:netwiz
  • 9
  • 8
  • 8
  • +1
30 Comments
 
LVL 42

Expert Comment

by:sedgwick
ID: 6380085
is there a difference between each page?

i assume that u wanna get some input in each and every page of your wizard.

if they are al the same and each one of them is collecting data from the user, what u need is a simple dialog with all the controls u need and the "next" & "finish" buttons, in case the user clicks next, u save all the input data and clean the dialog.

for example, in a phone book, each page could contain CEdit controls for name and phone number and a combobox for country and so on.

now u can create a struct which holds all those inputs, say like this:

struct person_card
{
CString phone;
CString name;
.
.
};

after clicking the next button u save the data like that:
person_card p;
p.phone = ....
p.name = ...

and then add it to a vector or array:
CArray<person_card*> m_personsArr;
m_personsArr.Add(&p);

hope i understand u correctly.

cheers

0
 
LVL 42

Expert Comment

by:sedgwick
ID: 6380093
in case each page is differently in a matter of different controls and stuff like that, its gonna be a bit complex.
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 6382803
To add a page to a wizard sequence, you must use the CPropertySheet::AddPage() fn.  This will add it to the end.  Although the Common Control supports the PSM_INSERTPAGE message, MFC does not support that functionality.  If necessary, you can use RemovePage() for all the following pages, then use AddPage() for the new one, then AddPage() to add back the removed ones.

I can describe a simple mechanism that will allow you to keep track of any number of these inserted pages, but
...
I believe that you should rethink your UI.

Let's say that these pages are for adding a new user to your system.  You want your wizard to let the operator add one or several users (username, password, favorite color).

I suggest that you provide a single Wizard page that shows a list of the currently added users and a button labeled [Add User].  It would bring up a modal dialog to allow input of the user info.  Upon OK, you will add the user to the list in the Wizard page.

Now in the Wizard page, disable the [Next] button until at least one user has been added.  There are a few other details, but this method is a much cleaner way to handle this type of scenario.

If you don't understand any part of what I have said, please post back and I'll explain (I see that you have insulted other experts with B's and C's and I am striving for a 4.0 grade poiunt average).  

Also, in your postback, please include an actual description of specifically what you are attempting to do -- that gives me a context to help me provide relevant examples.  Thanks!

-- Dan
0
 

Author Comment

by:netwiz
ID: 6382862
Dan, I can't change the mode of app as it is according to an accepted SRS. What you are saying is obviously a very good approach but I'm bound to follow the SRS.And for that I think sedqwick is showing me a right way.Can U give me a better solution of this than sedqwick?
0
 

Author Comment

by:netwiz
ID: 6382911
BTW sedqwick if the controls are different then how your idea can be implemented?Then you have to really "add" pages...dynamically.And that I think is a real problem with wizards.Do you have any solution?
0
 
LVL 42

Expert Comment

by:sedgwick
ID: 6382943
netwiz: if the pages / controls are different u either create them dynamicaly or create pages for the property sheet using the resource view.
u said before that u dont know how many pages u will need cause everytime the user click "next", another page is displayed, right?
0
 
LVL 42

Expert Comment

by:sedgwick
ID: 6382947
what is the essence of those pages?
what are they cotaining?
if the user won't click "finish" but "next", r u suppose to display page after page after page???
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 6382958
Dan said...
>>I can describe a simple mechanism that will allow you to keep track of any number of these inserted
pages...

Dan said...
>> Also, in your postback, please include an actual description of specifically what you are attempting
to do -- that gives me a context to help me provide relevant examples.  Thanks!
=-=-=-=-=-=-=-
I ask this often, and almost never get an answer.  I am puzzled as to why.  If you go to an auto mechanic, and say "I need help with the electrical system" and the mechanic asks, "Why? What about it needs to be fixed?"  ... would you ignore his question?

-- Dan

P.S. What is an SRS?  And if an SRS is bad, why not change it?
0
 

Author Comment

by:netwiz
ID: 6383437
Say there are several radio buttons in each page depending upon which next page comes where there are still something more to choose,depending upon which next page comes.And this process is never ending itself.It stops if the user clicks "Finish".

This is typical scenario.I'm not dealing with it currently... for my application same page will keep on coming until "Finish" is clicked.But as I described above problem can be posed that fashion also...what would be the solution then ?

Now back to my problem...as sedqwick has suggested I'm being able to show that "almost" as I want it but with one problem....When I press Back in say page no. n (ie,going to page no. n-1 and then again Next to go to page no. n the data previously stored in page n is not being displayed as the funcionality of the Next button is to get the data from the page.How to first display the previous value and then read(update) the new input (if anything new at all)?

To Dan...
SRS is Software Requirement Specification. It is how user wants a piece of software to behave ...it is not at all in your hand to change it as you like.And it is not always possible to tell all of it because it will be then a long story....I think I have described my problem fully.

0
 
LVL 42

Expert Comment

by:sedgwick
ID: 6383482
DanRollins: i think u know what SRS is (system requirement specifications)
0
 
LVL 42

Expert Comment

by:sedgwick
ID: 6383516
netwiz: u have no way doing it besides creating the page with its controls dynamicaly and stors the data in some data structure.

each page is a property page, after creating it with the controls on it, u add it to a property sheet so u can navigate in case of going "next" and "back".

first u have the property sheet ready as data member, in case u know how should look like the first page, create it using the resource view and add it to the property page.

for all the other pages (and for the first one in case its unpredictable too), create it dynamicaly with its controls, add it to your property sheet and each click on the "next" button, save the data in some data structure,
it might be difficult because u said that the pages are different.

when the user click "back" u can navigate and display the previous page in the property sheet using SetActivePage.

what kind of data there is on the pages?
is there any connection between the data of the pages?

in the bottom line its not a difficult task, the problem is not creating predefined pages but since they are differ from each other how would u save the input data?

i mean that if the pages are all dealing with numbers, u can have a vector/array of numbers, but u don't know what u gonna have, so...



0
 

Author Comment

by:netwiz
ID: 6383544
sedqwick, you said --- "for all the other pages (and for the first one in case its unpredictable too), create it dynamicaly with its controls, add it to your property sheet... "

But how to add those pages dynamically?I have tried adding pages dynamically in OnNext function using AddPage.It is not working.
0
 
LVL 42

Expert Comment

by:sedgwick
ID: 6383788
netwiz:

i created a property sheet in the resource view and also a dummy property page.
i managed display several property pages with different controls which i created dynamically,
BUT i couldn't find a way to save the controls and their position in case i wanna go to previous page.


0
 
LVL 42

Expert Comment

by:sedgwick
ID: 6383790
i used the same property page as a base to all the property pages but each one with different controls on it
0
 
LVL 2

Accepted Solution

by:
SamratAshok earned 200 total points
ID: 6384536
Hi netwiz

That is exactly what I did in my last project

and this was gist of my approach. (This is not how I implemented it, but this is the central idea!!!)
In my application there are something like 14 property pages that are loaded conditionally and
unfortunate as it may sound, it is the best UI design ever which everyone including most apathetic
client grp seemed to approve.

My Propsheet class looks like

class CMySheet : public CPropertySheet
{
    DECLARE_DYNAMIC(CMySheet)
protected:
    CPageOne m_FirstPage;

    CPageTwo m_SecondPage;
    CPageThree m_ThirdPage;

    void AddStarterPages();
public:
    void AddNextPage(BOOL bSecond)

...
}

// this function is called from all constructors
void CMySheet::AddStarterPages()
{
    // important to have wizard mode, since you cannot show all pages to user at once.
    SetWizardMode();

    AddPage(&m_FirstPage);
}

void CMySheet::AddNextPage(BOOL bSecond)
{
    if (bSecond)
    {
        int nIndex = GetPageIndex(&m_ThirdPage);

        if (nIndex > 0)
        {
            RemovePage(&m_ThirdPage);
        }

        AddPage(&m_SecondPage);
    }
    else
    {
        int nIndex = GetPageIndex(&m_SecondPage);

        if (nIndex > 0)
        {
            RemovePage(&m_SecondPage);
        }

        AddPage(&m_ThirdPage);
    }
}
....

In the property page
....
LRESULT CPageOne ::OnWizardNext()
{
    //somehow get access to the parent property sheet.
    CMySheet *pParSheet = DYNAMIC_DOWNCAST(CMySheet *, GetParent());

    VERIFY(UpdateData(TRUE));

    if (UserRequestedPageTwoType)
    {
        pParSheet->AddNextPage(TRUE);
    }
    else
    {
        pParSheet->AddNextPage(FALSE);
    }

    return CPropertyPage::OnWizardNext();
}
...
0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 49

Expert Comment

by:DanRollins
ID: 6389141
I think SamratAshok is on the right track.  My idea is to have something this in your CPropertySheet-derived class:
-=-=-=-==-=-=--=-=-=-=
in MySheet.h ...

CDlgPgPage1 m_pgStart;
CDlgPgPage2 m_pg2;
CDlgPgMany  m_pgMany[20]; // other ways to do this, or course
CDlgPgPage2 m_pgEnd;
int         m_nCntDups;

-=-=-=-==-=-=--=-=-=-=
in MySheet.cpp

CMySheet::InsertDupPg()
{
   Remove(m_pgEnd );
   AddPage( m_pgMany[m_nCntDups] );
   AddPage( &m_pgEnd );

   // init member vars for new insert
   m_pgMany[m_nCntDups].m_sSomeStr= "some defualt text"

   m_nCntDups++;
}

-=-=-=-==--=-=-=-===-=--==--==-

CMySheet::LoadPages()
{
   AddPage( &m_pgStart );
   AddPage( &m_pg2 );
   AddPage( &m_pgMany[0] );
   AddPage( &m_pgEnd );
   m_nCntDups= 1;

   //---------------- load page controls from vars
   m_pg2.m_sSomeVar= "hi there";

   m_pgMany[0].m_nSomeInt= 99;
   m_pgMany[0].m_nSomeStr= "this text";
}

CMySheet::SavePages()
{
   //---------------- save to vars from page controls
   sSomeString= m_pg2.m_sSomeVar;
 
   for (int j=0; j< m_nCntDups; j++ ) {
      //--- do something with members of ...
      n= m_pgMany[j].m_nSomeInt;
      s= m_pgMany[j].m_nSomeStr;
   }
}

-=-=-=-=-=-=-==--=-=
I don't have time to finish fleshing this out, but you should be able to see how it goes...  Insert one copy of the dup page at the start, then when the user has filled it in and requested to add a dup, call InsertDupPg().  You should be able to handle Next and Prev normally.

-- Dan
0
 

Author Comment

by:netwiz
ID: 6391505
Well let me try all the ideas....they are looking really great....
0
 

Author Comment

by:netwiz
ID: 6397104
one thing I like to know ...how to access the doc class from the propertypages...it's giving me a bit problem in implpementing the above ideas...as the main data will be stored in doc class.
0
 
LVL 2

Expert Comment

by:SamratAshok
ID: 6397385

How much data are we talking about? Most efficient way to do

it would be to modify property sheet (Wizard) constructor to accept a pointer to existing
strcutrure/record/class (whatever)  in memory, make a local copy of it in Wizard Constructor.
You will also need to save the user supplied pointer.

Provide Get/Set/Access functions in Wizard to access and modify your local copy of data.
This way Each Property page can manipulate the data that they handle.

When user clicks Finish (you will never have Apply) on property page, copy the data back to
location pointed by user supplied pointer.

Iam advicing making a local copy bcos that allows your /wizard etc to play with data freely
and also paves the way for implementation of Undo feature if you so need.

0
 
LVL 49

Expert Comment

by:DanRollins
ID: 6398556
>>how to access the doc class from the propertypages

I am pragmatic about this (not a C++ purist).  When I create the doc object (in my SDI projects), I save its 'this' pointer to a global variable (say gpMyDoc), which I make available to all source files by putting something like:

   extern CMyDoc* gpMyDoc;

in my main project .h file and

   CMyDoc* gpMyDoc= 0;
in my main CPP module and

   gpMyDoc= this;

in CMyDocument.OnNewDocument (or even in the ctor)

Others will tell you to use CView:GetDocument() or CMyMainFrame::GetActiveDocument() and coerce it to your document class.  And you should do that if you are working on an MDI project with multiple documents.

-- Dan
0
 
LVL 2

Expert Comment

by:SamratAshok
ID: 6398608
Hi Dan

I think you are missing a important point here. People don't avoid extern global vars
because they are non-pragmatist.

BUT because creating a global var is kind of inviting any of your current and future
team members to use the variable directly. As long as application is running, it is
never a problem, it becomes a nightmare when you need to upgrade the applications.

I personally hate having to cast and whenever I need to do that, I tend
to write a small function (mostly inline)  that takes hides casting inside itself.

Inspite of additional nuisance it creates, I would never never advocate idea of global vars
in any non-trivial project that will involve more than 2 developers in its life-time.

By using a internal member function, you expose only the functionality
that client requires. Of course, it implies that our job as a programmer to
ensure it is as reliable as possible.

===============================
P.S. This is intended to just be an opinion and I don't mean to disrespect anyone in anyway.
0
 

Author Comment

by:netwiz
ID: 6400058
SamratAshok pls explain ur technique of calling doc class from a propertypage in a bit details...I'm finding it hard to grasp the idea.
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 6400895
netwiz,
Most usage of Wizard pages and PropertySheets do not need to access an external document.  The standard technique is to have member variables for each PropertyPage.  

Load them up from from memory structures (or from your CDocument-derived object or the registry or whatever) in your CPropertySheet-derived object.  Call DoModal().  If the user clicks [OK] -- actually [Finish] in a Wizard -- then save all of the data acquired into that same object.

There is no need for working with a copy of the data because this load/save technique is inherently safe.  It does not modify the original data until the user gives the go ahead.

There is an alternative technique that you can play around with.  In the ClassWizard, there is a setting for a "Foreign Object."  You can identify your CDocument-derived object there.  It boils down to changing the Wizard-generated DDX calls from (eg):

  DDX_Text(pDX, IDC_EDIT1, m_sLastName)
to
  DDX_Text(pDX, IDC_EDIT1, pcMyDoc->m_sLastName )

It seems perfect, but you will find some peculiarities in how the ClassWizard works with these "foreign objects" -- for instance, the ClassWizard will certainly be unable to access an array of structures automatically.  You end up writing a bunch of DDX code yourself.  I've never found it to be worth the effort.

So, just use the load/save technique I described earlier.

-- Dan
0
 

Author Comment

by:netwiz
ID: 6405421
Actually its an MDI project and so Dan,your first suggestion is not working. Well I am trying one thing... I am storing variables in propertysheet and after calling DoModal I am trying to retrieve the data and store it to the doc class (CxyzDoc)from view class(CxyzView).

It is actually an Array of Arrays defined as:

typedef CArray<struct Node,struct Node> Nodes;
CArray<Nodes,Nodes> ChainNodes;
where Node is a struct.

In PropertySheet class I've defined an object as...

CxyzDoc::Nodes nodes;
I've added values to this "nodes"(ie,user added the values) from propertypages.After user clicks finish...ie, DoModal is over...I am trying to save the data as

CSrsDoc *doc = (CSrsDoc *) GetDocument();
CMyPropertySheet propSheet;

doc->ChainNodes.Add(propSheet.nodes);
Its giving an error:

error C2664: 'Add' : cannot convert parameter 1 from 'class CArray<struct CxyzDoc::Node,struct CxyzDoc::Node>' to 'class CArray<struct CxyzDoc::Node,struct CxyzDoc::Node>'

I am confused why its trying to convert these two...while they are just the SAME.

Anyone pls help.....
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 6405637
Stange error message! Is the error followed with a message about a missing copy constructor?  If so, you must write a copy ctor for your Nodes class.  

An easier way might be to make the ChainNodes an array of pointers to Nodes:

typedef CArray<Node,Node> Nodes;
typedef CArray<Nodes*,Nodes*> ChainNodes;

ChainNodes aaNodes;
Nodes aNodes;
Node node;

aNodes.Add( node );
aaNodes.Add( &aNodes );

>>I am confused why its trying to convert these two...

You are complicating the issue unnecessarily.  Doesn't each document have its own Nodes object?  And don't you need to work with just one Nodes array at a time -- doesn't the propertysheet work with just one document?) If so, there is probably no need to collect them into an array of arrays.  

-- Dan
0
 
LVL 2

Expert Comment

by:SamratAshok
ID: 6406270
There is nothing strange about this error.  Compiler does not know how to create your
second composite array. In order to implement arrays like this  you cannot simply typedef
the first array, but need to derive it and provide necessary functions.

When you pass composite objects around and do not use pointer or reference, compiler tries to make
a temporary copy of the object in memory and then pass the copy. For most cases, compiler
creates default, if you fail to provide one. In this case, compiler cannot create, so you must
provide! Look implementation of CArray for further insights (file:afxtempl.h)

Solution 1
=========
// I assumed some structure to keep my compiler happy
struct Node
{
    char *a;
};

// implement all of these functions
class Nodes : public CArray<struct Node,struct Node&>
{
public:
    Nodes()
    {
    };

    void operator=(Nodes &that)
    {
    };

    Nodes(Nodes &that)
    {
    };
};

...........................................

    CArray<Nodes,Nodes> ChainNodes;

    Nodes nodes;

    ChainNodes.Add(nodes);

***********************************************************

Solution2
==========

struct Node
{
    char *a;
};

class  Nodes : public CArray<struct Node,struct Node>
{
public:
    void operator=(Nodes &that)
    {
    };
};

.................................


    CArray<Nodes,Nodes&> ChainNodes;

    Nodes nodes;

    ChainNodes.Add(nodes);

****************************************************************

Only difference between the 2 solutions is placement of reference. In general,
if you use CArray, you rarely have both template args same. Also, not passing
reference or pointer is inefficient.

Now regarding passing to the PropertySheet, in your case, I think, you need to
pass pointer to Nodes (first array). You may have to scratch the idea of making
a local copy though.This part of your implementation is typical. Almost every property
sheet will edit a certain type of structure and client or base view/doc will hold
array/list/map of those structures. In your case, structure seems to be an array.

However, If everything Proppages can edit is contained within one instance of struct Node,
pass the pointer to struct Node.

In the later case, I also raise question for the need for second array.


Ciao
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 6407881
The error is strange in that, by itself it is puzzling -- "can't convert x to x" (it is already x!).  But coupled with the second line of the error message ("no copy constructor available") it does make some sense.

As a vetran programmer, I often drop back to "C Programming 101" to avoid complications.  When I need an array of objects, I declare it with an old-fashioned simple syntax:

    CNodes* apNodes[50]; //i don't expect more than 50 MDI docs
    int nNodesCnt= 0;
Now to add a new element,
    apNodes[nNodeCnt]= new Nodes;
    nNodesCnt++;

There is one specific advantage to this... the junior programmer who inherits this project will have no learning curve.  The template syntax throws many programmers (me included).  It yields strange error messages and you can't easily examine the array data with a debugger and the special functionality that it does provide is often not needed.

-- Dan
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 6418407
hi netwiz,
Do you have any additional questions?  Do any comments need clarification?

-- Dan
0
 

Author Comment

by:netwiz
ID: 6421042
Thank you all.DanRollins also has given some ideas but Dan, your programming style is not better than SamratAshok...so SamratAshok is getting this credit....also his idea of Array of Arrays is fabulous.

I took some time to implement the whole picture if some related problem arises...but thank God...it didn't happen.

Again thank you all very much....
0
 
LVL 2

Expert Comment

by:SamratAshok
ID: 6422109
Thanx Netwiz
and Thanx Dan for keeping the conversation rolling.
0

Featured Post

How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

Join & Write a Comment

Here is how to use MFC's automatic Radio Button handling in your dialog boxes and forms.  Beginner programmers usually start with a OnClick handler for each radio button and that's just not the right way to go.  MFC has a very cool system for handli…
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 …
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.
It is a freely distributed piece of software for such tasks as photo retouching, image composition and image authoring. It works on many operating systems, in many languages.

762 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

Need Help in Real-Time?

Connect with top rated Experts

17 Experts available now in Live!

Get 1:1 Help Now