Casilla de verificación para booleano anulable


97

Mi modelo tiene un valor booleano que debe ser anulable

public bool? Foo
{
   get;
   set;
}

así que en mi cshtml de Razor tengo

@Html.CheckBoxFor(m => m.Foo)

excepto que eso no funciona. Tampoco lo hace con (bool). Si lo hago

@Html.CheckBoxFor(m => m.Foo.Value)

eso no crea un error, pero no se vincula a mi modelo cuando se publica y foo se establece en nulo. ¿Cuál es la mejor manera de mostrar Foo en la página y vincularlo a mi modelo en una publicación?



2
Ese hilo ignora el problema, que tengo que poder detectar nulo como tercer valor. Un envío de formulario con la casilla de verificación sin marcar y un envío de formulario donde no se mostró la casilla de verificación son dos escenarios diferentes que deben tenerse en cuenta.
DMulligan

Respuestas:


107

Lo tengo para trabajar

@Html.EditorFor(model => model.Foo) 

y luego hacer un Boolean.cshtml en mi carpeta EditorTemplates y pegar

@model bool?

@Html.CheckBox("", Model.GetValueOrDefault())

dentro.


2
Funciona para mi. ¡Gracias!
Samuel

1
Mientras trabajaba con tipos booleanos que aceptan valores NULL, usé este '' '@ Html.CheckBoxFor (m => m.Foo.HasValue)' ''
Gaurav Aroraa

9
@GauravKumarArora Está creando una casilla de verificación para la HasValuepropiedad anulable , pero no el valor booleano real. Esto creará una casilla de verificación marcada independientemente del valor subyacente. Si el valor que acepta valores NULL tiene un valor, siempre será verdadero.
Siewers

1
Esta es definitivamente la solución más limpia y flexible de esta página. +1
T-moty

2
sólo una nota: 1. tienes que almacenar este archivo en "Vistas / Shared / EditorTemplates" 2. tienes que nombrar este archivo "Boolean.cshtml"
Jarrette

46

Respuesta encontrada en una pregunta similar: renderizar Nullable Bool como CheckBox . Es muy sencillo y simplemente funciona:

@Html.CheckBox("RFP.DatesFlexible", Model.RFP.DatesFlexible ?? false)
@Html.Label("RFP.DatesFlexible", "My Dates are Flexible")

Es como una respuesta aceptada de @afinkelstein, excepto que no necesitamos una 'plantilla de editor' especial


1
La otra ventaja de esto sobre la respuesta aceptada es que puede decidir si nulo debe equivaler a verdadero o falso caso por caso, según lo dicta la lógica de su aplicación, en lugar de tener un único valor predeterminado en toda la aplicación (o hacer dos editor ¡plantillas!).
ChrisFox

25

Tengo bool? IsDisabled { get; set; }en Model. Insertado si está en Ver.

<div class="inputClass" id="disabled">
    <div>
    @if(Model.IsDisabled==null)
    {
        Model.IsDisabled = false;
    }           
    @Html.CheckBoxFor(model => model.IsDisabled.Value)         
    </div>
</div> 

1
No muy bien porque cambia el valor en el modelo y, en muchos casos, falso no es igual a nulo. Te di un punto porque es un buen intento. Tenga en cuenta que también estoy de acuerdo con Darin :)
Samuel

7
No recomiende dar un punto porque es un buen intento, ya que podría indicar incorrectamente a otros lectores que la respuesta es útil cuando tiene problemas.
Chris

Una alternativa más atractiva es establecer el booleano en falso en el controlador si es nulo antes de renderizar la página, pero aún usa la idea .Value.
Graham Laight

15

Mi modelo tiene un valor booleano que debe ser anulable

¿Por qué? Esto no tiene sentido. Una casilla de verificación tiene dos estados: marcado / no marcado, o Verdadero / Falso si lo desea. No existe un tercer estado.

¿O espera, estás usando tus modelos de dominio en tus vistas en lugar de ver modelos? Ese es tu problema. Entonces, la solución para mí es usar un modelo de vista en el que definirás una propiedad booleana simple:

public class MyViewModel
{
    public bool Foo { get; set; }
}

y ahora la acción del controlador pasará este modelo de vista a la vista y generará la casilla de verificación adecuada.


26
Hay una gran variedad de razones por las que la entrada foo puede no aparecer en la página. Si aparece, puedo asumir que hay un valor, pero necesito poder diferenciar cuando el modelo se publica con foo sin marcar y cuando no se muestra foo.
DMulligan

@KMulligan, ¿qué tal si incluye otra propiedad booleana en su modelo de vista que indique si debe representar una casilla de verificación para la propiedad Foo y cuál podría almacenarse en un campo oculto para que cuando realice la devolución de datos sepa si debe tener en cuenta el valor Foo? o no.
Darin Dimitrov

