Pole wyboru dla wartości logicznej dopuszczającej wartość null

97

Mój model ma wartość logiczną, która musi mieć wartość zerową

public bool? Foo
{
   get;
   set;
}

więc w moim cshtml Razor mam

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

ale to nie działa. Ani też rzucanie za pomocą (bool). Jeśli zrobię

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

to nie tworzy błędu, ale nie wiąże się z moim modelem po wysłaniu, a foo ma wartość null. Jaki jest najlepszy sposób na wyświetlenie Foo na stronie i powiązanie go z moim modelem w poście?

DMulligan
źródło
2
Ten wątek ignoruje problem, że muszę być w stanie wykryć wartość null jako trzecią wartość. Przesłanie formularza z odznaczonym polem wyboru i przesłanie formularza, w którym pole wyboru nie zostało wyświetlone, to dwa różne scenariusze, które należy wziąć pod uwagę.
DMulligan

Odpowiedzi:

107

Mam to do pracy

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

a następnie tworzenie Boolean.cshtml w moim folderze EditorTemplates i przyklejanie

@model bool?

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

wewnątrz.

DMulligan
źródło
2
Pracuje dla mnie. Dzięki!
Samuel
1
Podczas pracy z typami logicznymi dopuszczającymi wartość null, użyłem tego '' '@ Html.CheckBoxFor (m => m.Foo.HasValue)' ''
Gaurav Aroraa
9
@GauravKumarArora Tworzysz pole wyboru dla HasValuewłaściwości dopuszczającej wartość null , ale nie dla rzeczywistej wartości logicznej. Spowoduje to utworzenie zaznaczonego pola wyboru, jeśli niezależnie od wartości bazowej. Jeśli dopuszczalna wartość null ma wartość, zawsze będzie to prawda.
Siewers
1
Jest to zdecydowanie bardziej przejrzyste i elastyczne rozwiązanie tej strony. +1
T-moty
2
tylko uwaga: 1. musisz zapisać ten plik w "Views / Shared / EditorTemplates" 2. Musisz nazwać ten plik "Boolean.cshtml"
Jarrette
46

Znalazłem odpowiedź na podobne pytanie - Rendering Nullable Bool as CheckBox . Jest to bardzo proste i po prostu działa:

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

To jak zaakceptowana odpowiedź od @afinkelstein, z wyjątkiem tego, że nie potrzebujemy specjalnego „szablonu edytora”

resnyanskiy
źródło
1
Inną zaletą tego w porównaniu z akceptowaną odpowiedzią jest to, że możesz zdecydować, czy null powinno równać się prawdzie czy fałszowi w poszczególnych przypadkach, zgodnie z logiką twojej aplikacji, zamiast mieć jedną wartość domyślną w całej aplikacji (lub tworząc dwa edytory szablony!).
ChrisFox
25

Mam bool? IsDisabled { get; set; }w Modelu. Wstawione, jeśli w widoku.

<div class="inputClass" id="disabled">
    <div>
    @if(Model.IsDisabled==null)
    {
        Model.IsDisabled = false;
    }           
    @Html.CheckBoxFor(model => model.IsDisabled.Value)         
    </div>
</div> 
VitalyB
źródło
1
Niezbyt dobre, ponieważ zmieniasz wartość w modelu iw wielu przypadkach fałsz nie jest równy null. Dałem ci punkt, ponieważ to niezła próba. Zauważ, że zgadzam się również z Darinem :)
Samuel
7
Odradzam dawanie punktów, ponieważ jest to miła próba, ponieważ może niepoprawnie wskazać innym czytelnikom, że odpowiedź jest przydatna, gdy ma problemy.
Chris
Lepszą alternatywą jest ustawienie wartości logicznej na false w kontrolerze, jeśli ma wartość null, przed renderowaniem strony, ale nadal należy używać idei .Value.
Graham Laight
15

Mój model ma wartość logiczną, która musi mieć wartość zerową

Czemu? To nie ma sensu. Pole wyboru ma dwa stany: zaznaczone / niezaznaczone lub Prawda / Fałsz, jeśli chcesz. Nie ma trzeciego państwa.

