How to use WPF to display localizable and dynamic text? - wpf

I'm new to WPF and trying to mash together some concepts I'm reading about.
What I'm trying to do is a build a localizable UI. For simplicity, let's say I am building a UI with string: "The file takes up 2 GB in disk space."
The "2 GB" portion is dynamic. The value could change depending upon the file the user selects. Secondly, a conversion should take from ulong (file size bytes) to string (friendly size, using appropriate units e.g. KB, MB, GB, TB, etc.).
I was thinking an IValueConverter would be most appropriate for the byte count to friendly-file-size conversion. I was also thinking that I'd store "The file takes up {0} in disk space." as a string resource.
I'm not sure the IValueConverter will be of use here. Can it be used with String.Format()? I don't see how it could be used in a binding directly, because we're inserting the conversion result into the localizable text template.
Is there a better way to approach this?

Bindings have a StringFormat property, you should be able to use that if you can somehow reference your localized string (possibly using a custom markup extension).

Use this handy bytes name to text converter and an IValueConverter
Converting bytes to GB in C#?
private string formatBytes(float bytes)
{
string[] Suffix = { "B", "KB", "MB", "GB", "TB" };
int i;
double dblSByte=0;
for (i = 0; (int)(bytes / 1024) > 0; i++, bytes /= 1024)
dblSByte = bytes / 1024.0;
return String.Format("{0:0.00} {1}", dblSByte, Suffix[i]);
}
public class BytesSuffixConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
float bytes = (float)value;
return formatBytes(bytes);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new Exception("Not Supported.");
}
}

Related

WPF stringformat show only digital

i want in wpf application show only decimal number in a textblock.
I have for example "35.56", i want show only ".56"
Thank you
You can represent 0.56 as only .56 using <TextBlock Text="{Binding Path=yourprop, StringFormat='#.##'}" /> but 35.56 cannot be represented using .56 using a StringFormat because 35.56 and .56 are two different values.
If you want to represent 35.56 as .56 for some strange reason, you'd better format the string using some custom logic in your view model:
public string YourPropFormatted
{
get
{
string s = yourprop.ToString("#.00", CultureInfo.InvariantCulture);
int index = s.IndexOf('.');
return (index > 0) ? s.Substring(index) : s;
}
}
XAML is a markup language and can't handle this.
Complementing mm8 answer with another way to get the same result is implementing a IValueConverter.
public class NumberToDecimalPartConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(!(value is double val))
return default(double).ToString(); // or ".00", you decide
string s = val.ToString("#.00", CultureInfo.InvariantCulture);
int index = s.IndexOf('.');
return index > 0 ? s.Substring(index) : s;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// you don't need convert back
throw new NotImplementedException();
}
}
Assuming the converter is placed at same namespace
<Window.Resources>
<local:NumberToDecimalPartConverter x:Key="NumberToDecimalPart"/>
</Window.Resources>
<TextBlock Text="{Binding Path=yourprop, Converter={StaticResource NumberToDecimalPart}}" />
When your property changes, the UI will be updated, right?
With converter, you have one more step that is convert the value.
So what I'm doing here it's when the property (your probably double value) change, the WPF will call the Convert function from NumberToDecimalPartConverter. When the property have Mode setted as TwoWay will call ConvertBack.

Issue in chaining IValueConvertes in WPF becauase of target type

