Link to home
Start Free TrialLog in
Avatar of Testsubbu
Testsubbu

asked on

SDI - Updating Student Record

I have created a dialog based MFC program, which will read in a student record (name, num of grades,type of test, letter grade and number grade) and then edit each of the data members (by calling a custom dialog through a button). The way I handled the "reading from file" and writing to file" from the Student class is by defining:
istream & operator >> (istream& is,CStudent& s) to input data and
stream & operator<<(ostream& os, const CStudent& s) to output data.
I would then open a file from the Dialog application this way:
ifstream ifl;
ifl.open("student1.txt");
Then I would call ifl >> s from the Project1Dlg.cpp to load data into the object "s" (which is of type CStudent), then would retrieve each of the data members and format them to be displayed onto the text boxes and list boxes. In the same way, I would call ifl<<s  from Project1Dlg.cpp to restore the file with the modifed data members.

The student record in the file student1.txt looks like this(the variables are listed along with it):
-----------------------------------------------------------------------------------------------------------
Joe Green      // mFirstname    mLastname
14                 // mNumofGrade
Test1 A 100   // mTestaname   mLetGrade   mNumGrade
Test2 A 99
Test3 A 98
Test4 A 97

Question:
Now, I would like to use the student record program but with an SDI application (doc view architecture).
I would like to make files of type .rec to be only displayed when selecting "open" (which I know how to do by changing the properties in the String Table).
The text of the file has to be displayed in the View window (raw format), and I would like to call a custom dialog from the menu choice to edit all the data in the single record and update them in memory.
I want to eliminate the Save/Exit buttons that I used in the dialog box, and want to save using the "Save" and "Save as" menu choices already available in the SDI app.
I've played around well with dialog boxes but am a little confused about how to handle SDI (doc/view and synchronization) ?? Should I used "CEditView" as my base class here? If so, how would I read the data from the CEditView and then store it into variables, which would need to be updated when I modify all the data in the record with the Custom dialog box?? How could I write back to the CEditView from the Control variables in the Custom dialog box??

Any sample code would be really useful. thanks for your help!

-shan
Avatar of Jaime Olivares
Jaime Olivares
Flag of Peru image

Hi shan
If your are working with MFC Doc/View framework then I suggest you not to use ifstream but CArchive.
CDocument derived classes are ready made to support serialization (load/save operations) by implementing the Serialize() function member. If you have created your application with AppWizard then it is there right now.

For a simple, first example of serialize have a look to:
http://www.codeproject.com/docview/asciiserialization.asp

To make your app use .rec files (assuming you have created your app with AppWizard, search in your resource a resource string called IDR_MAINFRAME, and read this article:
http://support.microsoft.com/default.aspx?scid=kb;en-us;129095

Good luck,
Jaime
Avatar of Testsubbu
Testsubbu

ASKER

Thanks for the input.
But in my case, should I use CEditView , CView or CFormView as the base class for the View Class ??
If I use CView, will the seriliaze() function(with customcode to readdata and writedata) automatically read and write the data?? Wouldn't CEditView be a better choice ??
Wouldn't CEditView be a better choice ??
CEditView will not provide you better serializing, since it is made in CDocument class, but for displaying purposes is a better choice for you.
To use CEditView, you have to create a new app with AppWizard, and ensure to replace CView with CEditView before finishing the Wizard.

At CDocument, your serialize function must read all file contents into a CString member, then you will call UpdateAllView, this will generate an OnUpdate() event in every view (just 1 in MDI), so you have to implement CEditView::OnUpdate() to read the doc's string and store into view with something like:
      GetEditCtrl()->SetText(yourdoc->thestring);
I tried reading the file contents into a CString member in the CDocument: Serialize function, and looks like it's not working right, and also I am getting an error when I try to save the opened file: (that did not happen before I added the piece of code at the bottom)
Here's the code:
void CApuDoc::Serialize(CArchive& ar)
{
      // CEditView contains an edit control which handles all serialization
      reinterpret_cast<CEditView*>(m_viewList.GetHead())->SerializeRaw(ar);
      CString theString;
      while(ar.ReadString(theString))
      {
                   AfxMessageBox(theString); //Display in Messagebox
      }      
}
ASKER CERTIFIED SOLUTION
Avatar of Jaime Olivares
Jaime Olivares
Flag of Peru 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
Hi Jaime,

Thankx for the help..I think am almost there...I am now able to read and display the file in the Edit view :
void CApuDoc::Serialize(CArchive& ar)
{
    CString strTemp;
   if (ar.IsLoading())
   {
     CString line;
     while(ar.ReadString(strTemp))
     {
                m_Contents += strTemp + "\r\n";   // m_Contents is a doc's member
      AfxMessageBox(m_Contents); //Display in Messagebox
     }    
    UpdateAllViews(NULL);
   }
}

void CApuView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
      CEdit& Edit =  CEditView::GetEditCtrl();
      Edit.SetReadOnly();
               Edit.SetWindowText(GetDocument()->m_Contents);      
}

