目录

概述——Blazor EditFormState控件

代码和示例

Blazor编辑设置

EditForm

EditContext

FieldIdentifier

输入控件

重新审视EditContext

EditFormState 控件

WeatherForecast

EditField

EditFieldCollection

EditFormState

一个简单的实现

总结


概述——Blazor EditFormState控件

这是描述一组有用的Blazor Edit控件的系列文章中的第一篇,这些控件解决了开箱即用编辑体验中的一些当前缺点,而无需购买昂贵的工具包。

代码和示例

该存储库包含一个项目,该项目实现了本系列中所有文章的控件。你可以在这里找到它。

示例站点位于https://cec-blazor-database.azurewebsites.net/。

您可以在https://cec-blazor-database.azurewebsites.net//testeditor 中查看稍后描述的测试表单。

Repo是未来文章的一项正在进行的工作,因此会发生变化和发展。

Blazor编辑设置

首先,让我们看看当前的表单控件以及它们如何协同工作。一个经典的形式看起来像这样:

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit"><DataAnnotationsValidator /><ValidationSummary /><InputText id="name" @bind-Value="exampleModel.Name" /><ValidationMessage For="@(() => exampleModel.Name)" /><button type="submit">Submit</button>
</EditForm>

EditForm

EditForm是整体包装。它:

  1. 创建html Form上下文。
  2. 连接任何Submit按钮——即,在表单内将type设置为submit的按钮。
  3. 创建/管理EditContext.
  4. 级联EditContext. EditForm中的所有控件将被捕获并以一种或另一种方式使用它。
  5. 为提交过程的父控件提供回调委托——OnSubmit、OnValidSubmit和OnInvalidSubmit。

EditContext

EditContext是编辑过程核心的类,提供整体管理。它操作的数据类是model: 定义为object类型。它可以是任何对象,但实际上是某种类型的数据类。唯一的先决条件是表单中使用的字段被声明为public读/写属性。

该EditContext可以是:

  • 直接作为EditContext参数传递给EditForm,
  • 或者模型的对象实例被设置为Model参数并从EditForm中创建一个EditContext实例。

要记住的重要一点是,一旦创建了另一个对象,就不要更改它的EditContext模型。虽然有可能,但不建议这样做。如果模型需要改变,代码刷新整个表单:会更安全!

FieldIdentifier

FieldIdentifier类代表一个模型属性的部分“系列化”。EditContext通过他们的FieldIdentifier踪迹和识别单个属性。Model是拥有该属性的对象,FieldName是通过反射得到的属性名。

输入控件

InputText和InputNumber和其他InputBase控件捕获级联EditContext.。通过使用他们的FieldIdentifier调用NotifyFieldChanged,任何值的变动被向上推至EditContext。

重新审视EditContext

在内部EditContext维护一个FieldIdentifier列表。FieldIdentifier对象在各种方法和事件中传递以识别特定字段。调用NotifyFieldChanged将FieldIdentifier对象添加到列表中。每当调用NotifyFieldChanged时EditContext触发OnFieldChanged。

IsModified提供对列表或个人FieldIdentifier状态的访问。MarkAsUnmodified重置集合中的单个FieldIdentifier或全部FieldIdentifiers。

EditContext还包含管理验证的功能,但实际上并没有这样做。我们将在下一篇文章中介绍验证过程。

EditFormState 控件

该EditFormState控件与所有编辑表单控件一样,捕获级联的EditState 。它的作用是:

  1. 构建由Model公开的public属性列表并维护每个属性的编辑状态——原始值与编辑值的相等性检查。
  2. 在字段值的每次更改时更新状态。
  3. 通过readonly属性公开状态。
  4. 提供在编辑状态更新时触发的EventCallback委托。

在我们查看控件之前,让我们看一下模型——在我们的例子中,WeatherForecast——以及一些支持类。

WeatherForecast

WeatherForecast 是典型的数据类。

  1. 每个字段都声明为具有默认值的属性。
  2. Validate实现IValidation。暂时忽略这一点,我们将在下一篇文章中查看验证。我已经按照你在Repo代码中看到的方式展示了它。
public class WeatherForecast : IValidation
{public int ID { get; set; } = -1;public DateTime Date { get; set; } = DateTime.Now;public int TemperatureC { get; set; } = 0;[NotMapped] public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);public string Summary { get; set; } = string.Empty;/// Ignore for now, but as you'll see it in the example repo it's shownpublic bool Validate(ValidationMessageStore validationMessageStore, string fieldname, object model = null){....}
}

EditField

EditField 是我们从模型中“序列化”属性的类。

  1. 基本字段是记录——它们只能在初始化时设置。
  2. EditedValue 携带该字段的当前值。
  3. IsDirty测试Value和EditedValue之间的相等性。
