分享
 
 
 

VC++/MFC 教程6&7(英文)

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

Lesson 6: SDI and MDI Applications

We are getting to some advanced stuff now. In this lesson I am not going to go in depth at all. I will just give you a flavor of the structure of a SDI (single document interface) and a MDI (multiple document interface) application. In the last lesson we will build a SDI application and you can see the nitty gritty there.

The SDI application is typically used when you intend to work with only one data set at a time. For instance, the program notepad.exe is a SDI application. Netscape is also an SDI application. At any one time, there is only one document open at a time. Word for Windows and the VC++ developer studio are MDI applications. In these you can have several documents opened at once. This is particularly useful when you want to cut and paste between documents. Another use for MDI applications is to have one document, but several different views open that view the data differently. A graphing application comes to mind where in one window you have a spreadsheet-like data list, and in another window you have a plot of the data. For small applications, a SDI application will usually be all you need. After you master it, the jump to MDI is a snap. Let's go over the structure of a SDI app.

Remember that in a dialog app, we had just two main classes. CWinApp and CDialog. Here again we have a CWinApp which serves the same purpose as it did in lesson 5. The CDialog class however is replaced by 3 other classes: CMainFrame, CDocument, and CView.

CDocument is a class that has no display, and typically doesn't react much with the messaging system of windows. It is used as a class to manage your data. MFC will create the code automatically which handles the event of File->Save, File->SaveAs, File->Close, and File->Open. All you need to do is to fill in the blank functions in the CDocument class.

Next is the CView. Most likely you will spend more time writing code to display and interact with the document's data then you will writing any other code. This is where the CView comes in. The CView is a class derived from CWnd, which is used for displaying your CDocument in some way. It is also one of the places where you can handle events like mouse clicks and what not. The heart of the CView is usually a call to get a pointer to your document followed by some drawing routines to display the data in your document.

The CMainFrame acts as a way to bridge the gap between your document/view classes and the rest of the application. Do you see that frame which goes all around applications boarders? That is the Main Frame window of the application. The title bar, the menu, the scroll bars, the status bar, and the tool bars are all part of the main frame window in an SDI application. You typically put the code to handle these objects in the CMainFrame class. The CMainFrame class is the main window of the application. The CView class is typically a child window of the CMainFrame. (For the most part, the child/parent window relations just tell windows what windows are 'stuck' to what other windows. If you move a parent window, all of the children will move also. If you destroy a parent window, all of the children will be destroyed. Etc.)

You should have a pretty good idea now of how SDI applications are constructed. MDI applications are similar, but there can be several CDocument classes in existence at the same time and there is an added class called CChildFrame which acts as a connection between the CView and the CMainFrame.

Lesson 7: Data Viewer

The moment you have been waiting for, we finally will make a useful application. If you have just skipped the last 6 lessons, then you will probably be able to follow along, but you may not really understand what you are doing. (But since you are the type of person that skips ahead, you are probably used to this.)

I have decided to make this example a data viewing application that takes a text file of data, reads it in, and then displays it. If that isn't enough, we are then going to use the timer to animate the data. Let's first assume that we are doing an experiment tracking the random motion of a drunken bug on a table. Every second we measure its distance from two adjacent sides of the table. These are what we will call the bug's x,y coordinates. Our data file looks like this:

390.789

362.245

386.032

366.429

386.559

369.289

385.557

370.483

384.841

372.370

385.785

371.975

389.348

371.005

377.266

379.550

376.916

382.096

373.959

384.111

373.109

384.387

370.598

382.973

370.067

383.667

369.099

377.171

366.549

379.162

368.245

383.977

366.427

385.877

364.343

388.575

365.326

389.769

368.751

389.556

369.598

386.514

389.381

384.817

387.311

381.979

388.205

382.978

386.632

387.414

385.150

388.393

384.099

390.620

382.926

394.712

385.771

396.611

375.693

393.622

376.697

392.655

394.063

397.035

391.727

401.327

379.119

400.460

381.912

407.491

384.119

407.505

383.090

406.474

384.888

408.943

386.664

409.806

386.207

409.759

388.031

411.599

387.911

411.545

Go cut and paste this to a file named BugPosition.dat if you want to follow along. We first fire up Visual C++ Developers Studio and create a new project. In this case I called the project 'BugTracks'. For the app wizard options select SDI application. Keep the default settings for the rest of the choices, EXCEPT deselect printing preview and docking toolbar.