Now, this works great but I still have a problem! The doc member variable m_Contents looks like this after I have read it
from the file:
Joe Green      
6                
Test2 A 99
Test3 A 98
Test4 A 97
Test5 B 80
Test6 C 60
Test7 A 100

I would like to extract from the CString (Joe as firstname, Green as lastname, 6 into the integer NumofGrades, all Test2,Test3 into an CString* array , LetterGrades into a char* array and NoGrades into an int* array). How can that be done & where could it be done?
This is a piece of code I tried, but doesn't work.

 CStringArray Array;
 int nNext,nPos;
 if(m_Contents.Right(1)!=" ") //to make sure we get the last variable
 m_Contents+=" ";
 while((nNext=m_Contents.Find(" ", nPos+1))>-1)
 {
     Array.Add(m_Contents.Mid(nPos, nNext-nPos));
      nPos=nNext+1;
 }
}
Hi Jaime,

I just figured out that I do not need to parse the CString and get the individual Strings from it.
I could use the algorithm I used before with ifstream and ofstream to directly read the file once it's open and then use my already written CStudent functions to read the variables.

CString DocName = GetDocument()->GetPathName();
AfxMessageBox(DocName);
ifstream ifl;
ifl.open(DocName);
ifl>>s;  // where >> is defined in the CStudent class

I just dont' know where to stick this code, should I put it on the" Menu Item Event which calls the Custom Dialog box" (which is going to update each of these variables using editcontrol boxes??)
>I would like to extract from the CString (Joe as firstname, Green as lastname, 6 into the integer NumofGrades, all
> Test2,Test3 into an CString* array , LetterGrades into a char* array and NoGrades into an int* array).

I suggest you to define some variables instead of simple m_Contents, by example:
CString m_FirstName, m_LastName;      // to store the name;
int m_NumOfGrades;  // to store array size

Define a structure like:
struct TGrade {
    CString TestName;
    char LetterGrade;
    int    NumericGrade;
}
So you can define an Array member like:
TGrade *m_Grades;   // will point to an array of grades
Also will be useful a flag variable to know if all data has been read correctly
bool m_DataValid;


Now, you can modify your serialize function to fill members properly:
void CApuDoc::Serialize(CArchive& ar)
{
  if (ar.IsLoading()) {
     m_DataValid = false;   // will be false until end of loading
     CString  line;
     if (!ar.ReadString(line))
             return;
     m_FirstName = line.SpanExcluding(" ");  // up to first space
     m_LastName = line.Right(line.GetLength()-m_FirstName.GetLength()-1);   // easy way to extract 2nd name, works in most cases

     if (!ar.ReadString(line))
             return;
     m_NumOfGrades = atoi(line);
     if (m_NumOfGrades<=0)
             return;

     m_Grades = new TGrade[m_NumOfGrades];    // Creates array dinamically according to size
     TGrade newGrade;
     for (int i=0; i<m_NumOfGrades; i++) {
           if (!ar.ReadString(line))
                return;
           newGrade.TestName = line.SpanExcluding(" ");  // up to first space
           line = line.Right(line.GetLength()-m_newGrade.TestName .GetLength()-1);   // similar to above
           sscanf( (LPCTSTR)line, "%c %i", &newGrade.LetterGrade, &newGrade.NumericGrade);          
     }
     m_DataValid = true;   // all data has been read correctly
     UpdateAllViews(NULL);
   }
}

Now all your data is correctly stored in many doc's members, at update function,  you will have to reconstruct all in a single string to pass it to the edit control.
Now is your turn, go ahead!
Thanks so much...u've been a lot of help to me !
I reconstructed the individual members into a single CString (m_contents) and was able to display it into the EditView. I also passed the individual doc members to the editboxes my Dialog (which is called from a menu). This works fine !!
But I just found a bug. When I say File->Open and load my file, the file get's loaded once, but when I say File->Open again, I do not want it to append the loaded contents into the existing file. I would like it to load the contents fresh again ??
Should I modify the OnUpdate function??
This is what I have in the OnUpdate function:

               Edit.SetWindowText(GetDocument()->m_Contents);    
