VTK與MFC單文件程式聯合程式設計
興趣需要,想做下VTK與MFC想結合的程式,MFC快要在桌面程式上面失去市場份額了,現在大多使用QT來做,但是本科的時候學的就是MFC,也相對來說比較熟悉,所以就想使用MFC來寫一個簡單的單文件程式。首先我們需要在編譯的時候將USEGUISUPPORT->USEMFC勾選上,才能在MFC平臺上使用VTK。網路上現在大多流行兩種VTK和MFC的方法,其實兩者結合的關鍵就是將VTK的繪製視窗vtkrenderwindow與MFC中的view視窗相一致,讓VTK上的繪製圖形能夠在MFC上的VIEW類上顯示出來。所以網上的方法一:就是使用vtkMFCWindow類,方法二:使用renWin->SetParentId(myhwnd);在這兩個方法的選擇上,水靈大神的在CSDN的部落格上面就是使用方法二,但是在他的期刊論文上則是推薦使用方法一,所以我就方法一進行了測試,但是很不幸,我按網上的步驟一步一步的新增程式碼,編譯無錯,但是在執行時出現異常,查其原因就是出現了空指標,在C++中,出現空指標無疑是最致命的,所以在網友的勸說下,我就使用了第二種方法進行測試,雖然在途中同樣出現空指標錯誤,但是經過思考解決了這一問題,我想其實可能第一種方法的問題也是差不多的,所以將程式碼和結果寫上,以免自己時間久了忘記了。
1.首先將程式中的控制檯程式貼上來,其實就是讀取一個格式為XYZ的三維點座標,並將這些點按順序連線起來,形成一個線圈。
#include <iostream>
#include <vector>
#include "vtkActor.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkProperty.h"
#include "vtkInteractorStyleTrackballCamera.h"
#include "vtkPoints.h"
#include "vtkPolyVertex.h"
#include "vtkUnstructuredGrid.h"
#include "vtkDataSetMapper.h"
#include "vtkPolyData.h"
#include "vtkCellArray.h"
#include "vtkInteractorStyleTrackball.h"
#include "vtkPolyDataMapper.h"
#include "vtkSmartPointer.h"
#include "vtkLine.h"
#include "vtkLineSource.h"
using namespace std;
void main(int argc, char* argv[])
{
vtkPoints *m_Points = vtkPoints::New();
vtkCellArray *vertices = vtkCellArray::New(); //_存放細胞頂點,用於渲染(顯示點雲所必須的)
vtkPolyData *polyData = vtkPolyData::New();
vtkPolyDataMapper *pointMapper = vtkPolyDataMapper::New();
vtkActor *pointActor = vtkActor::New();
vtkRenderer *ren1= vtkRenderer::New();
vtkRenderWindow *renWin = vtkRenderWindow::New();
vtkRenderWindowInteractor *iren = vtkRenderWindowInteractor::New();
vtkInteractorStyleTrackball *istyle = vtkInteractorStyleTrackball::New();
//_讀進點雲資料資訊
FILE*fp = NULL;
fp=fopen("point.txt","r"); //讀取TXT中的XYZ座標
if(!fp)
{
printf("開啟檔案失敗!!\n");
exit(0);
}
double x=0,y=0,z=0;
int i = 0;
while (!feof(fp))
{
fscanf(fp,"%lf %lf %lf",&x,&y,&z);
m_Points->InsertPoint(i,x,y,z); //_加入點資訊
//cout<<x<<" "<<y<<" "<<z<<endl;
cout<<x<<endl;
vertices->InsertNextCell(1); //_加入細胞頂點資訊----用於渲染點集
vertices->InsertCellPoint(i);
i ++;
}
fclose(fp);
cout<<"i-1="<<i-1<<endl;
vtkCellArray *lines=vtkCellArray::New();
for (int m=0;m<=i-1;m++)
{
if(m<i-1)
{
vtkSmartPointer<vtkLine> line =
vtkSmartPointer<vtkLine>::New();
line->GetPointIds()->SetId(0,m);
line->GetPointIds()->SetId(1,m+1);
lines->InsertNextCell(line);
}
else
{
vtkSmartPointer<vtkLine> line =
vtkSmartPointer<vtkLine>::New();
line->GetPointIds()->SetId(0,m);
line->GetPointIds()->SetId(1,1);
lines->InsertNextCell(line);
}
}
//_建立待顯示資料來源
polyData->SetPoints(m_Points); //_設定點集
polyData->SetVerts(vertices);//_設定渲染頂點
polyData->SetLines(lines);
pointMapper->SetInput(polyData);
pointActor->SetMapper(pointMapper);
pointActor->GetProperty()->SetColor(0.0,0.1,1.0);
pointActor->GetProperty()->SetAmbient(0.5);
pointActor->GetProperty()->SetPointSize(2);
//pointActor->GetProperty()->SetRepresentationToWireframe();
//pointActor->GetProperty()->SetRepresentationToSurface();
ren1->AddActor( pointActor );
ren1->SetBackground( 0, 0, 0);
renWin->AddRenderer( ren1 );
renWin->SetSize(800,800);
iren->SetInteractorStyle(istyle);
iren->SetRenderWindow(renWin); //互動
renWin->Render();
iren->Start();
//刪除各指標
m_Points->Delete();
vertices->Delete();
polyData->Delete();
pointMapper->Delete();
pointActor->Delete();
ren1->Delete();
renWin->Delete();
iren->Delete();
istyle->Delete();
}
(2)其後就是在MFC中建立一個單文件程式,配置好VTK的環境,這個時候既可以採用水靈大神在部落格裡面寫的CmakeList.txt的方法,也可以一個一個在MFC程式中將附加庫路徑,包含庫路徑等設定好,也是同樣的效果。
在設定好環境之後,我們在MFC中建立一個新類CVTK,為了將我們上述的控制檯程式作為一個類的形式寫進去,方便後面的呼叫。
首先在CVTK.h中新增標頭檔案並宣告各類的指標變數
// Vtk.h: interface for the CVtk class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_VTK_H__B872AED3_7A78_4473_BB74_44D3E9117A8F__INCLUDED_)
#define AFX_VTK_H__B872AED3_7A78_4473_BB74_44D3E9117A8F__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <iostream>
#include <vector>
#include "vtkActor.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkProperty.h"
#include "vtkInteractorStyleTrackballCamera.h"
#include "vtkPoints.h"
#include "vtkPolyVertex.h"
#include "vtkUnstructuredGrid.h"
#include "vtkDataSetMapper.h"
#include "vtkPolyData.h"
#include "vtkCellArray.h"
#include "vtkInteractorStyleTrackball.h"
#include "vtkPolyDataMapper.h"
#include "vtkSmartPointer.h"
#include "vtkLine.h"
#include "vtkLineSource.h"
class CVtk
{
public:
vtkPoints *m_Points;
vtkCellArray *lines;
vtkCellArray *vertices;
vtkPolyData *polyData;
vtkLine *line;
vtkPolyDataMapper *pointMapper;
vtkActor *pointActor;
vtkRenderer *ren1;
vtkRenderWindow *renWin;
vtkRenderWindowInteractor *iren;
int i;
//vtkInteractorStyleTrackball *istyle;
void BeginRenderOn(HWND myhwnd);
CVtk();
virtual ~CVtk();
void TextRenderer(void);
};
#endif // !defined(AFX_VTK_H__B872AED3_7A78_4473_BB74_44D3E9117A8F__INCLUDED_)
(3)我們在CVTK.cpp中新增一個成員函式TextRenderer(),其程式碼如下,關鍵是彈出一個檔案對話方塊,並可以選擇格式為XYZ的三維文字檔案,並將其插入到VtkPoints,並連線成一個封閉的線串。
void CVtk::TextRenderer(void)
{
CString FilePathName;
CString wenjianhouzhui;
CFileDialog dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,(LPCTSTR)_TEXT("DCIOM Files (*.dcm)|*.dcm|TIFF Files (*.tif)|*.tif|TXT Files (*.txt)|*.txt|BMP Files (*.bmp)|*.bmp|JPG Files (*.jpg)|*.jpg|All Files (*.*)|*.*||"),NULL);
if (dlg.DoModal()==IDOK)
{
FilePathName=dlg.GetPathName();
wenjianhouzhui=dlg.GetFileExt();
//wenjianhouzhui=dlg.GetFileExt();
//AfxMessageBox(wenjianhouzhui);
}
else
{
return ;
}
if(wenjianhouzhui=="txt")
{
FILE*fp = NULL;
fp=fopen(FilePathName,"r"); //讀取TXT中的XYZ座標
if(!fp)
{
AfxMessageBox("開啟檔案失敗!!\n");
exit(0);
}
double x=0,y=0,z=0;
int i = 0;
m_Points=vtkPoints::New();
vertices=vtkCellArray::New();
while (!feof(fp))
{
fscanf(fp,"%lf %lf %lf",&x,&y,&z);
m_Points->InsertPoint(i,x,y,z); //_加入點信
//cout<<x<<" "<<y<<" "<<z<<endl;
cout<<x<<endl;
vertices->InsertNextCell(1); //_加入細胞頂點資訊----用於渲染點集
vertices->InsertCellPoint(i);
i ++;
}
fclose(fp);
lines=vtkCellArray::New();
for (int m=0;m<=i-1;m++)
{
if(m<i-1)
{
line =vtkLine::New();
line->GetPointIds()->SetId(0,m);
line->GetPointIds()->SetId(1,m+1);
lines->InsertNextCell(line);
}
else
{
line =vtkLine::New();
line->GetPointIds()->SetId(0,m);
line->GetPointIds()->SetId(1,1);
lines->InsertNextCell(line);
}
}
//_建立待顯示資料來源
polyData=vtkPolyData::New();
polyData->SetPoints(m_Points); //_設定點集
polyData->SetVerts(vertices);//_設定渲染頂點
polyData->SetLines(lines);
pointMapper=vtkPolyDataMapper::New();
pointMapper->SetInput(polyData);
pointActor=vtkActor::New();
pointActor->SetMapper(pointMapper);
pointActor->GetProperty()->SetColor(0.0,0.1,1.0);
pointActor->GetProperty()->SetPointSize(2);
ren1=vtkRenderer::New();
ren1->AddActor( pointActor );
ren1->SetBackground(1,1,1);
//ren1->SetBackground( 0, 0, 0);
renWin=vtkRenderWindow::New();
renWin->AddRenderer( ren1 );
iren=vtkRenderWindowInteractor::New();
//istyle=vtkInteractorStyleTrackball::New();
//iren->SetInteractorStyle(istyle);
iren->SetRenderWindow(renWin);
}
}
(4)我們在CVTK.cpp中新增一個成員函式BeginRenderer(),其程式碼如下,作用就是獲取當前的視窗指標並將其設為VTK繪製視窗的父視窗
void CVtk::BeginRenderOn(HWND myhwnd)
{
renWin->SetParentId(myhwnd);
//開始實現
if(ren1)
{
renWin->Render();
}
}
(5)其後我們在CVTK的解構函式!~CVTK()中刪除宣告的指標物件
CVtk::~CVtk()
{
m_Points->Delete();
vertices->Delete();
line->Delete();
lines->Delete();
polyData->Delete();
pointMapper->Delete();
pointActor->Delete();
ren1->Delete();
renWin->Delete();
iren->Delete();
}
(6)到現在為止,將VTK的控制檯程式轉為MFC中的程式碼就已經完成了,其後就是在MFC中實現圖形的繪製,我們現在主選單上新增一個開啟檔案的按鈕,並在Cview類下面新增他的事件處理程式。
其事件處理程式如下,及在此呼叫CVTK的TextRenderer()函式,下面的m_pVtk為CVTK類的物件,上述宣告是在View的標頭檔案中進行的宣告,
void CMyMFCPointView::OnTxTFileopen()
{
// TODO: 在此新增命令處理程式程式碼
m_pVtk.TextRenderer();
Invalidate();
}
下面為view類標頭檔案的宣告
// MyMFCPointView.h : interface of the CMyMFCPointView class
//
/////////////////////////////////////////////////////////////////////////////
#if !defined(AFX_MYMFCPOINTVIEW_H__A74C4D7E_8977_4D06_9F00_610A95A7CFF9__INCLUDED_)
#define AFX_MYMFCPOINTVIEW_H__A74C4D7E_8977_4D06_9F00_610A95A7CFF9__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "Vtk.h"
class CMyMFCPointView : public CView
{
protected: // create from serialization only
CMyMFCPointView();
DECLARE_DYNCREATE(CMyMFCPointView)
// Attributes
public:
CMyMFCPointDoc* GetDocument();
/*CString FilePathName;
CString wenjianhouzhui;*/
// Operations
public:
CVtk m_pVtk;
double sx;
double sy;
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMyMFCPointView)
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CMyMFCPointView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
// Generated message map functions
protected:
//{{AFX_MSG(CMyMFCPointView)
// NOTE - the ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnTxTFileopen();
// afx_msg void OnFileOpen();
};
#ifndef _DEBUG // debug version in MyMFCPointView.cpp
inline CMyMFCPointDoc* CMyMFCPointView::GetDocument()
{ return (CMyMFCPointDoc*)m_pDocument; }
#endif
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_MYMFCPOINTVIEW_H__A74C4D7E_8977_4D06_9F00_610A95A7CFF9__INCLUDED_)
為了能將所畫的圖形隨著視窗大小變化而變化,起先開始我是在view的Onsize()函式中寫下如下程式碼,m_pVtk.renWin->SetSize(cx,cy);,但是這個時候會出現空指標錯誤,是因為,當視窗第一次建立的時候我們還沒有renWin這個指標物件,這個是我們在點選開啟檔案按鈕選擇好檔案之後才會建立的物件,所以此時會出現空指標錯誤,為了解決這個問題,我在view.h中定義了兩個變數sx,sy,然後在Onsize()函式中寫下如下的程式碼,目的是將當前視窗的大小的值賦給sx,sy。而我可以在其他的地方使用這個值。
void CMyMFCPointView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
// TODO: 在此處新增訊息處理程式程式碼
sx=cx;
sy=cy;
}
最後,我在view類的OnDraw()函式中繪圖,並實現圖形隨視窗大小變動的功能
void CMyMFCPointView::OnDraw(CDC* pDC)
{
CMyMFCPointDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
m_pVtk.BeginRenderOn(this->m_hWnd);
m_pVtk.renWin->SetSize(sx,sy);
}
至此,我們就完成了VTK與MFC相結合的程式,當我們點選開啟檔案按鈕之後,就會在view窗口出現所畫的線圈,並且隨視窗大小改變
可以在這個程式進行更多三維重建的功能,還得繼續開發。至於VtkMFCWindow類的可用性還未證實,留待以後的實驗。