写一个潦草的应用程序使用可视化组件框架

介绍 我写的上一篇文章介绍了可视化组件框架的一些基本概念。它的大部分 处理一些我用来让高级RTTI特性工作的技术,以及其他一些 VCF的核心特性,基础工具包。在本文中,我们将看到所有的东西实际上是 那是有原因的!我们将介绍组装一个简单的涂鸦应用程序的步骤 涵盖诸如绘图、事件、自定义VCF组件类和菜单等内容。据我所知,你们都气喘吁吁 有了期待,其实也有了数百万的粉丝来信,和数十万的崇拜 粉丝们(哈,哈),让我们开始吧!! WinMain()去了哪里? 与任何应用程序一样,我们需要一个起点,那么还有什么比臭名昭著的main()函数更好的起点呢? 哇,伙计,这是Win32,我们说的是这里!这里没有臭味!比尔说我们都是假想的 使用. WinMain () !Wrongo !因为我希望VCF有一个跨平台的架构,并且在所有其他操作系统中 程序在main()中启动,我必须弄清楚如何使其工作。谢天谢地,经过仔细检查 其他人的代码(还有其他方法吗?),我找到了一个这样做的例子,但仍然在创建windows 我认为这个应用程序是Win32应用程序,而不是控制台应用程序。通过改变链接器的设置,我们可以替换默认条目 指向其他地方,在本例中是main()函数。如果你查看任何程序的链接器设置 你在vc++中运行,你会看到子系统被设置为“windows”,这很重要。如果它被设置为“控制台” 首先出现的是一个控制台窗口,而不是普通窗口。你仍然可以像平常一样创建窗口,但是这个 对于表现良好的Windows应用程序来说,这几乎不是一种行为。因为子系统是windows,所以默认 应用程序的入口点也不同,这是WinMain通常执行的地方。这就是我们的 有个小窍门。我们可以设置入口点符号为“mainCRTStartup”,从而绕过 默认行为,并运行main()函数作为入口点。 好了,好了,技术废话已经讲够了,让我们来做点实际的事情吧!任何VCF程序的第一步都是 在堆栈上创建VCF::Application派生对象的实例。如果您不需要覆盖 任何功能,然后你可以简单地使用VCF::应用,否则使用你的隐藏复制Code

VCF::Application

derived类。完成之后,只需调用VCF::应用程序的静态方法VCF::Application::appMain(), 传入main()获取的argc和argv参数,以及 瞧! 你已经完成了!VCF将为您处理余下的问题,比如启动应用程序和调用正确的运行 方法。让我们在代码中检查一下: 隐藏,复制Code

int main(int argc, char *argv[])
{
	VCF::Application app;
	VCF::Application::appMain( argc, argv );
	return 0;
}

这说明只需使用默认的VCF::Application对象。下一个示例使用派生 类创建应用程序对象。 隐藏,复制Code

class ScribbleApplication : public Application {
public:
	ScribbleApplication(){};
	virtual ~ScribbleApplication(){};
};
int main(int argc, char *argv[])
{
	ScribbleApplication app;
	VCF::Application::appMain( argc, argv );
	return 0;
}