I was trying to chain converters as Town's answer in Is there a way to chain multiple value converters in XAML??
I like to make individual converters more strict by having targetType check as well :-
if (targetType != typeof(bool))
throw new InvalidOperationException("The target must be a
boolean");
But the chain fails as the end target type is different from the target at each stage.
I can remove the type check to make less strict as given in most of examples on SO, but I would prefer a chaining which respects each converter's type check as well. E.g. for better unit testing etc.
Also the interface IValueConverter doesn't expose the target type, I find it difficult to add that check myself.
public class InverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(bool))
throw new InvalidOperationException("The target must be a boolean");
if (!(value is bool))
throw new ArgumentException("Argument 'value' must be of type bool");
return !(bool)value;
}
....
}
[ValueConversion(typeof(bool), typeof(Visibility))]
public class VisibilityFromBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(Visibility))
throw new InvalidOperationException("The target must be a Visibility");
if (!(value is bool))
throw new ArgumentException("Argument 'value' must be of type bool");
var isVisible = (bool)value;
return isVisible ? Visibility.Visible : Visibility.Collapsed;
}
....
}
And the composite is like :-
<Converters:ValueConverterGroup x:Key="InvertAndVisible">
<Converters:InverseBooleanConverter />
<Converters:VisibilityFromBoolConverter />
</Converters:ValueConverterGroup>
But I get exception "The target must be a boolean" from InverseBooleanConverter as it expects target to be bool instead of Visibility (the end target of chain).
The original ValueConverterGroup code passes the final targetType into each stage, which is why your checks are failing. All you need to do is modify that behaviour to pass in the next converters targetType instead:
[ValueConversion(typeof(bool), typeof(Visibility))]
public class ValueConverterGroup : List<IValueConverter>, IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
for (int i = 0; i < this.Count(); i++)
{
var targ = (i == this.Count() - 1)
? targetType
: (this[i + 1].GetType().GetCustomAttributes(typeof(ValueConversionAttribute), false).First() as ValueConversionAttribute).SourceType;
value = this[i].Convert(value, targ, parameter, culture);
}
if (value.GetType() != (this.GetType().GetCustomAttributes(typeof(ValueConversionAttribute), false).First() as ValueConversionAttribute).TargetType)
throw new InvalidOperationException("Last target must be of type " + targetType.Name);
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
I think you may have misunderstood the targetType parameter. According to the doco, it is the type of the target bound property.
This means that for your InverseBooleanConverter the target type will be a System.Visibility type.
For this sort of checking you should check the type of the incoming (bound) object:
if (value != null && value.GetType() != typeof(bool))
throw new InvalidOperationException("The target must be a boolean");
But.... I would strongly suggest you don't throw exceptions from your converters - that can slow UI rendering down massively and they can be incredibly difficult to track down when you have a screenful of goodness (e.g. you have a grid with a couple of thousand rows in it, and one of the templated datagrid cells throws an exception - how are you going to identify it?). If you insist on throwing exceptions then at least surround it with an #if DEBUG define so that it isn't in your release code. Instead you should return DependencyProperty.UnsetValue if your converter cannot successfully convert a value. This ensures you don't get hard to track runtime exceptions, and it also ensures the binding subsystem can use things like the FallbackValue.

String interpolation in XAML

