当前位置: 首页 > 工具软件 > MediaPortal > 使用案例 >

MediaPortal界面部份源码剖析

段干飞翮
2023-12-01
 
1.GUI原理
本文涉及Media Portal 窗口的初始化, 消息路由, 响应, 窗口切换等内容. 不包括界面的渲染过程.
Media Portal GUI工作的核心代码在Core/guilib目录下,包括各种界面元素的基类,及管理者类。负责每个具体窗口工作和渲染的代码,在Plugins/WindowPlugins目录下, 以WindowPlugin.dll的方式输出。
在程序启动时, PluginManager会载入WindowPlugin.dll, 根据动态库的导出类型和MediaPortal.xml文件中PluginsWindows结点的配置,获得所有合法的窗口类, 保存在GUIWindowManger中的窗口对象列表中,并初始化。但此时的初始化,只是保存每个窗口对应配置文件的路径,并不载入皮肤。   
每一个窗口,都有一个对应的配置文件,这个文件详细描述窗口的属性以及窗口界面各种元素的属性。配置文件的名称在对应窗口子类的Init()函数中给出。 当一个窗口需要被激活为当前窗口时,根据这个配置文件的内容, 初始化界面背景,界面中的控件和皮肤。
窗口之间通过消息传递的方式进行通信。GUIMessage类详细描述消息的类型和参数。每个消息都会首先提交到GUIWindowManager类, 由它根据消息类型,目标窗口ID等参数再路由到目标窗口。
2.关键类分析
GUIWindowManager
GUIWindowManager是窗口管理类,负责载入和初始化所有的窗口. 路由各种消息到目标窗口, 响应当前激活窗口的按键,鼠标事件.渲染当前激活窗口, 以及不同窗口之间的切换.
GUIWindowManager::_listWindows 保存了所有窗口对象,以唯一的窗口ID区分.
类中有两个列表,分别保存所有需要中转的GUIMessage与 Action.
由DispatchThreadMessage方法分别调用SendMessage和 OnNewAction将这些消息路由到目标窗口. 窗口之间如果需要通信, 一般的方法是调用GUIGraphicContext.SendMessage,
而GUIWindowManager会处理这个消息。 我个人认为MediaPortal开发人员想在所有的消息中转处理之前,先修改图形上下文环境,以使界面得到必要的更新,而目前这个过程并没有完成,或者在其它地方已经做了处理。
       ActivateWindow函数负责窗口切换时的处理工作。 包括释放隐藏当前窗口并将其放到历史窗口队列中,初始化新的窗口并显示。
       Render函数负责通知当前窗口执行它自己的渲染。
       RoutedToWindow 则在有对话框弹出时,根据对话框ID设置路由窗口
    ActivateWindow 和SendMessage 函数在关键函数分析章节中有详细介绍。
 
GUIWindow
GUIWindow是窗口的基类, 它提供了窗口管理的所有方法,包括初始化和释放窗口, 渲染窗口, 处理本窗口的GUIMessage和 Action,或者中转给窗口中的控件。
此类枚举了所有窗口ID,窗口ID唯一标识一个窗口,并且作为消息路由和界面切换时的依据。
程序在启动时,会执行窗口的Init函数,但是大多只是保存一个窗口元素配置文件的路径。 在窗口需要显示时才会调用LoadSkin函数, 根据配置文件,载入窗口的控件, 及其它显示属性。 每个窗口的界面配置文件名称会在窗口派生类的Init函数中给出。
每个窗口有一个Children列表,负责保存窗口的控件,这些元素及其属性由配置文件中Control结点给出, 更多内容在关键函数分析章节LoadSkin的分析中会涉及到.
Render函数负责窗口本身的渲染,并执行窗口中每一个控件的渲染。
OnMessage函数负责处理路由到本窗口的消息, 默认会完成初始化,释放等操作,并会根据相关参数将按键和鼠标的消息,路由到具体的子窗口或子控件。 大多数具体操作的代码都可以从此函数中的断点向下跟踪到具体的子窗口类中, 
 