Lo hice originalmente, pero obtuve toneladas de estos tipos de valores primitivos que pueden o no estar incluidos en mis formularios por varias razones. Agregar booleanos para todos ellos comenzó a sentirse tonto. Cuando busqué en Google nullables, pensé que ese era el camino a seguir.
DMulligan

4
@KMulligan, podría escribir ayudantes, plantillas de editor, ... para que no tenga que repetir esto para cada propiedad que tenga tales propiedades.
Darin Dimitrov

25
@DarinDimitrov, claramente se perdió o ignoró el hecho de que el OP dijo que tenía que ser capaz de representar el aspecto "nulo" del bool. Te obsesionaste con el hecho de que él quería usar CheckBoxFor. En lugar de decirle que escribió "código incorrecto" porque usó su modelo de dominio (lo cual pudo haber hecho o NO) y decirle que las casillas de verificación están marcadas o desmarcadas, debería haber intentado responder su pregunta o no haber respondido.
Andrew Steitz

7

No se recomienda complicar una primitiva con campos ocultos para aclarar si False o Null.

La casilla de verificación no es lo que debería usar; en realidad, solo tiene un estado: marcado . De lo contrario, podría ser cualquier cosa.

Cuando el campo de su base de datos es un booleano que acepta valores NULL ( bool?), la UX debe usar 3 botones de opción, donde el primer botón representa su "Marcado", el segundo botón representa "No marcado" y el tercer botón representa su nulo, cualquiera que sea la semántica de nulo significa. Puede usar una <select><option>lista desplegable para ahorrar espacio, pero el usuario tiene que hacer clic dos veces y las opciones no son tan claras instantáneamente.

  1     0      null 
True  False  Not Set
Yes   No     Undecided
Male  Female Unknown
On    Off    Not Detected

RadioButtonList, definida como una extensión denominada RadioButtonForSelectList, crea los botones de opción por usted, incluido el valor seleccionado / marcado, y establece el valor <div class="RBxxxx">para que pueda usar css para hacer que sus botones de opción se vuelvan horizontales (pantalla: bloque en línea), verticales o en forma de tabla (display: inline-block; width: 100px;)

En el modelo (estoy usando cadena, cadena para la definición del diccionario como ejemplo pedagógico. ¿Puede usar bool ?, cadena)

public IEnumerable<SelectListItem> Sexsli { get; set; }
       SexDict = new Dictionary<string, string>()
        {
                { "M", "Male"},
                { "F", "Female" },
                { "U", "Undecided" },

        };

        //Convert the Dictionary Type into a SelectListItem Type
        Sexsli = SexDict.Select(k =>
              new SelectListItem
              {
                  Selected = (k.Key == "U"),
                  Text = k.Value,
                  Value = k.Key.ToString()
              });

<fieldset id="Gender">
<legend id="GenderLegend" title="Gender - Sex">I am a</legend>
    @Html.RadioButtonForSelectList(m => m.Sexsli, Model.Sexsli, "Sex") 
        @Html.ValidationMessageFor(m => m.Sexsli)
</fieldset>

public static class HtmlExtensions
{
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> expression,
    IEnumerable<SelectListItem> listOfValues,
    String rbClassName = "Horizontal")
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var sb = new StringBuilder();

if (listOfValues != null)
{
    // Create a radio button for each item in the list 
    foreach (SelectListItem item in listOfValues)
    {
        // Generate an id to be given to the radio button field 
        var id = string.Format("{0}_{1}", metaData.PropertyName, item.Value);

        // Create and populate a radio button using the existing html helpers 
        var label = htmlHelper.Label(id, HttpUtility.HtmlEncode(item.Text));

        var radio = String.Empty;

        if (item.Selected == true)
        {
            radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id, @checked = "checked" }).ToHtmlString();
        }
        else
        {
            radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id }).ToHtmlString();

        }// Create the html string to return to client browser
        // e.g. <input data-val="true" data-val-required="You must select an option" id="RB_1" name="RB" type="radio" value="1" /><label for="RB_1">Choice 1</label> 

        sb.AppendFormat("<div class=\"RB{2}\">{0}{1}</div>", radio, label, rbClassName);
    }
}

return MvcHtmlString.Create(sb.ToString());
}
}

4

Para mí, la solución fue cambiar el modelo de vista. Considere que está buscando facturas. Estas facturas se pueden pagar o no. Entonces, su búsqueda tiene tres opciones: Pagada, No pagada o "No me importa".

¿Tenía esto configurado originalmente como bool? campo:

public bool? PaidInvoices { get; set; }

Esto me hizo tropezar con esta pregunta. Terminé creando un tipo Enum y manejé esto de la siguiente manera:

@Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.NotSpecified, new { @checked = true })
@Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.Yes) 
@Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.No)

