65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Blazor 中构建数据库应用程序 - 第四部分 - UI 控件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2020年9月21日

CPOL

5分钟阅读

viewsIcon

17014

如何在 Blazor 数据库应用程序中构建 UI 控件

引言

本文是关于构建 Blazor 数据库应用程序系列文章的第四篇。  本文将介绍我们在 UI 中使用的组件,然后重点介绍如何使用 HTML 和 CSS 构建通用的 UI 组件。

  1. 项目结构和框架。
  2. 服务 - 构建 CRUD 数据层。
  3. 视图组件 - UI 中的 CRUD 编辑和查看操作。
  4. UI 组件 - 构建 HTML/CSS 控件。
  5. 视图组件 - UI 中的 CRUD 列表操作。

存储库和数据库

文章的仓库已移至 CEC.Blazor.SPA 仓库。  CEC.Blazor GitHub 仓库 已过时,将被删除。

存储库中的 /SQL 目录下有一个用于构建数据库的 SQL 脚本。

您可以在同一个网站上看到该项目的 Server 和 WASM 版本正在运行。.

Components

有关组件的详细信息,请阅读我的文章 深入了解 Blazor 组件

在 Blazor UI 中,除了起始页之外,一切都是组件。  是的,App、Router…  它们都是组件。  并非所有组件都会发出 HTML。

您可以将组件分为四类

  1. RouteViews - 这些是顶层组件。  View 与 Layout 结合以构成显示窗口。
  2. Layouts - Layouts 与 Views 结合以构成显示窗口。
  3. Forms - Forms 是控件的逻辑集合。  编辑表单、显示表单、列表表单、数据输入向导都是经典的表单。  Forms 包含控件 - 而不是 HTML。
  4. Controls - Controls 要么显示一些内容 - 发出 HTML - 要么执行一项工作。  文本框、下拉列表、按钮、网格都是经典的 HTML 发出控件。  App、Router、Validation 是执行工作的控件。

RouteViews

RouteViews 是应用程序特定的,RouteView 与 Form 之间的唯一区别在于 RouteView 通过 @Page 指令声明了一个或多个路由。  根 App 中声明的 Router 组件将 AppAssembly 设置为特定的代码程序集。  这是 Router 在启动时进行扫描以查找所有声明的路由的程序集。

在应用程序中,RouteViews 在 WASM 应用程序库中声明。

下面显示了天气预报查看器和列表视图。

// Blazor.Database/RouteViews/Weather/WeatherViewer.cs
@page "/weather/view/{ID:int}"

<WeatherForecastViewerForm ID="this.ID" ExitAction="this.ExitToList"></WeatherForecastViewerForm>

@code {
    [Parameter] public int ID { get; set; }

    [Inject] public NavigationManager NavManager { get; set; }

    private void ExitToList()
        => this.NavManager.NavigateTo("/fetchdata");
}
// Blazor.Database/RouteViews/Weather/FetchData.cs
@page "/fetchdata"

<WeatherForecastComponent></WeatherForecastComponent>

表单

我们在上一篇文章中讨论了 Forms。  它们是应用程序特定的。

下面的代码显示了天气查看器。  它全部是 UI 控件,没有 HTML 标记。

// Blazor.Database/Components/Forms/WeatherForecastViewerForm.razor
@namespace Blazor.Database.Components
@inherits RecordFormBase<WeatherForecast>

<UIContainer>
    <UIFormRow>
        <UIColumn>
            <h2>Weather Forecast Viewer</h2>
        </UIColumn>
    </UIFormRow>