现在我们已经提供了自己的ScribbleApplication类,而不是依赖于默认的应用程序 类。那么在幕后到底发生了什么呢?基本上,VCF运行时系统负责处理一堆事务 为你准备的东西。应用程序类的构造函数初始化FoundationKit, GraphicsKit,最后是ApplicationKit,它会初始化UIToolkit之类的东西 负责分发窗口特定的对等类,如窗口框架、文本小部件等),以及 用FoundationKit的ClassRegistry注册所有核心VCF组件类。下一件事 它所做的(这是相当重要的)就是为此将自身设置为当前应用程序对象 进程,可由应用程序的静态方法Application::getRunningInstance()检索, 它会返回一个指向app对象的指针。重要的是要注意,您不应该创建 您的进程有多个应用程序或应用程序派生对象。最后它创造了一个特殊的对等,已知 作为一个ApplicationPeer对象,它处理一些与运行应用程序相关的特定于操作系统的任务。 一旦创建了应用程序对象,我们就可以转移到appMain()方法,它只进行验证 我们实际上有一个有效运行的应用程序实例,调用应用程序对等点上的初始化函数 对象,它在Win32中做一些事情,比如调用InitCommonControlsEx()和OleInit() 设置应用程序的命令行(必要时供以后使用),然后调用应用程序的第一个命令行 通常覆盖的方法,initRunningApplication()。 Init是什么? initRunningApplication()调用允许开发人员初始化任何特定于应用程序的内容 他或她想要的启动代码。对于那些熟悉MFC的人来说,它类似于CWinApp的InitInstance() 方法。它通常是创建应用程序主窗口的地方。该函数返回年代真正的 或假,真 可以继续,false表示错误。本申报表 appMain()方法中使用的代码用于确定是否继续,并带有返回值 表明继续前进是安全的。此时返回false将导致应用程序的terminateRunningApplication() 方法,然后该进程将清理并退出。terminateRunningApplication () 是您可以重写的另一种方法,以确保任何特定于应用程序的数据得到正确清理。一次 应用程序已经初始化,对应用程序的run()方法进行了调用, 它反过来调用对等方的run()方法,然后在其中执行任何特定于操作系统的代码。在我们的 case(在Win32下),我们发现一个典型的消息循环(类似于以下内容): 隐藏,复制Code

MSG msg;
HACCEL hAccelTable = NULL;

while ( GetMessage( &msg, NULL, 0, 0 ) ) {		
	if (!TranslateAccelerator( msg.hwnd, hAccelTable, &msg ) ) {
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	}		
}

当循环结束(主窗口关闭)时,应用程序的terminateRunningApplication()为 调用,允许您优雅地退出并清理特定于应用程序的数据。对等的terminateApp () 方法,该方法执行特定于操作系统的关闭操作。最后,UIToolkit被告知要清理, 然后我们就退出了。作为一个使用VCF的开发人员,您永远不需要接触这些东西,但至少它有帮助 要知道发生了什么。需要注意的主要事情是用于初始化和终止的虚拟方法 通过重写initRunningApplication()和terminateRunningApplication().  创建主窗口 现在我们已经了解了如何创建应用程序对象并开始运行,那么我们如何创建实际的 窗户吗?一旦这个问题得到了答案,哪里是创建它们的最佳场所?一般来说,这是最好的创作场所 应用程序的窗口在initRunningApplication()方法中,您在 Application-derived类。实际上,创建windows是件容易的事(MFC开发人员应该抓住一些可靠的东西 在这一点上)。您所要做的就是创建一个VCF::Window的新实例(或者派生的类) 它)和presto你有一个窗口,你可以操纵。您还必须设置应用程序的主窗口,因此调用 setMainWindow()也必须在这一点上做。  VCF不像其他一些框架那样使用两步创建过程来创建控件 窗口是派生的)。我相信这极大地简化了编码,尽管必须进行一些调整 在基类中(但这对开发人员是隐藏的,并且与派生类无关)。当创建 VCF中的任何类型的组件实例,总是,总是在堆上使用new操作符进行操作。所以对于我们的 主窗口,我们编码如下: 隐藏,复制Code

Window* mainWindow = new Window();

不 隐藏,复制Code

Window mainWindow;

主窗口属于应用程序(这就是为什么我们调用setMainWindow()),它将销毁 当应用程序关闭时。然后,该窗口上的所有组件和控件将依次被销毁 当窗户被破坏时。这极大地简化了开发,因为您不必担心谁破坏 特殊情况是什么等等。当我们开始介绍添加控件时,我们会进一步讨论这个问题。 一旦您创建了一个窗口,您就可以轻松地设置它的属性,比如边界(通过setBounds() 方法)、标题(通过setCaption()方法)以及许多其他方法。我们把标题设为read “VCF Scribble App”,左、右分别为200,200,宽度和高度 分别为500和500。 隐藏,复制Code

mainWindow->setCaption( "VCF Scribble App" );
mainWindow->setBounds( &Rect(200,200,700,700) );