A może poczekaj, gdy używasz modeli domeny w widokach zamiast modeli widoku? To Twój problem. Więc rozwiązaniem dla mnie jest użycie modelu widoku, w którym zdefiniujesz prostą właściwość boolowską:

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

a teraz akcja kontrolera przekaże ten model widoku do widoku i wygeneruje odpowiednie pole wyboru.

Darin Dimitrov
źródło
26
Istnieje wiele różnych powodów, dla których dane wejściowe foo mogą w ogóle nie pojawiać się na stronie. Jeśli się pojawi, mogę założyć, że istnieje wartość, ale muszę być w stanie odróżnić, kiedy model jest wysyłany z foo jako niezaznaczony, a kiedy foo nie jest wyświetlany.
DMulligan,
@KMulligan, co powiesz na włączenie innej właściwości boolowskiej do modelu widoku, wskazującej, czy powinieneś renderować pole wyboru dla właściwości Foo i które może być przechowywane w ukrytym polu, aby podczas ogłaszania zwrotnego wiedziałeś, czy powinieneś wziąć pod uwagę wartość Foo albo nie.
Darin Dimitrov
Zrobiłem to pierwotnie, ale mam mnóstwo tych prymitywnych typów wartości, które mogą, ale nie muszą, być zawarte w moich formularzach z różnych powodów. Dodawanie do nich wszystkich wartości logicznych zaczęło wydawać się głupie. Kiedy przeszukałem nullable w Google, pomyślałem, że to jest właściwy sposób.
DMulligan,
4
@KMulligan, możesz napisać pomoce, szablony edytorów, ... żebyś nie musiał tego powtarzać dla każdej właściwości, która ma takie właściwości.
Darin Dimitrov
25
@DarinDimitrov, wyraźnie przeoczyłeś lub zignorowałeś fakt, że OP powiedział, że musi być w stanie przedstawić „zerowy” aspekt bool. Rozłączyłeś się z faktem, że chciał użyć CheckBoxFor. Zamiast powiedzieć mu, że napisał „zły kod”, ponieważ użył swojego modelu domeny (co mógł zrobić lub NIE) i powiedzieć mu, że pola wyboru są zaznaczone lub niezaznaczone, należy spróbować odpowiedzieć na jego pytanie lub nie odpowiedzieć.
Andrew Steitz
7

Komplikowanie prymitywu ukrytymi polami w celu wyjaśnienia, czy Fałsz czy Null nie jest zalecane.

Pole wyboru nie jest tym, czego powinieneś używać - tak naprawdę ma tylko jeden stan: zaznaczone . W przeciwnym razie może to być wszystko.

Gdy twoje pole bazy danych ma wartość logiczną ( bool?) dopuszczającą wartość null , interfejs użytkownika powinien używać przycisków 3-radiowych, gdzie pierwszy przycisk reprezentuje „zaznaczone”, drugi przycisk reprezentuje „nie zaznaczono”, a trzeci przycisk reprezentuje wartość null, niezależnie od semantyki null oznacza. Możesz użyć <select><option>listy rozwijanej, aby zapisać nieruchomość, ale użytkownik musi kliknąć dwa razy, a wybory nie są tak natychmiastowo jasne.

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

RadioButtonList, zdefiniowane jako rozszerzenie o nazwie RadioButtonForSelectList, buduje przyciski opcji, w tym wybraną / zaznaczoną wartość, i ustawia <div class="RBxxxx">tak, aby można było użyć css, aby ustawić przyciski radiowe w poziomie (display: inline-block), w pionie lub w formie tabeli (display: inline-block; width: 100px;)

