Casella di controllo per booleano nullable


97

Il mio modello ha un valore booleano che deve essere annullabile

public bool? Foo
{
   get;
   set;
}

quindi nel mio cshtml Razor ho

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

tranne che non funziona. Nemmeno il casting con (bool). Se lo faccio

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

ciò non crea un errore, ma non si lega al mio modello quando pubblicato e foo è impostato su null. Qual è il modo migliore per visualizzare Foo sulla pagina e associarlo al mio modello su un post?



2
Quel thread ignora il problema, che devo essere in grado di rilevare null come terzo valore. Un invio di modulo con la casella di controllo deselezionata e un invio di modulo in cui la casella di controllo non è stata visualizzata sono due diversi scenari che devono essere presi in considerazione.
DMulligan

Risposte:


107

Ce l'ho con cui lavorare

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

e quindi creare un Boolean.cshtml nella mia cartella EditorTemplates e attaccare

@model bool?

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

dentro.


2
Per me va bene. Grazie!
Samuel

1
Mentre lavoravo con tipi booleani nullable, ho usato questo '' '@ Html.CheckBoxFor (m => m.Foo.HasValue)' ''
Gaurav Aroraa

9
@ GauravKumarArora Stai creando una casella di controllo per la HasValueproprietà nullable , ma non il valore booleano effettivo. Questo creerà una casella di controllo selezionata se indipendentemente da quale sia il valore sottostante. Se il nullable ha un valore, sarà sempre vero.
Siewers

1
Questa è definitivamente la soluzione molto pulita e flessibile di questa pagina. +1
T-moty

2
solo una nota: 1. devi salvare questo file in "Views / Shared / EditorTemplates" 2. devi chiamare questo file "Boolean.cshtml"
Jarrette

46

Risposta trovata in una domanda simile - Rendering Nullable Bool come CheckBox . È molto semplice e funziona:

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

È come una risposta accettata da @afinkelstein tranne che non abbiamo bisogno di uno speciale "modello di editor"


1
L'altro vantaggio di questo rispetto alla risposta accettata è che puoi decidere se null deve corrispondere a vero o falso caso per caso come impone la logica della tua applicazione, invece di avere un unico predefinito in tutta l'applicazione (o fare due editor modelli!).
ChrisFox

25

Ho bool? IsDisabled { get; set; }in Model. Inserito se in visualizzazione.

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

1
Non molto buono perché si modifica il valore nel modello e in molti casi false non è uguale a null. Ti ho dato un punto perché è un bel tentativo. Nota che sono anche d'accordo con Darin :)
Samuel

7
Sconsiglia di dare un punto perché è un bel tentativo in quanto potrebbe indicare erroneamente ad altri lettori che la risposta è utile quando ha problemi.
Chris

Un'alternativa migliore è impostare Boolean su false nel controller se null prima di eseguire il rendering della pagina, ma utilizzare comunque l'idea .Value.
Graham Laight

15

Il mio modello ha un valore booleano che deve essere annullabile

Perché? Questo non ha senso. Una casella di controllo ha due stati: selezionata / deselezionata o Vero / Falso se lo desideri. Non esiste un terzo stato.

O aspetti di utilizzare i tuoi modelli di dominio nelle tue viste invece dei modelli di visualizzazione? Questo è il tuo problema. Quindi la soluzione per me è usare un modello di visualizzazione in cui definirai una semplice proprietà booleana:

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

e ora l'azione del controller passerà questo modello di visualizzazione alla visualizzazione e genererà la casella di controllo appropriata.


26
Ci sono una miriade di motivi diversi per cui l'input foo potrebbe non apparire affatto sulla pagina. Se appare, posso presumere che ci sia un valore, ma devo essere in grado di distinguere tra quando il modello è pubblicato con foo come deselezionato e quando foo non è mostrato.
DMulligan

@KMulligan, che ne dici di includere un'altra proprietà booleana nel tuo modello di visualizzazione che indica se dovresti visualizzare una casella di controllo per la proprietà Foo e che potrebbe essere memorizzata in un campo nascosto in modo che quando postback potresti sapere se dovresti prendere in considerazione il valore Foo o no.
Darin Dimitrov

