天天看點

MFC 多視圖同步畫圖解決方案

 今天看《深入淺出MFC》時,看到要做多視圖同步畫圖問題,此書上剛介紹完單視圖畫圖後,引出多視圖畫圖的問題(多視圖是指一個子視圖視窗中多個視圖區域,由SpliiterWnd分割生成的)。存在互相通知,并特别強調繪圖效率的問題。我迫不及待的用自己的想法實作了這個高效率繪圖問題。後來看了一下書上的方法,确實也不錯,但感覺比我的繪圖效率低。我的繪圖方法其實是因為書上開始講的單視圖繪圖的方法,給了我一個用此方法來提高繪圖效率的靈感。

單視圖畫圖的基本方法交代:

1:通過CStroke類(派生于COjbect,以實作序列化),記錄一次筆畫的所有點資訊,以及筆畫寬度,并完成相應繪畫工作(筆畫就是滑鼠左鍵按下,畫圖,到左鍵松開)。

2:Doc通過建立CObList,來儲存本次繪畫的所有筆畫。在儲存檔案時進行序列化。

3:View的OnDraw函數通過周遊CObList的每個對象,完成所有筆畫的現實工作。

4:直接通過OnLButtonDown,OnLButtonUp,OnMouseMove即時來完成繪圖,并在此儲存筆畫資訊。

就是因為第4點,讓我想到一個高效率繪圖方法。

首先介紹書上的方法:

書上采用UpdateAllView函數通知其他子視圖其他視圖進行重畫,因為UpdateAllView引發子視圖調用OnUpdate,然後在OnUpdate裡面利用UpdateAllView傳遞過來的pHint來設定所需重畫的最小矩形區域。以提高繪畫效率。

這裡我需要說明:為什麼要提高繪畫效率,因為傳統的方法是通過UpdateAllViews來通知子視圖重畫全部區域,但設想要實作即時繪畫,滑鼠移動的消息可是很多啊,如果在這個情況下不斷地UpdateAllViews 可想螢幕會多卡。

而我的思路是:基本可以概括為,發送相同消息給兄弟視圖,讓兄弟視圖利用OnLButtonDown,OnLButtonUp,OnMouseMove裡本身的即時繪圖來實作即時繪畫。

////////////////////////////////////////////////////////////////////思路簡介//////////////////////////////////////////////////////////////////////

////用CStroke類記錄一次筆畫過程中的點,以及點所對應的線的粗度                                                                                      ////

////由目前(激活的)視圖負責點記錄,并向其他兄弟視圖發送相同消息(帶有nFlags标記),來使得其它子視圖是負責                   ////

////目前(激活的)視圖在自己滑鼠消息中繪畫。                                                                                                                ////

///其他視圖通過nFlags标記位判斷此消息是兄弟視圖發送的消息,還是windows發送的消息以差別對待。如果是兄弟消息那麼就不 ///

////用儲存繪畫資訊(因為繪畫資訊隻保留一份就可以,由目前(激活的)視圖負責記錄) ,隻負責即時繪畫就可以。如果是windows///

/////的那麼此兄弟視圖目前成為了目前(激活的視圖)。                                                                                                        ////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

不過這樣的設計會牽扯到如下兩個問題:

1:如何區分windows消息和兄弟視圖發送的消息?

2:如何實作資料的單一副本,而不會存在每個子視圖一份資料(因為筆畫的資料建立,添加是在OnLButtonDown,OnLButtonUp,

OnMouseMove裡完成),是以不能照搬單視圖的方法。

解決方式:

1:其他視圖通過nFlags标記位判斷此消息是兄弟視圖發送的消息,還是windows發送的消息以差別對待。     

2:由滑鼠激活的視圖完成筆畫資料建立,以及添加工作。其他視圖是負責繪畫。

解決完上面兩方面就可以完成多視圖同步高效率繪畫問題。下面是我所設計的資料和函數:

//////////////////////////////////////////////////////////////////Doc.h////////////////////////////////////////////////////////////////////////

#pragma once

#include "Stroke.h"

class CStroke_MFCDoc : public CDocument