</UIContainer>
<UILoader Loaded="this.IsLoaded">
    <UIContainer>
        <UIFormRow>
            <UILabelColumn>
                Date
            </UILabelColumn>
            <UIInputColumn Cols="3">
                <InputReadOnlyText Value="@this.ControllerService.Record.Date.ToShortDateString()"></InputReadOnlyText>
            </UIInputColumn>
            <UIColumn Cols="7"></UIColumn>
        </UIFormRow>
        <UIFormRow>
            <UILabelColumn>
                Temperature °C
            </UILabelColumn>
            <UIInputColumn Cols="2">
                <InputReadOnlyText Value="@this.ControllerService.Record.TemperatureC.ToString()"></InputReadOnlyText>
            </UIInputColumn>
            <UIColumn Cols="8"></UIColumn>
        </UIFormRow>
        <UIFormRow>
            <UILabelColumn>
                Temperature °f
            </UILabelColumn>
            <UIInputColumn Cols="2">
                <InputReadOnlyText Value="@this.ControllerService.Record.TemperatureF.ToString()"></InputReadOnlyText>
            </UIInputColumn>
            <UIColumn Cols="8"></UIColumn>
        </UIFormRow>
        <UIFormRow>
            <UILabelColumn>
                Summary
            </UILabelColumn>
            <UIInputColumn Cols="9">
                <InputReadOnlyText Value="@this.ControllerService.Record.Summary"></InputReadOnlyText>
            </UIInputColumn>
        </UIFormRow>
    </UIContainer>
</UILoader>
<UIContainer>
    <UIFormRow>
        <UIButtonColumn>
            <UIButton AdditionalClasses="btn-secondary" ClickEvent="this.Exit">Exit</UIButton>
        </UIButtonColumn>
    </UIFormRow>
</UIContainer>

代码隐藏页面相对简单 - 复杂性在于父类中的样板代码。  它加载特定记录的 Controller 服务。

// Blazor.Database/Components/Forms/WeatherForecastViewerForm.razor.cs
public partial class WeatherForecastViewerForm : RecordFormBase<WeatherForecast>
{

    [Inject] private WeatherForecastControllerService ControllerService { get; set; }

    protected async override Task OnInitializedAsync()
    {
        this.Service = this.ControllerService;
        await base.OnInitializedAsync();
    }
}

UI 控件

UI 控件发出 HTML 和 CSS 标记。  这里所有的控件都基于 Bootstrap CSS 框架。  所有控件都继承自 ComponentBase,而 UI 控件继承自 UIBase

UIBase

UIBase 继承自 Component。  它构建了一个可以打开或关闭的 HTML DIV 块。

让我们详细看看 UIBase

HTML 块标签可以使用 Tag 参数设置。  它只能由继承的类设置。

protected virtual string HtmlTag => "div";

控件的 CSS 类是使用 CssBuilder 类构建的。  继承类可以设置一个主要的 CSS 值,并添加任意数量的次要值。  可以通过 AdditionalClasses 参数或定义 class 属性来添加附加的 CSS 类。

[Parameter] public virtual string AdditionalClasses { get; set; } = string.Empty;
protected virtual string PrimaryClass => string.Empty;
protected List<string> SecondaryClass { get; private set; } = new List<string>();

protected string CssClass
=> CSSBuilder.Class(this.PrimaryClass)
    .AddClass(SecondaryClass)
    .AddClass(AdditionalClasses)
    .AddClassFromAttributes(this.UserAttributes)
    .Build();

可以使用两个参数来隐藏或禁用控件。  当 Show 为 true 时,显示 ChildContent。  当 Show 为 false 时,如果 HideContent 不为 null,则显示 HideContent,否则不显示任何内容。

[Parameter] public bool Show { get; set; } = true;
[Parameter] public bool Disabled { get; set; } = false;
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment HideContent { get; set; }

最后,控件捕获任何附加属性并将其添加到标记元素中。

[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object> UserAttributes { get; set; } = new Dictionary<string, object>();

控件在代码中构建 RenderTree

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
    if (this.Show)
    {
        builder.OpenElement(0, this.HtmlTag);
        if (!string.IsNullOrWhiteSpace(this.CssClass)) builder.AddAttribute(1, "class", this.CssClass);
        if (Disabled) builder.AddAttribute(2, "disabled");
        builder.AddMultipleAttributes(3, this.UserAttributes);
        if (this.ChildContent != null) builder.AddContent(4, ChildContent);
        else if (this.HideContent != null) builder.AddContent(5, HideContent);
        builder.CloseElement();
    }
}

一些示例

本文的其余部分将更详细地介绍一些 UI 控件。

UIButton