W modelu (używam string, string dla definicji słownika jako przykładu pedagogicznego. Możesz użyć 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
źródło
4

Dla mnie rozwiązaniem była zmiana modelu widoku. Zastanów się, czy szukasz faktur. Te faktury mogą być opłacone lub nie. Twoje wyszukiwanie ma więc trzy opcje: płatne, bezpłatne lub „nie obchodzi mnie to”.

Miałem to pierwotnie ustawione jako bool? pole:

public bool? PaidInvoices { get; set; }

To sprawiło, że natknąłem się na to pytanie. Skończyło się na utworzeniu typu Enum i załatwiłem to w następujący sposób:

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

Oczywiście miałem je zawinięte w etykiety i określony tekst, mam na myśli tylko inną opcję do rozważenia.

Paweł
źródło
To jest przydatna reprezentacja. Jeśli masz ograniczoną przestrzeń, możesz to również osiągnąć za pomocą menu rozwijanego / wybierz, z tym wyjątkiem, że wybranie opcji wymaga dwóch kliknięć. Podobnie jak w przypadku selekcji, przyciski opcji mogą wyglądać bardzo różnie w różnych przeglądarkach, co może być piekłem projektanta.
Savage
4

Pole wyboru oferuje tylko 2 wartości (prawda, fałsz). Wartość logiczna dopuszczająca wartość null ma 3 wartości (prawda, fałsz, null), więc nie można tego zrobić za pomocą pola wyboru.

Dobrym rozwiązaniem jest skorzystanie z listy rozwijanej.

Model

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

Kontroler

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

Widok

@Html.DropDownListFor(m => m.myValue, valueList)
Gudradain
źródło
1
Jeśli nie zależy Ci na tym, aby użytkownik mógł ustawić wszystkie 3 możliwe wartości, nie ma znaczenia, że ​​nie możesz przedstawić wszystkich 3 za pomocą tylko pola wyboru.
Dave,
@Dave Myślę, że całkowicie przegapiłeś punkt. OP ma bool? co oznacza, że ​​musi wybrać jedną z 3 możliwych wartości. Jeśli chciał mieć tylko prawdę lub fałsz, powinien użyć bool, a nie bool?
Gudradain
1
Właściwie komentarz OP wskazuje na coś przeciwnego - że jeśli w formularzu znajduje się pole wyboru, nie chce, aby wartość null była w ogóle opcją, ponieważ ta opcja jest zakryta, gdy istnieje inny formularz bez pola wyboru.
Dave,
Użyłem tej samej strategii na stronie wyszukiwania. Możesz wyszukać tak, nie lub zignorować wartość logiczną w wyszukiwaniu. Nie jest to przypadek użycia! : D
Jess
4

Właściwie stworzyłbym dla niego szablon i użyłbym tego szablonu z EditorFor ().

Oto jak to zrobiłem:

  1. Utwórz mój szablon, który jest w zasadzie częściowym widokiem, który utworzyłem w katalogu EditorTemplates, w obszarze Udostępnione, w obszarze Widoki nazwij go jako (na przykład) 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. Użyj tego w ten sposób (w dowolnym widoku):

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

To wszystko.

Szablony są tak potężne w MVC, używaj ich .. Możesz stworzyć całą stronę jako szablon, którego użyjesz z @Html.EditorFor(); pod warunkiem, że przekażesz jego model widoku w wyrażeniu lambda.

t_plusplus
źródło
3

Najczystszym podejściem, jakie mogłem wymyślić, jest rozszerzenie dostępnych rozszerzeń, HtmlHelperprzy jednoczesnym ponownym wykorzystaniu funkcji zapewnianych przez 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);
}

Eksperymentowałem z „kształtowaniem” wyrażenia, aby umożliwić proste przejście do tubylca, CheckBoxFor<Expression<Func<T, bool>>>ale nie sądzę, żeby było to możliwe.

Red Taz
źródło
Musiałem zmienić sygnaturę metody, żeby to zadziałało: użyłem public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression, object htmlAttributes)i zadziałało cudownie.
Pierre-Loup Pagniez
1

W przeszłości miałem podobny problem.

Utwórz pole wyboru w HTML i ustaw atrybut name = "Foo". Powinno to nadal być wysyłane poprawnie.

