8

How do I use an IValueConverter to convert nulls into booleans?

I'm using wpf to try to display a bunch of boolean values (in checkboxes). When a new record is created, these values are null, and appear as 'indeterminate' in the checkboxes. I want the nulls to appear and save as 'false' values.

I tried to create a NullToBoolean converter that takes null values from the database and displays them as false, and then saves them as false when the user hits save. (Essentially, I'm trying to avoid the user having to click twice in the checkboxes (once to make it true, then again to make it false). This seems to work on import - ie null values are shown as false - but unless I do the two-click dance the value doesn't change in the database when I save.

My Converter:

[ValueConversion(typeof(bool), typeof(bool))]
public class NullBooleanConverter : IValueConverter
{

  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    if (value != null)
    {
      return value;
    }
    return false;
  }

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    if (value != null)
    {
      return value;
    }
    return null;
  }
}

One of the checkboxes I'm trying to have the Converter work with:

    <CheckBox Grid.Column="1" Grid.Row="0" Padding="5" Margin="5" VerticalAlignment="Center" Name="chkVarianceDescriptionProvided" IsThreeState="False">
      <CheckBox.IsChecked>
        <Binding Path="VarianceDescriptionProvided" Mode="TwoWay">
          <Binding.Converter>
            <utils:NullBooleanConverter />
          </Binding.Converter>
        </Binding>
      </CheckBox.IsChecked>
    </CheckBox>

I don't know if the problem is because my code is wrong, or if it's a case of the Converter thinking that nothing has changed, therefore it doesn't need to ConvertBack. I have tried all the Modes and switched code in Convert with ConvertBack, but nothing seems to work.

Can someone point out what I need to do to fix this?

mcalex
  • 6,628
  • 5
  • 50
  • 80
  • 1
    Man that looks ugly. Are you converting Null to false and not-null to true? Because you are not doing that. and on convert back, you're converting bool (which will never be null) back to a bool. – Meirion Hughes Mar 12 '13 at 09:21
  • 1
    Did you check if your converter code is hitting? – Sandeep Singh Rawat Mar 12 '13 at 09:24
  • 3
    By the way, "When a new record is created, these values are null..." is the problem; fix that. You should be making "view friendly" ViewModels / DataModels. – Meirion Hughes Mar 12 '13 at 09:32
  • @MeirionHughes I think your comment would be a better way to solve this than what I had put in my answer. I think you can put that as an answer yourself. –  Mar 12 '13 at 09:39
  • 1
    @MeirionHughes. No. Null to false and everything else (always boolean) to the given value. Any beautifying advice gladly taken on board :-). Comment 2) They are nullable in the database (which i can't change) and EF is taking care of all the creation. I thought I could use the ValueConverter to create a view-friendly ViewModel. To Sandeep Singh Rawat: yep, I've taken out my MessageBox/debug code, but am definitely hitting the methods. – mcalex Mar 12 '13 at 09:46
  • Why not just default the value to `false` in your data layer? – Dan Puzey Mar 12 '13 at 10:48

3 Answers3

23

Hmm, why using a converter, if you can have it out of the box?

<CheckBox IsChecked="{Binding VarianceDescriptionProvided, TargetNullValue=False}" />

For more information, pls have a look here.

DHN
  • 4,807
  • 3
  • 31
  • 45
  • I didn't know about TargetNullValue, many thanks. However, this doesn't save the 'False' values back to the database (ie, they are still NULL after saving). It does the same job as the converter currently does, so I'll use this instead while I fix my bigger problem. – mcalex Mar 13 '13 at 02:29
  • Ok, was just a try. I wasn't sure, if the value is passed to VM. Thanks for keeping me informed. :) – DHN Mar 13 '13 at 08:00
  • Is interesting, because the doco: (http://msdn.microsoft.com/en-us/library/system.windows.data.bindingbase.targetnullvalue.aspx) says it is a getter and setter - but then doesn't give a setting example. – mcalex Mar 13 '13 at 08:05
  • Well yes the property `TargetNullValue` itself has a getter and setter. That's why you can pass a value to it. But it still doesn't ensure that the value is passed through the binding to the VM. By thinking about it, it makes a deeper sense. With the `TargetNullValue` you're giving the view the opportunity to interpret a `NULL` without interfering the underlying VM or data, which might be processed in a sofisticated logic. It sticks to the principle of decoupling. :) – DHN Mar 13 '13 at 08:11
1

The real problem is the fact you are not initializing your data objects in the first place. Don't "fix", do it right to begin with; builders are good (for example). You also should be making ViewModels/DataModels rather than working with your Models (database, etc) directly.

public class MyObjectBuilder
{
     Checked _checked;

     public  MyObjectBuilder()
     {
          Reset()
     }

     private void Reset()
     { 
          _checked = new Checked(true); //etc
     }

     public MyObjectBuilder WithChecked(bool checked)
     {
          _checked = new Checked(checked);
     }

     public MyObject Build()
     {
         var built = new MyObject(){Checked = _checked;} 
         Reset();
         return built;
     }
}

then always initialise with the builder

myObjects.Add(new MyObjectBuilder().Build());

or

myObjects.Add(_injectedBuilder.Build()); // Initialises Checked to default 
myObjects.Add(_injectedBuilder.WithChecked(true).Build()); //True

While this doesn't fix your asked problem, it will fix your underlying problem in a way you can Unit Test. i.e. you can test to ensure the values added into your object list are always initialized.

Meirion Hughes
  • 24,994
  • 12
  • 71
  • 122
  • Thanks @Meirion-Hughes. I see what you mean, and can follow your ideas to get where I want to be. Defaults in object construction - on it. I didn't know about mvvm when I started on this, so it's currently wpf done old-skool (event handlers, a bit of spaghetti, data validation etc in code-behind) with mvvm features added as I learn how to fit them in. Aiming to make my next project fully mvvm, but have to work through the current hybrid I've got going on this one. Thanks again – mcalex Mar 13 '13 at 02:58
  • Google, *Caliburn Micro*. Its awesome. – Meirion Hughes Mar 13 '13 at 08:49
  • I've investigated a few frameworks. Unfortunately none of them detail how to turn spaghetti into mvvm. Caliburn certainly does look pretty impressive, and might be what I start the new project on. Cheers – mcalex Mar 13 '13 at 09:30
0

Simply correct your data before you perform data binding. That is the only option. The converter will only work make the check box show as 'unchecked' and update your data only when you interact with the control. For example:

foreach (var item in items)
{
    if (item.VarianceDescriptionProvided == null)
        item.VarianceDescriptionProvided = false;
}
Eli Arbel
  • 22,391
  • 3
  • 45
  • 71
  • Will fail if you have items added at random times; You would need to add an observable list, listen to it and fix any new item added to it. You may as well just ensure they are correct when you make them. – Meirion Hughes Mar 12 '13 at 10:07