Caixa de seleção para booleano anulável

97

Meu modelo tem um booleano que deve ser anulável

public bool? Foo
{
   get;
   set;
}

então no meu Razor cshtml eu tenho

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

exceto que não funciona. Nem a conversão com (bool). Se eu fizer

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

isso não cria um erro, mas não liga ao meu modelo quando postado e foo é definido como nulo. Qual é a melhor maneira de exibir Foo na página e vinculá-lo ao meu modelo em uma postagem?

DMulligan
fonte
Consulte: stackoverflow.com/questions/2490790/…
Michael Maddox
2
Esse segmento ignora o problema, que tenho que ser capaz de detectar nulo como um terceiro valor. Um envio de formulário com a caixa de seleção desmarcada e um envio de formulário em que a caixa de seleção não foi exibida são dois cenários diferentes que devem ser considerados.
DMulligan 01 de

Respostas:

107

Eu tenho que trabalhar com

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

e então fazendo um Boolean.cshtml na minha pasta EditorTemplates e colando

@model bool?

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

dentro.

DMulligan
fonte
2
Funciona para mim. Obrigado!
Samuel
1
Ao trabalhar com tipos booleanos anuláveis, usei este '' '@ Html.CheckBoxFor (m => m.Foo.HasValue)' ''
Gaurav Aroraa
9
@GauravKumarArora Você está criando uma caixa de seleção para a HasValuepropriedade anulável , mas não o valor booleano real. Isso criará uma caixa de seleção marcada, independentemente do valor subjacente. Se o valor anulável tiver um valor, ele sempre será verdadeiro.
Siewers
1
Esta é definitivamente a solução mais limpa e flexível desta página. +1
T-moty
2
apenas uma observação: 1. você deve armazenar este arquivo em "Views / Shared / EditorTemplates" 2. você deve nomear este arquivo "Boolean.cshtml"
Jarrette
46

Resposta encontrada em pergunta semelhante - Rendering Nullable Bool as CheckBox . É muito simples e funciona:

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

É como uma resposta aceita de @afinkelstein, exceto que não precisamos de um 'modelo de editor' especial

Resnyanskiy
fonte
1
A outra vantagem disso sobre a resposta aceita é que você pode decidir se nulo deve ser igual a verdadeiro ou falso caso a caso, conforme determina a lógica de seu aplicativo, em vez de ter um único padrão em todo o aplicativo (ou fazer dois editores modelos!).
ChrisFox
25

Eu tenho bool? IsDisabled { get; set; }no modelo. Inserido se estiver em Visualização.

<div class="inputClass" id="disabled">
    <div>
    @if(Model.IsDisabled==null)
    {
        Model.IsDisabled = false;
    }           
    @Html.CheckBoxFor(model => model.IsDisabled.Value)         
    </div>
</div> 
VitalyB
fonte
1
Não é muito bom porque você altera o valor no modelo e, em muitos casos, falso não é igual a nulo. Eu te dei um ponto porque é uma boa tentativa. Observe que eu também concordo com Darin :)
Samuel
7
Não recomende dar um ponto porque é uma boa tentativa , pois pode indicar incorretamente a outros leitores que a resposta é útil quando tem problemas.
Chris de
Uma alternativa mais atraente é definir o booleano como falso no controlador se for nulo antes de renderizar a página, mas ainda usar a ideia .Value.
Graham Laight
15

Meu modelo tem um booleano que deve ser anulável

Por quê? Isso não faz sentido. Uma caixa de seleção tem dois estados: marcada / desmarcada ou Verdadeiro / Falso, se preferir. Não existe um terceiro estado.

Ou espere, você está usando seus modelos de domínio em suas visualizações em vez de modelos de visualização? Esse é o seu problema. Portanto, a solução para mim é usar um modelo de visualização no qual você definirá uma propriedade booleana simples:

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

e agora você terá a ação do controlador passando este modelo de visualização para a visualização e gerar a caixa de seleção apropriada.