public class EditField
{public string FieldName { get; init; }public Guid GUID { get; init; }public object Value { get; init; }public object Model { get; init; }public object EditedValue { get; set; }public bool IsDirty{get{if (Value != null && EditedValue != null) return !Value.Equals(EditedValue);if (Value is null && EditedValue is null) return false;return true;}}public EditField(object model, string fieldName, object value){this.Model = model;this.FieldName = fieldName;this.Value = value;this.EditedValue = value;this.GUID = Guid.NewGuid();}public void Reset()=> this.EditedValue = this.Value;
}

EditFieldCollection

EditFieldCollection是EditField的IEnumerable集合。该类为集合提供了一组受控的setter和getter,并为IEnumerable接口实现了必要的方法。它还提供了一个IsDirty属性来公开集合的状态。

public class EditFieldCollection : IEnumerable
{private List<EditField> _items = new List<EditField>();public int Count => _items.Count;public Action<bool> FieldValueChanged;public bool IsDirty => _items.Any(item => item.IsDirty);public void Clear()=> _items.Clear();public void ResetValues()=> _items.ForEach(item => item.Reset());public IEnumerator GetEnumerator()=> new EditFieldCollectionEnumerator(_items);public T Get<T>(string FieldName){var x = _items.FirstOrDefault(item => item.FieldName.Equals(FieldName, StringComparison.CurrentCultureIgnoreCase));if (x != null && x.Value is T t) return t;return default;}public T GetEditValue<T>(string FieldName){var x = _items.FirstOrDefault(item => item.FieldName.Equals(FieldName, StringComparison.CurrentCultureIgnoreCase));if (x != null && x.EditedValue is T t) return t;return default;}public bool TryGet<T>(string FieldName, out T value){value = default;var x = _items.FirstOrDefault(item => item.FieldName.Equals(FieldName, StringComparison.CurrentCultureIgnoreCase));if (x != null && x.Value is T t) value = t;return x.Value != default;}public bool TryGetEditValue<T>(string FieldName, out T value){value = default;var x = _items.FirstOrDefault(item => item.FieldName.Equals(FieldName, StringComparison.CurrentCultureIgnoreCase));if (x != null && x.EditedValue is T t) value = t;return x.EditedValue != default;}public bool HasField(EditField field)=> this.HasField(field.FieldName);public bool HasField(string FieldName){var x = _items.FirstOrDefault(item => item.FieldName.Equals(FieldName, StringComparison.CurrentCultureIgnoreCase));if (x is null | x == default) return false;return true;}public bool SetField(string FieldName, object value){var x = _items.FirstOrDefault(item => item.FieldName.Equals(FieldName, StringComparison.CurrentCultureIgnoreCase));if (x != null && x != default){x.EditedValue = value;this.FieldValueChanged?.Invoke(this.IsDirty);return true;}return false;}public bool AddField(object model, string fieldName, object value){this._items.Add(new EditField(model, fieldName, value));return true;}

该Enumerator支持类。

public class EditFieldCollectionEnumerator : IEnumerator{private List<EditField> _items = new List<EditField>();private int _cursor;object IEnumerator.Current{get{if ((_cursor < 0) || (_cursor == _items.Count))throw new InvalidOperationException();return _items[_cursor];}}public EditFieldCollectionEnumerator(List<EditField> items){this._items = items;_cursor = -1;}void IEnumerator.Reset()=> _cursor = -1;bool IEnumerator.MoveNext(){if (_cursor < _items.Count)_cursor++;return (!(_cursor == _items.Count));}}
}

现在我们已经看到了支持类,转到主控件。

EditFormState

EditFormState被声明为一个组件并实现IDisposable。

public class EditFormState : ComponentBase, IDisposable

属性是:

  1. EditContext从级联中拿起。
  2. 向EditStateChanged父控件提供回调以告诉它编辑状态已更改。
  3. 为控件提供只读IsDirty属性,使用@ref检查控件状态。
  4. EditFields是我们填充并用于管理编辑状态的内部EditFieldCollection。
  5. disposedValue是IDisposable实现的一部分。
/// EditContext - cascaded from EditForm
[CascadingParameter] public EditContext EditContext { get; set; }/// EventCallback for parent to link into for Edit State Change Events
/// passes the current Dirty state
[Parameter] public EventCallback<bool> EditStateChanged { get; set; }/// Property to expose the Edit/Dirty state of the control
public bool IsDirty => EditFields?.IsDirty ?? false;private EditFieldCollection EditFields = new EditFieldCollection();
private bool disposedValue;