{

protected: // 僅從序列化建立

CStroke_MFCDoc();

DECLARE_DYNCREATE(CStroke_MFCDoc)

//資料

private:

//CTypedPtrList<CObList,CStroke*> m_StrokeList;

CObList m_StrokeList;//筆畫清單

int m_nPenWidth;//目前筆寬,通過此來調節新建立的筆的筆寬

bool bThinPen;//是否是細筆

CPen m_Pen;//目前筆

CStroke* m_pCurStroke;//目前筆畫指針

public:

enum{THIN_PEN=2,THICK_PEN=7};

// 屬性

// 操作

/*CTypedPtrList<CObList,CStroke*>*/CObList& GetStrokeList();

void InitDocument();

virtual void DeleteContents();

CStroke* NewStroke();

CPen* GetCurPen();

// 重寫

virtual BOOL OnNewDocument();

virtual void Serialize(CArchive& ar);

// 實作

virtual ~CStroke_MFCDoc();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// 生成的消息映射函數

DECLARE_MESSAGE_MAP()

virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);

void ChangePen(void);//在粗筆和細筆之間變化,并傳回是否為細筆

afx_msg void OnThickPen();

afx_msg void OnUpdateThickPen(CCmdUI *pCmdUI);

BOOL IsCapture(void);//子視圖中是否有捕獲滑鼠的

//向除了pWnd外的其他子視圖發送相同消息,以實作建立視窗同步

BOOL SendBTMsgToOtherViews(CWnd* pWnd,UINT message,UINT nFlags, CPoint point);

int GetViewsCount(void);//獲得與本文檔相關的目前子視圖個數

CStroke* GetCurStroke(void);//獲得目前筆畫指針

};

//////////////////////////////////////////////////////////////Doc.cpp/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"

#include "Stroke_MFC.h"

#include "Stroke_MFCDoc.h"

#include "Stroke_MFCView.h"

#define new DEBUG_NEW

// CStroke_MFCDoc

IMPLEMENT_DYNCREATE(CStroke_MFCDoc, CDocument)

BEGIN_MESSAGE_MAP(CStroke_MFCDoc, CDocument)

ON_COMMAND(ID_THICK_PEN, &CStroke_MFCDoc::OnThickPen)

ON_UPDATE_COMMAND_UI(ID_THICK_PEN, &CStroke_MFCDoc::OnUpdateThickPen)

END_MESSAGE_MAP()

// CStroke_MFCDoc 構造/析構

CStroke_MFCDoc::CStroke_MFCDoc()

// TODO: 在此添加一次性構造代碼

}

/*CTypedPtrList<CObList,CStroke*>*/CObList& CStroke_MFCDoc::GetStrokeList()

return m_StrokeList;

void CStroke_MFCDoc::InitDocument()

m_nPenWidth=THIN_PEN;

bThinPen=TRUE;

m_Pen.CreatePen(PS_SOLID,m_nPenWidth,RGB(0,0,0));

void CStroke_MFCDoc::DeleteContents()

while(!m_StrokeList.IsEmpty())

delete m_StrokeList.RemoveHead();

CDocument::DeleteContents();

CStroke* CStroke_MFCDoc::NewStroke()

m_pCurStroke=new CStroke(m_nPenWidth);

m_StrokeList.AddTail(m_pCurStroke);

SetModifiedFlag();

return m_pCurStroke;

CPen* CStroke_MFCDoc::GetCurPen()

return &m_Pen;

CStroke_MFCDoc::~CStroke_MFCDoc()

BOOL CStroke_MFCDoc::OnNewDocument()

if (!CDocument::OnNewDocument())

return FALSE;

// TODO: 在此添加重新初始化代碼

// (SDI 文檔将重用該文檔)

InitDocument();

return TRUE;

// CStroke_MFCDoc 序列化

void CStroke_MFCDoc::Serialize(CArchive& ar)

if (ar.IsStoring())

// TODO: 在此添加存儲代碼

else

// TODO: 在此添加加載代碼

m_StrokeList.Serialize(ar);

// CStroke_MFCDoc 診斷

void CStroke_MFCDoc::AssertValid() const

CDocument::AssertValid();