First lets figure out how to get our data into the program. Go to the ClassView and double click on your document class. It should be called CBugTracksDoc if you named your project 'BugTracks'. When you double click on the class name, the .h file will be opened in the editor. Right before the declaration of the class, lets declare a structure to hold the data. struct SBugData

{

float x;

float y;

};

Also I want to use one of microsoft's template classes. Templates are C++ things that allow you to write a function for an arbitrary data type. The one I like to use is the CArray class. Include the afxtempl.h before your structure declaration: #include <afxtempl.h>

and then in a public area of your class declare a CArray template class as follows: CArray <SBugData, SBugData> m_BugDataArray;

Yea, it looks funny, but that is the way you want to type it. This is sort of like declaring a data type of CArray but we have to tell the template class what type of data we want to store in the array. That is done between the brackets <,>. The first thing in the brackets is the type of data we want to put in the array. The second thing is what we will pass to the CArray class when we want to add a new element. Since we have a list of data points, it is obvious that we want to have an array of SBugData. The second parameter is also SBugData meaning we will just pass the data to the array. (Alternatively we could have passed a 'reference' to the data, but that is another lesson).

Let's go to the .cpp file for the document and add the code now. Expand the CBugTracksDoc in the ClassView. You should see the member functions for the document. Double click on OnNewDocument(). You will jump to the function in the .cpp file. This function is called every time a new document (file) is opened. All we want to do here is to clear out the array so it will be ready for the new data. Where you see the //TODO comment, add this line of code: m_BugDataArray.RemoveAll();

Now to fill up the array, jump to the Serialize() function. This is a function called when a new file is opened or saved. Instead of the good old FILE pointers you use in C with fopen, we are going to use microsoft's CArchive class. You will notice that a CArchive is passed (by reference) to the Serialize() function. This class has the same functionality as we get with the fread and fwrite functions. void CBugTracksDoc::Serialize(CArchive& ar)

{

// if not storing the data, read it

if (!ar.IsStoring())

{

SBugData Data;

CString strOneLine;

// read data in, one line at a time

while(ar.ReadString(strOneLine))

{

// convert the text to floats

sscanf(strOneLine,"%g %g\n",&Data.x,&Data.y);

// add the data to the array

m_BugDataArray.Add(Data);

}

}

}

For the very basic display, we just need to add some code to draw the data. Go to the function OnDraw() in the view class CBugTracksView. This is the function that is called every time the window needs refreshed. All drawing is done through the CDC class. The CDC class has several drawing functions, here we will only use the MoveTo and LineTo calls. void CBugTracksView::OnDraw(CDC* pDC)

{

// get a pointer to the document class

CBugTracksDoc* pDoc = GetDocument();

// get the total number of data points

int N=pDoc->m_BugDataArray.GetSize();

// draw all of the connecting lines

for(int i=0; i < N-2; i++)

{

pDC->MoveTo(pDoc->m_BugDataArray[i].x, pDoc->m_BugDataArray[i].y);

pDC->LineTo(pDoc->m_BugDataArray[i+1].x, pDoc->m_BugDataArray[i+1].y);

}

}

Well that is it! Compile and run the program. You will get a few warnings since our data is float, but screen coordinates are int, but that is harmless in this case. Once the program is running, go to File, Open and select the data file we created above. It should display the track in the lower-middle part of the window. We could call it quits here, but let's add a couple of more features.

First, I hate the File, Open menu. Lets make our application accept files that are dropped on the main window. Go to the InitInstance() function in our CWinApp class CBugTracksApp. Near the end of the function add this line: // Enable drag/drop open

m_pMainWnd->DragAcceptFiles();

Now let's take advantage of the status bar and put some useful text in it. The status bar is managed by the CStatusBar class, which is a protected member of CMainFrame. This means that we can't touch it from other classes. We can either move its declaration to a public part of the class or just a public member function to CMainFrame to change the status bar text. We will do the later. Right click on CMainFrame in the class view and select 'Add Member Function'. A dialog will pop up to help add the member function. Type in "void" (without the quotes) for the function type -- this is the return value of the function, and type in "ChangeStatusText(LPCTSTR text)" as the function declaration. Make sure that the 'access' is set to public. Press OK. This will automagically add the declaration to the .h file and a blank function to the .cpp file of CMainFrame. The LPCTSTR is one of many microsoft defines for data types. We could have alternately have typed "ChangeStatusText(const char *text)". LPCTSTR stands for Long Pointer to a Constant T STRing. A T-string is just a string that will work on computers with different character sets (like Japanese). On computers in the US, a T-string is just the same as a char *.