Por supuesto, los tenía envueltos en etiquetas y tenía el texto especificado, solo quiero decir, aquí hay otra opción a considerar.


Ésta es una representación útil. Si tiene espacio limitado, también puede lograr esto con el menú desplegable / seleccionar, excepto que se necesitan dos clics para seleccionar una opción. Al igual que con las selecciones, los botones de opción pueden verse muy diferentes entre los navegadores, lo que puede ser un infierno de diseñadores.
Savage

4

La casilla de verificación solo le ofrece 2 valores (verdadero, falso). El booleano anulable tiene 3 valores (verdadero, falso, nulo) por lo que es imposible hacerlo con una casilla de verificación.

Una buena opción es usar un menú desplegable.

Modelo

public bool? myValue;
public List<SelectListItem> valueList;

Controlador

model.valueList = new List<SelectListItem>();
model.valueList.Add(new SelectListItem() { Text = "", Value = "" });
model.valueList.Add(new SelectListItem() { Text = "Yes", Value = "true" });
model.valueList.Add(new SelectListItem() { Text = "No", Value = "false" });

Ver

@Html.DropDownListFor(m => m.myValue, valueList)

1
Si no le importa que el usuario pueda establecer los 3 valores posibles, no importa que no pueda representar los 3 usando solo una casilla de verificación.
Dave

@Dave Creo que no entendiste por completo. ¿El OP tiene bool? lo que significa que debe seleccionar uno de los 3 valores posibles. Si solo quisiera tener verdadero o falso, ¿debería usar un bool y no un bool?
Gudradain

1
En realidad, el comentario del OP indica lo contrario: que si hay una casilla de verificación en el formulario, no quiere que nulo sea una opción en absoluto, porque esa opción está cubierta cuando hay un formulario diferente sin casilla de verificación.
Dave

Usé esta misma estrategia en una página de búsqueda. Puede buscar Sí, No o ignorar el booleano en su búsqueda. No es un caso de uso! : D
Jess

4

De hecho, crearía una plantilla para él y usaría esa plantilla con un EditorFor ().

Así es como lo hice:

  1. Crear mi plantilla, que es básicamente una vista parcial que creé en el directorio EditorTemplates, en Compartido, en Vistas, asígnele el nombre (por ejemplo) CheckboxTemplate::

    @using System.Globalization    
    @using System.Web.Mvc.Html    
    @model bool?
    
    @{
        bool? myModel = false;
        if (Model.HasValue)
        {
            myModel = Model.Value;
        }
    }
    
    <input type="checkbox" checked="@(myModel)" name="@ViewData.TemplateInfo.HtmlFieldPrefix" value="True" style="width:20px;" />
  2. Úselo así (en cualquier vista):

    @ Html.EditorFor (x => x.MyNullableBooleanCheckboxToBe, "CheckboxTemplate")

Eso es todo.

Las plantillas son tan poderosas en MVC, úselas ... Puede crear una página completa como plantilla, que usaría con @Html.EditorFor(); siempre que pase su modelo de vista en la expresión lambda.


3

El enfoque más limpio que se me ocurrió es expandir las extensiones disponibles y al mismo HtmlHelpertiempo reutilizar la funcionalidad proporcionada por el marco.

public static MvcHtmlString CheckBoxFor<T>(this HtmlHelper<T> htmlHelper, Expression<Func<T, bool?>> expression, IDictionary<string, object> htmlAttributes) {

    ModelMetadata modelMeta = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    bool? value = (modelMeta.Model as bool?);

    string name = ExpressionHelper.GetExpressionText(expression);

    return htmlHelper.CheckBox(name, value ?? false, htmlAttributes);
}

Experimenté con 'dar forma' a la expresión para permitir un paso directo al nativo, CheckBoxFor<Expression<Func<T, bool>>>pero no creo que sea posible.


Tuve que cambiar la firma del método para que esto funcionara para mí: lo usé public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression, object htmlAttributes)y funcionó de maravilla.
Pierre-Loup Pagniez

1

Tuve un problema similar en el pasado.

Cree una entrada de casilla de verificación en HTML y establezca el atributo name = "Foo". Esto aún debería publicarse correctamente.

<input type="checkbox" name="Foo" checked="@model.Foo.Value" /> Foo Checkbox<br />

Esto no parece estar publicando, todavía me estoy volviendo nulo.
DMulligan

2
Esto podría tener problemas con la encuadernación del modelo
yoel halb

0

Simplemente verifique el valor nulo y devuélvalo falso:

@{ bool nullableValue = ((Model.nullableValue == null) || (Model.nullableValue == false)) ? false : true; }
@Html.CheckBoxFor(model => nullableValue)

Sabe que los metadatos de su propiedad se perderán, ¿verdad? Validación del lado del cliente, nombre de campo e identificación, etc.
T-moty

