This may be something similar to other but my understanding is not great so I'm trying to see if anyone can help me understand. I want to change the decimal place of a number, for example if the number is 0.5 I want it to convert it to .50, likewise if it was 0.25 then .25, etc, I'm guessing this is so simple but I can't seem to understand how to do this? My unit test code I have is below and the first one passes but not the others
Updated Unit Test
[TestFixture]
public class ProbabilityDisplayConverterTests
{
public ProbabilityDisplayConverter underTest = new ProbabilityDisplayConverter();
[Test]
public void Convert_ConvertsWholeDecimal()
{
var value = (string)underTest.Convert(1, typeof(decimal), null, CultureInfo.CurrentCulture);
Assert.AreEqual("1", value);
}
[Test]
public void Convert_ConvertsHalfToDecimal()
{
var value = (string)underTest.Convert(0.5, typeof(decimal), null, CultureInfo.CurrentCulture);
Assert.AreEqual(".50", value);
}
[Test]
public void Convert_ConvertsDecimal()
{
var value = (string)underTest.Convert(0.25, typeof(decimal), null, CultureInfo.CurrentCulture);
Assert.AreEqual(".25", value);
}
[Test]
public void Convert_ConvertsWholeNumberDecimal()
{
var value = (string)underTest.Convert(0.3, typeof(decimal), null, CultureInfo.CurrentCulture);
Assert.AreEqual(".30", value);
}
}
EDIT
public class ProbabilityDisplayConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value = value.ToString("G", CultureInfo.InvariantCulture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I´m not sure if I miss something in this question, but why not simply use the format option of the ToString method?
(0.5m).ToString(".00");
(0.15m).ToString(".00");
does exactly what the OP Need (of course you get your local decimal point char)
Try this
number = .0023;
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture));
reference: https://msdn.microsoft.com/ru-ru/library/dwhawy9k(v=vs.110).aspx
Related
I'm using a Type Converter to do some string manipulations on text being bound to TextBlocks in a UWP app. I used a type converter class that I have used in a number of UWP apps with no issues. But in a new UWP app I keep seeing the issue where the Binding value gets passed back and no changes have been made in the type converter to my passed values. I had been using a type converter that takes parameters to take custom actions and that isn't working as well as a simple new type converter I created. The odd thing is that the type converters work at run-time but as ignored at design-time.
Here are the parts of my code and XAML:
This is a data class that creates sample data for this repro to use for my bindings:
public class Alerts_DataClass
{
public string id { get; set; }
public string severity { get; set; }
public string status { get; set; }
public string title { get; set; }
public DateTime lastEventTime { get; set; }
}
public class AlertsSampleData : Alerts_DataClass
{
public AlertsSampleData()
{
Alerts_DataClass myAlert = new Alerts_DataClass { id = "636531490526731751_2124891065", title = "Category Type XVI", severity = "Medium", lastEventTime = new DateTime(2018, 2, 02) };
this.id = myAlert.id;
this.status = myAlert.status;
this.title = myAlert.title;
this.lastEventTime = myAlert.lastEventTime;
this.severity = myAlert.severity;
}
}
Here is the XAML:
<Page
x:Class="NestedBindingsTester.TypeConverterTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:NestedBindingsTester"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="using:System"
mc:Ignorable="d">
<Page.Resources>
<local:StringFormatConverterWithChangeType x:Key="StringFormatConverter"/>
<local:DebugDataBindingConverter x:Key="DebugBinding"/>
<local:ValueToStringConverter x:Key="valueToStringConverter" />
<local:AlertsSampleData x:Key="data2"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="103*"/>
<RowDefinition Height="897*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" d:DataContext="{StaticResource data2}">
<TextBlock Text="{Binding title, ConverterParameter=lower, Converter={StaticResource StringFormatConverter}, Mode=TwoWay}" Style="{StaticResource BaseTextBlockStyle}" Margin="50,0,0,0" />
<TextBlock Text="{Binding title, Converter={StaticResource valueToStringConverter}, Mode=TwoWay}" Style="{StaticResource BaseTextBlockStyle}" Margin="50,0,0,0" />
</StackPanel>
</Grid>
</Page>
And finally here are the two Type Converter modules. The first is the simple one, valueToStringConverter which I just have take any String text passed and return it as lower case:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Data;
namespace NestedBindingsTester
{
public sealed class ValueToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
//return value?.ToString();
string strThisTest = value.ToString().ToLower();
return strThisTest;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException("Converting from string is not supported.");
}
}
}
And the original type converter I was using that takes parameters as switches for various actions called StringFormatConverter. In my XAML example I am passing the parameter of 'lower' to tell it to convert the string value to lowercase:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.UI.Xaml.Data;
namespace NestedBindingsTester
{
public class DebugDataBindingConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
Debugger.Break();
return "This is";
////return value;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
Debugger.Break();
return "it";
////return value;
}
public object Convert(object value, Type targetType, object parameter, string language)
{
////throw new NotImplementedException();
return "Hello moto";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Sample usage:
/// Text="{Binding Budget, Mode=TwoWay, Converter={StaticResource StringFormatConverterWithChangeType}, ConverterParameter='\{0:C2\}'}"
/// </summary>
public class StringFormatConverterWithChangeType : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, string language)
{
// Used in UWP app
//
if (value != null)
{
// Arbitrarily remove dollar sign.
//value = value.ToString().Replace("$", "");
}
else
{
value = "**NULL**";
}
if (parameter != null)
{
string formatString = parameter.ToString();
if (!string.IsNullOrEmpty(formatString))
{
string strValue = value.ToString();
if (strValue != null && !string.IsNullOrWhiteSpace(strValue))
{
switch (formatString)
{
case "%": // Add %
return String.Format("{0}%", value);
case "mi": // Add miles
return String.Format("{0} miles", value);
case "caps": // Convert to Initial caps
return UpperCaseFirstCharacter(value.ToString());
case "lower": // Convert to Initial caps
return value.ToString().ToLower();
case "LiGa": // Convert from Liters to Gallons
{
//
// Convert kilometers to miles.
// 1 liter = 0.264172052 US gallons
//
double lConversion = 0.264172052;
double liters = System.Convert.ToDouble(strValue);
double gallons = lConversion * liters;
return String.Format("{0:N1} gallons", gallons);
}
case "TimeMath": // Compute time remaining until fully charged
{
int Minutes = System.Convert.ToInt16(value);
DateTime currentTime = DateTime.Now;
TimeSpan twoAndAHalfHours = new TimeSpan(0, Minutes, 0);
DateTime estimatedFullChargedTime = DateTime.Now.AddMinutes(Minutes);
string formattedTime = estimatedFullChargedTime.ToString("HH:mm");
return formattedTime;
}
case "UTCtoShortDate": // 02/03.2018
{
string utcDate = value.ToString(); // "2018-01-26T02:17:23.5099802Z";
DateTime dateTime;
DateTime.TryParse(utcDate, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out dateTime);
Debug.WriteLine(dateTime.ToString());
string strWDATP_Date = String.Format("{0:MM.dd.yyyy}", dateTime);
return strWDATP_Date;
}
case "{0:d}": // Short date.
case "{0:D}": // Long date.
////return String.Format(culture, formatString, System.Convert.ChangeType(value, typeof(DateTime), null));
return String.Format("US", formatString, System.Convert.ChangeType(value, typeof(DateTime), null));
}
}
else
{
return String.Format("US", formatString, value);
}
}
}
return value.ToString();
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
// Arbitrarily remove dollar sign.
//value = value.ToString().Replace("$", "");
}
if (parameter != null)
{
string formatString = parameter.ToString();
if (!string.IsNullOrEmpty(formatString))
{
string strValue = value.ToString();
if (strValue != null && !string.IsNullOrWhiteSpace(strValue))
{
switch (formatString)
{
case "%": // Add %
return String.Format("{0}%", value);
case "mi": // Add miles
return String.Format("{0} miles", value);
case "caps": // Convert to Initial caps
return UpperCaseFirstCharacter(value.ToString());
case "LiGa": // Convert from Liters to Gallons
{
//
// Convert kilometers to miles.
// 1 liter = 0.264172052 US gallons
//
double lConversion = 0.264172052;
double liters = System.Convert.ToDouble(strValue);
double gallons = lConversion * liters;
return String.Format("{0:N1} gallons", gallons);
}
case "TimeMath": // Compute time remaining until fully charged
{
int Minutes = System.Convert.ToInt16(value);
DateTime currentTime = DateTime.Now;
TimeSpan twoAndAHalfHours = new TimeSpan(0, Minutes, 0);
DateTime estimatedFullChargedTime = DateTime.Now.AddMinutes(Minutes);
string formattedTime = estimatedFullChargedTime.ToString("HH:mm");
return formattedTime;
}
case "{0:d}": // Short date.
case "{0:D}": // Long date.
return String.Format(culture, formatString, System.Convert.ChangeType(value, typeof(DateTime), null));
}
}
else
{
return String.Format(culture, formatString, value);
}
}
}
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public string UpperCaseFirstCharacter(string text)
{
//return Regex.Replace(text, "^[a-z]", m => m.Value.ToUpper());
return text.First().ToString().ToUpper() + text.Remove(0, 1).ToLower();
}
#endregion
};
}
I am using Visual Studio 2017 and targeting the Windows 10 Fall Creators Update SDK (16299).
I just can't spot why it's no longer working as these type converters have been working fine in two other UWP projects.
NOTE: I am using Binding in my binding for the TextBlocks since x:Bind does not work with design-time data.
Thanks for taking a look, I have been staring at this code for a while and can't find the problem.
Rick
I'm trying to put value (double type) in textbox and i would like to get a custom format.
To explain, i put 10001.1 or 10001,1 and i would like to have 10 001,1 in my texblock.
At the moment my result is 10,001.00 i don't know how can i change the format properly.
My textblock is bind on this :
private double? _Total = null;
public double? Total
{
get
{
if (FiltreAffaire != null)
_Total = ListeAfterFiltre.Sum(CF => CF.CommandeFournisseurLignes.Where(CFL => CFL.Affaire.Numero == FiltreAffaire.Split('-').First().Trim()).Sum(CFL => CFL.Total_HT));
else
return _ListeAfterFiltre.Sum(CF => CF.Montant_HT);
return _Total;
}
set
{
_Total = value;
}
}
Thanks
You can use a simple StringFormat to get your required value... if you are in a country that uses the comma as a decimal separator:
DoubleValue = 10001.1;
...
<TextBlock Text="{Binding DoubleValue, StringFormat={}{0:### ###.#}}" />
In England, this would output the following:
10 001.1
In France, this should output:
10 001,1
You can also do this in code using the NumberFormatInfo class:
NumberFormatInfo numberFormatInfo = new NumberFormatInfo();
numberFormatInfo.NumberDecimalDigits = 1;
numberFormatInfo.NumberDecimalSeparator = ",";
numberFormatInfo.NumberGroupSeparator = " ";
numberFormatInfo.NumberGroupSizes = new int[] { 3 };
string value = DoubleValue.ToString("N", numberFormatInfo);
This outputs:
10 001,1
I finally use something like this (on Textbox) :
CultureInfo ci = (CultureInfo)Thread.CurrentThread.CurrentUICulture.Clone();
ci.NumberFormat.NumberDecimalSeparator = ".";
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = ci;
and converter to set value.
ValueConverter="{StaticResource StringToDoubleConverterN3Key}"
public class StringToDoubleConverterN3 : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((double)value).ToString("N3").Replace('.', ',');
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
I'm pretty new to XAML and WPF, and I'm trying to build a Converter, which converts an integer to a month (string)
I know the code below doesn't work because it gets an object rather than a string to process, but I have no idea how to handle this object?
public class NumberToMonthConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value is int)
{
string strMonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(value);
return strMonthName;
}
}
}
You are pretty close, just cast value to int after you checked it is an int:
if (value is int)
{
return CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName((int)value);
}
else // you need this since you need to return a value
{ // throw an exception if the value is unexpected
throw new ArgumentException("value is not an int", "value");
}
why dont use TryParse?
int i;
if (int.TryParse(value, out i)) {
string strMonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(i);
return strMonthName;
}
else {
throw new Exception("value is not int!");
--OR--
return "Jan"; //For example
}
I have a class with a List<string> Tags that I want to display in a TextBox. For this I use a IValueConverter.
ListToStringConverter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var list = value as List<string>;
var returnvalue = string.Empty;
if(list != null)
list.ForEach(item => returnvalue += item + ", ");
return returnvalue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var strValue = value as string;
if(strValue != null)
return strValue.Split(new char[] { ',' });
return null;
}
Class Test:
public class Test : IDataErrorInfo
{
public Test()
{
Tags = new List<string>();
}
public List<string> Tags { get; set; }
string errors;
const string errorsText = "Error in Test class.";
public string Error
{
get { return errors; }
}
public string this[string propertyName]
{
get
{
errors = null;
switch (propertyName)
{
case "Tags":
if (Tags.Count <= 1)
{
errors = errorsText;
return "...more tags plz..";
}
break;
}
return null;
}
}
}
Now I want to Validate the Tags:
<TextBox Text="{Binding Test.Tags, Converter={StaticResource ListToStringConverter}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
But there is no error displayed. Maybe because of the conversion but how can i still accomplish a validation?
I don't see why it shouldn't work. If you will run this at first time - SL will highlight the TextBox with red border. But if you will try to change the list of tags - you will see nothing, because you in your converter you have a bug in ConvertBack you return type array of strings (string[]), but property expects object with type List, so you just need to update the ConvertBack method to:
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var strValue = value as string;
if (strValue != null)
return strValue.Split(new char[] { ',' }).ToList();
return null;
}
One more note about Convert method, you can use Join method:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var list = value as List<string>;
if (list != null)
return String.Join(", ", list);
return null;
}
And one more thing about combining the strings. Don't use operator +, because you can have performance issues with it, use StringBuilder instead.
I believe this code may require more effort to comprehend than average, so I'm using contextual code-comments to explain my predicament in an effort to make it a little easier to understand my question.
public class MyControl : System.Windows.FrameworkElement
{
public double Property1 { get; set; }
public double Property2 { get; set; }
}
...
<StackPanel>
<local:MyControl Name="ControlA" />
<local:MyControl Name="ControlB" />
<!--We want to use data-binding to force ControlA.MinWidth to be equal to
ControlA.Property1 * ControlB.Property2 at all times-->
</StackPanel>
...
static readonly MyPrimaryConverter PrimConv = new MyPrimaryConverter();
static readonly MySecondaryConverter SecConv = new MySecondaryConverter();
public Window1()
{
InitializeComponent();
System.Windows.Data.BindingExpressionBase BindExp1, BindExp2;
System.Windows.Data.Binding MyBinding;
MyBinding = new System.Windows.Data.Binding("Property1");
MyBinding.Source = ControlA;
MyBinding.Mode = System.Windows.Data.BindingMode.OneWay;
MyBinding.Converter = PrimConv;
MyBinding.ConverterParameter = ControlB;
BindExp1 = ControlA.SetBinding(
System.Windows.UIElement.MinWidthProperty, MyBinding);
//Binds correctly and shows "Status" as active
MyBinding = new System.Windows.Data.Binding("Property2");
MyBinding.Source = ControlB; // ControlB.Property2 is the source value now
MyBinding.Mode = System.Windows.Data.BindingMode.OneWay;
MyBinding.Converter = SecConv; //Uses the secondary converter
MyBinding.ConverterParameter = ControlA; //ControlA is the parameter now
BindExp2 = ControlA.SetBinding(
System.Windows.UIElement.MinWidthProperty, MyBinding);
/* Also binds correctly and shows "Status" as active,
but causes BindExp1 to show "Status" as "Detached"
****THIS IS THE PROBLEM I NEED HELP WITH****
How can I successfully bind ControlA.MinWidth to BOTH of the values
that are used to calculate MinWidth?
This is required so that MinWidth will be recalculated if EITHER
value changes at any time
Bonus points if you can show how to do it with
only one converter class instead of two */
}
...
[System.Windows.Data.ValueConversion(typeof(double), typeof(double))]
public class MyPrimaryConverter : System.Windows.Data.IValueConverter
{
public object Convert( object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if ((value is double) && (parameter is MyControl))
return ((double)value) * (parameter as MyControl).Property2;
/* This converter ALWAYS returns:
ControlA.Property1 * ControlB.Property2
value == ControlA.Property1 and parameter == ControlB,
so we return value * parameter.Property2 */
return System.Windows.DependencyProperty.UnsetValue;
}
public object ConvertBack( object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{ return System.Windows.DependencyProperty.UnsetValue; }
}
[System.Windows.Data.ValueConversion(typeof(double), typeof(double))]
public class MySecondaryConverter : System.Windows.Data.IValueConverter
{
public object Convert( object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if ((value is double) && (parameter is MyControl))
return ((double)value) * (parameter as MyControl).Property1;
/* This converter also ALWAYS returns:
ControlA.Property1 * ControlB.Property2
"value" and "parameter" are swapped:
value == ControlB.Property2 and parameter == ControlA
so we return value * parameter.Property1
instead of value * parameter.Property2 */
return System.Windows.DependencyProperty.UnsetValue;
}
public object ConvertBack( object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{ return System.Windows.DependencyProperty.UnsetValue; }
}
I think you should use a MultiValueConverter