Jump to the new function in the CMainFrame .cpp file and add the code to change the text on the status bar. To do this we'll just use the CWnd function SetWindowText. CStatusBar is derived from CWnd so we can always use any of the CWnd functions with it. A hint on how to find out about all of these strange new functions... use the help and look at the 'class members' for the class, and then look at the class members for all of the base classes from which it was derived. Your function should now look like this: void CMainFrame::ChangeStatusText(LPCTSTR text)

{

m_wndStatusBar.SetWindowText(text);

}

We have to call this function from somewhere, and I'll do it from the document. Go to the 'File View' which is the view I use most. Under 'Source Files' double-click on your document file "BugTracksDoc.cpp". Go to the top of that file and include the header file for CMainFrame right after the rest of the includes so that we can access the new function we just made. #include "MainFrm.cpp"

Next go to our Serialize() function and modify the reading code to spit some text out to the status bar. We first get a pointer to the main window, which is the CMainFrame window in SDI applications. Since the function AfxGetMainWnd() returns a CWnd*, we cast it to a CMainFrame*. Then we use the CString's Format function and CArray's GetSize function to create a text string for the status bar. void CBugTracksDoc::Serialize(CArchive& ar)

{

if (!ar.IsStoring())

{

SBugData data;

CString line;

CString strStatus;

// get a pointer to the main window

// (which is the mainframe for SDI applications)

CMainFrame *pMain = (CMainFrame*) AfxGetMainWnd();

while(ar.ReadString(line))

{

sscanf(line,"%g %g\n",&data.x,&data.y);

// tell the user your reading points

strStatus.Format("Reading point %d",m_BugDataArray.GetSize());

pMain->ChangeStatusText(strStatus);

m_BugDataArray.Add(data);

}

// tell the user the total number of points

strStatus.Format("Loaded %d points.", m_BugDataArray.GetSize());

pMain->ChangeStatusText(strStatus);

}

}

If you run the app, you'll notice all of the default menu items. We don't need most of these. Let's clean up the menus and add an item for animating the bug track which we'll code later.

Go to the Resource View. Under the Menu resource, double click on IDR_MAINFRAME. This is the menu resource for the main window. In the edit window, click on File. Then delete the menu entries for New, Save, and Save As. Also delete the main menu headings for Edit and View. Next, go to the empty box at the end of the menu and Add a new heading called 'Track' by selecting the empty box and

typing 'Track'. Drag the Track menu heading so that it is between File and Help. Click on the Track menu and then click on the empty sub menu box. Type in '&Animate\tAlt-A'. The & underlines the 'A' in Animate so that it is the menu Hot Key. The \t is just the scan code for a tab and the Alt-A will be our hot key to start the animation. For the ID, type in `ID_TRACK_ANIMATE', though this will be filled in automatically if you ever forget.

In order to make Alt-A our hot key, go to the Accelerator resources and double-click on IDR_MAINFRAME. In the edit window, double-click on the empty box at the end of the list. From the drop list for the ID, select the ID of your new menu item (ID_TRACK_ANIMATE). Press the `Next Key Typed' button and then press Alt-A. Hit enter to close the dialog.

Before we are done with resources, you should modify the icons to something more suitable for this app. I'm sure you can figure out how to do this. The only hints here are to make user and modify the 32x32 sized icon AND the 16x16 sized icon. If you want part of the icon to be transparent, use that greenish color with the two borders around it on the color palette.

Now we can get back to coding. It's time to add fancier drawing and animating. We will animate the bug track by drawing more and more segments of the path in red as time increases. The rest of the path will be drawn in black.

In order to keep track of the last segment in the path that is to be drawn in red, we have to add a member variable to our document. Go to the Class View, right click on the document class, and select Add Member Variable. Type in 'int' as the data type and 'm_nBugPosition' as the variable name. Make sure that it is public and press OK.