I'm thinking about ways of getting advance of C# 6 string interpolation in XAML, such as using them instead of value converters in some simple scenarios like replacing a zero by an empty string when binding to numbers.
From its design discussions:
An interpolated string is a way to construct a value of type String
(or IFormattable) by writing the text of the string along with
expressions that will fill in "holes" in the string. The compiler
constructs a format string and a sequence of fill-in values from the
interpolated string.
However, as I suspected, it seems that they can't be used from XAML since it uses a different compiler to generate the BAML and I find no trace of the strings in the generated .g.i.cs files.
Are string interpolations not supported in XAML?
What workarounds could there be? Maybe using markup extensions to dynamically compile the string interpolations?
This sounds a lot like the StringFormat attribute introduced in .Net 3.5. As you quote, "writing the text of the string along with expressions that will fill in 'holes' in the string", this can be performed within a XAML binding like this:
<TextBlock Text="{Binding Amount, StringFormat=Total: {0:C}}" />
Since you can use any of the custom string formats, there's a lot of power under the hood here. Or are you asking something else?
This is tricky to support due to the way that Binding works in WPF. String interpolations in the C# code can be compiled directly to string.Format calls and basically just provide a convenient syntactic sugar. To make this work with Binding, though, it's necessary to do some work at runtime.
I've put together a simple class that can do this, though it has a few limitations. In particular, it doesn't support passing through all the binding parameters and it's awkward to type in the XAML since you have to escape the curly braces (maybe worth using a different character?) It should handle multi-path bindings and arbitrarily complex format strings, though, as long as they are properly escaped for use in XAML.
In reference to one particular point in your question, this doesn't allow you to embed arbitrary expressions like you can do in interpolated strings. If you wanted to do that, you'd have to get a bit fancier and do something like on-the-fly code compilation in terms of the bound values. Most likely you'd need to emit a function call that takes the parameter values, then call that as a delegate from the value converter and have it execute the embedded expressions. It should be possible, but probably not easy to implement.
Usage looks like this:
<TextBlock Text="{local:InterpolatedBinding '\{TestString\}: \{TestDouble:0.0\}'}"/>
And here is the markup extension that does the work:
public sealed class InterpolatedBindingExtension : MarkupExtension
{
private static readonly Regex ExpressionRegex = new Regex(#"\{([^\{]+?)(?::(.+?))??\}", RegexOptions.Compiled);
public InterpolatedBindingExtension()
{
}
public InterpolatedBindingExtension(string expression)
{
Expression = expression;
}
public string Expression { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
//Parse out arguments captured in curly braces
//If none found, just return the raw string
var matches = ExpressionRegex.Matches(Expression);
if (matches.Count == 0)
return Expression;
if (matches.Count == 1)
{
var formatBuilder = new StringBuilder();
//If there is only one bound target, can use a simple binding
var varGroup = matches[0].Groups[1];
var binding = new Binding();
binding.Path = new PropertyPath(varGroup.Value);
binding.Mode = BindingMode.OneWay;
formatBuilder.Append(Expression.Substring(0, varGroup.Index));
formatBuilder.Append('0');
formatBuilder.Append(Expression.Substring(varGroup.Index + varGroup.Length));
binding.Converter = new FormatStringConverter(formatBuilder.ToString());
return binding.ProvideValue(serviceProvider);
}
else
{
//Multiple bound targets, so we need a multi-binding
var multiBinding = new MultiBinding();
var formatBuilder = new StringBuilder();
int lastExpressionIndex = 0;
for (int i=0; i<matches.Count; i++)
{
var varGroup = matches[i].Groups[1];
var binding = new Binding();
binding.Path = new PropertyPath(varGroup.Value);
binding.Mode = BindingMode.OneWay;
formatBuilder.Append(Expression.Substring(lastExpressionIndex, varGroup.Index - lastExpressionIndex));
formatBuilder.Append(i.ToString());
lastExpressionIndex = varGroup.Index + varGroup.Length;
multiBinding.Bindings.Add(binding);
}
formatBuilder.Append(Expression.Substring(lastExpressionIndex));
multiBinding.Converter = new FormatStringConverter(formatBuilder.ToString());
return multiBinding.ProvideValue(serviceProvider);
}
}
private sealed class FormatStringConverter : IMultiValueConverter, IValueConverter
{
private readonly string _formatString;
public FormatStringConverter(string formatString)
{
_formatString = formatString;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(string))
return null;
return string.Format(_formatString, values);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(string))
return null;
return string.Format(_formatString, value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}
I've done very limited testing, so I recommend more thorough testing and hardening before using this in production. Should hopefully be a good starting point for someone to make something useful, though.

How perform calculation on a XAML Binding value: reverse it, multiply it, subtract from it or add to it?

First; the question is rhetorical, I have an answer! I have gotten so much help from looking here that I wanted to give this neat trick back.
Imagine that you have a value that you want to bind to, but it is somehow or somewhat wrong.
I had a situation where I wanted to bind to a value, but when the value was 1, I needed 0, and vice-versa.
There was a time when I wanted to bind the width of an element to the width of a parent - 68px.
Enter the FirstDegreeFunctionConverter:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
namespace GenericWPF
{
/// <summary>
/// Will return a*value + b
/// </summary>
public class FirstDegreeFunctionConverter : IValueConverter
{
public double A { get; set; }
public double B { get; set; }
#region IValueConverter Members
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
double a = GetDoubleValue( parameter, A );
double x = GetDoubleValue( value, 0.0 );
return ( a * x ) + B;
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
double a = GetDoubleValue( parameter, A );
double y = GetDoubleValue( value, 0.0 );
return ( y - B ) / a;
}
#endregion
private double GetDoubleValue( object parameter, double defaultValue )
{
double a;
if( parameter != null )
try
{
a = System.Convert.ToDouble( parameter );
}
catch
{
a = defaultValue;
}
else
a = defaultValue;
return a;
}
}
How to use it?
You make a resource for each use in the resource section:
<GenericWPF:FirstDegreeFunctionConverter x:Key="ReverseOne"
A="-1"
B="1" />
<Border Opacity="{Binding Path=Opacity
, ElementName=daOtherField
, Converter={StaticResource ReverseOne}}" />
<GenericWPF:FirstDegreeFunctionConverter x:Key="ListboxItemWidthToErrorWidth"
A="1"
B="-68" />
<TextBox MaxWidth="{Binding Path=ActualWidth
, Converter={StaticResource ListboxItemWidthToErrorWidth}
, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" />
The name comes from the function y = a*x + b (Called a "first degree function" in Norwegian), and of course it would be possible to upgrade it to a second degree function y= a*x^2 + bx + c, but I haven't found a use for it yet.
I had a situation where I wanted to make columns based on width. Each time I got 200 pixels more width, I wanted the container to show me another column. At that time I hardcoded a converter, but I should have made a y=(a/x) + b converter instead.
Now, what should I have named this converter so that everybody understand what it is? Since I'm a Norwegian, I used the expression we learned in school, directly translated. Please, if you have a suggestion or an opinion, let me know.
Any refinements or improvements you have thought of would also be appreciated...
Maybe "LinearTransformConverter" would better communicate what the converter does for you, I'll think about it.
Any other proposals?
Tor
What is even better is a PolynomialConverter, here's the one-way version:
public class PolynomialConverter : IValueConverter
{
public DoubleCollection Coefficients { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double x = (double)value;
double output = 0;
for (int i = Coefficients.Count - 1; i >= 0 ; i--)
output += Coefficients[i] * Math.Pow(x, (Coefficients.Count - 1) - i);
return output;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//This one is a bit tricky, if anyone feels like implementing this...
throw new NotSupportedException();
}
}
Examples:
<!-- x^2 -->
<vc:PolynomialConverter Coefficients="1,0,0"/>
<!-- x + 5 -->
<vc:PolynomialConverter Coefficients="1,5"/>
<!-- 2x + 4 -->
<vc:PolynomialConverter Coefficients="2,4"/>
Alternatively one could use the ConverterParameter instead to not set the Coefficients in the converter itself.
DoubleCollection coefficients = DoubleCollection.Parse((string)parameter);
//...

How do you select the right size icon from a multi-resolution .ico file in WPF?

If I have a multi-resolution icon file (.ico), how can I insure that WPF picks the right sized one? Does setting the width and height of the Image force it, or does WPF simply resize the first icon in the ico file?
This is what I'm using currently (it works, but I'd like to avoid the resizing if that's what's happening).
<MenuItem.Icon>
<Image Source="MyIcons.ico" Width="16" Height="16" />
</MenuItem.Icon>
I'd like to declare this in Xaml if possible without having to code for it.
I use simple Markup Extension for that:
/// <summary>
/// Simple extension for icon, to let you choose icon with specific size.
/// Usage sample:
/// Image Stretch="None" Source="{common:Icon /Controls;component/icons/custom.ico, 16}"
/// Or:
/// Image Source="{common:Icon Source={Binding IconResource}, Size=16}"
/// </summary>
public class IconExtension : MarkupExtension
{
private string _source;
public string Source
{
get
{
return _source;
}
set
{
// Have to make full pack URI from short form, so System.Uri recognizes it.
_source = "pack://application:,,," + value;
}
}
public int Size { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var decoder = BitmapDecoder.Create(new Uri(Source),
BitmapCreateOptions.DelayCreation,
BitmapCacheOption.OnDemand);
var result = decoder.Frames.SingleOrDefault(f => f.Width == Size);
if (result == default(BitmapFrame))
{
result = decoder.Frames.OrderBy(f => f.Width).First();
}
return result;
}
public IconExtension(string source, int size)
{
Source = source;
Size = size;
}
public IconExtension() { }
}
Xaml usage:
<Image Stretch="None"
Source="{common:Icon Source={Binding IconResource},Size=16}"/>
or
<Image Stretch="None"
Source="{common:Icon /ControlsTester;component/icons/custom-reports.ico, 16}" />
(based on #Nikolay great answer and follow-up comment about binding)
You probably will be better off creating a Converter instead of a MarkupExtension so that you can leverage Binding. Using the same logic as provided by #Nikolay
/// <summary>
/// Forces the selection of a given size from the ICO file/resource.
/// If the exact size does not exists, selects the closest smaller if possible otherwise closest higher resolution.
/// If no parameter is given, the smallest frame available will be selected
/// </summary>
public class IcoFileSizeSelectorConverter : IValueConverter
{
public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var size = string.IsNullOrWhiteSpace(parameter?.ToString()) ? 0 : System.Convert.ToInt32(parameter);
var uri = value?.ToString()?.Trim();
if (string.IsNullOrWhiteSpace(uri))
return null;
if (!uri.StartsWith("pack:"))
uri = $"pack://application:,,,{uri}";
var decoder = BitmapDecoder.Create(new Uri(uri),
BitmapCreateOptions.DelayCreation,
BitmapCacheOption.OnDemand);
var result = decoder.Frames.Where(f => f.Width <= size).OrderByDescending(f => f.Width).FirstOrDefault()
?? decoder.Frames.OrderBy(f => f.Width).FirstOrDefault();
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
You must then create a resource from your converter class somewhere in a ResourceDictionary as usual:
<localConverters:IcoFileSizeSelectorConverter x:Key="IcoFileSizeSelector" />
And then you can use Binding:
<Image Source="{Binding Path=IconResource, Converter={StaticResource IcoFileSizeSelector}, ConverterParameter=16}" />
PS: in the converter code, we accept all inputs for parameter, even missing or invalid ones. That behaviour is more convenient if like me you like to play with live XAML edit.
It doesn't appear to be possible using Xaml only.
If the reason you're asking is that the icon looks blurry to you, check out this very good article on the topic that I used to solve that problem: http://blogs.msdn.com/dwayneneed/archive/2007/10/05/blurry-bitmaps.aspx
You will have to use a custom control that not only sizes the icon exactly, but ensures that it coincides exactly with the pixel grid. Only then will you avoid interpolation and therefore blurriness.
Trying to find some info on your query about image size selection in icons...will post back if I find any...

Resources