视图窗口化(Views Windowing)

14 Aug 2024

Views 提供了通过 Widget 对象创建对话框和其他类型窗口的支持。开发者通过实现一个 WidgetDelegate(或其子接口),为窗口提供显示所需的信息,并提供窗口事件的回调通知。

简单示例

以下是创建一个简单窗口的示例代码:

#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/utf_string_conversions.h"
#include "ui/gfx/canvas.h"
#include "ui/views/controls/label.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"

class WindowView : public views::WidgetDelegateView {
 public:
  WindowView() : label_(NULL) { Init(); }
  virtual ~WindowView() {}

 private:
  // Overridden from views::View:
  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
    canvas->FillRect(GetLocalBounds(), SK_ColorWHITE);
  }

  virtual void Layout() OVERRIDE {
    gfx::Size ps = label_->GetPreferredSize();
    label_->SetBounds((width() - ps.width()) / 2, (height() - ps.height()) / 2,
                      ps.width(), ps.height());
  }

  virtual gfx::Size GetPreferredSize() OVERRIDE {
    gfx::Size ps = label_->GetPreferredSize();
    ps.set_width(ps.width() + 200);
    ps.set_height(ps.height() + 200);
    return ps;
  }

  // Overridden from views::WidgetDelegate:
  virtual string16 GetWindowTitle() const OVERRIDE {
    return ASCIIToUTF16("Hello World Window");
  }

  virtual bool CanResize() const OVERRIDE { return true; }

  virtual bool CanMaximize() const OVERRIDE { return true; }

  virtual views::View* GetContentsView() OVERRIDE { return this; }

  void Init() {
    label_ = new views::Label(ASCIIToUTF16("Hello, World!"));
    AddChildView(label_);
  }

  views::Label* label_;

  DISALLOW_COPY_AND_ASSIGN(WindowView);
};

...

views::Widget::CreateWindow(new WindowView)->Show();

这个窗口在用户关闭时会自行删除,这将导致其内的 RootView 被销毁,包括 WindowView。

WidgetDelegate

WidgetDelegate 是一个接口,为 Widget 类提供显示窗口时所需的信息,如标题和图标,以及窗口是否可调整大小等属性。它还提供了如关闭等事件的回调函数。WidgetDelegate 有一个访问器 window(),它提供对关联窗口对象的访问。一个 Widget 有一个 ContentsView,由 WidgetDelegate 提供,它是一个插入到窗口客户区的 View。

DialogDelegate

DialogDelegate 是一种专门用于对话框的 WidgetDelegate,通常带有 OK/Cancel 按钮。DialogDelegate 及其关联的 ClientView(见下文)提供了内置的 OK/Cancel 按钮、Esc/Enter 的键盘处理程序及这些功能的启用。作为用户,你编写的 View 将插入到 DialogClientView 中提供对话框的内容,并实现 DialogDelegate 以代替 WidgetDelegate,后者提供按钮点击时的回调函数以及提供按钮文本等方法。

Client 和 Non-Client Views

arch.png

由于 Chrome 的非标准窗口设计,Views 支持自定义渲染的非客户区。这是通过 Window 类和 NonClientFrameView 子类来支持的。要提供自定义窗口框架,View 子类化 NonClientFrameView,允许重写的 View 来渲染和响应窗口非客户区的事件。Views 包含两个内置类型来实现这一点——CustomFrameView 和 NativeFrameView。它们用于标准对话框和顶级窗口。

对于浏览器窗口,使用了不同的 NonClientFrameView 子类(如 GlassBrowserFrameView 和 OpaqueBrowserFrameView)。为允许使用这些子类,浏览器重写了 Window 的 CreateFrameViewForWindow 方法,以构造适当的框架视图。

除了 RootView 之外,窗口 View 层次结构中的顶层 View 是 NonClientView。这个 View 是一个容器,包含上面描述的 NonClientFrameView 和 ClientView 或其子类。ClientView 子类包含窗口客户区的内容(标题栏/框架内的内容)。一个常见的 ClientView 子类是 DialogClientView,它提供了内置的 OK/Cancel 按钮处理和对话框主题背景。要使用的具体 ClientView 由 WidgetDelegate 在其 CreateClientView 实现中指定。DialogDelegate 的默认实现会自动创建一个 DialogClientView。自定义 WidgetDelegate 可以实现此方法以返回其自己的 ClientView,例如 BrowserView(浏览器窗口的 WidgetDelegate 实现者)返回它自己。ClientView API 相对简单,但它可以在进行非客户区命中测试时执行操作,这是在 TabStrip 内的可拖动标题栏和窗口调整大小角的实现方式。

ClientView 和 NonClientFrameView 是兄弟视图,因为 Views 通常会在它们被插入 View 层次结构时进行一次性初始化,而 Windows Vista 及更新版本的 DWM 切换意味着需要在启用或禁用 DWM 时更换 NonClientFrameView。因此,如果 ClientView 是 NonClientFrameView 的子视图,它将被重新父化,这意味着它的子视图可能会重新初始化,导致负面影响。

创建一个窗口

使用上面定义的 WindowView 创建窗口的简单代码如下:

views::Widget* window = views::Widget::CreateWindow(new WindowView);
window->Show();

这段代码展示了如何使用 Views 创建和管理自定义窗口。