【问题标题】:Blazor- EditForm InputCheckbox nullable bools issue work aroundBlazor-EditForm InputCheckbox 可为空的布尔问题解决方法
【发布时间】:2021-01-22 06:47:01
【问题描述】:

我正在尝试为从 inputbase 派生的 Blazor 上的编辑表单创建定制输入,但是我正在努力掌握它,因为我这周最近才开始使用 Blazor,而一般来说,本月才开始使用 C#。

我找到了 https://www.meziantou.net/creating-a-inputselect-component-for-enumerations-in-blazor.htm(或找到下面粘贴的代码) 并能够将它用于 inputselect 内的可为空枚举,但是尝试将其复制为可为空的输入复选框无济于事。我想知道是否有人有链接或知道如何调整它以使其正常工作。

提前谢谢你,我几乎整天都在我的电脑上,所以请随时提问,尽量不要责备我哈哈。

// file: Shared/InputSelectEnum.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Reflection;
using Humanizer;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;


// Inherit from InputBase so the hard work is already implemented ????
// Note that adding a constraint on TEnum (where T : Enum) doesn't work when used in the view, Razor raises an error at build time. Also, this would prevent using nullable types...
namespace OrderServiceFrontEnd.Shared
{
    public sealed class InputSelectEnum<TEnum> : InputBase<TEnum>
    {
        // Generate html when the component is rendered.
        protected override void BuildRenderTree(RenderTreeBuilder builder)
        {
            builder.OpenElement(0, "select");
            builder.AddMultipleAttributes(1, AdditionalAttributes);
            builder.AddAttribute(2, "class", CssClass);
            builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValueAsString));
            builder.AddAttribute(4, "onchange", EventCallback.Factory.CreateBinder<string>(this, value => CurrentValueAsString = value, CurrentValueAsString, null));

            // Add an option element per enum value
            var enumType = GetEnumType();
            foreach (TEnum value in Enum.GetValues(enumType))
            {
                builder.OpenElement(5, "option");
                builder.AddAttribute(6, "value", value.ToString());
                builder.AddContent(7, GetDisplayName(value));
                builder.CloseElement();
            }

            builder.CloseElement(); // close the select element
        }

        protected override bool TryParseValueFromString(string value, out TEnum result, out string validationErrorMessage)
        {
            // Let's Blazor convert the value for us ????
            if (BindConverter.TryConvertTo(value, CultureInfo.CurrentCulture, out TEnum parsedValue))
            {
                result = parsedValue;
                validationErrorMessage = null;
                return true;
            }

            // Map null/empty value to null if the bound object is nullable
            if (string.IsNullOrEmpty(value))
            {
                var nullableType = Nullable.GetUnderlyingType(typeof(TEnum));
                if (nullableType != null)
                {
                    result = default;
                    validationErrorMessage = null;
                    return true;
                }
            }

            // The value is invalid => set the error message
            result = default;
            validationErrorMessage = $"The {FieldIdentifier.FieldName} field is not valid.";
            return false;
        }

        // Get the display text for an enum value:
        // - Use the DisplayAttribute if set on the enum member, so this support localization
        // - Fallback on Humanizer to decamelize the enum member name
        private string GetDisplayName(TEnum value)
        {
            // Read the Display attribute name
            var member = value.GetType().GetMember(value.ToString())[0];
            var displayAttribute = member.GetCustomAttribute<DisplayAttribute>();
            if (displayAttribute != null)
                return displayAttribute.GetName();

            // Require the NuGet package Humanizer.Core
            // <PackageReference Include = "Humanizer.Core" Version = "2.8.26" />
            return value.ToString().Humanize();
        }

        // Get the actual enum type. It unwrap Nullable<T> if needed
        // MyEnum  => MyEnum
        // MyEnum? => MyEnum
        private Type GetEnumType()
        {
            var nullableType = Nullable.GetUnderlyingType(typeof(TEnum));
            if (nullableType != null)
                return nullableType;

            return typeof(TEnum);
        }
    }

}

Blazor 组合:

<InputSelectEnum @bind-Value="@_Order.IOSAppDetails.PhasedRelease"/>