当组件初始化时,它会捕获Model属性并填充EditFields初始数据。最后一步是连接EditContext.OnFieldChanged到FieldChanged,因此每当字段值更改时都会调用FieldChanged。

protected override Task OnInitializedAsync()
{Debug.Assert(this.EditContext != null);if (this.EditContext != null){// Populates the EditField Collectionthis.GetEditFields();// Wires up to the EditContext OnFieldChanged eventthis.EditContext.OnFieldChanged += FieldChanged;}return Task.CompletedTask;
}/// Method to populate the edit field collection
protected void GetEditFields()
{// Gets the model from the EditContext and populates the EditFieldCollectionthis.EditFields.Clear();var model = this.EditContext.Model;var props = model.GetType().GetProperties();foreach (var prop in props){var value = prop.GetValue(model);EditFields.AddField(model, prop.Name, value);}
}

该FieldChanged事件处理程序中从EditFields中查找EditFiel并通过调用SetField设置它的EditedValue。然后使用当前的dirty状态触发EditStateChanged回调。

/// Event Handler for Editcontext.OnFieldChanged
private void FieldChanged(object sender, FieldChangedEventArgs e)
{// Get the PropertyInfo object for the model property// Uses reflection to get property and valuevar prop = e.FieldIdentifier.Model.GetType().GetProperty(e.FieldIdentifier.FieldName);if (prop != null){// Get the value for the propertyvar value = prop.GetValue(e.FieldIdentifier.Model);// Sets the edit value in the EditFieldEditFields.SetField(e.FieldIdentifier.FieldName, value);// Invokes EditStateChangedthis.EditStateChanged.InvokeAsync(EditFields?.IsDirty ?? false);}
}

最后,我们有一些实用方法和IDisposable实现。

/// Method to Update the Edit State to current values public void UpdateState(){this.GetEditFields();this.EditStateChanged.InvokeAsync(EditFields?.IsDirty ?? false);}// IDisposable Implementationprotected virtual void Dispose(bool disposing){if (!disposedValue){if (disposing){if (this.EditContext != null)this.EditContext.OnFieldChanged -= this.FieldChanged;}disposedValue = true;}}public void Dispose(){// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' methodDispose(disposing: true);GC.SuppressFinalize(this);}
}

一个简单的实现

为了测试组件,这里有一个简单的测试页面。

上下更改温度,您应该会看到状态按钮更改颜色和文本。

您可以在https://cec-blazor-database.azurewebsites.net/editstateeditor 上查看此示例。

@using Blazor.Database.Data
@page "/test"<EditForm Model="@Model" OnValidSubmit="@HandleValidSubmit"><EditFormState @ref="editFormState" EditStateChanged="this.EditStateChanged"></EditFormState><label class="form-label">ID:</label> <InputNumber class="form-control" @bind-Value="Model.ID" /><label class="form-label">Date:</label> <InputDate class="form-control" @bind-Value="Model.Date" /><label class="form-label">Temp C:</label> <InputNumber class="form-control" @bind-Value="Model.TemperatureC" /><label class="form-label">Summary:</label> <InputText class="form-control" @bind-Value="Model.Summary" /><div class="text-right mt-2"><button class="btn @btncolour">@btntext</button><button class="btn btn-primary" type="submit">Submit</button></div><div></div>
</EditForm>
@code {protected bool _isDirty = false;protected string btncolour => _isDirty ? "btn-danger" : "btn-success";protected string btntext => _isDirty ? "Dirty" : "Clean";protected EditFormState editFormState { get; set; }private WeatherForecast Model = new WeatherForecast(){ID = 1,Date = DateTime.Now,TemperatureC = 22,Summary = <span class="pl-pds">"Balmy"};private void HandleValidSubmit(){this.editFormState.UpdateState();}private void EditStateChanged(bool editstate)=> this._isDirty = editstate;
}

总结

如果您之前没有实现过此类功能,则此控件的真正优势可能不会立即显现出来,但我们将在后续文章中使用它来构建编辑器表单。在接下来的文章看起来在验证过程中,如何构建一个简单的自定义验证。第三篇文章着眼于表单锁定,使用此控件作为流程的一部分。

如果您在以后发现这篇文章很好,最新版本将在此处提供。

https://www.codeproject.com/Articles/5297299/A-Blazor-Edit-Form-State-Control

