Null To Boolean IValueConverter not working - c#

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?

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.

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.

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

Related

TargetNullValue = '' not working WPF C#

I have the following textbox , not sure why the formatting won't go to normal but you get the point.
<TextBox LostFocus="TextBox_LostFocus">
<TextBox.Text>
<Binding Path="InputVolts"
TargetNullValue=''
FallbackValue=''>
<Binding.ValidationRules>
<u:NonEmptyStringValidator/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
This textbox is bound to the following property:
public int? InputVolts
{
get { return _InputVolts; }
set { Set(ref _InputVolts, value);}
}
In my form, if I type a number like 240 into the textbox the view model will update with that value. If I try and delete the 240 from the textbox, the view model does not update the InputVolts property accordingly.
I am aware of the TargetNullValue solution from the following Post
EDIT:
Okay I found the problem, my NonEmptyStringValidator is causing this issue. I want to still have this validation rule on my text box though. Is there anyway to adjust this to still keep the validation rule but make the textbox return back to null when deleted?
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
ValidationResult validationResult = new ValidationResult(false, "Value cannot be empty.");
if (value != null)
{
string valueAsString = value as string;
if (valueAsString.Length > 0)
validationResult = ValidationResult.ValidResult;
}
return validationResult;
}
I also tried using the FallBackValue and nothing seems to be working. Any help would be greatly appreciated!
Thank you,
The <u:NonEmptyStringValidator/> might prevent the update to an empty string.

How to trigger a WPF Background Converter for testing