void CStroke_MFCDoc::Dump(CDumpContext& dc) const

CDocument::Dump(dc);

#endif //_DEBUG

// CStroke_MFCDoc 指令

BOOL CStroke_MFCDoc::OnOpenDocument(LPCTSTR lpszPathName)

if (!CDocument::OnOpenDocument(lpszPathName))

// TODO: 在此添加您專用的建立代碼

void CStroke_MFCDoc::ChangePen(void)

m_nPenWidth = bThinPen ? THICK_PEN : THIN_PEN;

bThinPen =! bThinPen;

m_Pen.DeleteObject();

void CStroke_MFCDoc::OnThickPen()

// TODO: 在此添加指令處理程式代碼

ChangePen();

void CStroke_MFCDoc::OnUpdateThickPen(CCmdUI *pCmdUI)

// TODO: 在此添加指令更新使用者界面處理程式代碼

pCmdUI->Enable(!m_StrokeList.IsEmpty());

pCmdUI->SetCheck(bThinPen);

BOOL CStroke_MFCDoc::IsCapture(void)

POSITION pos = m_viewList.GetHeadPosition();

while(NULL != pos)

CStroke_MFCView* pView = (CStroke_MFCView*)m_viewList.GetNext(pos);

if(pView->IsCapture())

BOOL CStroke_MFCDoc::SendBTMsgToOtherViews(CWnd* pWnd,UINT message,UINT nFlags, CPoint point)

if(pView != (CStroke_MFCView*)pWnd)

SendMessage(pView->GetSafeHwnd(),message,(WPARAM)0,LPARAM(point.y<<16|point.x));

int CStroke_MFCDoc::GetViewsCount(void)

return m_viewList.GetCount();

CStroke* CStroke_MFCDoc::GetCurStroke(void)

//////////////////////////////////////////////////////////////view.h///////////////////////////////////////////////////////////////////////////////

class CStroke_MFCView : public CView

CStroke_MFCView();

DECLARE_DYNCREATE(CStroke_MFCView)

//((CStroke*)&) m_pCurStroke;

CPoint m_pointPre;

CStroke_MFCDoc* GetDocument() const;

virtual void OnDraw(CDC* pDC); // 重寫以繪制該視圖

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual ~CStroke_MFCView();

afx_msg void OnFilePrintPreview();

afx_msg void OnRButtonUp(UINT nFlags, CPoint point);

afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

afx_msg void OnMouseMove(UINT nFlags, CPoint point);

BOOL IsCapture(void);

#ifndef _DEBUG // Stroke_MFCView.cpp 中的調試版本

inline CStroke_MFCDoc* CStroke_MFCView::GetDocument() const

{ return reinterpret_cast<CStroke_MFCDoc*>(m_pDocument); }

//////////////////////////////////////////////////////////////view.cpp////////////////////////////////////////////////////////////////////////////

// CStroke_MFCView

IMPLEMENT_DYNCREATE(CStroke_MFCView, CView)

BEGIN_MESSAGE_MAP(CStroke_MFCView, CView)

// 标準列印指令

ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CStroke_MFCView::OnFilePrintPreview)

ON_WM_LBUTTONDOWN()

ON_WM_LBUTTONUP()

ON_WM_MOUSEMOVE()

// CStroke_MFCView 構造/析構

static int nFirst=0;

CStroke_MFCView::CStroke_MFCView()

// TODO: 在此處添加構造代碼

CStroke_MFCView::~CStroke_MFCView()

BOOL CStroke_MFCView::PreCreateWindow(CREATESTRUCT& cs)

// TODO: 在此處通過修改

// CREATESTRUCT cs 來修改視窗類或樣式

return CView::PreCreateWindow(cs);

// CStroke_MFCView 繪制

void CStroke_MFCView::OnDraw(CDC* pDC)

CStroke_MFCDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

if (!pDoc)

return;

// TODO: 在此處為本機資料添加繪制代碼

/*CTypedPtrList<CObList,CStroke*>*/CObList& pStrokeList = pDoc->GetStrokeList();

POSITION pos = pStrokeList.GetHeadPosition();

CStroke* pStroke = (CStroke*)pStrokeList.GetNext(pos);