You have to clear m_Contents before filling it:

OnUpdate(....) {
     m_Contents = "";
     // Do all your operations to fill it
     Edit.SetWindowText(GetDocument()->m_Contents);
}

Also, to avoid a memory leak, you have to destroy previous information by handling the OnNewDocument() and the destructor function. So, I suggest you to create a doc's Clean() function so you can invoke it from both mentioned functions. Something like:

CYourDoc::Clean()
{
    if (m_Grades != NULL)
          delete [] m_Grades;
    m_Grades = NULL;
}

CYourDoc::~CYourDoc()
{
      Clean();
}

BOOL CCartaDoc::OnNewDocument()       // Create it using ClassWizard (by pressing Ctrl-W)
{
      Clean();
      return CDocument::OnNewDocument();
}
Also you will need:
CYourDoc::CYourDoc()
{
      m_Grades = NULL;   // ensure pointer is correctly initializated
}
SOLUTION
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
Experts..sorry for the short break..am back with some more questions on the same topic..
Completed the following:

- able to read and display the file in the edit view "CApuView" (SDI application with CEditView as base class for the View)
- able to select a menu option to go to a dialog box (EditDlg)
- able to update the edit controls and listbox fields in the EditDlg by reading from the current file that is open in the view
 I did this by -->
           a.   declaring CApuDoc* m_pDoc in the public section of EditDlg.h
           b.   In the Event Handler (menu) which opens the EditDlg, I wrote:   m_dlg.m_pDoc = GetDocument()
                 (where m_dlg is an instance of EditDlg)
           c.  Then in the OnInitDialog of the EditDlg, I wrote:
               CString DocName = m_pDoc->GetPathName();
      CStudent s;      // this is the main object class which is updated with every change              
      ifstream ifl;
      ifl.open(DocName);
                ifl.close();
      m_FirstEdit = s.m_strFirstName;
      m_LastEdit = s.m_strLastName;
   
- I also provided buttons and respective event handlers within the EditDlg box to track the change of data in the controls
and update the CStudent class respectively.

But now I just have one problem.  Once the user presses, "Save and Exit" button from this dialog box, I would like to capture all the changes made in the Dialog box controls and update the Primary View (which is CApuView) with these changes, and then close the dialog box.
Am able to save the changes back to the file, and close the dialog box but the changes do not reflect in the View. What should I do:
Here is what I have for the "Save and Exit" button:
      s.m_strFirstName = m_FirstEdit;
      s.m_strLastName = m_LastEdit;
      CString DocName = m_pDoc->GetPathName();
      ofstream ofl;
      ofl.open(DocName);
      if (ofl)
      ofl << s;
      ofl.close();
       m_pDoc->UpdateAllViews(NULL);
      OnOK();   // NOw exit the application
'

CAN Someone PLEASE HELP ME ON THIS!!!

-shan
Just thought wud also let u know what am doing in CApuDoc and CApuView -->

void CApuDoc::Serialize(CArchive& ar)
{
   CString strTemp;
   m_Contents = "";
   if (ar.IsLoading())
   {
     CString line;
     while(ar.ReadString(strTemp))
     {
     m_Contents += strTemp + "\r\n";   // m_Contents is a doc's member
     }    
    UpdateAllViews(NULL);
   }
}


void CApuView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
      CEdit& Edit =  CEditView::GetEditCtrl();
      Edit.SetReadOnly();
    Edit.SetWindowText(GetDocument()->m_Contents);  
}
Experts,
This is the final question I have..so if this is resolved..I shud be done...sorry for the long wait....any input would be greatly appreciated.
-shan
>Here is what I have for the "Save and Exit" button:
>     s.m_strFirstName = m_FirstEdit;
>     s.m_strLastName = m_LastEdit;
>     CString DocName = m_pDoc->GetPathName();
>     ofstream ofl;
>     ofl.open(DocName);
>     if (ofl)
>     ofl << s;
>     ofl.close();
>      m_pDoc->UpdateAllViews(NULL);
>     OnOK();   // NOw exit the application

Well, this is not a standard practice, better you can update proper Doc's members, and use OnSaveDocument() method to save the file.