setCaption()采用VCF::字符串作为参数,其中VCF::字符串 只不过是std::basic_string<VCFChar>的类型定义。最终我想要 编写一个实际的类,该类具有与std::basic_string相同的STL接口,但也可以处理Unicode, 所以所有内部只使用Unicode操作系统调用也可以使用Unicode,但现在是这样 工作得很好(那些对开发类似的东西感兴趣的人,将非常感谢您的帮助!!) 设置边界需要一个指向Rect类的指针,我们通过一个临时堆栈对象提供这个类。 矩形有左、上、右和下的成员,因此对于矩形200、200和宽度 高度为500,500时,我们传入200,200,700,700,这些是Rect构造函数的参数 左边,上面,右边,和下面。Rect类在内部以双精度存储数据 为了更好的准确性和不需要来回转换这么多,允许更少的舍入错误(希望如此)。 实际上,如果您查看VCF应用程序工具包类和图形工具包类,所有坐标都被处理为 双打。我发现许多窗口系统(Win32除外)都是这样做的,并决定将其包含在内 这个在VCF。  自定义主窗口 现在我们可以运行我们的应用并显示一个窗口,但它本身不是hor非常有用,让我们 继续并定制一些东西。我们将从VCF::Window派生一个新类,并添加更多控件, 以及本文中介绍的其他特性。在第一个过程中,我们将添加两个面板到 一个窗口将向右对齐,另一个窗口将向其余窗口对齐 客户区。让我们看看代码是什么样子的,然后我们会介绍它是如何工作的。 隐藏,复制Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        panel1 = new Panel();
        panel1->setBounds( &Rect(0,0,100,200) );
        this->add( panel1, ALIGN_RIGHT );

        panel2 = new Panel();
        this->add( panel2, ALIGN_CLIENT );

    };

    virtual ~ScribbleWindow() {};

    VCF::Panel* panel1;
    VCF::Panel* panel2;
};