【问题讨论】:

  • 发布 another 控件的代码并没有帮助。你在哪里卡住复选框,你期望什么? HTML 不支持 Iirc 三态。
  • InputCheckbox 有什么问题(或遗漏)?
  • 问题在于将可空布尔值绑定到 ,我发送的链接是为 绑定可空枚举的解决方法
  • 我遇到的问题是我无法将可为空的属性绑定到 Blazor 中的默认 InputCheckbox。我在尝试将可为空的枚举绑定到选择时遇到了这个问题,但是我能够根据我的原始帖子在线找到解决方案。我希望能够为 InputCheckbox 完成同样的事情,这样我就可以将一个可为空的 bool 绑定到它,但是我在网上找不到任何这样的例子,而且我是 Blazor 的新手,我不确定如何继续。
  • 为了提供一些额外的上下文,我试图在数据源中创建一个简单的 CRUD 样式接口,但是 API 可以为 JSON 响应中的许多键返回空值。我目前将该 JSON 映射到由可为空类型属性组成的模型,然后希望将这些属性绑定到 UI 以进行操作。如果我完全以错误的方式处理这个问题,请随时纠正我。

标签: c# html-parsing blazor nullable blazor-editform


【解决方案1】:

作为将值设置为 null 的 [x] 标签的替代方法:

如果您喜欢在 true, false, null 之间循环,您可以强制 OnChangeAction 将值设置为 null,如果它之前是 false

利用控件上的hidden 属性来隐藏和显示&lt;input&gt;&lt;label&gt;

@inherits InputBase<bool?>

<input type="checkbox" value="@CurrentValue" @attributes="AdditionalAttributes" class="@CssClass" style="width:15px; height:15px;vertical-align:-2px;"
       @onchange="EventCallback.Factory.CreateBinder<bool?>(this,OnChangeAction,this.CurrentValueAsBool)" hidden="@_HideCheckBox" />
<label @onclick="SetValueToTrue" hidden="@(!_HideCheckBox)" style="width:15px;height:15px;margin-left:-5px;">[?]</label>

@code {

    bool _HideCheckBox { get; set; } = false;

    bool? _CurrentValueAsBool;
    private bool? CurrentValueAsBool
    {
        get
        {
            if (string.IsNullOrEmpty(CurrentValueAsString))
                _CurrentValueAsBool = null;
            else
            {
                if (bool.TryParse(CurrentValueAsString, out bool _currentBool))
                    _CurrentValueAsBool = _currentBool;
                else
                    _CurrentValueAsBool = null;
            }

            SetCheckBoxCheckedAttribute(_CurrentValueAsBool);

            return _CurrentValueAsBool;
        }
        set => _CurrentValueAsBool = value;
    }


    void SetCheckBoxCheckedAttribute(bool? _currentValueAsBool)
    {
        bool _isChecked = _currentValueAsBool.HasValue ? _currentValueAsBool.Value : false;
        var _checkBoxAttributes = AdditionalAttributes != null ? AdditionalAttributes.ToDictionary(kv => kv.Key, kv => kv.Value) : new Dictionary<string, object>(); ;

        if (!_isChecked)
        {
            _ = _checkBoxAttributes.ContainsKey("checked") ? _checkBoxAttributes["checked"] = false : _checkBoxAttributes.TryAdd("checked", false);
            if (!_currentValueAsBool.HasValue)
                _HideCheckBox = true;
        }
        else
        {
            _HideCheckBox = false;
            _ = _checkBoxAttributes.ContainsKey("checked") ? _checkBoxAttributes["checked"] = true : _checkBoxAttributes.TryAdd("checked", true);
        }
        AdditionalAttributes = _checkBoxAttributes;
    }


    protected override bool TryParseValueFromString(string value, out bool? result, out string validationErrorMessage)
    {
        validationErrorMessage = null;

        if (string.IsNullOrEmpty(value))
        {
            result = null;
        }
        else
        {
            if (bool.TryParse(value, out bool _result))
            {
                result = _result;
            }
            else
            {
                validationErrorMessage = "Unable to parse value!";
                result = null;
                return false;
            }
        }
        return true;
    }

    private Action<Nullable<bool>> OnChangeAction
    {
        get => (_inputValue) =>
        {
            //ignore input value if previously false, to force it to null
            if (this.CurrentValueAsString == bool.FalseString)
            {
                _inputValue = null;
                this.CurrentValueAsString = string.Empty;
            }
            else
                this.CurrentValueAsString = _inputValue.HasValue ? _inputValue.Value.ToString() : string.Empty;
        };
    }

    void SetValueToTrue(MouseEventArgs e)
    {
        this.CurrentValueAsString = bool.TrueString;
    }

}

您可以将&lt;label&gt; 更改为&lt;image&gt; 或使用字体图标使其更漂亮。