0

También enfrenté el mismo problema. Intenté el siguiente enfoque para resolver el problema porque no quiero cambiar la base de datos y volver a generar el EDMX.

@{

   bool testVar = (Model.MYVar ? true : false);

 }

<label>@Html.CheckBoxFor(m => testVar)testVar</label><br />

0

Al hacer un EditorTemplate para un modelo que contiene un bool anulable ...

  • Divida el bool que acepta valores NULL en 2 booleanos:

    // Foo is still a nullable boolean.
    public bool? Foo 
    {
        get 
        { 
            if (FooIsNull)
                return null;
    
            return FooCheckbox;  
        }
        set
        {
            FooIsNull   = (value == null);
            FooCheckbox = (value ?? false);
        }
    }
    
    // These contain the value of Foo. Public only so they are visible in Razor views.
    public bool FooIsNull { get; set; }
    public bool FooCheckbox { get; set; }
  • Dentro de la plantilla del editor:

    @Html.HiddenFor(m => m.FooIsNull)
    
    @if (Model.FooIsNull)
    {
        // Null means "checkbox is hidden"
        @Html.HiddenFor(m => m.FooCheckbox)
    }
    else
    {
        @Html.CheckBoxFor(m => m.FooCheckbox)
    }
  • No devuelva la propiedad original Foo, porque ahora se calcula a partir de FooIsNull y FooCheckbox.


0

Todas las respuestas anteriores vinieron con sus propios problemas. La forma más fácil / limpia de la OMI es crear un ayudante

Navaja MVC5

App_Code / Helpers.cshtml

@helper CheckBoxFor(WebViewPage page, string propertyName, bool? value, string htmlAttributes = null)
{
    if (value == null)
    {
        <div class="checkbox-nullable">
            <input type="checkbox" @page.Html.Raw(htmlAttributes)>
        </div>
    }
    else if (value == true)
    {
        <input type="checkbox" value="true" @page.Html.Raw(htmlAttributes) checked>
    }
    else
    {
        <input type="checkbox" value="false" @page.Html.Raw(htmlAttributes)>
    }
}

Uso

@Helpers.CheckBoxFor(this, "IsPaymentRecordOk", Model.IsPaymentRecordOk)

En mi escenario, una casilla de verificación que acepta valores NULL significa que un miembro del personal aún no le había hecho la pregunta al cliente, por lo que está envuelta en un estilo .checkbox-nullablepara que pueda diseñar adecuadamente y ayudar al usuario final a identificar que no es truenifalse

CSS

.checkbox-nullable {
    border: 1px solid red;
    padding: 3px;
    display: inline-block;
}

0

Métodos de extensión:

        public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression)
        {
            return htmlHelper.CheckBoxFor<TModel>(expression, null);
        }
        public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression, object htmlAttributes)
        {
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            bool? isChecked = null;
            if (metadata.Model != null)
            {
                bool modelChecked;
                if (Boolean.TryParse(metadata.Model.ToString(), out modelChecked))
                {
                    isChecked = modelChecked;
                }
            }
            return htmlHelper.CheckBox(ExpressionHelper.GetExpressionText(expression), isChecked??false ,  htmlAttributes);
        }

0

Los botones de opción son útiles, pero no le permiten anular la selección . Si desea este comportamiento, considere usar un menú desplegable / seleccionar. El siguiente código generará el SelectListy esto se vincula con éxito a un booleano anulable:

public static SelectList GetBoolTriState()
{
    var items = new List<SelectListItem>
    {
        new SelectListItem {Value = "", Text = ""},
        new SelectListItem {Value = "True", Text = "Yes"},
        new SelectListItem {Value = "False", Text = "No"},
    };

    return new SelectList(items, "Value", "Text");
}

-1
@{  bool testVar = ((bool)item.testVar ? true : false); }
  @Html.DisplayFor(modelItem => testVar)

1
esto arrojará una excepción si testVar es nulo.
Danwyand


-4

Esta es una pregunta antigua y las respuestas existentes describen la mayoría de las alternativas. Pero hay una opción simple, si tienes bool? en su modelo de vista, y no le importa nulo en su interfaz de usuario:

@Html.CheckBoxFor(m => m.boolValue ?? false);

2
¿Intentaste esto? Dado que los métodos xxxFor esperan una expresión de miembro, apostaría mucho dinero a que esto generará una falla en el tiempo de ejecución. Está tratando de determinar la propiedad o el campo de destino, sin evaluar nada en este momento.
JRoughan

2
error de tiempo de ejecución "Las plantillas solo se pueden usar con acceso a campo, acceso a propiedad, índice de matriz de una dimensión o expresiones de indexación personalizadas de un solo parámetro".
ePezhman
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.