如您所见,我们有VCF::Panel类的两个实例,panel1和panel2,然后创建 他们使用新的操作符。面板是一个简单的 控件实现了VCF::Container接口类,从而允许它容纳其他子控件。 面板也有一个默认边框,用于在控件的外部绘制3D边缘。在我们的例子中,我们是 将panel1添加到窗口并将其向右对齐,而我们将添加panel2并将其对齐 剩下的客户区域。一旦我们创建了panel1,我们就设置它的边界,其宽度为100。这 因为当控件向右或向左对齐时,控件仍将保持其原始宽度,但只有 改变它的高度。下一步是添加控件,这是通过窗口的add()方法完成的, 在这里,我们传递控件和一个enum,它指定了我们想要的对齐方式(我们的选择是ALIGN_LEFT, ALIGN_RIGHT、ALIGN_TOP、ALIGN_BOTTOM、ALIGN_CLIENT和ALIGN_NONE 用于父控件内的固定坐标)。与panel1一样,我们以同样的方式创建panel2 然后,再次使用add方法,这次传入panel2,并对齐ALIGN_CLIENT。 注意,我们不必为panel2设置边界;因为它是添加一个对齐 ALIGN_CLIENT,父容器(在本例中是窗口)将处理重新调整和重新定位 的控制。现在,我们有了一个带有两个子控件的窗口,这两个子控件都是对齐的,并且将是对齐的 当我们调整父窗口的大小时,自动为我们调整大小。另外,闪烁这个烦人的老问题 窗户走了! !调整大小到你的心的内容,你将看到没有闪烁!这是因为VCF会自动 在重绘消息期间为您提供双重缓冲。但是,您可以通过设置调用控件的方法来关闭此功能 方法并传入false(传入false) true将再次打开双缓冲)。 添加控件 向其他控件添加控件是非常有用和方便的事情,所以让我们深入一点细节 至于它是如何工作的。可以容纳其他控件的控件称为容器 在已。任何控件都可以是容器,只要它正确地实现了VCF:: container的方法 接口类。为了帮助实现这一点,有一个类实现了这些方法,称为VCF::AbstractContainer 您可以继承它来将此功能添加到您的类中。如果我们看一下宣言 Panel类,我们可以看到它是这样做的。容器的主要方法是 add()方法和remove()方法。add()方法有两种形式, 首先是添加控件,容器将简单地使用在控件中指定的对齐方式 控件的对齐属性,第二个获取控件和对齐类型,以指定如何添加控件 当您添加控件时,您还需要指定新添加的控件的父控件,这将发生 自动添加到add()方法中。您不应该自己直接设置控件的父类,如 这可能会导致不一致的行为。向父控件添加控件也意味着父控件将 当父元素被删除时,删除它的子元素,这样您就不必担心释放相关的内存 控制只 在添加控件时,可以指定对齐类型,该类型决定容器将如何调整控件的大小 孩子控制。对齐到左侧(ALIGN_LEFT)将调整控件的大小,以便其左侧将 始终对齐容器控件的客户端边缘的最左端,或对齐容器控件的右边缘 以前添加的控件左对齐。控件将保持其宽度,但顶部、左侧和底部将保持宽度 将由容器决定。类似的行为发生在右对齐、顶部对齐或底部对齐(ALIGN_RIGHT, ALIGN_TOP ALIGN_BOTTOM分别)。如果对齐方式为ALIGN_CLIENT, 然后,在其他对齐的控件已定位之后,将该控件的大小调整为任何剩余的客户端空间。 如果控件不希望执行任何对齐规则,那么它的对齐应该设置为ALIGN_NONE, 这是所有基本控件的默认对齐方式。ALIGN_NONE表示指定的坐标 总是受到尊重,并且在调整容器大小时不会改变。 自定义控件:ScribbleView控件 这很好,但我们让它更有趣。我们现在有两个面板,一个 将容纳一些按钮,另一个将容纳一个我们将创建的自定义控件作为我们的涂鸦 绘制表面。无论何时您想要在VCF中创建自定义控件,您都可以使用几个选项来继续操作。 您的第一个问题是,您是想从零开始,还是只是增强一个已经存在的控件, 例如,向Label控件添加斜面文本效果。如果你想从头开始,往往最好的地方 派生自VCF::CustomControl类。你要面对的下一个问题是你是否想要 控件可以是“轻”或“重”。重量控制是一种使用 显示和路由事件的本地窗口系统资源,换句话说,在Win32下,一个控件 有一个HWND和一个HDC与之关联。这方面的一个好例子是窗口或面板控件,两者都是 被认为是重重量控制。另一方面,轻量级控件可以与它的用户共享这些资源 重权重的父节点(在这条线上的某个位置),从而减少了正在使用的本地资源的数量。一个例子 其中的VCF::Label控件。这是非常有用的,特别是当你 构建作为另一个更复杂控件(如标题或工具栏按钮)的组件的控件。那些 使用过Java的人可以在本地重量级对等类中识别出这一点,而那些来自Delphi的对等类 背景将看到与TWndControl和TGraphicControl类的相似性。为 对于那些有MFC背景的人来说,MFC中没有可比的类(遗憾的是),所有的都是a 窗口,除非你去麻烦写一些类似轻量级控制自己的东西。 出于我们的目的,我们将构建一个轻量级的自定义控件,因此我们将从VCF::CustomControl派生它。 为了让控件变得更重或更轻,我们向构造函数传递一个布尔标志 的VCF::CustomControl, true表示 该控件是重量级控件,对于轻量级控件为false 控件(默认值为true)。让我们看一些代码: 隐藏,复制Code

class ScribbleView: public VCF::CustomControl {
public:
    ScribbleView():
    VCF::CustomControl( false )
    {
    }

    virtual ~ScribbleView(){}
};

接下来我们要做的是自定义控件的绘制。要做到这一点,我们只需重写 paint()方法,如有必要,调用超类,然后执行我们自己的操作。当 实现paint()方法时,传入一个指向GraphicsContext对象的指针, 这就是所有绘画的处理方式。GraphicsContext也包含了所有的绘图状态信息 作为绘制2D原语的各种方法。在进一步深入之前,让我们先看看代码和 然后我会解释更多。 隐藏,复制Code

class ScribbleView: public VCF::CustomControl {
public:
    ScribbleView():
    VCF::CustomControl( false )
    {
    }

    virtual ~ScribbleView(){}