然后看起来像这样:

组件的使用保持不变。

@*.other fields.*@

<label>Nullable value:</label><NullableBoolCheckBox @bind-Value="someModel.SomeNullBoolValue" />
<br />
<strong>value is: @(someModel.SomeNullBoolValue.HasValue?$"{someModel.SomeNullBoolValue}":"null")</strong>
<br />

@*.other fields.*@

【讨论】:

  • 这是一个状态不确定的复选框。很好……
【解决方案2】:

您可以从InputBase&lt;bool?&gt; 类继承并使用一些附加属性处理绑定值。

在这个示例中,我没有使用“代码隐藏”方法,尽管它看起来或多或少相同。

组件位于名为NullableBoolCheckBox.razor的文件中

@inherits InputBase<bool?>

<input type="checkbox" value="@CurrentValue" @attributes="AdditionalAttributes" class="@CssClass" style="width:15px; height:15px;vertical-align:-2px;"
       @onchange="EventCallback.Factory.CreateBinder<bool?>(this,OnChangeAction,this.CurrentValueAsBool)" />
<label @onclick="SetValueToNull" style="width:15px;height:15px;">[x]</label>

@code {

    bool? _CurrentValueAsBool;
    private bool? CurrentValueAsBool
    {
        get
        {
            if (string.IsNullOrEmpty(CurrentValueAsString))
                _CurrentValueAsBool = null;
            else
            {
                if (bool.TryParse(CurrentValueAsString, out bool _currentBool))
                    _CurrentValueAsBool = _currentBool;
                else
                    _CurrentValueAsBool = null;
            }

            SetCheckBoxCheckedAttribute(_CurrentValueAsBool);

            return _CurrentValueAsBool;
        }
        set => _CurrentValueAsBool = value;
    }


    void SetCheckBoxCheckedAttribute(bool? _currentValueAsBool)
    {
        bool _isChecked = _currentValueAsBool.HasValue ? _currentValueAsBool.Value : false;
        var _attributes = AdditionalAttributes != null ? AdditionalAttributes.ToDictionary(kv => kv.Key, kv => kv.Value) : new Dictionary<string, object>(); ;

        if (!_isChecked)
        {
            _ = _attributes.ContainsKey("checked") ? _attributes["checked"] = false : _attributes.TryAdd("checked", false);
        }
        else
        {
            _ = _attributes.ContainsKey("checked") ? _attributes["checked"] = true : _attributes.TryAdd("checked", true);
        }
        AdditionalAttributes = _attributes;
    }

    protected override bool TryParseValueFromString(string value, out bool? result, out string validationErrorMessage)
    {
        validationErrorMessage = null;

        if (string.IsNullOrEmpty(value))
        {
            result = null;
        }
        else
        {
            if (bool.TryParse(value, out bool _result))
            {
                result = _result;
            }
            else
            {
                validationErrorMessage = "Unable to parse value!";
                result = null;
                return false;
            }
        }
        return true;
    }

    private Action<Nullable<bool>> OnChangeAction { get => (_inputValue) => CurrentValueAsString = _inputValue.HasValue ? _inputValue.Value.ToString() : null; }

    void SetValueToNull(MouseEventArgs e)
    {
        this.CurrentValueAsString = string.Empty;
    }
}

它可以像任何其他组件一样使用。

例如:

<EditForm Model="someModel">

@*.more fields.*@

        <label>Nullable value:</label><NullableBoolCheckBox @bind-Value="someModel.SomeNullBoolValue" />
        <br />
        <strong>value is: @(someModel.SomeNullBoolValue.HasValue?$"{someModel.SomeNullBoolValue}":"null")</strong>

@*.more fields.*@

</EditForm>

链接到表单的模型有一个属性:public bool? SomeNullBoolValue { get; set; },它绑定到复选框。

看起来像这样:

如果您不想使用 [x] 标签重置值,您可能可以执行点击计数之类的操作来循环查看值 true, false, null

【讨论】:

  • 非常好...为你点赞。现在使这个组件不确定复选框;也就是用selected(v)取消selected()和indeterminate(-),分别用true、false和null表示。您可能需要使用 JSInterop。我不确定 Blazor 中当前是否提供这样的小部件
  • 对于我的多巴胺修复,我添加了另一个答案来显示不使用 JSInterop 的循环 :-)
猜你喜欢
  • 2021-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-16
相关资源
最近更新 更多