Darin Dimitrov
fonte
26
Há uma infinidade de razões diferentes pelas quais a entrada foo pode não aparecer na página. Se aparecer, posso assumir que há um valor, mas preciso ser capaz de diferenciar entre quando o modelo é postado com foo como desmarcado e quando foo não é mostrado.
DMulligan
@KMulligan, que tal incluir outra propriedade booleana em seu modelo de visualização indicando se você deve renderizar uma caixa de seleção para a propriedade Foo e que pode ser armazenada em um campo oculto para que quando você postar você saiba se deve levar em consideração o valor Foo ou não.
Darin Dimitrov
Fiz isso originalmente, mas obtive toneladas desses tipos de valores primitivos que podem ou não estar incluídos em meus formulários por vários motivos. Adicionar booleanos para todos eles começou a parecer bobo. Quando pesquisei anuláveis ​​no Google, percebi que esse era o caminho a percorrer.
DMulligan
4
@KMulligan, você pode escrever ajudantes, modelos de editor, ... para que não precise repetir isso para cada propriedade que tenha tais propriedades.
Darin Dimitrov
25
@DarinDimitrov, você claramente perdeu ou ignorou o fato de que o OP disse que ele tinha que ser capaz de representar o aspecto "nulo" do bool. Você ficou preso no fato de que ele queria usar o CheckBoxFor. Em vez de dizer a ele que escreveu "código incorreto" porque usou seu modelo de domínio (o que ele pode ou NÃO ter feito) e dizer que as caixas de seleção estão marcadas ou desmarcadas, você deve ter tentado responder à pergunta ou não respondeu.
Andrew Steitz
7

Não é recomendado complicar uma primitiva com campos ocultos para esclarecer se False ou Null.

A caixa de seleção não é o que você deve usar - na verdade, ela só tem um estado: marcada . Caso contrário, pode ser qualquer coisa.

Quando o campo do seu banco de dados é um booleano anulável ( bool?), o UX deve usar 3-Radio Buttons, onde o primeiro botão representa seu "Checked", o segundo botão representa "Not Checked" e o terceiro botão representa seu null, qualquer que seja a semântica de significa nulo. Você poderia usar uma <select><option>lista suspensa para salvar o imóvel, mas o usuário precisa clicar duas vezes e as opções não são tão claras instantaneamente.

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

O RadioButtonList, definido como uma extensão chamada RadioButtonForSelectList, cria os botões de opção para você, incluindo o valor selecionado / marcado, e define o <div class="RBxxxx">para que você possa usar css para fazer seus botões de opção ficarem na horizontal (display: bloco em linha), vertical ou em estilo de tabela (exibição: bloco embutido; largura: 100px;)

No modelo (estou usando string, string para a definição do dicionário como um exemplo pedagógico. Você pode usar 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());
}
}
Jules Bartow
fonte
4

Para mim, a solução foi mudar o modelo de visualização. Considere que você está procurando faturas. Essas faturas podem ser pagas ou não. Portanto, sua pesquisa tem três opções: Pago, Não Pago ou "Não me importo".

Eu tinha definido originalmente como um bool? campo:

public bool? PaidInvoices { get; set; }

Isso me fez tropeçar nessa questão. Acabei criando um tipo Enum e lidei com isso da seguinte maneira:

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

Claro que eu os embrulhei em rótulos e especificou o texto, só quero dizer aqui está outra opção a ser considerada.

Paulo
fonte
Esta é uma representação útil. Se você tiver espaço limitado, também poderá fazer isso com drop-down / select, exceto que leva dois cliques para selecionar uma opção. Assim como acontece com as seleções, os botões de opção podem ser muito diferentes entre os navegadores, o que pode ser um inferno para o designer.
Savage de
4

A caixa de seleção oferece apenas 2 valores (verdadeiro, falso). O booleano anulável tem 3 valores (verdadeiro, falso, nulo), então é impossível fazer isso com uma caixa de seleção.

Uma boa opção é usar um menu suspenso.

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

Visão

@Html.DropDownListFor(m => m.myValue, valueList)
Gudradain
fonte
1
Se você não se importa com o fato de o usuário poder definir todos os 3 valores possíveis, não importa que você não possa representar todos os 3 usando apenas uma caixa de seleção.
Dave
@Dave Eu acho que você perdeu completamente o ponto. O OP tem um bool? o que significa que ele precisa selecionar um dos 3 valores possíveis. Se ele quisesse apenas verdadeiro ou falso, ele deveria usar um bool e não um bool?
Gudradain
1
Na verdade, o comentário do OP indica o contrário - que se houver uma caixa de seleção no formulário, ele não deseja que null seja uma opção, porque essa opção é coberta quando há um formulário diferente sem nenhuma caixa de seleção.
Dave de
Usei essa mesma estratégia em uma página de pesquisa. Você pode pesquisar Sim, Não ou ignorar o booleano em sua pesquisa. Não é um caso de uso! : D
Jess
4

Na verdade, eu criaria um modelo para ele e usaria esse modelo com um EditorFor ().