    virtual void paint( GraphicsContext* ctx ) {
          CustomControl::paint(ctx); 

          Color color(0.85f,0.85f,0.85f);
          ctx->setColor( &color );
          Rect r( 5, 5, getWidth() - 5, getHeight()-5 );
          ctx->rectangle( &r );
          ctx->fillPath();
    }
};

paint()方法的第一行首先调用超类paint()方法, 然后创建一个颜色对象,并设置为浅灰色。GraphicsContext的 然后通过调用setColor()方法设置颜色。这样做会导致GraphicsContext 为此后描边或填充的所有路径使用指定的颜色。在这之后我们画了一个矩形 通过调用GraphicsContext的rectangle()方法,传递一个指向矩形的指针 对象,然后要实际呈现矩形,我们调用fillPath(),它填充任何路径操作 使用GraphicsContext的当前颜色。 GraphicsContext通过指定一系列像PostScript一样的绘图操作来工作, 如moveTo, lineTo,矩形,椭圆等,然后你可以填充或描边路径生成 之前的功能。填充或描边路径之后,路径操作被删除,然后继续。 除了这些较低级的2D原语调用之外,还可以使用一系列高级调用 绘制更复杂的几何图形,以及利用GraphicsContext的转换矩阵 比如缩放、平移、旋转和剪切(可能会在另一篇文章中讨论)。 这还不算太糟,让我们把我们的控件添加到我们的ScribbleWindow中 只看到它; 隐藏,复制Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        panel1 = new Panel();
        panel1->setBounds( &Rect(0,0,100,200) );
        this->add( panel1, ALIGN_RIGHT );

        panel2 = new Panel();
        this->add( panel2, ALIGN_CLIENT );
                    
        scribView = new ScribbleView();
        panel2->add( scribView, ALIGN_CLIENT );
    };

    virtual ~ScribbleWindow() {};

    VCF::Panel* panel1;
    VCF::Panel* panel2;
    ScribbleView* scribView;
};

鼠标事件,ScribbleView 现在我们的控制显示出来,但除此之外它做不了很多。既然我们想在里面画画 连接鼠标事件的事件处理。一个控件有三个方法,可以重写为这个目的: mouseDown()、mouseMove()和mouseUp(),每个都传递一个指针 一个MouseEvent对象。MouseEvent对象有类似于鼠标按钮被按下的信息 还能够确定当前是否按下了Alt、Control或Shift键。通过重写这些方法 在我们的控件上,我们可以自定义控件的行为,即每当鼠标左键时绘制线条 按下并拖动鼠标。所以让我们看看它的实际效果! 隐藏,收缩,复制Code

class ScribbleView: public VCF::CustomControl {
public:

     //other methods/constructors/destructors omitted...

     virtual void mouseDown( MouseEvent* event ) {
          CustomControl::mouseDown( event );
          dragPt = *event->getPoint();
          GraphicsContext* ctx = this->getContext();
          ctx->moveTo( dragPt.m_x, dragPt.m_y );
     }

     virtual void mouseMove( MouseEvent* event ) {
          CustomControl::mouseMove( event );
          if ( event->hasLeftButton() ) {
               Point* pt = event->getPoint();
               GraphicsContext* ctx = this->getContext();
               ctx->moveTo( dragPt.m_x, dragPt.m_y );
               dragPt = *pt;
               ctx->lineTo( dragPt.m_x, dragPt.m_y );
               ctx->setColor( Color::getColor( "red" ) );
               ctx->strokePath();	
          }
     }

private:
     Point dragPt;
};