pStroke->Draw(pDC);

// CStroke_MFCView 列印

void CStroke_MFCView::OnFilePrintPreview()

AFXPrintPreview(this);

BOOL CStroke_MFCView::OnPreparePrinting(CPrintInfo* pInfo)

// 預設準備

return DoPreparePrinting(pInfo);

void CStroke_MFCView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

// TODO: 添加額外的列印前進行的初始化過程

void CStroke_MFCView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

// TODO: 添加列印後進行的清理過程

void CStroke_MFCView::OnRButtonUp(UINT nFlags, CPoint point)

ClientToScreen(&point);

OnContextMenu(this, point);

void CStroke_MFCView::OnContextMenu(CWnd* pWnd, CPoint point)

theApp.GetContextMenuManager()->ShowPopupMenu(IDR_POPUP_EDIT, point.x, point.y, this, TRUE);

// CStroke_MFCView 診斷

void CStroke_MFCView::AssertValid() const

CView::AssertValid();

void CStroke_MFCView::Dump(CDumpContext& dc) const

CView::Dump(dc);

CStroke_MFCDoc* CStroke_MFCView::GetDocument() const // 非調試版本是内聯的

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CStroke_MFCDoc)));

return (CStroke_MFCDoc*)m_pDocument;

// CStroke_MFCView 消息處理程式

void CStroke_MFCView::OnLButtonDown(UINT nFlags, CPoint point)

// TODO: 在此添加消息處理程式代碼和/或調用預設值

if(0 != nFlags)//如果是系統發送的話,由該view唯一建立線條

GetDocument()->SendBTMsgToOtherViews(this,WM_LBUTTONDOWN,nFlags,point);

GetDocument()->NewStroke();

GetDocument()->GetCurStroke()->AddPoint(point);

m_pointPre = point;

SetCapture();

CView::OnLButtonDown(nFlags, point);

void CStroke_MFCView::OnLButtonUp(UINT nFlags, CPoint point)

if(!GetDocument()->IsCapture()/*GetCapture() != this*/)

if(0 != nFlags)

GetDocument()->SendBTMsgToOtherViews(this,WM_LBUTTONUP,nFlags,point);

CClientDC dc(this);

CPen* pCurPen = GetDocument()->GetCurPen();

CPen* pOldPen = dc.SelectObject(pCurPen);

dc.MoveTo(m_pointPre);

dc.LineTo(point);

dc.SelectObject(pOldPen);

ReleaseCapture();

CView::OnLButtonUp(nFlags, point);

void CStroke_MFCView::OnMouseMove(UINT nFlags, CPoint point)

if(!GetDocument()->IsCapture()/*GetCapture() != this*/)//如果沒有這一句,滑鼠移動的時候也會接受消息,但本不應該有這個程式處理的

GetDocument()->SendBTMsgToOtherViews(this,WM_MOUSEMOVE,nFlags,point);

CView::OnMouseMove(nFlags, point);

BOOL CStroke_MFCView::IsCapture(void)

return this == GetCapture();

//////////////////////////////////////////////////////////////CStroke.h//////////////////////////////////////////////////////////////////////////

#include "afx.h"

class CStroke :

public CObject

int m_nPenWidth;

CArray<CPoint,CPoint> m_pointArray;

CStroke(void);

CStroke(int nPenWidth);

void AddPoint(CPoint point);

void Draw(CDC* pDC);

void virtual Serialize(CArchive& ar);

DECLARE_SERIAL(CStroke)

~CStroke(void);

//////////////////////////////////////////////////////////////CStroke.cpp////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"

IMPLEMENT_SERIAL(CStroke,CObject,1);

CStroke::CStroke(void)

m_nPenWidth=2;

CStroke::CStroke(int nPenWidth)

m_nPenWidth=nPenWidth;

void CStroke::AddPoint(CPoint point)

m_pointArray.Add(point);

void CStroke::Draw(CDC* pDC)

CPen pen(PS_SOLID,m_nPenWidth,RGB(0,0,0));

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

int size=m_pointArray.GetSize();

if(size>0)

pDC->MoveTo(m_pointArray[0]);