GUIControl
       控件基类,主要提供默认的初始化,释放,消息处理, 渲染处理等功能。 大多数具体的实现都由控件派生类负责。 HitTest函数负责判断坐标是否在本控件内, 这为GUIWindow在向控件路由消息时,提供了依据。
 
GUIButtonControl
       本类是CUIControl的派生类,表示界面中的一个按钮。
GUIButtonControl::OnAction 负责对一个按钮操作的默认响应。界面中的点击Action在路由到此后, 会产生一个GUI_MSG_CLICKED消息,由GUIWindowManager开始路由到目的窗口或者操作. 在此需要关注的是,如果控件属性中配置了action属性,则会直接执行GUIWindowManager.OnAction . 如果配置了hyperlink属性,则会直接切换到目标窗口.
 
GUIControlFactory
    控件创建类, 封装了控件创建的细节, 向外提供Create方法,并返回一个GUIControl 对象,Create方法根据一个XML中的Control节点, 实例化一个控件对象。控件的默认属性由references.xml 提供。 GetControlType函数会提供由结点属性向控件类名的转化方法。所有控件类型在此函数体内全部列出。
 
PluginManager
       插件管理类, 不做具体分析, 但是需要关注PluginManager::LoadWindowPlugin函数, 它根据WindowPlugin.dll的导出类型和MediaPortal.xml中的配置, 过滤出GUIWindow的子类, 以及要在主界面的GUIMenuControl中显示的项.
 
MediaPortalApp
       NediaPortalApp负责程序全局, 这里只是提到MediaPortalApp.OnMessage会对
GUIWindowManager.SendMessage的消息感兴趣. 而界面切换的
GUI_MSG_GOTO_WINDOW 和全屏的GUI_MSG_SWITCH_FULLSCREEN两个消息会在此处理.
 
GUIGraphicContext
    图形界面上下文环境类. 负责界面渲染的上下文环境. 不做具体分析, 只是提到窗口界面在产生消息时会先调用GUIGraphicContext.SendMessage, 然后GUIWindowManager会对这个消息感兴趣, 并负责路由这个消息到目标窗口或者控件.
 
 
3.关键函数分析
GUIWindowManager:: ActivateWindow()
   Medial Portal的很多按钮消息处理,实质都是释放隐藏当前窗口,初始化并显示另一个窗口。 调用GUIWindowManager::SendThreadMessage() 发送GUI_MSG_GOTO_WINDOW消息, 消息参数中带上目标窗口ID即可完成. MediaPortalApp中的OnMessage会对这个消息感兴趣,并处理. 但消息经过中转, 最终实质上会由GUIWindowManager::ActivateWindow处理
       切换窗口流程:
1.      如果有路由窗口显示在屏幕中,则向其发送GUI_MSG_WINDOW_DEINIT消息,将其关闭. 路由窗口一般就是模式对话框.
2.      将上一个窗口的指针指向当前窗口, 并放入历史窗口队列, 以方便界面后退.
3.      根据新窗口ID获得新窗口, 如果失败,则从历史窗口队列中获得上一个窗口
4.      向上一个窗口发送GUI_MSG_WINDOW_DEINIT消息,将其关闭.
5.      向当前新窗口发送GUI_MSG_WINDOW_INIT消息,初始化.
 
 
GUIWindowManager::SendMessage()
负责将消息队列中的一个消息转发出去,是界面消息中转的中枢,从此函数开始跟踪,始终可以到达消息最终处理的源码处。   
消息传递的路径为:
1.          先遍历订阅过消息的所有窗口,每个对此消息感兴趣的窗口,都可以完成自己的处理任务。 
2.          如果有对话框显示在界面上,并且消息的目标窗口ID与其ID相同,则将消息传递给它,并返回。 
3.          如果消息SendToTargetWindow属性为真,并且也能找到目标窗口,则将消息传递给目标窗口,并返回。
4.          将消息传递给当前激活窗口。
 