我们要覆盖的两个方法是mouseDown()和mouseMove()。在这两种情况下我们确保先调用超类的方法,然后再做我们自己的事情。在mouseDown () 方法获取一个MouseEvent对象的指针,该对象已检索到当前鼠标坐标 通过调用事件的getPoint()方法,并将其存储在类的成员变量中。在 方法,我们通过一个快速调用来验证是否按下了鼠标左键 返回到MouseEvent的hasLeftButton()方法,该方法将返回true 如果左侧按钮是按下的,则获取当前点,然后在控件的GraphicsContext. 上绘制红线。 添加事件处理程序 现在我们已经完成了基本的ScribbleView类,我们需要连接一些事件处理程序 让我们的应用程序更有用一些。我们要做的是在右对齐的面板上添加两个命令按钮, 这个会被禁用,直到在ScribbleView上绘制了一些东西,这种情况下它会 如果用户点击它,它会清除ScribbleView。第二个将退出 如果点击应用。为了使外观更美观,我们还将在右对齐面板的顶部添加一个标签,并设置 它的标题读“命令”。所以,废话不多说,让我们看看一些代码吧! 隐藏,收缩,复制Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        panel1 = new Panel();
        panel1->setBounds( &Rect(0,0,100,200) );
        this->add( panel1, ALIGN_RIGHT );

        panel2 = new Panel();
        this->add( panel2, ALIGN_CLIENT );

        scribView = new ScribbleView();
        panel2 ->add( scribView, ALIGN_CLIENT );

        label1 = new Label();
        label1->setCaption( "Commands" );
        label1->setBounds( &Rect(10, 10, 80, 35) );
        panel1->add( label1, ALIGN_TOP );

        btn1->setBounds( &Rect(10, 50, 80, 75) );
        panel1->add( btn1 );
        btn1->setCaption( "Clear" );
        btn1->setEnabled( false );
        btn2 = new CommandButton();
        btn2->setBounds( &Rect(10, 90, 80, 115) );
        panel1->add( btn2 );
        btn2->setCaption( "Exit" );
    };

    virtual ~ScribbleWindow() {};

    VCF::Panel* panel1;
    VCF::Panel* panel2;
    ScribbleView* scribView;
};

正如您所看到的,这一部分与我们之前在代码的其他部分中看到的非常相似 写作。标签和两个按钮(btn1和btn2)都是在堆上创建的,然后添加到 它们适当的父控件。现在,我们将通过首先在ScribbleWindow中设置函数来添加事件处理 实现我们希望在函数被调用时发生的功能。 隐藏,收缩,复制Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        panel1 = new Panel();
        panel1->setBounds( &Rect(0,0,100,200) );
        this->add( panel1, ALIGN_RIGHT );

        panel2 = new Panel();
        this->add( panel2, ALIGN_CLIENT );

        scribView = new ScribbleView();
        panel2 ->add( scribView, ALIGN_CLIENT );

        label1 = new Label();
        label1->setCaption( "Commands" );
        label1->setBounds( &Rect(10, 10, 80, 35) );
        panel1->add( label1, ALIGN_TOP );

        btn1->setBounds( &Rect(10, 50, 80, 75) );
        panel1->add( btn1 );
        btn1->setCaption( "Clear" );
        btn1->setEnabled( false );
        btn2 = new CommandButton();
        btn2->setBounds( &Rect(10, 90, 80, 115) );
        panel1->add( btn2 );
        btn2->setCaption( "Exit" );
    };

    virtual ~ScribbleWindow() {};

    void onScribbleViewMouseUp( MouseEvent* e ) {
        btn1->setEnabled( true );
    }

    void onBtn2Clicked( ButtonEvent* e ) {
        this->close();
    }

    void onBtn1Clicked( ButtonEvent* e ) {
        btn1->setEnabled( false );
        scribView->repaint();
    }

    VCF::Panel* panel1;
    VCF::Panel* panel2;
    ScribbleView* scribView;
};

