Visual Basic控制与Visual C++开发 Visual Basic 是一个易于掌握,方便灵活的 Windows可视化开发系统, 开放性是 它的一个重要特点,可通过Visual Basic 控制来实现对Basic 语言的扩充。 Visual Basic 控制既可以由Microsoft公司提供,也可以由其他软件开发者提供, 如同利用封装了一些成员函数和数据成员的C++ 对象一样,为开发者提供许多方便 。另外在界面设计中,许多三维Visual Basic 控制可方便地美化界面,节省大量开 发工作。Visual C++ 的基础类库MFC提供CVBC ontrol 类,支持Visual Basic 控 制,其AppStudio 支持Visual Basic 控制的使用和其属性的设置。另外Visual C++的 Class Wizard 也支持Visual Basic 控制的消息框架代码自动生成,为 Visual C++开发者使用Visual Basic 控制提供了极大的方便。 可以把调用的Visual Basic 控制看成一个非常灵活的窗口,一些控制可以接 收输入,但所有的控制都提供一些可见的输出。常用于描述Visual Basic 控制的 概念是"方法","属性 " 和"事件"。Visual Basic 控制的"方法"与C++类成员函数 非常类似,但这些方法被限制在预先定义好的集合之内。其中大多包括 Ad-dItem,RemoveItem,Move和Refresh。Visual Ba sic 控制的属性相当于C++类 数据成员,每一个Visual Basic控制都可以定义自己的属性集合。Visual C++的 CVBControl 类成员函数 GetFloatProperty,GetNumProperty,GetPictureProper-ty,GetStrProperty分别 可用于获得Visual Basic 控制的 浮点属性值,整数属性值,图形属性值,字符串属性值。标准 Visual Basic 控制作 为对事件的反应,会向对话框发送通知消息。在AppStudio中(假设已选中一个对话 框)使用ClassWiz-ard,能自动在程序的主模块中产生对AfxRegisterVBXEvent的调 用。登记一个指定名字的VB事件并返回一个识别此事件的单元,此函数通常使用一 全程初始程序为信息映射定义VB事件。在主模块头文件中应包含相应的 extern 语句。例如我们在使用GRID控制中的SelChange事件,那么 Cla ss Wizard 会产生 类似如下的消息入口代码:在类头文件中原型声明afx_msg void OnSe lChangeGrid(UINT, int, CWnd*, LPVOID); 通过以下消息映射宏将函数和应用框架联系在一起: BEGIN_MESSAGE_MAP(CGridEntry, CFormView) ON_VBXEVENT(VBN_SELCHANGE, IDC_GRID, OnSelChangeGrid) END_MESSAGE_MAP() 并在主程序模块头文件中生成: extern UINT NEAR VBN_SELCHANGE; 在主程序模块CPP文件中生成: UINT NEAR VBN_SELCHANGE= AfxRegisterVBEvent("SELCHANGE"); 由于这些代码是借助工具产生的,在Visual C++中使用VB控制是方便 有力的。下面主要谈谈在利用Visual C++ 基础类库开发编程中使用 Visual Basic 控制的几个技巧: 界面功能 在VB 3.0 专业版中提供了一组三维Visual Basic 控制,在AppStudio中通过 File\Inc lude Controls命令装入 VB 3.0的控制后,便在AppStudio 工具箱 上添加了一些新的控制, 均可在对话框中使用,其中常用于界面设计的有以下 几个方面。 Gange Control (标尺度量控制) 类似于滚动条,可在界面设计中设置度量数据变化状态并显示,可作为一种动 态显示方式替代滚动条。它通过picture属形显示不同的图形,如表盘形状态, 温度计形状等。通过指针运动比较形象地显示数据的变化情况。 三维旋钮(spin button control) 该旋钮可作二维显示,也可作三维显示。用于替代滚动操作数据的输入及控制 数据的变化。它不同于标尺度量控制,必须依靠与其他控制联系起来共同对数 据进行控制和显示。 键盘状态控制(key status control) 该控制用于在应用程序的窗口中显示capslock,unlock,ins,scrolllock 的状态。 三维控制 3D control VB三维控制提供了界面设计的立体效果,利用这些三维控制可很容易地制作出具 有艺术效果的三维外观图形用户界面。其中三维检查框,单选钮和框架,类似于 二维的属性,只有一些三维字体及阴影,网格及颜色等属性,提供给用户直接在属 性窗口中设计其三维外观并观察其效果。 改造Visual Basic 控制实例: 1.GRID 控制 GRID 控制包含一个可寻址单元的矩形阵列,程序设置其行列数及初始值,并且负 责从各个单元存取数据。运行时只能通过鼠标来选择一个或一组单元。由于用 户不能直接往单元上输入数据,与其说GRID是一种输入控制,不如说它是一种输 出控制。在Visual C++ 中对G RID控制进行改造,并封装于一个CEntryGrid类中, 实现类似电子表格一样的,可直接往单元中输入数据和键盘方向键控制单元焦点 转移等操作。GRID控制除了能够支持大多数标准Vi sual Basic控制的属性、事 件和方法之外,还具有一些独特的属性和事件,本篇中所用到的事件和属性如下: Col ( Row ) 数值属性,当前列(行) Cols ( Rows ) 数值属性,总列(行)数 ColWidth ( RowHeigtht ) 数值(索引)属性,用逻辑twip 表示的列宽度(行高度) FixedCols ( FixedRows ) 数值属性,固定列(行)数 Text 字符串属性,由Col 和Row所表示单元中的字符串 SelStartCol ( SelEndCol ) 数值属性,选择起始(终止)列 SelStartRow ( SelEndRow ) 数值属性,选择起始(终止)行 SelChange事件, 当用户选中一个新的单元时,负责通知程序 RowColChange 事件, 当单元的当前行列号改变时,负责通知程序 2. CMyEdit 类: CMyEdit window 移动窗口类是Cedit的派生类,完成过滤所需的键盘信息给GRID 控制,其成员函数 ProcessKeys(UINT nChar) 先获取 GRID 控制的行列信息,再 根据nChar表示的键盘信息 ,相应地移动GRID 控制的单元焦点,这是CMyEdit类的 核心功能函数。重载了的成员函数 O nGetDlgCode()为编辑窗口选择一种特定种 类的输入风格:DLGC_WANTALLKEYS(接收所有的键盘输入)。成员函数OnKeyDown实 现当非系统键被按下时,过滤上下方向键 信息。成员函数 OnChar通过调用 ProcessKeys实现完成过滤所需的键盘信息给GRID控制。CMyEdit 类定义如下: class CMyEdit : public Cedit { public: BOOL ProcessKeys(UINT nChar); protected: afx_msg UINT OnGetDlgCode(); afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); DECLARE_MESSAGE_MAP() }; //框架消息映射的实现 BEGIN_MESSAGE_MAP(CMyEdit, CWnd) ON_WM_GETDLGCODE() ON_WM_KEYDOWN() ON_WM_CHAR() END_MESSAGE_MAP() UINT CMyEdit::OnGetDlgCode() { return Cedit::OnGetDlgCode() | DLGC_WANTALLKEYS; } void CMyEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { if (nChar == VK_UP || nChar == VK_DOWN) ProcessKeys(nChar); else Cedit::OnKeyDown(nChar, nRepCnt, nFlags); } void CMyEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { if (!ProcessKeys(nChar)) Cedit::OnChar(nChar, nRepCnt, nFlags); } BOOL CMyEdit::ProcessKeys(UINT nChar) { //本移动窗口和GRID控制都是对话框的子窗口,先获取GRID控制的指针 CGridEntry* pParent = (CGridEntry*) GetParent(); CVBControl* pGrid = pParent->m_pGrid; //获取GRID控制的一些状态信息 int nRow = (int)pGrid->GetNumProperty("Row"); int nCol = (int)pGrid->GetNumProperty("Col"); int nRows = (int)pGrid->GetNumProperty("Rows"); int nCols = (int)pGrid->GetNumProperty("Cols"); int nFirstRow = (int)pGrid->GetNumProperty("FixedRows"); int nFirstCol = (int)pGrid->GetNumProperty("FixedCols"); int nOldCol = nCol; switch (nChar) { case VK_TAB: // Shift + Tab 表示退后一列 if (GetKeyState(VK_SHIFT) & 0x8000) nCol--; else nCol++; break; case VK_RETURN: nCol++; break; case VK_UP: nRow--; break; case VK_DOWN: nRow++; break; default: return FALSE; } if (nCol >= nCols) // 移到GRID的右边时换下一行 { nCol = nFirstCol; nRow++; } if (nCol < nFirstCol) // 移到GRID的左边时换上一行 { nCol = nCols - 1; nRow--; } if (nRow < nFirstRow) // 移到GRID的上端时换下端 nRow = nRows - 1; if (nRow >= nRows) // 移到GRID的下端时换上端 nRow = nFirstRow; //设置移动后GRID的当前单元 pParent->m_bEventLockout = (nCol != nOldCol); //only allow one change event pGrid->SetNumProperty("Row", nRow); pParent->m_bEventLockout = FALSE; pGrid->SetNumProperty("Col",nCol); // generate Row/Col change event return TRUE; } 改造后的Grid 类CGridEntry : CGridEntry 类是CFormView的派生类,数据成员m_pGrid指向GRID控制,实现可直 接在GRID单元上输入的"电子表格"。M_edit是CMyEdit 类对象。成员函数 SizeToFit()用于调整编辑窗口大小,使其与网格单元一致。成员函数PositionEdit() 用于调整编辑窗口位置,接收新的输入焦点。函数FindCellPosition 实现根据网 格的行列号,返回单元的左上角坐标。 今后使用CGridEntry 类,仅仅需在App studio 中定制 class CGridEntry : public CFormView { DECLARE_DYNCREATE(CGridEntry) protected: CGridEntry(); public: enum { IDD = IDD_GRIDENTRY }; //对话框资源ID值 CVBControl* m_pGrid; CMyEdit m_edit; public: BOOL m_bInitialized; protected: void SizeToFit(); void PositionEdit(); Cpoint FindCellPosition(int nRow, int nCol); virtual void DoDataExchange(CDataExchange* pDX); virtual void OnInitialUpdate(); // first time after construct afx_msg void OnRowColChangeGrid(UINT, int, CWnd*, LPVOID); afx_msg void OnChangeGridEdit(); afx_msg void OnClickGrid(UINT, int, CWnd*, LPVOID); DECLARE_MESSAGE_MAP() }; IMPLEMENT_DYNCREATE(CGridEntry, CFormView) BEGIN_MESSAGE_MAP(CGridEntry, CFormView) ON_VBXEVENT(VBN_ROWCOLCHANGE, IDC_GRID, OnRowColChangeGrid) ON_EN_CHANGE(IDC_GRIDEDIT, OnChangeGridEdit) ON_VBXEVENT(VBN_CLICK, IDC_GRID, OnClickGrid) END_MESSAGE_MAP() CGridEntry::CGridEntry(): CFormView(CGridEntry::IDD) { m_pGrid = NULL; m_bInitialized = FALSE; } void CGridEntry::OnInitialUpdate() // first time after construct { // 动态划分一个控件的子集,并使m_edit从属于CGridEntry对象 m_edit.SubclassDlgItem(IDC_GRIDEDIT, this); CFormView::OnInitialUpdate(); //设置GRID控制窗口的风格包含 WS_CLIPSIBLINGS风格 HWND hWndGrid = m_pGrid->GetSafeHwnd(); DWORD dwStyles = ::GetWindowLong(hWndGrid, GWL_STYLE); ::SetWindowLong(hWndGrid, GWL_STYLE, dwStyles | WS_CLIPSIBLINGS); if (!m_bInitialized) { m_bEventLockout = TRUE; int nRows = (int)m_pGrid->GetNumProperty("Rows"); int nCols = (int)m_pGrid->GetNumProperty("Cols"); char buf[80]; m_pGrid->SetNumProperty("Col", 0); //设置GRID的行号 for (int nRow = 1; nRow < nRows; nRow++) { wsprintf(buf, "%d", nRow); m_pGrid->SetNumProperty("Row", nRow); m_pGrid->SetStrProperty("Text", buf); } m_pGrid->SetNumProperty("Row", 0); //设置GRID的列号 for (int nCol = 1; nCol < nCols; nCol++) { wsprintf(buf, "%c", `A` + nCol - 1); m_pGrid->SetNumProperty("Col", nCol); m_pGrid->SetStrProperty("Text", buf); } for (nRow = 1; nRow < nRows; nRow++) { //在此添加初始GRID单元的代码 } m_pGrid->SetNumProperty("Row", 1); m_pGrid->SetNumProperty("Col", 1); // force edit to reposition m_bEventLockout = FALSE; } SizeToFit(); PositionEdit(); } void CGridEntry::DoDataExchange(CDataExchange* pDX) { CFormView::DoDataExchange(pDX); DDX_VBControl(pDX, IDC_GRID, m_pGrid); } void CGridEntry::OnRowColChangeGrid(UINT, int, CWnd*, LPVOID) { PositionEdit(); } #define TWIPS_PER_INCH 1440 void CGridEntry::SizeToFit() { m_pGrid->SetNumProperty("LeftCol", m_pGrid->GetNumProperty("FixedCols")); m_pGrid->SetNumProperty("TopRow", m_pGrid->GetNumProperty("FixedRows")); //ptSize 为GRID的逻辑尺寸 Cpoint ptSize = FindCellPosition((int)m_pGrid->GetNumProperty("Rows"),(int) m_pGrid->GetNumProperty("Cols")); if (ptSize.x != (int)m_pGrid->GetNumProperty("Width") || ptSize.y != (int)m_pGrid->GetNumProperty("Height")) {//GRID的大小与逻辑尺寸不一致时,调用Move 方法重新设置GRID的大小及尺寸 Crect rect; rect.left = (int)m_pGrid->GetNumProperty("Left"); rect.top = (int)m_pGrid->GetNumProperty("Top"); rect.right = rect.left + ptSize.x; rect.bottom = rect.top + ptSize.y; m_pGrid->Move(rect); ptSize.x += 2 * rect.left; ptSize.y += 2 * rect.top; //设置滚动条 SetScrollSizes(MM_TEXT, (Csize)ptSize); } } // 返回单元左上角的相对逻辑坐标, // 当 nRow == maxRow and nCol == maxCol 时,返回值即为GRID的大小 Cpoint CGridEntry::FindCellPosition(int nRow, int nCol) { ASSERT(nRow>= 0 && nRow <= m_pGrid->GetNumProperty("Rows")); ASSERT(nCol >= 0 && nCol <= m_pGrid->GetNumProperty("Cols")); Cpoint ptPos(0, 0); // Get left edge of requested cell by summing widths of previous cells int nLeftCol = (int)m_pGrid->GetNumProperty("LeftCol"); int nLeftFix = (int)m_pGrid->GetNumProperty("FixedCols"); for (int I = 0; I < nCol; I++) { //累加列宽度 if (I < nLeftFix || I >= nLeftCol) // only count displayed cells ptPos.x += (int)m_pGrid->GetNumProperty("ColWidth", I); } // Get top edge of requested cell by summing Heights of previous cells int nTopRow = (int)m_pGrid->GetNumProperty("TopRow"); int nTopFix = (int)m_pGrid->GetNumProperty("FixedRows"); for (I = 0; I < nRow; I++ I++) { //累加行高度 if (I < nTopFix || I >= nTopRow) // only count displayed cells ptPos.y += (int)m_pGrid->GetNumProperty("RowHeight", I); } // ptPos 为逻辑twips单位,需转变为点象素单位 CClientDC dc(this); ptPos.x = MulDiv(ptPos.x, dc.GetDeviceCaps(LOGPIXELSX), TWIPS_PER_INCH); ptPos.y = MulDiv(ptPos.y, dc.GetDeviceCaps(LOGPIXELSY), TWIPS_PER_INCH); ptPos.x += nCol; // add one pixel per column for gap ptPos.y += nRow; // add one pixel per row for gap return ptPos; } void CGridEntry::PositionEdit() { //移动编辑窗口到适当位置,并从GRID单元取出文本添充当前编辑窗口 int nRow = (int)m_pGrid->GetNumProperty("Row"); int nCol = (int)m_pGrid->GetNumProperty("Col"); Cpoint ptPos = FindCellPosition(nRow, nCol); // ptPos为相对坐标需加上GRID的左上角坐标 ptPos.x += (int)m_pGrid->GetNumProperty("Left"); ptPos.y += (int)m_pGrid->GetNumProperty("Top"); CClientDC dc(this); int nWidth= MulDiv((int)m_pGrid->GetNumProperty("ColWidth", nCol), (int)dc.GetDeviceCaps(LOGPIXELSX), TWIPS_PER_INCH) + 2; int nHeight= MulDiv((int)m_pGrid->GetNumProperty("RowHeight", nRow), (int)dc.GetDeviceCaps(LOGPIXELSY), TWIPS_PER_INCH) + 2; //移动编辑窗口位置及调整其大小,并添充当前编辑窗口 m_edit.MoveWindow(ptPos.x, ptPos.y, nWidth, nHeight, TRUE); Cstring s = m_pGrid->GetStrProperty("Text"); m_edit.SetWindowText(s); // Set to reflect cell`s data m_edit.ShowWindow(SW_SHOW); m_edit.SetSel(0, -1); m_edit.Invalidate(); } void CGridEntry::OnChangeGridEdit() { Cstring str; BOOL bValid; int nValue = GetDlgItemInt(IDC_GRIDEDIT, &bValid, TRUE); if (!bValid) return; // must be a valid int in order to copy. m_edit.GetWindowText(str); m_pGrid->SetStrProperty("Text", str); int nRow = (int)m_pGrid->GetNumProperty("Row"); int nCol = (int)m_pGrid->GetNumProperty("Col"); } void CGridEntry::OnClickGrid(UINT, int, CWnd*, LPVOID) { SizeToFit(); PositionEdit(); }