MFC的“文档/视图”机制最大的好处是可以很容易实现“一档多视”。但其内部机制是非常复杂,我们没有必要也不可能自己用托管C++方法去设计这样的机制,不过对于常用的文档、视图和窗口的经典问题,我们仍有必要对此进行深入探讨。这些问题包括:切分窗口、视图切换以及一档多视。限于篇幅,这里仅就切分窗口以及窗格之间的数据传送作深入研究。
一、创建切分窗口
我们知道,MFC的切分窗口是一种“特殊”的文档窗口,它可以有许多窗格(pane),在窗格中又可包含若干个视图。并且它有静态切分和动态切分之分,而且静态切分允许的最大窗格数目为16 x 16。但在.NET中,切分窗口的概念已经发生了很大的变化,它将切分窗口中所用的切分条Splitter变成一个类,因此在.NET中,用托管C++创建切分需要另外方法,并且也不能像MFC那样可以随意静态切分256个窗格。事实上,在实际运用中,由于现在的编程工具提供丰富的Win32界面功能,因而我们大多数所使用的切分窗口通常只需2到3个窗格,而更复杂的界面则由其他方法来实现。
由于动态切分窗口在.NET中用托管C++很难实现,因此我们这里仅对静态切分作探讨。
先看一个简单的示例代码:
#using
using namespace System;
#using "System.dll"
#using "System.Windows.Forms.dll"
#using "System.Drawing.dll"
using namespace System::ComponentModel;
using namespace System::Windows::Forms;
using namespace System::Drawing;
__gc class WinForm: public Form
{
private:
TreeView *m_pTreeDirs; // 定义树视图类对象
ListView *m_pListFiles; // 定义列表视图类对象
Splitter *m_pSplitter; // 定义切分条类对象
public:
WinForm()
{
InitForm();
}
void Dispose()
{
Form::Dispose();
}
void InitForm()
{
Text = S"切分窗口的应用示例";
ClientSize = Drawing::Size(600, 400); // 设置窗口客户区大小
m_pTreeDirs = new TreeView();
m_pSplitter = new Splitter();
m_pListFiles = new ListView();
m_pTreeDirs->Size = Drawing::Size(200,400);
m_pTreeDirs->Dock = DockStyle::Left;
m_pTreeDirs->TabIndex = 0;
m_pSplitter->TabStop = false;
m_pSplitter->BorderStyle = BorderStyle::Fixed3D;
m_pSplitter->Dock = DockStyle::Left;
m_pSplitter->TabIndex = 1;
m_pListFiles->Dock = DockStyle::Fill;
Controls->Add(m_pListFiles);
Controls->Add(m_pSplitter);
Controls->Add(m_pTreeDirs);
}
};
#ifdef _UNICODE
int wmain(void)
#else
int main(void)
#endif
{
Application::Run(new WinForm());
return 0;
}
运行后,结果如图1所示。需要说明的是,由于.NET的切分需要正确的控件Z次序,否则不会出现预期的结果,通常Splitter在TreeView之前。上述切分是将两个控件分隔,若要切分三个或三个以上时需要使用Panel控件来作为其他控件的容器,例如下列的代码,其结果如图2所示。
...
__gc class WinForm: public Form
{
private:
TreeView *m_pTreeDirs;
ListView *m_pListFiles;
TextBox *m_pTextBox;
Splitter *m_pSplitter1;
Splitter *m_pSplitter2;
Panel *m_pPanel;
public:
...
void InitForm()
{
Text = S"切分窗口的应用示例";
ClientSize = Drawing::Size(600, 400);
// 所有的控件进行实例化
m_pTreeDirs = new TreeView();
m_pListFiles = new ListView();
m_pTextBox = new TextBox();
m_pSplitter1 = new Splitter();
m_pSplitter2 = new Splitter();
m_pPanel = new Panel();
// 先将m_pSplitter2、m_pTextBox和m_pListFiles加入m_pPanel中
m_pSplitter2->TabStop = false;
m_pSplitter2->BorderStyle = BorderStyle::Fixed3D;
m_pSplitter2->Dock = DockStyle::Top;
m_pSplitter2->Size = Drawing::Size(300,2);
m_pTextBox->Multiline = true;
m_pTextBox->Dock = DockStyle::Top;
m_pTextBox->Size = Drawing::Size(300,200);
m_pTextBox->Text = S"这是一个编辑框";
m_pListFiles->Dock = DockStyle::Fill;
m_pPanel->Controls->Add(m_pListFiles);
m_pPanel->Controls->Add(m_pSplitter2);
m_pPanel->Controls->Add(m_pTextBox);
// 再将m_pSplitter1、m_pPanel和m_pTreeDirs加入Controls容器中
m_pSplitter1->TabStop = false;
m_pSplitter1->BorderStyle = BorderStyle::Fixed3D;
m_pSplitter1->Dock = DockStyle::Left;
m_pSplitter1->Size = Drawing::Size(2,400);
m_pTreeDirs->Size = Drawing::Size(300,400);
m_pTreeDirs->Dock = DockStyle::Left;
m_pTreeDirs->TabIndex = 0;
m_pPanel->Dock = DockStyle::Fill;
Controls->Add(m_pPanel);
Controls->Add(m_pSplitter1);
Controls->Add(m_pTreeDirs);
}
};
...
二、窗格之间的数据传送
由于两个窗格是由相应的控件来创建的,因而它们之间的数据传输可直接进行。例如下面的例子很有趣,其结果如图3所示。
...
__gc class WinForm: public Form
{
private:
Label *m_pLabelDraw;
Splitter *m_pSplitter;
Panel *m_pPanel;
NumericUpDown *m_pSpinX;
NumericUpDown *m_pSpinY;
int m_nRectX, m_nRectY;
bool m_bChanged;
public:
WinForm()
{
m_nRectX = m_nRectY = 10;
m_bChanged = true;
InitForm();
}
void Dispose()
{
Form::Dispose();
}
void InitForm()
{
Text = S"切分窗口的应用示例";
ClientSize = Drawing::Size(600, 400);
m_pLabelDraw = new Label();
m_pSplitter = new Splitter();
m_pPanel = new Panel();
m_pSpinX = new NumericUpDown();
m_pSpinY = new NumericUpDown();
// 构造左边的m_pPanel
Label *label1 = new Label();
label1->Location = Drawing::Point(24, 36);
label1->Size = Drawing::Size(24, 16);
label1->Text = "X = ";
m_pSpinX->Location = Drawing::Point(56, 32);
m_pSpinX->Size = Drawing::Size(72, 21);
m_pSpinX->Minimum = 10;
m_pSpinX->Maximum = 1000;
m_pSpinX->ValueChanged += new EventHandler(this, &WinForm::DoValueChanged);
Label *label2 = new Label();
label2->Location = Drawing::Point(24, 76);
label2->Size = Drawing::Size(24, 16);
label2->Text = "Y = ";
m_pSpinY->Location = Drawing::Point(56, 72);
m_pSpinY->Size = Drawing::Size(72, 21);
m_pSpinY->Minimum = 10;
m_pSpinY->Maximum = 1000;
m_pSpinY->ValueChanged += new EventHandler(this, &WinForm::DoValueChanged);
GroupBox *groupBox1 = new GroupBox();
groupBox1->Controls->Add(label1);
groupBox1->Controls->Add(m_pSpinX);
groupBox1->Controls->Add(label2);
groupBox1->Controls->Add(m_pSpinY);
groupBox1->Location = Drawing::Point(8, 18);
groupBox1->Size = Drawing::Size(160, 120);
groupBox1->TabStop = false;
groupBox1->Text = "坐标设置";
m_pPanel->Controls->Add(groupBox1);
m_pPanel->Size = Drawing::Size(176, 144);
m_pPanel->Dock = DockStyle::Left;
// 再将m_pSplitter、m_pPanel和m_pLabelDraw加入Controls容器中
m_pSplitter->TabStop = false;
m_pSplitter->Dock = DockStyle::Left;
m_pSplitter->BackColor = Drawing::Color::Green;
m_pLabelDraw->BackColor = Drawing::SystemColors::Window;
m_pLabelDraw->BorderStyle = BorderStyle::Fixed3D;
m_pLabelDraw->Dock = DockStyle::Fill;
m_pLabelDraw->MouseDown += new MouseEventHandler(this, &WinForm::DoMouseDown);
Controls->Add(m_pSplitter);
Controls->Add(m_pLabelDraw);
Controls->Add(m_pPanel);
Paint += new PaintEventHandler(this, &WinForm::OnPaint);
}
void DoMouseDown(Object *sender, MouseEventArgs *e)
{
if (e->Button == MouseButtons::Left){
m_nRectX = e->X;
m_nRectY = e->Y;
m_bChanged = false;
m_pSpinX->Value = m_nRectX;
m_pSpinY->Value = m_nRectY;
Invalidate();
m_bChanged = true;
}
}
void DoValueChanged(Object *sender, EventArgs *e)
{
if (m_bChanged) {
m_nRectX = (int)m_pSpinX->Value;
m_nRectY = (int)m_pSpinY->Value;
Invalidate();
}
}
void OnPaint(Object *sender, PaintEventArgs *e)
{
m_pLabelDraw->Update();
DrawUserRect();
}
void DrawUserRect(void)
{
Graphics* g = m_pLabelDraw->CreateGraphics();
SolidBrush* backSolid = new SolidBrush(Drawing::Color::White);
g->FillRectangle(backSolid,m_pLabelDraw->ClientRectangle);
SolidBrush* foreSolid = new SolidBrush(Drawing::Color::Blue);
g->FillRectangle(foreSolid,m_nRectX-10,m_nRectY-10,20,20);
}
};
...
从上面的代码可以看出:
(1) 若在控件中进行GDI+图形绘制时,需要调用控件Update方法,以避免系统更新它。这一点与MFC是相似的。
(2) 由于当在右边窗口左击鼠标时,将鼠标的位置坐标传给NumericUpDown,此时NumericUpDown会产生ValueChanged事件,从而调用DoValueChanged,这使得整个界面的用户交互变得非常迟钝。因此,我们在这里使用m_bChanged变量来控制它,解决了上述问题。
(3) 对于比上面还要复杂的应用程序,我们推荐使用MFC的文件组织形式以及根据MFC“文档/视图”机制的思想去设计我们相关的托管类。
三、结束语
从前面的几篇文章,我们可以看出使用托管C++开发.NET应用程序是比较方便的,除了在Windows Forms可视化设计上略嫌不足(事实上,上述所有的控件布局都是通过Visual C#的窗口编辑器来进行的)。它的最大优点是代码短,编译速度奇快,生成的EXE文件也不大。当然,用托管C++开发.NET,其应用绝不止以上所述内容,以后将推出更多这样的文章,谢谢大家。
一、创建切分窗口
我们知道,MFC的切分窗口是一种“特殊”的文档窗口,它可以有许多窗格(pane),在窗格中又可包含若干个视图。并且它有静态切分和动态切分之分,而且静态切分允许的最大窗格数目为16 x 16。但在.NET中,切分窗口的概念已经发生了很大的变化,它将切分窗口中所用的切分条Splitter变成一个类,因此在.NET中,用托管C++创建切分需要另外方法,并且也不能像MFC那样可以随意静态切分256个窗格。事实上,在实际运用中,由于现在的编程工具提供丰富的Win32界面功能,因而我们大多数所使用的切分窗口通常只需2到3个窗格,而更复杂的界面则由其他方法来实现。
由于动态切分窗口在.NET中用托管C++很难实现,因此我们这里仅对静态切分作探讨。
先看一个简单的示例代码:
#using
using namespace System;
#using "System.dll"
#using "System.Windows.Forms.dll"
#using "System.Drawing.dll"
using namespace System::ComponentModel;
using namespace System::Windows::Forms;
using namespace System::Drawing;
__gc class WinForm: public Form
{
private:
TreeView *m_pTreeDirs; // 定义树视图类对象
ListView *m_pListFiles; // 定义列表视图类对象
Splitter *m_pSplitter; // 定义切分条类对象
public:
WinForm()
{
InitForm();
}
void Dispose()
{
Form::Dispose();
}
void InitForm()
{
Text = S"切分窗口的应用示例";
ClientSize = Drawing::Size(600, 400); // 设置窗口客户区大小
m_pTreeDirs = new TreeView();
m_pSplitter = new Splitter();
m_pListFiles = new ListView();
m_pTreeDirs->Size = Drawing::Size(200,400);
m_pTreeDirs->Dock = DockStyle::Left;
m_pTreeDirs->TabIndex = 0;
m_pSplitter->TabStop = false;
m_pSplitter->BorderStyle = BorderStyle::Fixed3D;
m_pSplitter->Dock = DockStyle::Left;
m_pSplitter->TabIndex = 1;
m_pListFiles->Dock = DockStyle::Fill;
Controls->Add(m_pListFiles);
Controls->Add(m_pSplitter);
Controls->Add(m_pTreeDirs);
}
};
#ifdef _UNICODE
int wmain(void)
#else
int main(void)
#endif
{
Application::Run(new WinForm());
return 0;
}
运行后,结果如图1所示。需要说明的是,由于.NET的切分需要正确的控件Z次序,否则不会出现预期的结果,通常Splitter在TreeView之前。上述切分是将两个控件分隔,若要切分三个或三个以上时需要使用Panel控件来作为其他控件的容器,例如下列的代码,其结果如图2所示。
...
__gc class WinForm: public Form
{
private:
TreeView *m_pTreeDirs;
ListView *m_pListFiles;
TextBox *m_pTextBox;
Splitter *m_pSplitter1;
Splitter *m_pSplitter2;
Panel *m_pPanel;
public:
...
void InitForm()
{
Text = S"切分窗口的应用示例";
ClientSize = Drawing::Size(600, 400);
// 所有的控件进行实例化
m_pTreeDirs = new TreeView();
m_pListFiles = new ListView();
m_pTextBox = new TextBox();
m_pSplitter1 = new Splitter();
m_pSplitter2 = new Splitter();
m_pPanel = new Panel();
// 先将m_pSplitter2、m_pTextBox和m_pListFiles加入m_pPanel中
m_pSplitter2->TabStop = false;
m_pSplitter2->BorderStyle = BorderStyle::Fixed3D;
m_pSplitter2->Dock = DockStyle::Top;
m_pSplitter2->Size = Drawing::Size(300,2);
m_pTextBox->Multiline = true;
m_pTextBox->Dock = DockStyle::Top;
m_pTextBox->Size = Drawing::Size(300,200);
m_pTextBox->Text = S"这是一个编辑框";
m_pListFiles->Dock = DockStyle::Fill;
m_pPanel->Controls->Add(m_pListFiles);
m_pPanel->Controls->Add(m_pSplitter2);
m_pPanel->Controls->Add(m_pTextBox);
// 再将m_pSplitter1、m_pPanel和m_pTreeDirs加入Controls容器中
m_pSplitter1->TabStop = false;
m_pSplitter1->BorderStyle = BorderStyle::Fixed3D;
m_pSplitter1->Dock = DockStyle::Left;
m_pSplitter1->Size = Drawing::Size(2,400);
m_pTreeDirs->Size = Drawing::Size(300,400);
m_pTreeDirs->Dock = DockStyle::Left;
m_pTreeDirs->TabIndex = 0;
m_pPanel->Dock = DockStyle::Fill;
Controls->Add(m_pPanel);
Controls->Add(m_pSplitter1);
Controls->Add(m_pTreeDirs);
}
};
...
二、窗格之间的数据传送
由于两个窗格是由相应的控件来创建的,因而它们之间的数据传输可直接进行。例如下面的例子很有趣,其结果如图3所示。
...
__gc class WinForm: public Form
{
private:
Label *m_pLabelDraw;
Splitter *m_pSplitter;
Panel *m_pPanel;
NumericUpDown *m_pSpinX;
NumericUpDown *m_pSpinY;
int m_nRectX, m_nRectY;
bool m_bChanged;
public:
WinForm()
{
m_nRectX = m_nRectY = 10;
m_bChanged = true;
InitForm();
}
void Dispose()
{
Form::Dispose();
}
void InitForm()
{
Text = S"切分窗口的应用示例";
ClientSize = Drawing::Size(600, 400);
m_pLabelDraw = new Label();
m_pSplitter = new Splitter();
m_pPanel = new Panel();
m_pSpinX = new NumericUpDown();
m_pSpinY = new NumericUpDown();
// 构造左边的m_pPanel
Label *label1 = new Label();
label1->Location = Drawing::Point(24, 36);
label1->Size = Drawing::Size(24, 16);
label1->Text = "X = ";
m_pSpinX->Location = Drawing::Point(56, 32);
m_pSpinX->Size = Drawing::Size(72, 21);
m_pSpinX->Minimum = 10;
m_pSpinX->Maximum = 1000;
m_pSpinX->ValueChanged += new EventHandler(this, &WinForm::DoValueChanged);
Label *label2 = new Label();
label2->Location = Drawing::Point(24, 76);
label2->Size = Drawing::Size(24, 16);
label2->Text = "Y = ";
m_pSpinY->Location = Drawing::Point(56, 72);
m_pSpinY->Size = Drawing::Size(72, 21);
m_pSpinY->Minimum = 10;
m_pSpinY->Maximum = 1000;
m_pSpinY->ValueChanged += new EventHandler(this, &WinForm::DoValueChanged);
GroupBox *groupBox1 = new GroupBox();
groupBox1->Controls->Add(label1);
groupBox1->Controls->Add(m_pSpinX);
groupBox1->Controls->Add(label2);
groupBox1->Controls->Add(m_pSpinY);
groupBox1->Location = Drawing::Point(8, 18);
groupBox1->Size = Drawing::Size(160, 120);
groupBox1->TabStop = false;
groupBox1->Text = "坐标设置";
m_pPanel->Controls->Add(groupBox1);
m_pPanel->Size = Drawing::Size(176, 144);
m_pPanel->Dock = DockStyle::Left;
// 再将m_pSplitter、m_pPanel和m_pLabelDraw加入Controls容器中
m_pSplitter->TabStop = false;
m_pSplitter->Dock = DockStyle::Left;
m_pSplitter->BackColor = Drawing::Color::Green;
m_pLabelDraw->BackColor = Drawing::SystemColors::Window;
m_pLabelDraw->BorderStyle = BorderStyle::Fixed3D;
m_pLabelDraw->Dock = DockStyle::Fill;
m_pLabelDraw->MouseDown += new MouseEventHandler(this, &WinForm::DoMouseDown);
Controls->Add(m_pSplitter);
Controls->Add(m_pLabelDraw);
Controls->Add(m_pPanel);
Paint += new PaintEventHandler(this, &WinForm::OnPaint);
}
void DoMouseDown(Object *sender, MouseEventArgs *e)
{
if (e->Button == MouseButtons::Left){
m_nRectX = e->X;
m_nRectY = e->Y;
m_bChanged = false;
m_pSpinX->Value = m_nRectX;
m_pSpinY->Value = m_nRectY;
Invalidate();
m_bChanged = true;
}
}
void DoValueChanged(Object *sender, EventArgs *e)
{
if (m_bChanged) {
m_nRectX = (int)m_pSpinX->Value;
m_nRectY = (int)m_pSpinY->Value;
Invalidate();
}
}
void OnPaint(Object *sender, PaintEventArgs *e)
{
m_pLabelDraw->Update();
DrawUserRect();
}
void DrawUserRect(void)
{
Graphics* g = m_pLabelDraw->CreateGraphics();
SolidBrush* backSolid = new SolidBrush(Drawing::Color::White);
g->FillRectangle(backSolid,m_pLabelDraw->ClientRectangle);
SolidBrush* foreSolid = new SolidBrush(Drawing::Color::Blue);
g->FillRectangle(foreSolid,m_nRectX-10,m_nRectY-10,20,20);
}
};
...
从上面的代码可以看出:
(1) 若在控件中进行GDI+图形绘制时,需要调用控件Update方法,以避免系统更新它。这一点与MFC是相似的。
(2) 由于当在右边窗口左击鼠标时,将鼠标的位置坐标传给NumericUpDown,此时NumericUpDown会产生ValueChanged事件,从而调用DoValueChanged,这使得整个界面的用户交互变得非常迟钝。因此,我们在这里使用m_bChanged变量来控制它,解决了上述问题。
(3) 对于比上面还要复杂的应用程序,我们推荐使用MFC的文件组织形式以及根据MFC“文档/视图”机制的思想去设计我们相关的托管类。
三、结束语
从前面的几篇文章,我们可以看出使用托管C++开发.NET应用程序是比较方便的,除了在Windows Forms可视化设计上略嫌不足(事实上,上述所有的控件布局都是通过Visual C#的窗口编辑器来进行的)。它的最大优点是代码短,编译速度奇快,生成的EXE文件也不大。当然,用托管C++开发.NET,其应用绝不止以上所述内容,以后将推出更多这样的文章,谢谢大家。