[TOC]
The goal of this example is to get you familiar with the basics of the Views framework and things to watch out for when developing with Views.
We will be creating the following UI:
To create this simple dialog, you would need to add the following files:
login_bubble_dialog_example.h
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LOGIN_BUBBLE_DIALOG_EXAMPLE_H_
#define LOGIN_BUBBLE_DIALOG_EXAMPLE_H_
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
class LoginBubbleDialogView : public views::BubbleDialogDelegateView {
public:
static void Show(views::View* anchor_view,
views::BubbleBorder::Arrow anchor_position);
~LoginBubbleDialogView() override;
private:
LoginBubbleDialogView(views::View* anchor_view,
views::BubbleBorder::Arrow anchor_position);
};
#endif // LOGIN_BUBBLE_DIALOG_EXAMPLE_H_
login_bubble_dialog_example.cc
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "login_bubble_dialog_example.h"
#include "ui/views/border.h"
// static
void LoginBubbleDialogView::Show(views::View* anchor_view,
views::BubbleBorder::Arrow anchor_position) {
BubbleDialogDelegateView::CreateBubble(
new LoginBubbleDialogView(anchor_view, anchor_position))
->Show();
}
LoginBubbleDialogView::~LoginBubbleDialogView() = default;
LoginBubbleDialogView::LoginBubbleDialogView(
views::View* anchor_view,
views::BubbleBorder::Arrow anchor_position)
: BubbleDialogDelegateView(anchor_view, anchor_position) {}
Make sure to add these files to the relevant build file of your project.
Calling Show()
will create a simple dialog with OK and Cancel buttons and a
window close button. These UI elements are provided by default by the
BubbleDialogDelegateView
.
To populate the login dialog, we first create a LayoutManager
in the
dialog’s constructor.
login_bubble_dialog_example.cc
LoginBubbleDialogView::LoginBubbleDialogView(
views::View* anchor_view,
views::BubbleBorder::Arrow anchor_position)
: BubbleDialogDelegateView(anchor_view, anchor_position) {
...
const LayoutProvider* provider = LayoutProvider::Get();
set_margins(provider->GetDialogInsetsForContentType(
views::DialogContentType::kControl, views::DialogContentType::kControl));
const int related_control_padding =
provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL);
const int label_padding =
provider->GetDistanceMetric(views::DISTANCE_RELATED_LABEL_HORIZONTAL);
SetLayoutManager(std::make_unique<views::TableLayout>())
->AddColumn(views::LayoutAlignment::kStart,
views::LayoutAlignment::kStretch,
views::TableLayout::kFixedSize,
views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
.AddPaddingColumn(TableLayout::kFixedSize, label_padding)
.AddColumn(views::LayoutAlignment::kStretch,
views::LayoutAlignment::kStretch, 1.0f,
views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
.AddRows(1, views::TableLayout::kFixedSize)
.AddPaddingRow(TableLayout::kFixedSize, related_control_padding)
.AddRows(1, views::TableLayout::kFixedSize);
}
This creates a 2x2 TableLayout
with horizontal padding specified by the layout constant
DISTANCE_RELATED_LABEL_HORIZONTAL
. The first column will hold the form’s
Label
s and the second will hold the
Textfield
s for user input.
To help with adding rows to the TableLayout
, add the following scoped helper
and relevant headers.
login_bubble_dialog_example.cc
#include <string>
#include "ui/views/controls/label.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/layout/table_layout.h"
...
namespace {
// Adds a label textfield pair to the login dialog's layout.
views::Textfield* AddFormRow(views::View* parent,
const std::u16string& label_text) {
views::Label* label =
parent->AddChildView(std::make_unique<views::Label>(label_text));
views::Textfield* textfield =
parent->AddChildView(std::make_unique<views::Textfield>());
textfield->SetAssociatedLabel(label);
constexpr int kDefaultTextfieldWidth = 30;
constexpr int kMinimumTextfieldWidth = 5;
textfield->SetDefaultWidthInChars(kDefaultTextfieldWidth);
textfield->SetMinimumWidthInChars(kMinimumTextfieldWidth);
return textfield;
}
} // namespace
...
The call to
SetAssociatedLabel()
sets the accessible label relationship between the
Label
and the
Textfield
and copies the Label
’s accessible name to the Textfield
.
Next we need to create localized text constants for the bubble dialog title, button label, username and password. UI Localization document will guide you on how to create these string constants.
Create the following string constants:
IDS_LOGIN_DIALOG_TITLE
IDS_LOGIN_DIALOG_OK_BUTTON_LABEL
IDS_LOGIN_DIALOG_USERNAME_ACCESSIBLE_NAME
IDS_LOGIN_DIALOG_PASSWORD_ACCESSIBLE_NAME
Add the following to the bubble dialog header and implementation files.
login_bubble_dialog_example.h
#include "ui/views/controls/textfield/textfield.h"
...
class LoginBubbleDialogView : public views::BubbleDialogDelegateView {
...
private:
...
views::Textfield* username_ = nullptr;
views::Textfield* password_ = nullptr;
};
login_bubble_dialog_example.cc
#include "ui/base/l10n/l10n_util.h"
...
LoginBubbleDialogView::LoginBubbleDialogView(
views::View* anchor_view,
views::BubbleBorder::Arrow anchor_position)
: BubbleDialogDelegateView(anchor_view, anchor_position) {
...
SetTitle(l10n_util::GetStringUTF16(IDS_LOGIN_DIALOG_TITLE));
SetButtonLabel(ui::DIALOG_BUTTON_OK, l10n_util::GetStringUTF16(
IDS_STARTER_DIALOG_OK_BUTTON_LABEL));
...
username_ = AddFormRow(this, l10n_util::GetStringUTF16(
IDS_LOGIN_DIALOG_USERNAME_ACCESSIBLE_NAME));
password_ = AddFormRow(this, l10n_util::GetStringUTF16(
IDS_LOGIN_DIALOG_PASSWORD_ACCESSIBLE_NAME));
password_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD);
}
This adds the username and password fields to the form and sets the username with default focus.
To respond to a click on the login button, we define a callback that gets passed in by the caller. Update the code as follows.
login_bubble_dialog_example.h
#include <string>
...
class LoginBubbleDialogView : public views::BubbleDialogDelegateView {
public:
using OnSubmitCallback = base::OnceCallback<void(std::u16string username,
std::u16string password)>;
static void Show(View* anchor_view,
BubbleBorder::Arrow anchor_position,
OnSubmitCallback accept_callback);
...
private:
LoginBubbleDialogView(View* anchor_view,
BubbleBorder::Arrow anchor_position,
OnSubmitCallback accept_callback);
...
}
login_bubble_dialog_example.cc
#include "base/functional/bind.h"
...
// static
void LoginBubbleDialogView::Show(View* anchor_view,
BubbleBorder::Arrow anchor_position,
OnSubmitCallback accept_callback) {
BubbleDialogDelegateView::CreateBubble(
std::make_unique<LoginBubbleDialogView>(anchor_view, anchor_position,
std::move(accept_callback)))
->Show();
}
...
LoginBubbleDialogView::LoginBubbleDialogView(
View* anchor_view,
BubbleBorder::Arrow anchor_position,
OnSubmitCallback accept_callback)
: BubbleDialogDelegateView(anchor_view, anchor_position) {
...
const auto on_submit = [](const LoginBubbleDialogView* bubble_view,
OnSubmitCallback accept_callback) {
std::move(accept_callback)
.Run(bubble_view->username_->GetText(),
bubble_view->password_->GetText());
};
SetAcceptCallback(base::BindOnce(on_submit, base::Unretained(this),
std::move(accept_callback)));
...
}
Now the dialog will call the accept_callback
after the login button has been
clicked before the dialog closes.
Next we want to disable the bubble dialog’s login button until both the username and password fields have been populated.
We achieve this by having the LoginBubbleDialogView
inherit from
TextfieldController
,
and on each Textfield
update it checks to make sure that both fields are populated before enabling
the login button.
Modify the code as follows:
login_bubble_dialog_example.h
#include "ui/views/controls/textfield/textfield_controller.h"
...
class LoginBubbleDialogView : public views::BubbleDialogDelegateView,
public views::TextfieldController {
public:
...
// TextfieldController:
void ContentsChanged(Textfield* sender,
const std::u16string& new_contents) override;
...
};
login_bubble_dialog_example.cc
views::Textfield* AddFormRow(LoginBubbleDialogView* bubble,
const std::u16string& label_text) {
...
textfield->set_controller(bubble);
return textfield;
}
...
void LoginBubbleDialogView::ContentsChanged(
views::Textfield* sender,
const std::u16string& new_contents) {
SetButtonEnabled(ui::DIALOG_BUTTON_OK, !username_->GetText().empty() &&
!password_->GetText().empty());
DialogModelChanged();
}
LoginBubbleDialogView::LoginBubbleDialogView(
views::View* anchor_view,
views::BubbleBorder::Arrow anchor_position,
LoginBubbleDialogController* controller)
: BubbleDialogDelegateView(anchor_view, anchor_position),
controller_(controller) {
SetButtonEnabled(ui::DIALOG_BUTTON_OK, false);
...
username_ = AddFormRow(
this,
l10n_util::GetStringUTF16(IDS_LOGIN_DIALOG_USERNAME_ACCESSIBLE_NAME));
password_ = AddFormRow(
this,
l10n_util::GetStringUTF16(IDS_LOGIN_DIALOG_PASSWORD_ACCESSIBLE_NAME));
password_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD);
}
The final code should resemble the following:
login_bubble_dialog_example.h
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LOGIN_BUBBLE_DIALOG_EXAMPLE_H_
#define LOGIN_BUBBLE_DIALOG_EXAMPLE_H_
#include <string>
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/textfield/textfield_controller.h"
namespace views::examples {
class LoginBubbleDialogView : public BubbleDialogDelegateView,
public TextfieldController {
public:
using OnSubmitCallback = base::OnceCallback<void(std::u16string username,
std::u16string password)>;
static void Show(View* anchor_view,
BubbleBorder::Arrow anchor_position,
OnSubmitCallback accept_callback);
~LoginBubbleDialogView() override;
// TextfieldController:
void ContentsChanged(Textfield* sender,
const std::u16string& new_contents) override;
private:
LoginBubbleDialogView(View* anchor_view,
BubbleBorder::Arrow anchor_position,
OnSubmitCallback accept_callback);
Textfield* username_ = nullptr;
Textfield* password_ = nullptr;
};
} // namespace views::examples
#endif // LOGIN_BUBBLE_DIALOG_EXAMPLE_H_
login_bubble_dialog_example.cc
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "login_bubble_dialog_example.h"
#include <memory>
#include <string>
#include "base/functional/bind.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/border.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/table_layout.h"
#include "ui/views/layout/layout_provider.h"
namespace views::examples {
namespace {
// Adds a label textfield pair to the login dialog's layout.
Textfield* AddFormRow(LoginBubbleDialogView* bubble,
const std::u16string& label_text) {
Label* label = bubble->AddChildView(std::make_unique<Label>(label_text));
Textfield* textfield = bubble->AddChildView(std::make_unique<Textfield>());
textfield->SetAssociatedLabel(label);
textfield->set_controller(bubble);
constexpr int kDefaultTextfieldWidth = 30;
constexpr int kMinimumTextfieldWidth = 5;
textfield->SetDefaultWidthInChars(kDefaultTextfieldWidth);
textfield->SetMinimumWidthInChars(kMinimumTextfieldWidth);
return textfield;
}
} // namespace
// static
void LoginBubbleDialogView::Show(View* anchor_view,
BubbleBorder::Arrow anchor_position,
OnSubmitCallback accept_callback) {
BubbleDialogDelegateView::CreateBubble(
std::make_unique<LoginBubbleDialogView>(anchor_view, anchor_position,
std::move(accept_callback)))
->Show();
}
LoginBubbleDialogView::~LoginBubbleDialogView() = default;
void LoginBubbleDialogView::ContentsChanged(
Textfield* sender,
const std::u16string& new_contents) {
SetButtonEnabled(ui::DIALOG_BUTTON_OK, !username_->GetText().empty() &&
!password_->GetText().empty());
DialogModelChanged();
}
LoginBubbleDialogView::LoginBubbleDialogView(
View* anchor_view,
BubbleBorder::Arrow anchor_position,
OnSubmitCallback accept_callback)
: BubbleDialogDelegateView(anchor_view, anchor_position) {
SetButtonEnabled(ui::DIALOG_BUTTON_OK, false);
const auto on_submit = [](const LoginBubbleDialogView* bubble_view,
OnSubmitCallback accept_callback) {
std::move(accept_callback)
.Run(bubble_view->username_->GetText(),
bubble_view->password_->GetText());
};
SetAcceptCallback(base::BindOnce(on_submit, base::Unretained(this),
std::move(accept_callback)));
SetTitle(l10n_util::GetStringUTF16(IDS_EXAMPLE_LOGIN_DIALOG_TITLE));
SetButtonLabel(
ui::DIALOG_BUTTON_OK,
l10n_util::GetStringUTF16(IDS_EXAMPLE_LOGIN_DIALOG_OK_BUTTON_LABEL));
const LayoutProvider* provider = LayoutProvider::Get();
set_margins(provider->GetDialogInsetsForContentType(
DialogContentType::kControl, DialogContentType::kControl));
const int related_control_padding =
provider->GetDistanceMetric(DISTANCE_RELATED_CONTROL_VERTICAL);
const int label_padding =
provider->GetDistanceMetric(DISTANCE_RELATED_LABEL_HORIZONTAL);
SetLayoutManager(std::make_unique<TableLayout>())
->AddColumn(LayoutAlignment::kStart,
LayoutAlignment::kStretch,
TableLayout::kFixedSize,
TableLayout::ColumnSize::kUsePreferred, 0, 0)
.AddPaddingColumn(TableLayout::kFixedSize, label_padding)
.AddColumn(LayoutAlignment::kStretch,
LayoutAlignment::kStretch, 1.0f,
TableLayout::ColumnSize::kUsePreferred, 0, 0)
.AddRows(1, TableLayout::kFixedSize)
.AddPaddingRow(TableLayout::kFixedSize, related_control_padding)
.AddRows(1, TableLayout::kFixedSize);
username_ =
AddFormRow(this,
l10n_util::GetStringUTF16(
IDS_EXAMPLE_LOGIN_DIALOG_USERNAME_ACCESSIBLE_NAME));
password_ =
AddFormRow(this,
l10n_util::GetStringUTF16(
IDS_EXAMPLE_LOGIN_DIALOG_PASSWORD_ACCESSIBLE_NAME));
password_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD);
}
} // namespace views::examples
The generated UI would be:
The example code can be run inside Views example app. You can find the code
inside the Views example at
ui/views/examples/login_bubble_dialog_example.cc
.
You can try out this example by running the following command:
$ autoninja -C out\Default views_examples
$ out\Default\views_examples --enable-examples="Login Bubble Dialog"