Blazor编辑表单状态控件相关推荐

  1. asp.net夜话之三:表单和控件

    在今天我主要要介绍的有如下知识点: HTML表单的提交方式 HTM控件 获取HTML表单内容 乱码问题 SQL注入 服务器端表单 HTML服务器控件 HTML表单的提交方式对于一个普通HTML表单来说 ...

  2. H5--新增表单元素控件属性事件

    H5网站重构 先简单回顾一下H5新标签:结构性标签.功能性标签 结构性标签:负责web上下文件结构的定义 功能性标签:功能性内容的表达 表单-新增控件类型 HTML5 增加了多个新的表单输入类型,这些 ...

  3. python嵌套html开发gui_python GUI库图形界面开发之PyQt5表单布局控件QFormLayout详细使用方法与实例...

    PyQt5布局控件QFormLayout简介 QFormLayout是label-field式的表单布局,顾明思议,就是实现表单方式的布局,表单是提示用户进行交互的一种模式,主要有两列组成,第一列用于 ...

  4. 驰骋工作流引擎表单设计控件-字段类控件(2)

    2019独角兽企业重金招聘Python工程师标准>>> Technorati Tags: 开源工作流引擎, 驰骋.net工作流引擎, 开源表单引擎, ccform, ccflow, ...

  5. Coolite Toolkit学习笔记九:表单布局控件FormLayout与FromPanel

    FormLayout在我们平时开发中使用频率非常高,使用它可快速.方便的实现表单界面布局,以及使用一些其他控件自身的特性组合起来实现丰富强大的UI界面.下面通过一个简单示例来体验FormLayout的 ...

  6. 自定义表单picturecontrol控件使用

    一.后台配置 1)资料附件维护 定义附件类别和名称,以及属性: 2)附件文件夹配置 配置附件在Web服务器上存储的物理路径: 物理路径 二.存储表注意事项 1)Race Form Studio的数据库 ...

  7. VBA,表单及其控件,如何取得表单控件的名字,大小写注意

    1 如何取得表单控件的名字? 1.1 问题的由来 表单控件和 表单的 activeX控件,以及窗体控件不同 表单控件不能直接编写其相关代码,也看不到其属性栏 但是可以直接绑定一个sub 但是问题来了: ...

  8. php表单常用控件,常用表单控件元素(一)

    表单是网页中数据采集的工具 包含: (一)表单标签 (二)表单域 (三)表单按钮 表单标签 的属性 1 name 表单的名称 2 action 当表单提交时向何处发送表单数据 3 target 在何处 ...

  9. H3 BPM MVC表单SheetOffice控件使用分享

    SheetOffice控件使用分享 1. 控件属性及说明 Template:套用的模板目录(套用模板会使用到) 模板中必须包含书签: a. Body,这个是在代码中写死了的,是把当前文档的内容插入到模 ...

最新文章

  1. Python 第十五天 串讲
  2. 多任务版udp聊天器
  3. 如何将Felgo程序部署到Android中
  4. Spring Android 1.0.0.M3 发布
  5. c++11或c++14或c++17参数包的使用
  6. Mongodb-副本集
  7. 认识Hibernate
  8. 经典排序算法(八)--选择排序Selection Sort
  9. LiveNVR传统安防摄像机互联网直播-主要功能模块及相关技术特点与性能指标
  10. Spring AOP aspectjweaver.jar与aopalliance-1.0.jar 下载地址
  11. 《ParaView Tutorial》
  12. 知名建筑景观3D可视化渲染软件lumion10.5
  13. u8反启用固定资产_U8和T3固定资产没有启用不能结账的处理方法
  14. Keil编译错误 error: L6050U: The code size of this image (99784 bytes) exceeds the maxim
  15. Android 进阶笔记,包含常用的技术框架、博客社区、书籍等。
  16. fastdb 简介 查询语言
  17. Java之切割时间段
  18. 值得你收藏的Notes应用模板
  19. cad一键卸载工具叫什么_autodesk软件一键卸载工具
  20. 最新软件工程师薪资大揭秘!你的薪资达到平均水平了吗?

热门文章

  1. python 数组 元组 列表_python_Day_02[数组、列表、元组之篇]
  2. 有个内含单词的超大文本文件_如果你家有个大阳台,你会选择封阳台吗?
  3. C语言指针实数组输入输出,C语言:回来两个数组中第一个元素的指针,并输出这个值...
  4. hql 字符串where语句_Hibernate HQL基础 限定查询条件
  5. layer 同步调用_YYText源码解读-YYText同步/异步渲染流程(一)—UIView与CALayer
  6. 三维图形 纵坐标_《从柯氏四级培训效果评估到三维培训课程需求评估》
  7. 绘制自己组合的k线图_短线投资者必备的四种双K线组合抄底技巧,次次选中黑马股,精准率超高...
  8. servlet中文乱码_Servlet入门 信息过滤
  9. 字体海报设计灵感|这海报的字体够别致,脑洞够大!
  10. UI设计素材模板|表单的临摹学习技巧