CEGUI添加自定义控件[通俗易懂]

CEGUI添加自定义控件[通俗易懂]CEGUI添加自定义控件全流程

大家好,又见面了,我是你们的朋友全栈君。

用CEGUI做界面将近3个月了,比较忙,而且自己懒了许多,没能像以前那样抽出大量时间研究CEGUI,查阅更多的资料书籍,只是在工作间隙,将官网上的一些资料和同事推荐的《CEGUI深入解析》看了看。

国人写的这本书还是不错的,从中我学到的一些CEGUI很重要的知识,希望更多的中国人出更多的技术书籍,让后人受益。这本书我断断续续3个月才看完,实在是看得很慢。最近两天有时间,在看完之后,为了加深自己的理解,根据书中的推荐,写了一个Timer控件,来熟悉一些CEGUI的控件创建,使用过程。在这过程中,有些地方不好搞,特别是CEGUILayoutEditor编辑器的源码编译比较麻烦,不过用了一天时间,基本搞通了整个流程,这里记录总结一下,以便日后回顾。

本人使用CEGUI 0.7.4 和 CEGUILayoutEditor 0.7.1(因为官网主页最新的是0.7.1的)

  1. 创建控件对应的头文件和源文件

    cegui将控件相关的头文件放到include/elements目录下,将控件相关的源文件放在src/elements目录下。
    计时器控件的两个基本功能:

    1. 可以给控件设置图片。
    2. 可以显示文本,且文本会每秒更新。
    3. 提供每秒更新事件,以及倒计时结束事件,这样用户可以设置自己的回调函数。

    通过分析,我们的计时器控件只需要继承自CEGUI::PushButton控件即可,将其所有图片设置同一张背景图。然后在控件的构造函数中,订阅窗口每帧的更新事件,并在事件处理函数中完成计时,文本显示,触发事件等功能。下面是计时器控件的属性相关代码:

    /***********************************************************************
        filename:   CEGUITimerWindowProperties.h
        created:    7/31/2013
        author:     xujie
    *************************************************************************/
    
    #ifndef _CEGUITimerWindowProperties_h_
    #define _CEGUITimerWindowProperties_h_
    
    #include "CEGUIProperty.h"
    
    
    // Start of CEGUI namespace section
    namespace CEGUI
    {
    
    
    namespace TimerWindowProperties
    {
    class TimerFormatText : public Property
    {
    public:
    	TimerFormatText() : Property(
    		"TimerFormatText",
    		"Property to get/set the text of timer.  Value is a String.",//$(CurrentTime)
    		"$(CurrentTime)")
    	{}
    
    	String	get(const PropertyReceiver* receiver) const;
    	void	set(PropertyReceiver* receiver, const String& value);
    };
    
    class TotalTime : public Property
    {
    public:
        TotalTime() : Property(
            "TotalTime",
            "Property to get/set the total time of timer.  Value is a String.",
            "15")
        {}
    
        String	get(const PropertyReceiver* receiver) const;
        void	set(PropertyReceiver* receiver, const String& value);
    };
    
    }
    
    } // End of  CEGUI namespace section
    
    
    #endif	// end of guard _CEGUIFrameWindowProperties_h_
    

    /***********************************************************************
        filename:   CEGUITimerWindowProperties.h
        created:    7/31/2013
        author:     xujie
    *************************************************************************/
    #include "elements/CEGUITimerWindowProperties.h"
    #include "elements/CEGUITimerWindow.h"
    #include "CEGUIPropertyHelper.h"
    #include "CEGUIExceptions.h"
    
    
    // Start of CEGUI namespace section
    namespace CEGUI
    {
    
    namespace TimerWindowProperties
    {
    String	TimerFormatText::get(const PropertyReceiver* receiver) const
    {
    	return static_cast<const TimerWindow*>(receiver)->GetTimerFormatText();
    }
    
    
    void	TimerFormatText::set(PropertyReceiver* receiver, const String& value)
    {
    	static_cast<TimerWindow*>(receiver)->SetTimerFormatText( value );
    }
    
    String	TotalTime::get(const PropertyReceiver* receiver) const
    {
        return PropertyHelper::floatToString( static_cast<const TimerWindow*>(receiver)->GetTotalTime() );
    }
    
    
    void	TotalTime::set(PropertyReceiver* receiver, const String& value)
    {
        static_cast<TimerWindow*>(receiver)->SetTimer( PropertyHelper::stringToFloat( value ) );
    }
    
    }
    
    } // End of  CEGUI namespace section
    

    我为计时器设置了两个属性。

    1. 计时器格式化字符串属性。这个属性是方便用户再CEGUILayoutEditor进行设置的,比如:“还有$(CurrentTime)进入竞技场”,其中的$(CurrentTime)代表当前控件的事件。控件设置窗口文本时,会将$(CurrentTime)替换为秒数。我们这里不做太多复杂的时间格式(年-月-日-小时-分钟-秒),因为主要是为了熟悉控件创建流程。
    2. 总时间属性。这个属性表示计时器控件倒计时的总时间。如15秒钟,那么计时器就会倒计时15秒。

    下面是计时器控件的逻辑代码:

    /***********************************************************************
        filename:   CEGUITimerWindow.h
        created:    7/31/2013
        author:     xujie
    
        purpose:    Interface to a default window
    *************************************************************************/
    
    #ifndef _CEGUITimerWindow_h_
    #define _CEGUITimerWindow_h_
    
    #include "CEGUIPushButton.h"
    #include "CEGUITimerWindowProperties.h"
    
    #if defined(_MSC_VER)
    #	pragma warning(push)
    #	pragma warning(disable : 4251)
    #endif
    
    // Start of CEGUI namespace section
    namespace CEGUI
    {
    class CEGUIEXPORT TimerWindow : public PushButton
    {
    public:
        /*************************************************************************
            Constants
        *************************************************************************/
        // type name for th is widget
        static const String WidgetTypeName;             //!< The unique typename of this widget
        static const String EventNamespace;
    
        static const String EventTimerEnded;
        static const String EventOneSecondPast;
    
        static TimerWindowProperties::TimerFormatText TimerFormatTextProperty;
        static TimerWindowProperties::TotalTime TotalTimeProperty;
        /*************************************************************************
            Construction and Destruction
        *************************************************************************/
        /*!
        \brief
            Constructor for GUISheet windows.
        */
        TimerWindow(const String& type, const String& name);
    
    
        /*!
        \brief
            Destructor for GUISheet windows.
        */
        virtual ~TimerWindow(void) {}
    
        void SetTimer( float fTotalTime );
        float GetTotalTime() const;
        float GetCurrentTime() const;
    
        void SetTimerFormatText( const String& strFormatText );
        const String& GetTimerFormatText() const;
    
    protected:
        void SetTimerText( int nSecond );
    
    protected:
        bool OnUpdate( const CEGUI::EventArgs& Arg );
    	/*!
    	\brief
    		Return whether this window was inherited from the given class name at some point in the inheritance hierarchy.
    
    	\param class_name
    		The class name that is to be checked.
    
    	\return
    		true if this window was inherited from \a class_name. false if not.
    	*/
    	virtual bool	testClassName_impl(const String& class_name) const
    	{
    		if (class_name=="TimerWindow")	return true;
    		return Window::testClassName_impl(class_name);
    	}
    
    private:
        float m_fTotalTime;
        float m_fCurrentTime;
        String m_strTimerFormatText;
    };
    } // End of  CEGUI namespace section
    
    
    #endif
    

    /***********************************************************************
        filename:   CEGUITimerWindow.cpp
        created:    7/31/2013
        author:     xujie
    
        purpose:    Interface to a default window
    *************************************************************************/
    
    #include "elements/CEGUITimerWindow.h"
    #include <sstream>
    
    
    // Start of CEGUI namespace section
    namespace CEGUI
    {
    /*************************************************************************
    	Constants
    *************************************************************************/
    // type name for this widget
    const String TimerWindow::WidgetTypeName( "CEGUI/TimerWindow" );
    const String TimerWindow::EventNamespace( "TimerWindow" );
    
    const String TimerWindow::EventTimerEnded( "TimerEnded" );
    const String TimerWindow::EventOneSecondPast( "OneSecondPast" );
    
    TimerWindowProperties::TimerFormatText TimerWindow::TimerFormatTextProperty;
    TimerWindowProperties::TotalTime TimerWindow::TotalTimeProperty;
    
    
    /*************************************************************************
        Constructor
    *************************************************************************/
    TimerWindow::TimerWindow(const String& type, const String& name) :
        PushButton(type, name), 
        m_fTotalTime( 0 ), 
        m_fCurrentTime( 0 ),
        m_strTimerFormatText( "$(CurrentTime)" )
    {
        subscribeEvent( 
            CEGUI::Window::EventWindowUpdated, 
            CEGUI::Event::Subscriber( &TimerWindow::OnUpdate, this ) );
    
        addProperty( &TimerFormatTextProperty );
        addProperty( &TotalTimeProperty );
    }
    
    void TimerWindow::SetTimer( float fTotalTime )
    {
        m_fTotalTime = fTotalTime;
        m_fCurrentTime = fTotalTime;
    }
    
    float TimerWindow::GetTotalTime() const
    {
        return m_fTotalTime;
    }
    
    float TimerWindow::GetCurrentTime() const
    {
        return m_fCurrentTime;
    }
    
    void TimerWindow::SetTimerFormatText( const String& strFormatText )
    {
        m_strTimerFormatText = strFormatText;
    }
    
    const String& TimerWindow::GetTimerFormatText() const
    {
        return m_strTimerFormatText;
    }
    
    void TimerWindow::SetTimerText( int nSecond )
    {
        String strCurrentTimeTag( "$(CurrentTime)" );
    
        //get current time text
        std::stringstream ss;
        ss << nSecond;
        String strCurrentTimeText = ss.str().c_str();
    
        //replace current time tag to real time text
        String strText = m_strTimerFormatText;
        String::size_type nCurrentTimeTagIndex = strText.find( strCurrentTimeTag );
        if( String::npos != nCurrentTimeTagIndex )
        {
            strText.replace( nCurrentTimeTagIndex, strCurrentTimeTag.size(), strCurrentTimeText );
        }
    
        setText( strText );
    }
    
    bool TimerWindow::OnUpdate( const CEGUI::EventArgs& Arg )
    {
        const CEGUI::UpdateEventArgs* pUpdateEventArg = dynamic_cast< const CEGUI::UpdateEventArgs* >( &Arg );
        if( NULL == pUpdateEventArg )
        {
            return false;
        }
    
        if( m_fCurrentTime < 0.0f )
        {
            return true;
        }
    
        float fPreviousTime = m_fCurrentTime;
        m_fCurrentTime -= pUpdateEventArg->d_timeSinceLastFrame;
    
        //one second pasts
        if( ( int )fPreviousTime != ( int )m_fCurrentTime  )//if the two variables are positive
        {
            CEGUI::UpdateEventArgs oArg = *pUpdateEventArg;
    
            fireEvent( EventOneSecondPast, oArg, EventNamespace );
            SetTimerText( ( int )fPreviousTime );
        }
    
        //one second pasts and timer ended
        if( fPreviousTime * m_fCurrentTime < 0.0f )//if m_fCurrentTime is less than 0
        {
            CEGUI::UpdateEventArgs oArg = *pUpdateEventArg;
    
            fireEvent( EventOneSecondPast, oArg, EventNamespace );
            fireEvent( EventTimerEnded, oArg, EventNamespace );
            SetTimerText( ( int )fPreviousTime );
        }
    
        return true;
    }
    
    //----------------------------------------------------------------------------//
    }

    代码具体含义不做解释了,逻辑还是比较简单的。这些文件我都是先拷贝CEGUI原本的一些控件代码,然后更改的。其实可以做一个CEGUI新控件模板文件,这样以后添加新控件就方便多了。
    控件的逻辑模块就完成了,我们还需要将控件加到CEGUI系统中,在System::addStandardWindowFactories()函数中,添加一行:

    WindowFactoryManager::addFactory< TplWindowFactory<TimerWindow> >();

    这样就可以创建控件了。但是控件还没有LookNFeel和渲染窗口类。但因为我们是继承CEGUI::PushButton,而且没有添加新的渲染信息,所以我们可以直接复用CEGUI::PushButton的LooknFeel和渲染窗口类。我们在datafiles\schemes\TaharezLook.schemedatafiles\schemes\TaharezLookWidgets.scheme两个文件中添加如下一行xml文本。

    <FalagardMapping WindowType="TaharezLook/TimerWindow" TargetType="CEGUI/TimerWindow"  Renderer="Falagard/Button"       LookNFeel="TaharezLook/Button" />

    其中两个文件中,第一个是给CEGUI系统用的,第二个是给CEGUILayoutEditor用的。

    完成这些步骤,我们可以在CEGUI的sample中写相应代码创建TimerWindow控件了。

    CEGUI::TimerWindow* pTimerWindow = dynamic_cast< CEGUI::TimerWindow* >( 
            CEGUI::WindowManager::getSingleton().createWindow( "TaharezLook/TimerWindow", "Root/TimerWindow" ) );
        if( pTimerWindow )
        {
            pTimerWindow->setProperty( "NormalImage", "set:TaharezLook image:ButtonMiddleNormal" );
            pTimerWindow->setProperty( "HoverImage", "set:TaharezLook image:ButtonMiddleNormal" );
            pTimerWindow->setProperty( "PushedImage", "set:TaharezLook image:ButtonMiddleNormal" );
            pTimerWindow->setProperty( "DisabledImage", "set:TaharezLook image:ButtonMiddleNormal" );
            pTimerWindow->setPosition( CEGUI::UVector2( cegui_absdim( 0 ), cegui_absdim( 200 ) ) );
            pTimerWindow->setSize( CEGUI::UVector2( cegui_absdim( 700 ), cegui_absdim( 100 ) ) );
            pTimerWindow->SetTimer( 15.0f );
            pTimerWindow->SetTimerFormatText( L"剩余$(CurrentTime)秒进入竞技场" );
    
            pTimerWindow->subscribeEvent( 
                CEGUI::TimerWindow::EventTimerEnded, 
                CEGUI::Event::Subscriber( &FirstWindowSample::_OnTimerEnded, this ) );
            root->addChildWindow( pTimerWindow );
        }

    因为我的CEGUI::String类中添加接受Utf16字符串的构造函数,所以可以直接在代码里面使用Unicode字符串,如:

    pTimerWindow->SetTimerFormatText( L"剩余$(CurrentTime)秒进入竞技场" );

    例外,我们CEGUI已经支持中文显示,不知道的朋友可以到网上查阅一下相关教程。大体流程是:为CEGUI提供一个中文字符文件,然后让控件使用这种字体文件就可以显示中文了。

  2. 第一部分我们完成了代码创建控件,使用控件。接下来,我们要完成让CEGUILayoutEditor可以使用新的控件。为了让CELayoutEditor使用我们新控件,我们必须为其提供我们CEGUIBase.dll,这样编辑器才能识别使用新控件。
    其实,我们主要做的,还是为CELayoutEditor提供一个可用的CEGUIBase.dll,但是我发现,直接将下载CELayoutEditor工具中的CEGUIBase.dll替换掉,无法运行,提示refCount函数入口找不到,这说明官网提供的Release版本不是使用CEGUI 0.7.4 SDK。所以我们必须要编译CELayoutEditor源码,才能得到一个能识别我们新的CEGUIBase.dll的CELayoutEditor.exe。

    编译CELayoutEditor源码工程【参考教程】:

    1. 到CEGUI官网下载CEGUILayoutEditor源码工程,网址:CELayoutEditor源码下载地址,国内好像不能访问这网站,所以需要用到翻墙工具。如果你没有翻墙的话,推荐使用Goagent(捣鼓半天或者1天就可以永久翻墙了)。
    2. 下载完后,打开vc++9\CELayoutEditor.sln的解决方案,编译时发现,它需要很多源码中没有头文件和lib文件。这是因为CELayoutEditor使用wxWidgetUI库开发的编辑界面,所以需要wxWidget头文件和lib。下载:wxWidget源码下载地址。下载完成后打开\build\msw目录,打开其中wx.dsw文件。做如下修改:
      在下列文件中修改#define wxUSE_GLCANVAS 0为 #define wxUSE_GLCANVAS 1 :
      wxWidgets-2.8.11\include\wx\msw\setup.h
      wxWidgets-2.8.11\include\wx\msw\setup0.h
      wxWidgets-2.8.11\include\wx\univ\setup.h
      选择“Unicode Debug”编译选项进行编译。
    3. 修改CELayoutEditor项目属性配置。在头文件包含路径,以及lib文件路径,以及PostEvent命令中,解决方案使用了$(CEGUI_7),$(WXWIDGETS_7),$(CE_LAYOUT_EDITOR_7)等环境变量,我们需要新建3个环境变量(CEGUI_7,WXWIDGETS_7,CE_LAYOUT_EDITOR_7),分别对应CEGUI SDK目录,wxWidgets SDK目录,CELayoutEditor SDK目录,然后重启vs,选择Debug进行编译(因为我们上面使用Unicode Debug编译选项进行编译的,同样可以使用Unicode Release,然后CELayoutEditor也使用Release进行编译)。

      如果你发现PostEvent命令执行失败,那么可能是你的环境变量的路径中带有空格,因为Dos命令中,空格是参数分隔符,所以如果路径带空格的话,需要用双引号将路径包裹起来。我们到项目属性中【PostEvent】中,将copy命令的原路径和目标路径加上双引号。所以也推荐CELayoutEditor官网将项目属性中的,【PostEvent】命令中的路径带上双引号,以方便编译。

    4. 如果幸运的话,你现在可以调试着运行CELayoutEditor。
    5. 此时你已经可以选择【AddWindow】-》【Taharaze】-》【TimerWindow】,来创建我们新添加的控件了(记住要在$(CEGUI_7)datafiles\schemes\TaharezLookWidgets.scheme中也添加上你的新控件)。但是在控件属性上,我们还无法设置我们新控件的两个属性【TimerFormatText】【TotalTime】,为了能让CELayoutEditor中识别这两个属性,我们需要修改CELayoutEditor.ini文件。

      打开CELayoutEditor.ini文件,定位到【SupportedProperties】字段,在等号后面追加

      TimerFormatText,text;TotalTime,text;

      结果如下:

      SupportedProperties=TimerFormatText,text;TotalTime,text;Alpha,float;ClickStepSize,float;MaximumValue,float;Visible,bool;AlwaysOnTop,bool;ClippedByParent,bool;InheritsAlpha,bool;Selected,bool;ReadOnly,bool;CloseButtonEnabled,bool;DragMovingEnabled,bool;FrameEnabled,bool;SizingEnabled,bool;TitlebarEnabled,bool;MultiSelect,bool;Sort,bool;DraggingEnabled,bool;BackgroundEnabled,bool;InheritsTooltipText,bool;HoverImage,text;PushedImage,text;DisabledImage,text;NormalImage,text;Font,font;TitlebarFont,font;VerticalAlignment,vert_align;HorizontalAlignment,horz_align;VertFormatting,vert_text_format;HorzFormatting,horz_text_format;Tooltip,text;Image,text;TextColours,text;
      
      

      这个配置文件,此字段太长了,有点难看。

    6. 修改完成后,我们在新添加的TimerWindow控件上,能找到这个两个属性,并进行设置了。加载layout文件,测试很顺利。