Jump to the OnNewDocument() in the document class. Add this line to initialize the new variable to -1. We will use the value -1 to designate that the track is not being animated. m_nBugPosition = -1;

Next let's add the message handler for our 'Animate' hot key and menu. Press Ctrl-W to bring up the class wizard. In the class name drop box select the view class (CBugTracksView) and in the Object ID list, select the ID of our new menu and hot key command (ID_TRACK_ANIMATE). You'll see the two possible choices in the Messages list. Double-click on COMMAND to add a function to handle our new command. You will be prompted for a function name. Just accept the default one OnTrackAnimate() and press OK. You will see the function appear in the Member Function list near the bottom of the dialog. Double-click on the function to jump directly to the code. We set m_nBugPosition to zero and start a timer that will redraw the bug tracks in intervals of 0.2 seconds. void CBugTracksView::OnTrackAnimate()

{

// get the document

CBugTracksDoc* pDoc = GetDocument();

// set the position to the first data point

pDoc->m_nBugPosition=0;

// create a timer with id=1 and delay of 200 milliseconds

SetTimer(1,200, NULL);

Next we need to handle the timer message. Ctrl-W back to the class view. Make sure you are looking at the view class, select the class as the Object ID, then double-click WM_TIMER in the message list to handle the timer message. Again, double-click on the function name to jump to the code. In the OnTimer function we will first check the ID of the timer to make sure we are responding to the correct timer. In this case we set the timer ID to 1. Then we will invalidate the window so that it will be repainted. void CBugTracksView::OnTimer(UINT nIDEvent)

{

if(nIDEvent==1)

{

// tell windows the view needs redrawn

// note: the last parameter is the erase flag.

// if it is TRUE, things will flicker like crazy.

InvalidateRect(NULL,FALSE);

}

CView::OnTimer(nIDEvent);

}

All that is left now is to fix up the OnDraw() function in the view class. We need to first draw the red tracks, then the blue ones, then increment the position m_nBugPosition. If m_nBugPosition is larger than the number of positions we will set it to -1 and kill the timer.

One of the new things in this code is the CPen class that is needed to change the color of the line. The way these graphical objects work is that you 'select' the object in to the CDC class. When you are done with it, you select the old one that was in there previously and delete the one you just used. void CBugTracksView::OnDraw(CDC* pDC)

{

CBugTracksDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// make pens for solid lines of thickness 2

CPen RedPen(PS_SOLID, 2, RGB(255,0,0));

CPen BluePen(PS_SOLID, 2, RGB(0,0,255));

CPen *pOldPen = pDC->SelectObject(&RedPen);

int i, N=pDoc->m_BugDataArray.GetSize();

// draw any tracks which need animated

for(i=0; i < pDoc->m_nBugPosition-1; i++)

{

pDC->MoveTo(pDoc->m_BugDataArray[i].x,)pDoc->m_BugDataArray[i].y);

pDC->LineTo(pDoc->m_BugDataArray[i+1].x,pDoc->m_BugDataArray[i+1].y);

}

// change pens

pDC->SelectObject(&BluePen);

// start drawing non animated tracks, but need to check for a

// valid starting postion

int start=pDoc->m_nBugPosition;

if(start<0) start=0;

for(i=start; i < N-2; i++)

{

pDC->MoveTo(pDoc->m_BugDataArray[i].x,)pDoc->m_BugDataArray[i].y);

pDC->LineTo(pDoc->m_BugDataArray[i+1].x,pDoc->m_BugDataArray[i+1].y);

}

// deselect pens and delete them

pDC->SelectObject(pOldPen);

RedPen.DeleteObject();

BluePen.DeleteObject();

// move to next position or quit animating

if(pDoc->m_nBugPosition!=-1) pDoc->m_nBugPosition++;

if(pDoc->m_nBugPosition>=N)

{

pDoc->m_nBugPosition=-1;

// stop timer 1

KillTimer(1);

// redraw and erase so all lines are in initial state (blue)

InvalidateRect(NULL);

}

}

Ctrl-F5 the program to build and run it. Fix any bugs and you are done! Of course many improvements can be made, like scaling and centering the path to better fit the view, printing of the path, etc... but I think you have enough to go on. Good luck! (and don't be afraid of that F1 key)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有