我们一次只看一个函数。第一个函数onScribbleViewMouseUp()将是 当鼠标按钮在ScribbleView控件上被释放时调用。当这种情况发生时,启用 btn1的状态设置为true,表示该按钮已启用 (未变灰),可以接受用户输入。下一个函数onBtn2Clicked()将在任何时候调用 用户单击btn2(或通过编程调用click()方法),并导致 要关闭的窗口,由于它被设置为应用程序的主窗口,因此也将导致应用程序 戒烟也要干净。最后一个函数onBtn1Clicked()在单击btn1时被调用, 它将btn1的enabled状态设置为false,这将使按钮变为灰色 输出(或禁用它),并调用ScribbleView的repaint()方法(继承的方法) ,这反过来导致控件清除自己,擦除控件中的内容 过程又是; 现在我们已经了解了回调函数的功能,我们如何将它们连接到对象的事件 我们对fire off感兴趣吗?在VCF中,这是通过一个类似于Java侦听器的系统来完成的 类,它本身是观察者模式的实现。VCF中的侦听器是一个c++接口类 定义在事件发生时触发的一个或多个方法。例如,ButtonListener 接口有一个名为onButtonClicked()的方法,该方法将在侦听对象时被调用 (在本例中是按钮)触发适当的事件(对于按钮,当单击() 方法被调用。要监听特定对象,我们需要在对象上调用适当的add listener方法 愿意听。如果我们想要监听一个VCF::CommandButton上的按钮点击事件 对象时,我们将调用按钮的addButtonListener()方法,并传入一个已实现的对象 ButtonListener c++接口。为了方便这一点,有一些特殊的类,通常是 在与侦听器接口相同的头中定义,称为处理程序,如ButtonHandler类中的处理程序 它实现了ButtonListener接口。处理程序类有一系列成员变量 函数指针,每个为侦听器接口实现的方法都有一个函数指针。另外,处理程序 也有一个指向作为处理程序源的对象的指针,以及成员函数指针指向的对象。 这听起来比实际要复杂得多,所以让我们看一些代码,希望能让事情更清楚一些。 隐藏,收缩,复制Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        //...initialization code omitted 

        ButtonHandler* bh = new ButtonHandler( this );
        bh->m_buttonClicked = (OnButtonEvent)ScribbleWindow::onBtn1Clicked;
        this->addEventHandler( "ButtonHandler", bh );
        btn1->addButtonListener( bh );

        bh = new ButtonHandler( this );
        bh->m_buttonClicked = (OnButtonEvent)ScribbleWindow::onBtn2Clicked;
        this->addEventHandler( "ButtonHandler2", bh );
        btn2->addButtonListener( bh );

        MouseHandler* mh = new MouseHandler( this );
        this->addEventHandler( "MouseHandler", mh );
        mh->m_mouseUp = (OnMouseEvent)ScribbleWindow::onScribbleViewMouseUp;
        view->addMouseListener( mh );
    };

    virtual ~ScribbleWindow() {};

    void onScribbleViewMouseUp( MouseEvent* e ) {
        btn1->setEnabled( true );
    }

    void onBtn2Clicked( ButtonEvent* e ) {
        this->close();
    }

    void onBtn1Clicked( ButtonEvent* e ) {
        btn1->setEnabled( false );
        scribView->repaint();
    }

    VCF::Panel* panel1;
    VCF::Panel* panel2;
    ScribbleView* scribView;
};