L'ho fatto originariamente, ma ho ottenuto tonnellate di questi tipi di valori primitivi che possono o non possono essere inclusi nei miei moduli per vari motivi. L'aggiunta di booleani per tutti loro ha iniziato a sembrare sciocco. Quando ho cercato su Google nullables ho pensato che fosse la strada da percorrere.
DMulligan

4
@KMulligan, potresti scrivere helper, modelli di editor, ... in modo da non dover ripetere questo per ogni proprietà che ha tali proprietà.
Darin Dimitrov

25
@DarinDimitrov, hai decisamente mancato o ignorato il fatto che l'OP ha detto che doveva essere in grado di rappresentare l'aspetto "nullo" del bool. Sei rimasto bloccato sul fatto che voleva usare CheckBoxFor. Invece di dirgli che ha scritto "codice errato" perché ha utilizzato il suo modello di dominio (cosa che potrebbe o non può aver fatto) e dirgli che le caselle di controllo sono selezionate o deselezionate, avresti dovuto tentare di rispondere alla sua domanda o non rispondere.
Andrew Steitz

7

Complicare una primitiva con campi nascosti per chiarire se False o Null non è consigliato.

Casella di controllo non è quello che si dovrebbe utilizzare - in realtà ha una sola condizione: Controllato . Altrimenti, potrebbe essere qualsiasi cosa.

Quando il campo del database è un valore booleano nullable ( bool?), la UX dovrebbe utilizzare 3 pulsanti di opzione, dove il primo pulsante rappresenta "Selezionato", il secondo pulsante rappresenta "Non selezionato" e il terzo pulsante rappresenta il tuo null, qualunque sia la semantica di null significa. È possibile utilizzare un <select><option>elenco a discesa per salvare gli immobili, ma l'utente deve fare clic due volte e le scelte non sono così chiare all'istante.

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

Il RadioButtonList, definito come un'estensione denominata RadioButtonForSelectList, costruisce i pulsanti di opzione per te, incluso il valore selezionato / verificato, e imposta il in <div class="RBxxxx">modo che tu possa usare css per rendere i tuoi pulsanti di opzione orizzontali (display: inline-block), verticale o in modo da tavolo (display: inline-block; larghezza: 100px;)

Nel modello (sto usando string, string per la definizione del dizionario come esempio pedagogico. Puoi usare bool ?, string)

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

Per me la soluzione era cambiare il modello di visualizzazione. Considera che stai cercando le fatture. Queste fatture possono essere pagate o meno. Quindi la tua ricerca ha tre opzioni: Pagato, Non pagato o "Non mi interessa".

Avevo originariamente impostato come bool? campo:

public bool? PaidInvoices { get; set; }

Questo mi ha fatto inciampare in questa domanda. Ho finito per creare un tipo Enum e l'ho gestito come segue:

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

Ovviamente li avevo avvolti in etichette e avevo il testo specificato, voglio solo dire che ecco un'altra opzione da considerare.


Questa è una rappresentazione utile. Se hai uno spazio limitato, puoi farlo anche con menu a discesa / selezione, tranne per il fatto che sono necessari due clic per selezionare un'opzione. Come per le selezioni, i pulsanti di opzione possono avere un aspetto molto diverso tra i browser, il che può essere l'inferno del designer.
Savage

4

La casella di controllo ti offre solo 2 valori (vero, falso). Nullable booleano ha 3 valori (true, false, null) quindi è impossibile farlo con una casella di controllo.

Una buona opzione è invece utilizzare un menu a discesa.

Modello

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

Controller

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" });

Visualizza

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

1
Se non ti interessa che l'utente possa impostare tutti e 3 i valori possibili, non importa che tu non possa rappresentarli tutti e 3 usando solo una casella di controllo.
Dave

@ Dave, penso che tu abbia completamente perso il punto. L'OP ha un bool? significa che deve selezionare uno dei 3 valori possibili. Se voleva avere solo vero o falso, dovrebbe usare un bool e non un bool?
Gudradain

1
In realtà, il commento dell'OP indica il contrario: se c'è una casella di controllo nel modulo, non vuole affatto che null sia un'opzione, perché quell'opzione è coperta quando c'è un modulo diverso senza casella di controllo.
Dave

Ho usato la stessa strategia in una pagina di ricerca. Puoi cercare Sì, No o ignorare il valore booleano nella tua ricerca. V'è un caso d'uso! : D
Jess

4

In realtà creerei un modello per questo e lo userei con un EditorFor ().

Ecco come l'ho fatto:

  1. Crea il mio modello, che è fondamentalmente una vista parziale che ho creato nella directory EditorTemplates, sotto Condiviso, sotto Viste chiamalo come (ad esempio) 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. Usalo in questo modo (in qualsiasi vista):

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

È tutto.

I modelli sono così potenti in MVC, usali .. Puoi creare un'intera pagina come modello, che useresti con il @Html.EditorFor(); a condizione di passare il suo modello di visualizzazione nell'espressione lambda ..


3

L'approccio più pulito che potrei trovare è espandere le estensioni disponibili HtmlHelperriutilizzando ancora le funzionalità fornite dal framework.

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);
}

Ho provato a "modellare" l'espressione per consentire un passaggio diretto al nativo, CheckBoxFor<Expression<Func<T, bool>>>ma non credo sia possibile.


Ho dovuto cambiare la firma del metodo affinché funzionasse per me: l'ho usato public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression, object htmlAttributes)e ha funzionato meravigliosamente.
Pierre-Loup Pagniez

1

Ho avuto un problema simile in passato.

Crea un input per la casella di controllo in HTML e imposta l'attributo name = "Foo" Questo dovrebbe comunque essere pubblicato correttamente.

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

Questo non sembra essere pubblicato, sto ancora diventando nullo.
DMulligan

2
Ciò potrebbe avere problemi con l'associazione del modello
yoel halb

0

Controlla semplicemente il valore null e restituisci false:

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

Sai che i metadati della tua proprietà andranno persi, giusto? Convalida lato client, nome e ID campo, ecc.
T-moty

0

Ho anche affrontato lo stesso problema. Ho provato il seguente approccio per risolvere il problema perché non voglio cambiare il DB e generare nuovamente EDMX.

@{

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

 }

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

0

Quando si crea un EditorTemplate per un modello che contiene un valore bool nullable ...

  • Dividi il valore bool nullable in 2 booleani:

    // 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; }
  • All'interno del modello dell'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)
    }
  • Non eseguire il postback della proprietà originale Foo, perché ora viene calcolata da FooIsNull e FooCheckbox.


0

Tutte le risposte sopra sono arrivate con i propri problemi. Il modo più semplice / pulito IMO è creare un aiutante

Rasoio 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)>
    }
}

Utilizzo

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

Nel mio scenario, una casella di controllo nullable significa che un membro del personale non ha ancora posto la domanda al cliente, quindi è racchiusa in un in .checkbox-nullablemodo che tu possa modellare in modo appropriato e aiutare l'utente finale a identificare che non è né truefalse

CSS

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

0

Metodi di estensione:

        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

I pulsanti di opzione sono utili, ma non consentono di deselezionare . Se desideri questo comportamento, considera l'utilizzo di un menu a discesa / selezione. Il codice seguente genererà il SelectListe questo si associa correttamente a un valore booleano nullable:

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
questo genererà un'eccezione se testVar è null.
danwyand

-1

puoi provare in questo modo

@Html.TextBoxFor(m => m.foo, new {   type = "checkbox" })

-4

Questa è una vecchia domanda e le risposte esistenti descrivono la maggior parte delle alternative. Ma c'è una semplice opzione, se hai bool? nel tuo viewmodel e non ti interessa nulla nella tua interfaccia utente:

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

2
Hai provato questo? Poiché i metodi xxxFor si aspettano un'espressione membro, scommetterei che questo genererà un errore di runtime. Sta cercando di determinare la proprietà o il campo di destinazione, senza valutare nulla a questo punto.
JRoughan

2
errore di runtime "I modelli possono essere utilizzati solo con accesso ai campi, accesso alle proprietà, indice di matrice a dimensione singola o espressioni dell'indicizzatore personalizzato a parametro singolo."
ePezhman
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.