<input type="checkbox" name="Foo" checked="@model.Foo.Value" /> Foo Checkbox<br />
Chris Lucian
źródło
To nie wydaje się być wysyłane, wciąż otrzymuję wartość null.
DMulligan,
2
To może mieć problemy z wiązaniem modelu
yoel halb
0

Po prostu sprawdź wartość null i zwróć do niej false:

@{ bool nullableValue = ((Model.nullableValue == null) || (Model.nullableValue == false)) ? false : true; }
@Html.CheckBoxFor(model => nullableValue)
Rodrigo Boratto
źródło
Wiesz, że metadane Twojej nieruchomości zostaną utracone, prawda? Walidacja po stronie klienta, nazwa i identyfikator pola, itp.
T-moty
0

Ja też stanąłem przed tym samym problemem. Wypróbowałem następujące podejście, aby rozwiązać problem, ponieważ nie chcę zmieniać bazy danych i ponownie generować EDMX.

@{

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

 }

<label>@Html.CheckBoxFor(m => testVar)testVar</label><br />
Amit Kumar
źródło
0

Podczas tworzenia EditorTemplate dla modelu, który zawiera wartość bool dopuszczającą wartość null ...

  • Podziel wartość zerową bool na 2 wartości logiczne:

    // 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; }
  • W szablonie edytora:

    @Html.HiddenFor(m => m.FooIsNull)
    
    @if (Model.FooIsNull)
    {
        // Null means "checkbox is hidden"
        @Html.HiddenFor(m => m.FooCheckbox)
    }
    else
    {
        @Html.CheckBoxFor(m => m.FooCheckbox)
    }
  • Nie ogłaszaj zwrotnie oryginalnej właściwości Foo, ponieważ jest ona teraz obliczana na podstawie FooIsNull i FooCheckbox.


źródło
0

Wszystkie powyższe odpowiedzi miały swoje własne problemy. Najłatwiejszym / najczystszym sposobem IMO jest stworzenie pomocnika

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

Stosowanie

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

W moim scenariuszu pole wyboru dopuszczające wartość zerową oznacza, że ​​członek personelu nie zadał jeszcze pytania klientowi, więc jest opakowany .checkbox-nullabletak, aby można było odpowiednio stylizować i pomóc użytkownikowi końcowemu zidentyfikować, że nie jest to trueanifalse

CSS

.checkbox-nullable {
    border: 1px solid red;
    padding: 3px;
    display: inline-block;
}
Zanderwar
źródło
0

Metody rozszerzeń:

        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
źródło
0

Przyciski opcji są przydatne, ale nie pozwalają na odznaczenie . Jeśli chcesz tego zachowania, rozważ użycie listy rozwijanej / wybierz. Poniższy kod wygeneruje SelectListi powiąże się pomyślnie z wartością logiczną dopuszczającą wartość null:

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");
}
Brutalny
źródło
-1
@{  bool testVar = ((bool)item.testVar ? true : false); }
  @Html.DisplayFor(modelItem => testVar)
edrdesigner
źródło
1
spowoduje to zgłoszenie wyjątku, jeśli testVar ma wartość null.
danwyand
-1

możesz spróbować w ten sposób

@Html.TextBoxFor(m => m.foo, new {   type = "checkbox" })
Yağmur Bilgin
źródło
-4

To stare pytanie, a istniejące odpowiedzi opisują większość alternatyw. Ale jest jedna prosta opcja, jeśli masz bool? w Twoim modelu widoku i nie przejmujesz się null w swoim interfejsie użytkownika:

@Html.CheckBoxFor(m => m.boolValue ?? false);
Jeff Dege
źródło
2
Próbowałeś tego? Ponieważ metody xxxFor oczekują wyrażenia członkowskiego, założę się, że spowoduje to awarię środowiska wykonawczego. Próbuje określić docelową właściwość lub pole, nie oceniając niczego w tym momencie.
JRoughan
2
błąd czasu wykonywania „Szablony mogą być używane tylko z dostępem do pól, dostępem do właściwości, indeksem tablicy jednowymiarowej lub niestandardowymi wyrażeniami indeksatora z jednym parametrem”.
ePezhman