转chromeUI

学习Chrome源码也有一小段时间了,对该浏览器的UI部分小有了解,于是将自己的一些心得贴上来,希望对有需要的朋友有所帮助。

说明:
本人所使用的chrome源码比较早,所以下载最新的代码多少会和文中有些差异。
代码在windows下使用visual studio 2008 编译。
源码下载地址 (http://code.google.com/chromium/ )

推荐阅读:
http://www.cnblogs.com/duguguiyu/archive/2008/10/02/1303095.html

Ui部分的通用(说“通用”是因为chrome的主窗口框架是单独定制的)代码主要在目录树的“src/chrome/views”目录下。其中
controls目录封装用到的控件,例如Label、Textfield等
widget目录主要封装系统相关的UI底层细节,特别是UI消息机制。
window目录主要封装UI Frame相关的细节,例如窗口的标题栏、系统按钮以及Frame、Dialog等的代理(设计模式术语)接口。
如果想了解Chrome绘图技术,可以去Skia项目(http://code.google.com/p/skia )看看

一个简单的程序

//test.h

#include "chrome/views/view.h"
#include "chrome/views/window/window_delegate.h"

class TestWindow : 
    public views::View,
    public views::WindowDelegate{
public:

    virtual View* GetContentsView() {
        return this;
    }

    virtual gfx::Size GetPreferredSize(){
        return gfx::Size(400,300);
    }

    static void CreateTestWindow();

};
////////////////////////

//test.cpp

#include "test.h"
#include "chrome/views/window/window.h"

void TestWindow::CreateTestWindow(){
    views::Window::CreateChromeWindow(NULL,gfx::Rect(),new TestWindow)->Show();
}

结果如下:
chrome UI 学习笔记 - yolcy - 写着玩

源码分析

TestWindow 需要继承自两个类 views::View 和views::WindowDelegate。
在chrome中,任何控件(这里忽略操作系统原生控件的某些特殊情况)包括一个Frame都必须是一个View。
一个顶层窗口(例如Frame、Dialog(Dialog继承自DialogDelegate,该代理继承自 WindowDelegate))必须继承自views::WindowDelegate以便传递该窗口的一些参数,例如窗口的尺寸。
GetPreferredSize()含义很明显是告诉系统初始窗口大小(确切的说是窗口内部的根View的大小,实际窗口大小要大于该值)为(400*300),这里其实可选。
GetContentsView()函数将自己作为一个窗口的根View传给所附属的Frame(或Dialog)。
中间的黑色是因为客户区没有任何东西,所以背景贴图和背景贴图未覆盖的地方被显示出来。
内部的白色框是因为系统默认会在客户区画白色的边框。

为了方面其他代码调用,这里提供一个静态函数CreateTestWindow()打开该窗口

UI消息机制(针对windows平台)

Windows 每一个控件都单独处理消息,chrome一个整的的窗口可以看做一个(这里忽略原生空间的封装)(Windows)控件,所以所有控件的消息都会发到一块去,所以chrome Ui框架有一套机制来分发到具体的(Chrome)控件。

Windows版本的chrome的UI部分基于WTL(http://sourceforge.net/projects/wtl/)。chrome通过在此处(src\chrome\views\widget\widget_win.h)中的绑定来获取windows UI消息。接着Chrome内置的一套消息分发机制将该消息发送到具体的chrome 控件。

Chrome控件树

前面提到,chrome每一个控件都是一个views::View。views::View可以认为是chrome中所有控件的基类。在这个类中定义了一个通用的操作和默认实现,例如鼠标单击处理函数,如果某控件需要自定义鼠标处理事件,可以在自己的类中覆盖基类的默认实现。
每一个views::View可以包含子views::View。所以每一个都包含一个父views::View和若干子views::View的指针,下面是源码(src\chrome\views\view.h)的定义

// This view's parent
View *parent_;

// This view's children.
typedef std::vector<View*> ViewList;
ViewList child_views_;

这棵树的根节点便是views::RootView(src\chrome\views\widget\root_view.h)。这个View的主要用途就是从views::Widget(src\chrome\views\widget\widget.h)中接收UI消息。
这棵树的最初几层可以参考chrome源码(src\chrome\views\window\non_client_view.h)

////////////////////////////

// NonClientView

//

// The NonClientView is the logical root of all Views contained within a

// Window, except for the RootView which is its parent and of which it is the

// sole child. The NonClientView has two children, the NonClientFrameView which

// is responsible for painting and responding to events from the non-client

// portions of the window, and the ClientView, which is responsible for the

// same for the client area of the window:

//

// +- views::Window ------------------------------------+

// | +- views::RootView ------------------------------+ |

// | | +- views::NonClientView ---------------------+ | |

// | | | +- views::NonClientView subclass ---+ | | |

// | | | | | | | |

// | | | | << all painting and event receiving >> | | | |

// | | | | << of the non-client areas of a >> | | | |

// | | | | << views::Window. >> | | | |

// | | | | | | | |

// | | | +----------------------------------------+ | | |

// | | | +- views::ClientView or subclass --------+ | | |

// | | | | | | | |

// | | | | << all painting and event receiving >> | | | |

// | | | | << of the client areas of a >> | | | |

// | | | | << views::Window. >> | | | |

// | | | | | | | |

// | | | +----------------------------------------+ | | |

// | | +--------------------------------------------+ | |

// | +------------------------------------------------+ |

// +----------------------------------------------------+

//

// The NonClientFrameView and ClientView are siblings because due to theme

// changes the NonClientFrameView may be replaced with different

// implementations (e.g. during the switch from DWM/Aero-Glass to Vista Basic/

说明:
views::Window 表示一个实际的窗口,不过它并不是一个views::View。
views::RootView 如前面所述,即整个控件树的根。
Views::NonClientView 表示整个实际的控件树的根,实际上views::RootView只有一颗子树Views::NonClientView ,所以个人认为这两个View可以合并在一块。也许是为了从逻辑上分开吧,即RootView向上和OS层打交道,而 NonClientView则专注于下层的控件View
views::NonClientView subclass 表示整个非客户区的View。例如窗口的标题栏、窗口图标、关闭按钮、最大化/最小化按钮、边框等就定义在此。实际上chrome默认使用views::NonClientView的子类CustomFrameView (src\chrome\views\window\custom_frame_view.h)。此外chrome还提供原生View的封装NativeFrameView(src\chrome\views\window\native_frame_view.h ,该类本人未作测试。)。
views::ClientView or subclass 是窗口客户区的根,上面test实例中的TestWindow 也是一个views::View。该View就是挂在【views::ClientView or subclass】的下面(作为它的子树)。具体的挂在通过函数GetContentsView() 实现。

Chrome消息分发机制

下面以一个鼠标移入事件说明,views::View中定义了函数OnMouseEntered,当鼠标移入某控件时,该函数被调用。下面是相关的函数声明。

// This method is invoked when the mouse enters this control.

  //

  // Default implementation does nothing. Override as needed.

  virtual void OnMouseEntered(const MouseEvent& event);

当views::RootView收到windows消息时,例如鼠标移动的消息,它便会根据鼠标的位置获取具体的View,然后调用该View的相关处理函数。
因为相关的处理函数都在views::View中有一份默认实现,所以不关系该事件的View可以不理会。而如果需要定制处理函数只需重载相关的处理函数即可。
Chrome查找具体的View通过 GetViewForPoint函数,递归调用。View首先查找所有子View,如果事件的位置在某个子View的区域内,则调用该子View的 GetViewForPoint函数。否则返回自己。下面是RootView分发“鼠标进入”事件的源码

void RootView::OnMouseMoved(const MouseEvent& e) {
  View* v = GetViewForPoint(e.location());
  // Find the first enabled view.

  while (v && !v->IsEnabled())
    v = v->GetParent();
  if (v && v != this) {
    if (v != mouse_move_handler_) {
      if (mouse_move_handler_ != NULL) {
        MouseEvent exited_event(Event::ET_MOUSE_EXITED, 0, 0, 0);
        mouse_move_handler_->OnMouseExited(exited_event);
      }

      mouse_move_handler_ = v;

      MouseEvent entered_event(Event::ET_MOUSE_ENTERED,
                               this,
                               mouse_move_handler_,
                               e.location(),
                               0);
      mouse_move_handler_->OnMouseEntered(entered_event);
    }
posted @ 2016-03-14 18:12  kevinzhwl  阅读(324)  评论(0编辑  收藏  举报