Aqui está como eu fiz:

  1. Create My template, que é basicamente uma visualização parcial que criei no diretório EditorTemplates, em Compartilhado, em Visualizações nomeie como (por exemplo) 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. Use-o assim (em qualquer visualização):

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

Isso é tudo.

Os modelos são tão poderosos em MVC, use-os .. Você pode criar uma página inteira como um modelo, que você usaria com o @Html.EditorFor(); desde que você passe seu modelo de visão na expressão lambda ..

t_plusplus
fonte
3

A abordagem mais limpa que eu poderia propor é expandir as extensões disponíveis e, HtmlHelperao mesmo tempo, reutilizar a funcionalidade fornecida pela estrutura.

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

Eu experimentei 'moldar' a expressão para permitir uma passagem direta para o nativo, CheckBoxFor<Expression<Func<T, bool>>>mas não acho que seja possível.

Taz Vermelho
fonte
Tive que mudar a assinatura do método para que funcionasse comigo: usei public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression, object htmlAttributes)e funcionou maravilhosamente bem.
Pierre-Loup Pagniez
1

Eu tive um problema semelhante no passado.

Crie uma entrada de caixa de seleção em HTML e defina o nome do atributo = "Foo". Isso ainda deve postar corretamente.

<input type="checkbox" name="Foo" checked="@model.Foo.Value" /> Foo Checkbox<br />
Chris Lucian
fonte
Isso não parece estar postando, estou ficando null ainda.
DMulligan
2
Isso pode ter problemas com a ligação do modelo
yoel halb
0

Basta verificar o valor nulo e retornar falso para ele:

@{ bool nullableValue = ((Model.nullableValue == null) || (Model.nullableValue == false)) ? false : true; }
@Html.CheckBoxFor(model => nullableValue)
Rodrigo Boratto
fonte
Você sabe que seus metadados de propriedade serão perdidos, certo? Validação do lado do cliente, nome do campo e id, etc.
T-moty
0

Eu também enfrentei o mesmo problema. Tentei a seguinte abordagem para resolver o problema porque não quero alterar o banco de dados e gerar novamente o EDMX.

@{

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

 }

<label>@Html.CheckBoxFor(m => testVar)testVar</label><br />
Amit Kumar
fonte
0

Ao fazer um EditorTemplate para um modelo que contém um bool anulável ...

  • Divida o bool anulável em 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; }
  • No modelo do 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)
    }
  • Não poste a propriedade original Foo, porque ela agora é calculada a partir de FooIsNull e FooCheckbox.


fonte
0

Todas as respostas acima vieram com seus próprios problemas. A maneira mais fácil / limpa da IMO é criar um ajudante

MVC5 Razor

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)

No meu cenário, uma caixa de seleção anulável significa que um membro da equipe ainda não fez a pergunta ao cliente, então ela está enrolada em um .checkbox-nullablepara que você possa estilizar apropriadamente e ajudar o usuário final a identificar que não é truenemfalse

CSS

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

Métodos de extensão:

        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);
        }
Jim
fonte
0

Os botões de opção são úteis, mas não permitem que você desmarque . Se você deseja esse comportamento, considere usar um menu suspenso / selecione. O código a seguir irá gerar o SelectListe este se vincula a um booleano anulável:

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");
}
Selvagem
fonte
-1
@{  bool testVar = ((bool)item.testVar ? true : false); }
  @Html.DisplayFor(modelItem => testVar)
edrdesigner
fonte
1
isso lançará uma exceção se testVar for nulo.
danwyand
-1

você pode tentar assim

@Html.TextBoxFor(m => m.foo, new {   type = "checkbox" })
Yağmur Bilgin
fonte
-4

Esta é uma pergunta antiga e as respostas existentes descrevem a maioria das alternativas. Mas há uma opção simples, se você tiver bool? em seu viewmodel, e você não se preocupa com null em sua IU:

@Html.CheckBoxFor(m => m.boolValue ?? false);
Jeff Dege
fonte
2
Você tentou isso? Como os métodos xxxFor esperam uma expressão de membro, aposto um bom dinheiro que isso irá gerar uma falha de tempo de execução. Ele está tentando determinar a propriedade ou campo de destino, não avaliando nada neste momento.
JRoughan
2
erro de tempo de execução "Os modelos podem ser usados ​​apenas com acesso a campo, acesso à propriedade, índice de matriz de dimensão única ou expressões de indexador personalizadas de parâmetro único."
ePezhman de