MYUI的基本使用 - demo讲解

MYUI 文档目录: MYUI 界面库 (前言)

正文:
大家下载MYUI仓库后,切换到 Branch_csdn_study 分支,用 VS2019 打开 MYUI.sln 文件,按 F5 即可运行 myui 的 demo。

如果想使用 myui ,你可以把 myui 项目编译成 lib 或 dll ,然后再自己的项目中引用 myui.h 头文件即可。当然你也可以将 myui 集成到你的项目中,只需要将代码拷贝一下即可,MYUI 以简单方便为主,不会让开发者进行复杂的操作的。

MYUI窗口创建流程:
在讲解 demo 之前,我先告诉大家MYUI窗口创建的基本流程,以便大家更好地理解 demo 中的代码:
第一步:在需要使用UI的线程中(一般就是主线程,也就是WinMain 函数)对MYUI进行初始化
第二步:创建一个继承 CWindowUI 的类,并实现 OnNotify 和 OnEvent 两个回调函数
第三步:在OnEvent 的 EnumEvent::WindowInit 事件通知里面,利用 CBuilder 类和 xml 文件创建UI界面。然后再利用 CWindowUI::AttachFrameView 函数,把UI界面附加到窗口之上。
第四步:在 OnNotify 回调中实现你对控件通知的处理,比如按钮点击事件的处理。
第五步:既然窗口类已经实现了,那么你就可以在 WinMain 中创建一个窗口对象,并用 ShowModal 函数把窗口展示出来。

demo 讲解:
哈哈,看上去挺简单把,那么接来下让我们来看看 demo 的相关源码:


int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
					LPSTR szCmdLine, int iCmdShow)
{
	TCHAR strFloder[MAX_PATH];
	GetCurrentDirectory(MAX_PATH, strFloder);
	//_Module.Init( 0, hInstance);

	ARGBREF refColor = RGB(1, 2, 3);
	ARGBREF refColor2 = ARGB(5, 1, 2, 3);

	MYUI::CUIThread::Init(hInstance);

	CSysFunction::SetThreadDpiAwarenessContext(CSysFunction::UIDPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
	CFrameWindow * window = new CFrameWindow();
	RECT rect;
	rect.left = 0;
	rect.top = 0;
	rect.right = 800;
	rect.bottom = 640;

	window->CreateEx(hInstance,NULL, WS_EX_LAYERED * 0 , WS_VISIBLE | WS_OVERLAPPEDWINDOW,
		//WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_SYSMENU,
		TEXT("HelloWin"), TEXT("HelloWin"), &rect);

	window->SetIcon(IDI_ICON1);
	window->CenterWindow();
	window->ShowModal();
	delete window;

	MYUI::CUIThread::Uninit();
	//_Module.Term();

    return 0;
}

先解释一下 MYUI::CUIThread::Init() ,这个是用来初始化UI库的。MYUI 只支持单线程使用,要知道单线程UI才是最规范的操作,那如果其他线程想弹出一个对话框怎么办?你可以SendMessage 或 PostMessage 到主线程的窗口进行弹出,特别说明一下 SendMessage 函数保证了多线程安全的。其实MYUI 也提供了 CUIThread::WorkerActive 和 CUIThread::WorkerExecute 两个函数来支持线程操作。这两个函数的作用很大,我们后面的文章会介绍,有兴趣的同学可以自己看看实现源码。
MYUI::CUIThread::Init() 函数要求在使用MYUI的线程上调用(一般主线程就是UI线程),当不再需要MYUI 后,要记得调用 MYUI::CUIThread::Uninit() 函数。

demo 中还调用了 CSysFunction::SetThreadDpiAwarenessContext 函数,CSysFunction 类是为了实现不同系统和编译器中的函数兼容而封装的。而 SetThreadDpiAwarenessContext 加上 demo 中的参数,意思是启用当前线程对DPI的响应。
由于MYUI内部实现了DPI自适应机制,控件和文本都会自动放大缩小。如果你还想在不同的 DPI 下,提供对应大小的高清倍图。比如在200%DPI 下提供二倍图,那么你可以在 CWindowUI::OnEvent 回调中,监听 DpiChange 事件。当匹配到 200%DPI 时,调用 SetSkin 函数,设置新的皮肤路径(这个路径下的文件,当然就是二倍图了,注意文件名的统一)。

当UI库初始化完成,也启用了DPI响应,我们 new 了一个 CFrameWindow 对象,不用说,这个就是我们的窗口对象了。而 WinMain 函数中对 CFrameWindow 对象的调用过程在干吗,相信不用我解释,大家都知道它在干什么了。那么我们直接进入 CFrameWindow 内部代码的讲解。看看 CFrameWindow.h 头文件的部分定义:


class CBuilderControl : public IBuilderCallback
{
public:
	virtual CControlUI * CreateControl(LPCTSTR strClass);
};

class CFrameWindow : public CWindowUI , public IControlHooker
{
public:
	CFrameWindow();
	~CFrameWindow();

	void OnNotify(MUINOTIFY&notify);
	LRESULT OnEvent(MUIEVENT &event);

public:
	virtual bool OnBefore(CControlUI* pControl, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT &lResule);
	virtual bool OnAfter(CControlUI* pControl, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT &lResule);

protected:
	virtual LRESULT CALLBACK WndProc(UINT message, WPARAM wParam, LPARAM lParam);

	LRESULT OnCreate2(WPARAM wParam, LPARAM lParam);
	LRESULT OnCreate(WPARAM wParam, LPARAM lParam);
	LRESULT OnReady(WPARAM wParam, LPARAM lParam);

	LRESULT OnTimer(WPARAM wParam, LPARAM lParam);

private:
    CMenuUI * m_pMenUI;
};

先说一下 CFrameWindow ,前面已经说过,想实现MYUI窗口对象,只需要继承 CWindowUI 这个类,并且实现 OnNotify 和 OnEvent 这两个虚函数即可,是不是很简单。
OnNotify 是控件通知回调,当控件产生点击之类的事件,便会发出通知。而 OnEvent 是窗口事件通知。关于两个回调会通知哪些事件,这里贴一下代码

	enum EnumNotify
	{
		//处理消息后,如果返回false,表明不需要调用OnNotify
		NonMessage = 0,
		ClickItem,//控件单击消息
		DbClickItem,//控件双击消息
		SelectChange,//滚动条

		CheckItem, //用法跟SelectItem一样,主要是STATE_CHECK状态会配合特殊标志,如:STATE_UNKNOW
        
		//存在多项的控件,选择(左击或右击)了某一项,wParam是控件的状态,
		//通过判断wParam,来判断选中还是取消选中
		SelectItem,
		ActiveItem,//存在多项的控件,左双击了某一项
		TextChange,//文本发生了改变,一般关联编辑框
		SetFocus,//控件获得焦点
		KillFocus,//控件失去焦点
		TimerCall,//消息通知

		ShowTip,//请求展示tip
	};

	enum EnumEvent
	{
		WindowInit = 0,//WM_NCCREATE
		RequestRenderEngine,//WM_CREATE
		WindowReady,//WM_CREATE
		WindowDestroy,//WM_NCDESTROY
		WindowShow,//WM_SHOWWINDOW
		OnFinal,//最后一条消息,一般在里面执行delete this

		DpiChange,
		SetFocued,//WM_SETFOCUS
		KillFocued,//WM_KILLFOCUS
		OnTimer ,//WM_TIMER
		DragOver,//一般是拖动结束时,触发的消息
	};

初次使用某些控件,大家可以不知道会触发哪些事件,到时候跟踪一下代码,或者在 OnNotify 和 OnEvent 打个断点,很容易就能够测试出来。

再说 OnEvent 回调中的 EnumEvent::WindowInit 事件,其实对应的是 WM_CREATE 消息通知,这个事件中 demo 调用了 OnCreate 函数,让我们看看这个函数在干吗:

LRESULT CFrameWindow::OnCreate(WPARAM wParam, LPARAM lParam)
{
	TCHAR strSkin[MAX_PATH] = {0};
#if 0
	wsprintf(strSkin, _T("file='%s'"), _T("skin/"));
#else
	wsprintf(strSkin, _T("file='%s'"), _T("../Debug/skin/"));
#endif
	SetSkin(strSkin);
	//SetSkin(_T("skin\\"));

	CControlUI * pRootLayout = nullptr;
	
	CBuilderControl * pCallback = new CBuilderControl();
	CBuilder * pBuilder = new CBuilder(this, pCallback);
	pRootLayout = pBuilder->Create(strSkin, _T("frame.xml"));
	delete pBuilder;
	delete pCallback;
	this->AttachFrameView(pRootLayout);
	
	m_pMenUI = new CMenuUI();
	m_pMenUI->Create(m_hInstance, _T("MYMENU"), strSkin, _T("menu.xml"));

    CControlUI * pControl = pRootLayout->FindControlByName(_T("btnMenu"));

    if (pControl)
    {
        pControl->SetMenu(m_pMenUI);
    }

	CControlUI* pEdit = FindControl(_T("edtMessage"));
	if (pEdit)
	{
		//pEdit->SetText(_T("床前\t明月光\n疑是\t\t地上霜\n举头\t\t\t望明月\r\n低头\t\t\t\t思故乡"));
		pEdit->SetText(_T("床前\n明月光\r\n疑是\n\n地上霜\n举头\n望\n明月"));

		//pEdit->SetText(_T("床前\t明月光\r\n疑是\t\t地上霜\n"));
	}

	return 0;
}

总的来说,OnCreate 中,首先调用了 SetSkin 设置皮肤路径,然后通过 CBuilder 类,利用 frame.xml 文件来创建了控件。这里有两点需要跟大家介绍一下。
一,CBuilder 默认只能够创建MYUI已经提供的控件,如果 xml 中包含了自定义控件,那么 CBuilder 会寻求 IBuilderCallback 接口的帮助(这里我们的 CBuilderControl 类继承了 IBuilderCallback 接口),当 IBuilderCallback 接口不为空,则调用里面 CreateControl 函数,让 CreateControl 函数来帮我们实现自定义控件的创建,很明显,这个 CreateControl 的实现需要开发者提供。
二,一个窗口只能包含一个主控件。所谓主控件你可以理解为树根,而子控件为树枝树叶。窗口对控件的管理,大部分都是通过这种树结构,从树根往树叶方向执行的。如果你熟悉窗口原理,就会知道WIN32中窗口和控件其实是同一种东西。但在MYUI中,窗口和控件的概念是完全分离的,它们是两个概念的东西。如果你想把主控件附加到窗口之上,必须像 demo 一样调用 AttachFrameView 函数。

此外 OnCreate 函数中还创建了一个特殊的控件 —— 菜单。菜单特殊是因为它的本质是弹出一个窗口,所以它的创建动作跟 WinMain 中创建窗口的动作有点相似。至于菜单的大致使用方法,大家可以先参考 demo ,更详细的我们在后面的文章再介绍。

OnCreate 函数介绍完了,有些同学可能会好奇 OnReady 函数和它对应的 EnumEvent::WindowReady 消息是干嘛的。EnumEvent::WindowReady 其实是在 WM_CREATE 消息中通过 PostMessage 发出的。为什么要这样做呢?因为开发者在 WM_CREATE 创建控件后,由于 WM_SIZE 消息还没到达,这个时候控件的一些属性的值是还没完全确定下来的。PostMessage - EnumEvent::WindowReady 消息就是为了在控件属性完全确定下来后,再发一条消息给用户,让用户自己判断是否需要修改某些东西。
上面的说法,貌似在告诉开发者,EnumEvent::WindowInit 消息是用来创建对象的,而 EnumEvent::WindowReady 消息时用来设置对象属性的。嗯,这样做确实比较规范,但如果不是特别的需要,其实你在 EnumEvent::WindowReady 不用处理任何东西。

当窗口和控件都初始化完毕后,界面正式呈现在用户眼前,如果对控件进行操作,将会再 OnNotify 回调中产生对应的事件,开发者也可以在里面写入业务处理逻辑。

好了,关于demo 的代码就介绍到这里了,其实它并不难,大家多看几遍就会明白。然后我们再说说用来创建UI的 xml 文件。在 CFrameWindow::OnCreate 中,我们知道UI界面主要是通过 frame.xml 的描述来创建的。常规的 xml 文件格式如下:

<myui>
	<全局声明 1/> <!--窗口用到的一些属性定义-->
	<全局声明 2/>
	<全局声明 3/>
	<window> <!--所有窗口属性 -->
		<Control/>  <!--所有子控件 -->
	</window>
</myui>

其中myui这个标签是必须的,只有检测到这个标签,MYUI界面库才会往下解析其他语句。至于其他属性,大家可以对 demo 调试来知道它的作用,也可以查看 Attribute.xml 文档说明。

资源打包:
好了,我们已经知道一个最基础的MYUI窗口已经创建出来,但实际的项目中,资源往往需要打包到 exe 中,那我们再来介绍一下 MYUI 的资源打包方法。

结语:
好了,demo 的介绍就到这里。目前给大家介绍的都是 MYUI 最简单的用法。其他功能的使用和实现,可以到 MYUI 文章目录中查看。另外如果想用好 MYUI ,下面几个知识点,大家最好了解一下:

  1. MYUI 如何利用 CUIThread 实现 MVC 模型
  2. 如何开发新控件
  3. 如果利用 MYUI 对 xml 脚本的支持,更好地实现UI 风格统一
  4. MYUI 如何分发消息
  5. MYUI 如何渲染控件
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值