我们将首先为按钮设置事件处理程序,首先为btn1,然后为btn2。 我们首先在堆上使用new创建一个新的ButtonHandler对象 操作符,将源对象传递给构造函数,在本例中是ScribbleWindow实例。 ButtonHandler的m_buttonClicked成员变量被设置为指向ScribbleWindow的 onBtn1Clicked()方法,然后将处理程序添加到ScribbleWindow的事件列表 当事件处理程序被销毁时,它将为我们清除它列表中的所有事件处理程序。最后一步 是实际添加ButtonHandler对象作为按钮(btn1)的侦听器,完成了吗 通过调用btn1的addButtonListener()方法并传入按钮处理程序对象。在这 点,我们已经连接并准备从btn1接收事件!对另一个也做同样的事情 两个事件处理程序。您可能会问,“但是为什么不直接实现ButtonHandler接口呢? 在涂鸦窗课堂上?”如果你提前知道你只会一直这样做的话,这是可行的 侦听一个对象,但是如果您希望侦听使用同一侦听器的多个对象,该怎么办呢 接口?您最终将使用一系列糟糕的if语句来测试对象的类型,为此只做一件事 对象类型,另一种对象类型对应另一种对象,以此类推。这使它更干净(在我看来), 希望能有更好的伸缩性,也能更直接,这样你就能"看到"那个函数 将被该事件的某某对象调用。 有趣的涂鸦菜单 在本文中我们要讨论的最后一件事是向ScribbleWindow添加菜单项。在那里 菜单有两种主要类型:存在于窗口框架(通常在窗口顶部)的菜单和弹出式菜单 通常依赖上下文的菜单。第一种类型的菜单称为VCF::MenuBar类, 另一种是VCF::PopupMenu。我们的示例将包含这两种类型,我们的主菜单 和一个与我们的ScribbleView控件关联的弹出菜单。两VCF::菜单条 和VCF::PopupMenu有一个根菜单项,您可以使用它附加其他菜单项。最简单的 附加菜单项的方法是创建一个DefaultMenuItem的新实例,传递字符串标题 菜单项的父菜单项和该菜单项的菜单栏或弹出菜单放入构造函数中 的新菜单项。这将自动将新创建的菜单项添加到传入的父菜单项中。添加 菜单项的事件处理程序与前面一样完成,只是我们使用MenuItemHandler。让我们看 在本文的最后一段代码中,我们将看到它的实际应用。 隐藏,收缩,复制Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        //...initialization code

        //previous event handler code...
        this->setMenuBar( new MenuBar() );

        MenuBar* menuBar = this->getMenuBar();
        MenuItem* item = menuBar->getRootMenuItem();

        DefaultMenuItem* file = new DefaultMenuItem( "&File", item, menuBar ); 	

        DefaultMenuItem* fileExit = new DefaultMenuItem( "&Exit", file, menuBar ); 

        DefaultMenuItem* view = new DefaultMenuItem( "&View", item, menuBar ); 	

        viewClear= new DefaultMenuItem( "&Clear", view, menuBar ); 	

        MenuItemHandler* menuHandler = new MenuItemHandler( this );
        menuHandler->m_menuItemClicked = (OnMenuItemEvent)ScribbleWindow::onFileExitClicked;
        fileExit->addMenuItemListener( menuHandler );
        this->addEventHandler( "FileExit", menuHandler );

        MenuItemHandler* viewClearMenuHandler = new MenuItemHandler( this );
        viewClearMenuHandler->m_menuItemClicked = (OnMenuItemEvent)ScribbleWindow::onViewClearClicked;
        viewClear->addMenuItemListener( viewClearMenuHandler );
        this->addEventHandler( "viewClear", viewClearMenuHandler );

        PopupMenu* popup = new PopupMenu( this );
		
        DefaultMenuItem* popupRoot = new DefaultMenuItem( "root", NULL, popup );
        DefaultMenuItem* popupEditClear = new DefaultMenuItem( "&Clear", popupRoot, popup );

        popupEditClear->addMenuItemListener( viewClearMenuHandler );

        popup->setRootMenuItem( popupRoot );

        scribbleView->setPopupMenu( popup );
    };

    virtual ~ScribbleWindow() {};

    //previous event handler functions here

    void onFileExitClicked( MenuItemEvent* e ) {
        this->close();
    }

    void onViewClearClicked( MenuItemEvent* e ) {
        btn1->setEnabled( false );
        viewClear->setEnabled( false );
        scribbleView->repaint();
    }

    VCF::Panel* panel1;
    VCF::Panel* panel2;
    ScribbleView* scribView;
    DefaultMenuItem* viewClear;
};

要创建主菜单,我们需要创建一个新的VCF::MenuBar,然后设置主窗口的菜单栏。 一旦完成,我们就可以像前面描述的那样开始向根添加菜单项。我们创建一个"文件" 以及“退出”菜单项,以及“查看”和“清除”菜单项。就像命令一样 按钮,我们创建一个事件处理程序,这一次以VCF::MenuItemHandler的形式,并分配它的 m_menuItemClicked指向ScribbleWindow的onViewClearClicked() 方法。弹出菜单也是以同样的方式创建的,并且它与viewClear菜单项共享一个处理程序,since 它只显示一个命令“Clear”。 唉呀!…这是所有人 我们有一个Scribble应用,它有大约200行代码,它的子代码都是自动对齐的 窗口,双缓冲,允许你在左手边画画/涂鸦,清除屏幕,退出应用, 当绘图的状态发生变化时,也会更新clear按钮的状态。这还包括事件 处理各种我们想要监听并在它们触发事件时响应的对象。希望我讲完了 解释所有这些是如何工作的,并提供可供开发的备选c++体系结构,这是一个合理的工作 Win32应用程序。 本文转载于:http://www.diyabc.com/frontweb/news12330.html

原文地址:https://www.cnblogs.com/Dincat/p/13473987.html