这是一个标准的 Bootstrap 按钮。 

  1. Type 设置按钮类型。
  2. 设置了 PrimaryClass
  3. ButtonClick 处理按钮单击事件并调用 EventCallback。
  4. ShowDisabled 处理按钮状态。
// Blazor.SPA/Components/UIComponents/Base/UIButtons.cs
@namespace Blazor.SPA.Components
@inherits UIBase
@if (this.Show)
{
    <button class="@this.CssClass" @onclick="ButtonClick" type="@Type" disabled="@this.Disabled" @attributes="UserAttributes">
        @this.ChildContent
    </button>
}
@code {
    [Parameter] public string Type { get; set; } = "button";
    [Parameter] public EventCallback<MouseEventArgs> ClickEvent { get; set; }
    protected override string PrimaryClass => "btn mr-1";
    protected async Task ButtonClick(MouseEventArgs e) => await this.ClickEvent.InvokeAsync(e);
}

这里有一些代码展示了该控件的用法。

<UIButton Show="true" Disabled="this._dirtyExit" AdditionalClasses="btn-dark" ClickEvent="() => this.Exit()">Exit</UIButton>

UIColumn

这是一个标准的 Bootstrap 列。 

  1. Cols 定义了列数。
  2. PrimaryCss 是从 Cols 构建的。
  3. BaseRenderTreeBuilder 将控件构建为一个 div。 
// Blazor.SPA/Components/UIControls/Base/UIColumn.cs
public class UIColumn : UIBase
{
    [Parameter] public virtual int Cols { get; set; } = 0;
    protected override string PrimaryClass => this.Cols > 0 ? $"col-{this.Cols}" : $"col";
}

UILoader

这是一个包装器控件,旨在节省在子内容中实现错误检查。  它仅在 IsLoaded 为 true 时渲染其子内容。  该控件节省了在子内容中实现大量错误检查的功夫。

@namespace Blazor.SPA.Components
@inherits UIBase

@if (this.Loaded)
{
    @this.ChildContent
}
else
{
    <div>Loading....</div>
}

@code {
    [Parameter] public bool Loaded { get; set; }
}

您可以在编辑和查看表单中看到该控件的用法。

UIContainer/UIRow/UIColumn

这些控件通过构建具有正确 CSS 的 DIV 来创建 Bootstrap 网格系统 - 即容器、行和列。

public class UIContainer : UIBase
{
    protected override string PrimaryClass => "container-fluid";
}
class UIRow : UIBase
{
    protected override string PrimaryClass => "row";
}
public class UIColumn : UIBase
{
    [Parameter] public virtual int Cols { get; set; } = 0;
    protected override string PrimaryClass => this.Cols > 0 ? $"col-{this.Cols}" : $"col";
}
// CEC.Blazor/Components/UIControls/UIBootstrapContainer/UILabelColumn.cs
public class UILabelColumn : UIColumn
{
    protected override string _BaseCss => $"col-{Columns} col-form-label";
}

这里有一些代码展示了这些控件的用法。

<UIContainer>
    <UIRow>
        <UILabelColumn Columns="2">
            Date
        </UILabelColumn>
        ............
    </UIRow>
..........
</UIContainer>

总结

本文概述了如何使用组件构建 UI 控件,并详细检查了一些示例组件。  您可以在 GitHub 仓库中看到所有库 UIControls。

一些需要注意的关键点

  1. UI 控件允许您从 Forms 和 Views 等更高级别的组件中抽象出标记。
  2. UI 控件为您提供控制,并对 HTML 和 CSS 标记施加一定的纪律。
  3. View 和 Form 组件更加简洁易懂。
  4. 根据需要使用尽可能少或尽可能多的抽象。
  5. UILoader 这样的控件,只会让生活更轻松!

如果您在未来很久之后阅读本文,请查看存储库中的自述文件以获取本文集的最新版本。

历史

* 2020 年 9 月 21 日:初始版本。

* 2020年11月17日:Blazor.CEC 库重大更改。ViewManager 更改为 Router,以及新的 Component 基类实现。

* 2021 年 3 月 29 日:对 Services、项目结构和数据编辑进行了重大更新。

© . All rights reserved.