通过CELayoutEditor编辑器,修改TextDemo.layou并运行Sample_TextDemo例子的截图:
CEGUI自定义控件运行截图
整个流程终于跑通,也让我非常开心。如果你遇到了什么意外,请留言,一起进步。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/163751.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 轻松实现在web页面中直接播放rtsp视频流「建议收藏」

    轻松实现在web页面中直接播放rtsp视频流「建议收藏」帮你轻松实现在页面中直接播放rtsp视频流使用准备ffmpeg运行rtsp2web前端代码课外知识写在前面我之前研究在web中直接播放rtsp视频流时,写过一篇文章:【前端】rtsp与rtmp视频流的播放方法。阅读这篇文章对你的学习有很大帮助。在文章中我有过详细的分析和解读,给出了结论:要想在web中实时播放rtsp视频流:借助后端转码推流将是必要的操作。实现我用node.js实现了转码推流的功能,并将其打包成rtsp2web发布到了npm上。……

    2022年10月10日
  • ffmpeg下载安装教程_Anaconda安装ffmpeg

    ffmpeg下载安装教程_Anaconda安装ffmpeg最近在处理一些音频数据,ffmpeg是一款非常好用处理音视频的工具包。那什么是ffmpeg呢?FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序,可以结合Java开发一些处理视频音频的功能。1.ffmpeg下载首先打开ffmpeg官网下载然后点击windows对应的图标,再点击下面的”WindowsEXEFile”随便选一个点进去选择一个版本下载。2.下载后解压,配置环境变量下载解压后就能在bin文件夹下能看到三个可执行程序:ffmpeg、ffpl

  • Java常用的几种属性拷贝工具类使用总结

    怕什么真理无穷,进一步有近一步的欢喜文章目录开头聊几句Java属性拷贝工具类使用总结字段和属性使用说明**org.springframework.beans.BeanUtils#copyProperties**org.apache.commons.beanutils.PropertyUtils#_copyProperties_org.apache.commons.beanutils.BeanUtils#_copyProperties原理探索Spring#BeanUtilsapache.commons#.

  • ipsec iptables_iptables -p

    ipsec iptables_iptables -piptablesiptables[-t表名]命令选项[链名][条件匹配][-j目标动作或跳转]-t表名可以省略,指定规则存放在哪个表中,默认为filter表用于存放相同功能的规则filter表:负责过滤功能能,nat表:网络地址转换功能mangle表:拆解报文做出修改并重新封装的功能raw表:关闭nat表上启用的连接追踪机制命令选项-A在…

  • 前端异步(async)解决方案(所有方案)[通俗易懂]

    前端异步(async)解决方案(所有方案)[通俗易懂]javascript是一门单线程语言,即一次只能完成一个任务,若有多个任务要执行,则必须排队按照队列来执行(前一个任务完成,再执行下一个任务)。这种模式执行简单,但随着日后的需求,事务,请求增多,这种单线程模式执行效率必定低下。只要有一个任务执行消耗了很长时间,在这个时间里后面的任务无法执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导…

  • present continuous passive_representative和representation

    present continuous passive_representative和representation1.安装ExtremeComponents安装文件在/components/web/extremetable/下,包括jar文件,images图片和css文件以及properties文件。以及web.xml,sample.jsp示例。连环指定:images文件,css文件,properties文件可随意摆放properties文件中指定images文件的位置,web.xml中指定pr…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号