for(int i=1;i<size;i++)

pDC->LineTo(m_pointArray[i]);

pDC->SelectObject(pOldPen);

void CStroke::Serialize(CArchive& ar)

if(ar.IsStoring())

ar<<m_nPenWidth;

ar>>m_nPenWidth;

m_pointArray.Serialize(ar);

CStroke::~CStroke(void)

在滾動視窗中使顯示時,為了使激活視圖(被滑鼠激活的)和兄弟視圖在邏輯上同步,必須讓激活視圖完成兩個任務。

1:進行裝置坐标繪畫。

2:邏輯坐标添加點資訊

注意裝置坐标隻是在繪圖上使用,而檔案儲存的是邏輯坐标。

兄弟視圖要完成的任務隻有,完成邏輯坐标繪畫。這就和激活視圖有着既然相反的繪畫方式。

滾動視窗的響應代碼:

// Stroke_MFCDoc.h : CStroke_MFCDoc 類的接口

//

CRect rc;

int m_nThinPen;//細筆

int m_nThickPen;//粗筆

CSize m_sizeDoc;

CSize GetSize() const {return m_sizeDoc;}

afx_msg void OnPenWidth();

void UpdatePen(void);

//////////////////////////////////////////////////////////////////Doc.cpp////////////////////////////////////////////////////////////////////////

// Stroke_MFCDoc.cpp : CStroke_MFCDoc 類的實作

#include "PenWidthDialog.h"

ON_COMMAND(ID_PEN_WIDTH, &CStroke_MFCDoc::OnPenWidth)

m_nPenWidth = /*THIN_PEN*/m_nThinPen=2;

m_nThickPen = 5;

m_sizeDoc.SetSize(800,900);

m_nPenWidth = bThinPen ? /*THICK_PEN*/m_nThickPen : m_nThinPen;

//pCmdUI->Enable(!m_StrokeList.IsEmpty());

SendMessage(pView->GetSafeHwnd(),message,(WPARAM)0x10000000,LPARAM(point.y<<16|point.x));

void CStroke_MFCDoc::OnPenWidth()

CPenWidthDialog dlg;

dlg.m_nThickPen = m_nThickPen;

dlg.m_nThinPen = m_nThinPen;

if(IDOK == dlg.DoModal())

m_nThickPen = dlg.m_nThickPen;

m_nThinPen = dlg.m_nThinPen;

UpdatePen();

void CStroke_MFCDoc::UpdatePen(void)

m_nPenWidth = bThinPen ? m_nThinPen : m_nThickPen;

//////////////////////////////////////////////////////////////////view.h////////////////////////////////////////////////////////////////////////

// Stroke_MFCView.h : CStroke_MFCView 類的接口

class CStroke_MFCView : public /*CView*/CScrollView

virtual void OnInitialUpdate(); // 構造後第一次調用

//void DPToLP(CClientDC* pDC,CPoint& point);

//////////////////////////////////////////////////////////////////view.cpp////////////////////////////////////////////////////////////////////////

// Stroke_MFCView.cpp : CStroke_MFCView 類的實作

IMPLEMENT_DYNCREATE(CStroke_MFCView, /*CView*/CScrollView)

BEGIN_MESSAGE_MAP(CStroke_MFCView, /*CView*/CScrollView)

ON_COMMAND(ID_FILE_PRINT, &/*CView*/CScrollView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, &/*CView*/CScrollView::OnFilePrint)

void CStroke_MFCView::OnInitialUpdate()

CScrollView::OnInitialUpdate();

// TODO: 計算此視圖的合計大小

SetScrollSizes(MM_TEXT, GetDocument()->GetSize());

if(0x10000000 != nFlags)//如果是系統發送的話,由該view唯一建立線條

//将裝置坐标轉換成邏輯坐标儲存起來

OnPrepareDC(&dc);

dc.DPtoLP(&point);

//必須将邏輯坐标轉換完後才能發送,使得兄弟視圖畫的不是裝置坐标而是邏輯坐标

if(0x10000000 != nFlags)

else//如果是兄弟發來的消息,那麼自己應該會邏輯坐标。而不是裝置坐标

繼續閱讀