I'm sitting in front of the following unit test without getting it to work properly
[TestMethod]
public void EvenIndexesZeroShouldHaveWhiteBackground()
{
var converterBinding = new Binding("BackgroundConverter");
converterBinding.Converter = new BackgroundConverter();
var lvi0 = new ListViewItem() { Background = Brushes.Gray };
var lv = new ListView();
lvi0.SetBinding(ListViewItem.BackgroundProperty, converterBinding);
lv.Items.Add(lvi0);
Assert.AreEqual(Brushes.White, converterBinding.Converter.Convert(lvi0, null, null, CultureInfo.InvariantCulture));
}
I was able to get another converter tested by directly calling the Convert(...) method, but it received a simple data type.
I have the feeling that I somehow need to trigger the converter when adding lvi0to the ListView(or manually afterwards) but I don't know how to do it.
Can anyone point me in the right direction?
I'm new to WPF and haven't fully gotten my head around the Bindings and Dependency Properties yet :(
[UPDATE]
The current problem is that the Convertmethod isn't called. It's not the content of the converter or the result it is giving back.
[UPDATE 2]
#Tatranskymedved comment pointed me into the right direction and calling the converter directly (as proposed by #PeterDuniho) now works. I have updated the code snippet above accordingly.
[UPDATE 3]
Here is the Converter. I HAVE to pass in a ListViewItem since this is what the it is working on. Changing it is currently not an option.
public class BackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ListViewItem item = value as ListViewItem;
if (item == null) return Brushes.White;
ListView listView = ItemsControl.ItemsControlFromItemContainer(item) as ListView;
// Get the index of a ListViewItem
if (listView == null)
return Brushes.White;
int index = listView.ItemContainerGenerator.IndexFromContainer(item);
if (index % 2 == 0)
{
return Brushes.WhiteSmoke;
}
return Brushes.White;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
The basic idea is: In WPF You are using some Window/UserControl, where are layouts and controls. If any of the controls should have binded its property to the ViewModel, property of the Control must be defined as DependencyProperty. If You are just using some already defined controls, You do not need to known them.
When You are creating own UserControl, it has to have a DependencyProperty, so You can bind one end to it.
Now You have to realize, what do You want to test. Is it the binding? Or the converter itself?
For the binding test, You can refer to this: Unit test WPF Bindings
Or: http://www.wpf-tutorial.com/data-binding/debugging/
However, talking about unit tests, You should test the Converter directly instead of putting them to the complicated chain objects like Binding. That is the basic motivation if the test won't work, You can say "the problem is with the Converter", not with the binding or the object You will bind to.
Only thing You need to check if the type of value You setting is the correct one. For WPF Control's BackgroundProperty it should be System.Windows.Media.Brush as on MSDN.

Binding Checkbox "IsChecked" value to external XML file via XmlDataProvider (utilizing MVVM)

After searching and trying several options over the last week, I can't seem to find what I am looking for; maybe someone here can help. While reading through this, please keep in mind that I am attempting to utilize MVVM as strictly as possible, though I am relatively new to WPF. As a side note, I am using Mahapps.Metro to style my window and controls, found here.
I have an XML file that my application uses for configuration (I cannot use the app.config file because the application cannot install on the users' systems). The application will look for this file at start-up and if it does not find the file, it will create it. Below is a snippet of the XML:
<?xml version="1.0" encoding="utf-8"?>
<prefRoot>
<tabReport>
<cbCritical>True</cbCritical>
</tabReport>
</prefRoot>
I reference the XML file in my Window.Resources:
<Controls:MetroWindow.Resources>
<XmlDataProvider x:Key="XmlConfig"
Source="%appdata%\Vulnerator\Vulnerator_Config.xml"
XPath="prefRoot"
IsAsynchronous="False"
IsInitialLoadEnabled="True"/>
</Controls:MetroWindow.Resources>
And utilize this as the DataContext for my MainWindow:
<Controls:MetroWindow DataContext="{DynamicResource XmlConfig}">
Next, I set up a "string-to-bool" converter:
class StringToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
bool? isChecked = (bool?)value;
return isChecked;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
string isChecked = value.ToString();
return isChecked;
}
return string.Empty;
}
}
Finally, I bind IsChecked to the appropriate XPath:
<Checkbox x:Name="cbCritical"
Content="Critical"
IsChecked="{Binding XPath=//tabReport/cbCritical,
Converter={StaticResource StringToBool}}" />
After all of this, the applciation loads, but IsChecked is set to false... Any and all ideas would be helpful here; thanks in advance!
I figured out the issue... XAML does not handle % in a file path as expected. To correct, I removed the following from my XAML XmlDataProvider declaration:
Source="%appdata%\Vulnerator\Vulnerator_Config.xml"
XPath="prefRoot"
I then set the Source and XPath properties in my code-behind (.xaml.cs):
public MainWindow()
{
InitializeComponent();
Uri xmlPath = new Uri (Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + #"\Vulnerator\Vulnerator_Config.xml");
(this.Resources["XmlConfig"] as XmlDataProvider).Source = xmlPath;
(this.Resources["XmlConfig"] as XmlDataProvider).XPath = "prefRoot";
}
Now, when the application loads, the checkbox is set to the inner value of the XML node specified. Also, I set the Binding Mode=TwoWay to OneTime; two way binding to an XmlDataProvider doesn't occur as expected. To get around this, I am going to bind a command to the Checkbox to update a Dictionary<string, string> (created at startup in my view-model constructer) with the new IsChecked value. I will use the Dictionary to control what the application does based off of user input, and write the new Dictionary user values to the XML file once the application is closed.

Best way to change multiple properties with WPF bindings

I have a user control containing a text box and a label, the label display the length of the input text (with some formatting). I want to change the background color of the text box if the text is longer than 160 characters.
I was thinking of achieving this with bindings, but since the length of the text contain tag to be replaced I'm not willing to have 2 different binding making the same computing.
I don't succeed in changing
I can think of three way to achieves this :
1) create a hidden label with all tags replaced in his text, then have two simple converter to bind display the message length and change the background color. 3 converter for such a basic task seems too much to me.
2) Use the text_changed event to do the work. This work but it seems to me its not the way to do things in WPF.
3) Use a multibinding and pass my form as a source, this should work but looks too much 'god object' approach to me.
What do you think of that ? Am I missing a cleaner/simpler solution ?
Any suggestion is welcome, Thanks in advance.
You can create another property TBBackColor, and bind your textbox BackgroundColor to it.
Something like:
Public System.Windows.Media.Brush TBBackColor
{
get
{
return (TBText.Length>160)? new SolidColorBrush(Color.Red): new SolidColorBrush(Color.White);
}
}
And remember in your TBText property (if that is the one bind to your TextBox: Text) you need to raise propertychanged event for TBBackColor too.
Using a converter is a good idea in this case, but you won't need multiple converters. Instead, we define one converter with multiple parameters:
public class TextBoxValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || string.IsNullOrEmpty(parameter as string))
throw new ArgumentException("Invalid arguments specified for the converter.");
switch (parameter.ToString())
{
case "labelText":
return string.Format("There are {0} characters in the TextBox.", ((string)value).Count());
case "backgroundColor":
return ((string)value).Count() > 20 ? Brushes.SkyBlue : Brushes.White;
default:
throw new ArgumentException("Invalid paramater specified for the converter.");
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And then in your XAML you use it like this:
<TextBox Name="textBox" Background="{Binding RelativeSource={RelativeSource Self}, Path=Text, Converter={StaticResource converter}, ConverterParameter=backgroundColor}"/>
<Label Content="{Binding ElementName=textBox, Path=Text, Converter={StaticResource converter}, ConverterParameter=labelText}"/>

C# Set the visibility of a label using the result of String.IsNullOrEmpty

Hi I am trying to set the visibility of a label based on a textbox string being empty. I have the following code:
MyLabel.Visible = String.IsNullOrEmpty(MyTextBox.Text);
Why does MyLabel not appear when the textbox is left empty?
Update
I have tried placing this code in the Text_Changed event of the textbox and it still doesn't work.
This was an update issue, it does work on the Text_Changed event. However the issue is it does not work when triggered on the proccessing of the form.
Here is the code triggered from my controller class to give everyone a better insight as to what is going on:
using (var frm = new frmAdd(PersonType.Carer))
{
var res = frm.ShowDialog();
if (res == System.Windows.Forms.DialogResult.OK)
{
if (frm.ValidateInformation()) // the above code is called in here
{
// process the information here...
}
}
}
Also I forgot to mention that this form is in a Class Library project (dll).
Depends on where the code is being run. If you need interactivity, i.e. the label disappears when a character is typed into the textbox, you need to run it on the Keyup event of the textbox. You may also need to repaint the label.
If you are updating the Visible property in the text changed event you are probably running into the following problem. When the form first starts up the text is set to an empty string. But because this is the initial value it hasn't changed so to speak and hence no event is raised.
You may need to perform this update directly in your Form constructor. See if that fixes the problem.
I would add a Trim, to be more consistent to the User in case of spaces left:
MyLabel.Visible = String.IsNullOrEmpty(MyTextBox.Text.Trim());
For the rest it is a matter of triggering the code at the right time. TextChanged should cover everything but the inital state, as addressed by JaredPar. Although I would use Form_Load, not the constructor.
Edit, after the clarification: If your Label an TextBox are on frmAdd then the question is moot, the form itself is no longer shown after ShowDialog returns.
I suspect you want to use data binding to set the visibility of the label.
This discussion might help you: WPF Data Binding : enable/disable a control based on content of var?
Update, some code:
public string MyText
{
get { return _myText; }
set { _myText = value; OnPropertyChanged("MyText"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void theTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
_myText = theTextBox.Text;
OnPropertyChanged("MyText");
}
}
[ValueConversion(typeof(string), typeof(System.Windows.Visibility))]
public class StringToVisibilityConverter : System.Windows.Data.IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
System.Windows.Visibility vis;
string stringVal = (string)value;
vis = (stringVal.Length < 1) ? Visibility.Visible : Visibility.Hidden;
return vis;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
In the XAML:
<TextBlock Background="AliceBlue" >
<TextBlock.Visibility>
<Binding ElementName="window1" Path="MyText" Converter="{StaticResource stringToVisibilityConverter}"/>
</TextBlock.Visibility>
</TextBlock>

Categories

Resources