PluginManager::LoadWindowPlugin()
Media Portal 在程序启动时,会获得所有的窗口信息,并放入到GUIWindowManager的窗口队列中. 每个窗口由一个唯一的窗口ID标识, 窗口ID在GUIWindow中有枚举. 在初始载入时,一般只调用子窗口的Init()获得窗口界面配置信息的XML文件, LoadSkin函数会在窗口需要显示时才调用, 以节省资源和时间.
载入窗口信息的流程:
1.      LoadFrom(.//WindowPlugin.dll), 并获得dll中的所有导出类型.
2.      遍历所有导出类型, 如果当前类型满足条件: a..是类 b.不是虚类 c.是GUIWindow的子类. 则认为是一个合法的窗口类.
3.      窗口的ID大于0, 并且在MediaPortal.xml 的pluginswindows结点中将类全名配置为yes的类, 则放入到窗口队列中, 但由于在判断时,默认值为真, 所以只要配置文件中没配置为no的窗口类, 都可以放入到窗口队列中. 每个放入到窗口队列的类,都需要调用Init() 至少获得窗口界面配置信息的XML文件的路径.
4.      如果窗口类继承ISetupForm接口, 则放入到PluginManager::_setupFroms队列中. 而窗口名可以显示在MediaPortal主界面菜单按钮中的条件之一, 就是存在于这队列中.    其它条件详见GUIHome::LoadButtonNames();
 
GUIWindow::LoadSkin()
每个窗口的界面显示元素及其属性,都会由一个XML文件配置. 这个文件的的名字会在每个子窗口类的Init()中给出. 大多数窗口在程序初始化时, 只会保存这个文件的路径. 是否载入皮肤并初始化由默认为假的 GUIWindow::SupportsDelayedLoad属性决定. 窗口初始化操作一般是收到GUI_MSG_WINDOW_INIT时, 调用LoadSkin()完成的. 窗口初始化的完全流程可以从GUIWindow::OnMessage 中处理初始化消息时开始跟踪. 此处我们只详细讨论根据XML文件的配置信息, 初始化窗口界面的过程. 
1.      如果配置文件路径为空, 或者窗口的控件列表不为空, 则直接返回.
2.      如果文件载入失败,或者根结点不是window, 则返回.
3.      分析 “window/amination” 结点, 目前我们不关心.
4.      分析 “window/di” “window/defaultcontrol” 结点. 获得窗口ID和默认控件ID, 这是一个窗口必须的元素, 则获取失败, 则直接返回.
5.      分析overlay, autohidetopbar, disabletopbar 等属性.
6.      分析 “window/controls/*” 下面的所有子结点. 每个control结点代表一个控件及其属性, MediaPortal同时支持将一些公共的控件独立在一个XML文件中,由<import>path</importy> 导入. 根据控件结点属性初始化GUIControl 的详细内容见GUIControlFactory.Create()    每个控件都会放入到GUIWindow::Children中.
4. 消息传递流程:(以mouseclick举例)
System::WinProc
MedaiPortalApp::mouseclick
GUIGraphicsContext::OnAction
GUIWindowManager::OnActionReceived() 进入队列,由DispatchThreadMesage
MediaPortalApp::OnAction
GUIWindowManager::OnAction()
GUIWindow::OnAction()
GUIControl::OnAction() 产生一个GUIMessage
GUIGraphicsContext.SendMessage();
GUIWindowManger::SendMessage();
GUIWindow::OnMessage
GUIControl::OnMessage();
5.示例:
A,在主窗口中的MenuControl增加一个按钮
B, 在My TV主窗口中增加一个按钮及消息响应
   1.在窗口对应配置文件中增加一个control结点, 详细描述其ID,位置等属性。
   2.在窗口对应类中根据ID申请一个按钮对象
   3.在窗口对应类中OnClick 或者OnMessage 中增加按钮消息响应的代码。
 
 类似资料: