How to display dynamic culture formatted number in a WPF UserControl - c#

I would like to dynamically set the culture format of the Number textblock with culture and number values passed through to MyUserControl. The MyCulture and Number values are passed to MyCustomControl and will be of the form "en-GB", "en-US" etc.
I did something similar in asp.NET MVC with an extension method but need help for how to piece this together in WPF.
Example MVC Extension Method
public static MvcHtmlString CulturedAmount(this decimal value,
string format, string locale)
{
if (string.IsNullOrEmpty(locale))
locale = HttpContext.Current.Request.UserLanguages[0];
return MvcHtmlString.Create(value.ToString(format,
CultureInfo.CreateSpecificCulture(locale)));
}
Window
//MyMoney is a decimal, MyCulture is a string (e.g. "en-US")
<MyCustomControl Number="{Binding MyMoney}" Culture="{Binding MyCulture}"
Text="Some Text" />
MyCustomControl
<StackPanel>
<TextBlock Text="{Binding Number, ElementName=BoxPanelElement,
StringFormat={}{0:C}}" /> //display this with specific culture
<TextBlock Text="{Binding Text, ElementName=BoxPanelElement}" />
</StackPanel>

If I understand your question correctly you want to bind the culture for a specific TextBlock.
You can't bind the properties of a Binding so binding ConverterCulture won't work.
There is a Language property on FrameworkElement which works fine to set like this
<TextBlock Language="en-US"
Text="{Binding Number,
ElementName=BoxPanelElement,
StringFormat={}{0:C}}"/>
However, when trying to bind this property I get a weird exception
I'm probably going to ask a question on this exception myself
Binding for property 'Language' cannot use the target element's
Language for conversion; if a culture is required, ConverterCulture
must be explicitly specified on the Binding.
According to this answer by Thomas Levesque this should be possible though so maybe I did something wrong.. WPF xml:lang/Language binding
All I got working was using an attached behavior which in turn updated Language when MyCulture updated.
<TextBlock local:LanguageBehavior.Language="{Binding MyCulture}"
Text="{Binding MyNumber,
ElementName=BoxPanelElement,
StringFormat={}{0:C}}"/>
LanguageBehavior
public class LanguageBehavior
{
public static DependencyProperty LanguageProperty =
DependencyProperty.RegisterAttached("Language",
typeof(string),
typeof(LanguageBehavior),
new UIPropertyMetadata(LanguageBehavior.OnLanguageChanged));
public static void SetLanguage(FrameworkElement target, string value)
{
target.SetValue(LanguageBehavior.LanguageProperty, value);
}
public static string GetLanguage(FrameworkElement target)
{
return (string)target.GetValue(LanguageBehavior.LanguageProperty);
}
private static void OnLanguageChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = target as FrameworkElement;
element.Language = XmlLanguage.GetLanguage(e.NewValue.ToString());
}
}

It seems like a converter is the answer. The interface includes a values for culture.
Convert(object value, Type targetType, object parameter, CultureInfo culture)
But I could not find syntax for passing culture.
Sorry this is not a full and tested answer but I ran out of time.
URL on binding culture.
http://msdn.microsoft.com/en-us/library/system.windows.data.binding.converterculture.aspx
The syntax for passing a a parameter is:
Converter={StaticResource colorConverter}, ConverterParameter=GREEN}"
You may need to pass culture as a string using ConverterParameter.
I agree with Meleak that cannot bind the parameter to a converter. Gave him a +1.
But I think you can fool it with a MultiBinding converter.
<TextBlock Name="textBox2" DataContext="{StaticResource NameListData}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource myCutlureConverter}"
ConverterParameter="FormatLastFirst">
<Binding Path="InputValue"/>
<Binding Path="CultureTxt"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>

Related

x:Bind DataTemplate in DataGridTemplateColumn.CellTemplate is not displaying content

Following the response on this windows-toolkit issue, I'm using x:Bind to bind elements of an ObservableCollection of AlertEntry's to DataGridColumn cells. My XAML is as follows:
<controls:DataGrid ItemsSource="{x:Bind ViewModel.Alerts, Mode=OneWay}" AutoGenerateColumns="True" IsReadOnly="True">
<controls:DataGrid.Columns>
<controls:DataGridTemplateColumn Header="Time" >
<controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="ace:AlertEntry">
<TextBlock Text="{x:Bind Timestamp, Converter={StaticResource StringFormatConverter}, ConverterParameter='{}{H:mm:ss}'}"/>
</DataTemplate>
</controls:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>
</controls:DataGrid.Columns>
</controls:DataGrid>
And my AlertEntry class:
public class AlertEntry
{
public DateTime Timestamp;
public ACEEnums.AlertLevel Level;
public ACEEnums.AlertType Type;
public string Info;
public AlertEntry(ACEEnums.AlertLevel level, ACEEnums.AlertType type, string info = "")
{
Timestamp = DateTime.Now;
Level = level;
Type = type;
Info = info;
}
}
When elements are added to ViewModel.Alerts I can see highlightable rows are added to the DataGrid, but they display no content. When I remove the binding and add a fixed text value, it displays that value correctly every time a row is added.
The AlertEntry items in ViewModel.Alerts correctly contain data.
I've confirmed the StringFormatConverter works in other bindings. In fact the StringFormatConverter is not ever being called.
I'm using MVVM-Light and UWP.
Thank you!
For the testing, the problem may occur in your StringFormatConverter, TextBlock Text property only allow string value, So we need return string type value in Convert method.
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
return null;
if (parameter == null)
return value;
var dt = (DateTime)value;
return dt.ToString((string)parameter);
}
And x:Bind support bind function , you could try use Text="{x:Bind Timestamp.ToString()}" to verify above.
My DataGrid is an element of a StackPanel and I was declaring my converters as static resources of the StackPanel like so:
<StackPanel.Resources>
<helper:StringFormatConverter x:Key="StringFormatConverter" />
<helper:AlertToString x:Key="AlertToString" />
</StackPanel.Resources>
I moved the converters to be resources of the whole page instead, and at that point they started working.
<Page.Resources>
<helper:StringFormatConverter x:Key="StringFormatConverter" />
<helper:AlertToString x:Key="AlertToString" />
</Page.Resources>

Why does the Value of the TextBox get reset to the previous value instead of showing an error?

I have a sample where I bind a view model's properties with some TextBox controls, including validation rules. In most cases, this works fine. But when I try to include the IsFocused property of the bound TextBox, I am having trouble in the case when an invalid number is entered in the control.
When I input the wrong number in the TextBox controls that are bound directly to the view model's property, the errors are shown as expected (red border around the TextBox). But in the TextBox that is bound with a MultiBinding that includes both the view model property and the IsFocused property of the TextBox, the error is not shown and the value gets reset to the previous valid value.
For example, if a number less than 10 is invalid, and I input 3, when the TextBox loses focus, a red border normally would appear in the TextBox signaling the error. But in the TextBox which includes IsFocused as a source for its binding, the value changes back to the previous valid value (if there was a 39 before I entered 3, the TextBox changes back to 39).
Using the code below you can reproduce the issue:
TestViewModel.cs
public class TestViewModel
{
public double? NullableValue { get; set; }
}
MainWindow.xaml
<Window x:Class="TestSO34204136TextBoxValidate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO34204136TextBoxValidate"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:TestViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Nullable: "/>
<TextBox VerticalAlignment="Top" Grid.Column="1">
<TextBox.Text>
<MultiBinding Mode="TwoWay">
<Binding Path="NullableValue"/>
<Binding Path="IsFocused"
RelativeSource="{RelativeSource Self}"
Mode="OneWay"/>
<MultiBinding.ValidationRules>
<l:ValidateIsBiggerThanTen/>
</MultiBinding.ValidationRules>
<MultiBinding.Converter>
<l:TestMultiBindingConverter/>
</MultiBinding.Converter>
</MultiBinding>
</TextBox.Text>
</TextBox>
<TextBox VerticalAlignment="Top" Grid.Column="2"/>
</Grid>
</Window>
TestMultiBindingConverter.cs
public class TestMultiBindingConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] != null)
return values[0].ToString();
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
if (value != null)
{
double doubleValue;
var stringValue = value.ToString();
if (Double.TryParse(stringValue, out doubleValue))
{
object[] values = { doubleValue };
return values;
}
}
object[] values2 = { DependencyProperty.UnsetValue };
return values2;
}
}
ValidateIsBiggerThanTen.cs
public class ValidateIsBiggerThanTen : ValidationRule
{
private const string errorMessage = "The number must be bigger than 10";
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var error = new ValidationResult(false, errorMessage);
if (value == null)
return new ValidationResult(true, null);
var stringValue = value.ToString();
double doubleValue;
if (!Double.TryParse(stringValue, out doubleValue))
return new ValidationResult(true, null);
if (doubleValue <= 10)
return error;
return new ValidationResult(true, null);
}
}
Why are the errors not showing for the TextBox in the above example?
The cause of the behavior you're seeing is specifically that you've bound the TextBox's IsFocused property in your MultiBinding. This directly has the effect of forcing an update of the target of the binding when the focus changes.
In the scenario where validation fails, there is a very brief moment when the validation rule has fired, the error is set, but the focus hasn't actually been changed yet. But this all happens too fast for a user to see. And since validation failed, the source of the binding is not updated.
So when the IsFocused property value changes, after the validation and rejection of the entered value happens, the next thing to happen is that the binding is re-evaluated (because one of the source properties changed!) to update the target. And since the actual source value never changed, the target (the TextBox) reverts from whatever you typed back to whatever was stored in the source.
How should you fix this? It depends on the exact behavior desired. You have three basic options:
Keep binding to IsFocused, and add UpdateSourceTrigger="PropertyChanged". This will keep the basic current behavior of copying the old value back when focus is lost, but will at least provide the user with immediate validation feedback as the value is edited.
Remove binding to IsFocused altogether. Then the target of the binding won't depend on that, and won't be re-evaluated when focus changes. Problem solved. :)
Keep binding to IsFocused, and add logic so that the interaction with validation does not result in copying a stale value back to the TextBox.
Based on our comments back and forth, it seems that the third option above is the preferred one for your scenario, as you desire to format the text representation of the value differently when the control has focus vs. when it does not.
I am skeptical of the wisdom of a user interface that formats data differently depending on whether the control is focused or not. Of course, it makes complete sense for focus changes to affect the overall visual presentation, but that would generally involve things like underlining, highlighting, etc. Displaying a completely different string depending on whether the control is focused seems likely to interfere with user comprehension and possibly annoy them as well.
But I'm in agreement that this is a subjective point, and clearly in your case you have this specific behavior that is desirable for your specification and needs to be supported. So with that in mind, let's look at how you can accomplish that behavior…
If you want to be able to bind to the IsFocused property, but not have changes to focus copy over the current contents of the control if the source has not actually been updated yet (i.e. if a validation error prevented that from happening), then you can also bind to the Validation.HasError property, and use that to control the converter's behavior. For example:
class TestMultiBindingConverter : IMultiValueConverter
{
private bool _hadError;
public object Convert(object[] values,
Type targetType, object parameter, CultureInfo culture)
{
bool? isFocused = values[1] as bool?,
hasError = values[2] as bool?;
if ((hasError == true) || _hadError)
{
_hadError = true;
return Binding.DoNothing;
}
if (values[0] != null)
{
return values[0].ToString() + (isFocused == true ? "" : " (+)");
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value,
Type[] targetTypes, object parameter, CultureInfo culture)
{
if (value != null)
{
double doubleValue;
var stringValue = value.ToString();
if (Double.TryParse(stringValue, out doubleValue))
{
object[] values = { doubleValue };
_hadError = false;
return values;
}
}
object[] values2 = { DependencyProperty.UnsetValue };
return values2;
}
}
The above adds a field _hadError that "remembers" what's happened recently to the control. If the converter is called while validation is detecting an error, the converter returns Binding.DoNothing (which has the effect its name suggests :) ), and sets the flag. Thereafter, no matter what happens, as long as that flag is set the converter will always do nothing.
The only way that the flag will get cleared is if the user eventually enters text that is valid. Then the converter's ConvertBack() method will be called to update the source, and in doing so it can clear the _hadError flag. This ensures that the control contents will never get overwritten due to binding updates, except when there has been no error since the last time the source was updated.
Here's the XAML example above updated to use the additional binding input:
<Window x:Class="TestSO34204136TextBoxValidate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO34204136TextBoxValidate"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:TestViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Nulleable: "/>
<TextBox x:Name="textBoxWrapper" Grid.Column="1" VerticalAlignment="Top">
<TextBox.Text>
<MultiBinding x:Name="TextBoxBinding" Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged">
<Binding Path="NulleableValue"/>
<Binding Path="IsFocused"
RelativeSource="{RelativeSource Self}"
Mode="OneWay"/>
<Binding Path="(Validation.HasError)"
RelativeSource="{RelativeSource Self}"
Mode="OneWay"/>
<MultiBinding.ValidationRules>
<l:ValidateIsBiggerThanTen/>
</MultiBinding.ValidationRules>
<MultiBinding.Converter>
<l:TestMultiBindingConverter/>
</MultiBinding.Converter>
</MultiBinding>
</TextBox.Text>
</TextBox>
<TextBox VerticalAlignment="Top" Grid.Column="2"/>
</Grid>
</Window>
I should point out, in case it's not obvious: the _hadError field is for the converter itself. For the above to work correctly, you'll need a separate instance of the converter for each binding to which it's applied. There are alternative ways to track such a flag for each control uniquely, but I feel an extended discussion of the options in that respect are outside the scope of this question. Feel free to explore on your own, and post a new question regarding that aspect if you are unable to address the issue adequately on your own.

WPF Localization: DynamicResource with StringFormat?

I am doing localization in .NET 4 with a ResourceDictionary. Does anyone have a solution for using a value with string format?
For instance, let's say I have a value with the key "SomeKey":
<ResourceDictionary ...>
<s:String x:Key="SomeKey">You ran {0} miles</s:String>
</ResourceDictionary>
Using it in a TextBlock:
<TextBlock Text="{DynamicResource SomeKey}" />
How would I combine, for example, an integer with the value of SomeKey as a format string?
You need to bind to a ViewModel.Value somehow, and then use a (nested) binding to a format string.
When you have only one value:
<TextBlock
Text="{Binding Path=DemoValue, StringFormat={StaticResource SomeKey}}" />
When you also have {1} etc then you need MultiBinding.
Edit:
When you really want to change languages in a live Form then the sensible way is probably to do all formatting in the ViewModel. I rarely use StringFormat or MultiBinding in MVVM anyway.
So, I finally came up with a solution that allows me to have format strings in my ResourceDictionary and be able to dynamically change the language at runtime. I think it could be improved, but it works.
This class converts the resource key into its value from the ResourceDictionary:
public class Localization
{
public static object GetResource(DependencyObject obj)
{
return (object)obj.GetValue(ResourceProperty);
}
public static void SetResource(DependencyObject obj, object value)
{
obj.SetValue(ResourceProperty, value);
}
// Using a DependencyProperty as the backing store for Resource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ResourceProperty =
DependencyProperty.RegisterAttached("Resource", typeof(object), typeof(Localization), new PropertyMetadata(null, OnResourceChanged));
private static void OnResourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//check if ResourceReferenceExpression is already registered
if (d.ReadLocalValue(ResourceProperty).GetType().Name == "ResourceReferenceExpression")
return;
var fe = d as FrameworkElement;
if (fe == null)
return;
//register ResourceReferenceExpression - what DynamicResourceExtension outputs in ProvideValue
fe.SetResourceReference(ResourceProperty, e.NewValue);
}
}
This class allows the value from the ResourceDictionary to be used as the format parameter in String.Format()
public class FormatStringConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] == DependencyProperty.UnsetValue || values[0] == null)
return String.Empty;
var format = (string)values[0];
var args = values.Where((o, i) => { return i != 0; }).ToArray();
return String.Format(format, args);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Example Usage 1: In this example, I use the FormatStringConverter in the MultiBinding to convert its Binding collection into the desired output. If, for instance, the value of "SomeKey" is "The object id is {0}" and the value of "Id" is "1" then the output will become "The object id is 1".
<TextBlock ap:Localization.Resource="SomeKey">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource formatStringConverter}">
<Binding Path="(ap:Localization.Resource)" RelativeSource="{RelativeSource Self}" />
<Binding Path="Id" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example Usage 2: In this example, I use a binding with a Converter to change the resource key to something more verbose to prevent key collisions. If, for instance, I have the enum value Enum.Value (displayed by default as "Value"), I use the converter to attach its namespace to make a more unique key. So the value becomes "My.Enums.Namespace.Enum.Value". Then the Text property will resolve with whatever the value of "My.Enums.Namespace.Enum.Value" is in the ResourceDictionary.
<ComboBox ItemsSource="{Binding Enums}"
SelectedItem="{Binding SelectedEnum}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock ap:Localization.Resource="{Binding Converter={StaticResource enumToResourceKeyConverter}}"
Text="{Binding Path=ap:Localization.Resource), RelativeSource={RelativeSource Self}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Example Usage 3: In this example, the key is a literal and is used only to find its corresponding value in the ResourceDictionary. If, for instance, "SomeKey" has the value "SomeValue" then it will simply output "SomeValue".
<TextBlock ap:Localization.Resource="SomeKey"
Text="{Binding Path=ap:Localization.Resource), RelativeSource={RelativeSource Self}}"/>
If you're trying to bind and format a Miles property to a 'TextBlock' you can do as follows:
<TextBlock Text="{Binding Miles, StringFormat={StaticResource SomeKey}}"/>

Reverse bool value in binding

I have a TextBlock like this:
<TextBlock Visibility="{Binding IsOnline, Converter={StaticResource boolToVisibilityConverter}}">
boolToVisibility returns Visible if IsOnline is true. But in a situation I want textblock be Collapsed if IsOnline is true.
I can make another converter which acts in reverse, but I want to know isn't it possible to do that in XAML with current converter?
You could potentially use a ConverterParameter value to determine whether to invert the output, for instance:
<TextBlock Visibility="{Binding IsOnline, ConverterParameter=true, Converter={StaticResource boolToVisibilityConverter}}" />
And in the converter itself:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool invertOuput = false;
if (parameter != null) {
bool.TryParse((string)parameter, out invertOuput)
}
// TODO: Converter logic
}
As far as I'm concerned, you'll have to make another converter, although, your converter (not reverese) already exists:
http://msdn.microsoft.com/pl-pl/library/system.windows.controls.booleantovisibilityconverter(v=vs.110).aspx

How can I display a different ToolTip based on the DataContext DataType in Wpf?

I have an abstract UserControl that I want to show a ToolTip on. This ToolTip should be different based on the Type of the DataContext which is defined in the derived UserControls.
Is there a way to define a different ToolTip for each type in the base class? If not, how can I set this ToolTip in the derived UserControl?
Here is how I thought I would go:
<UserControl ...
<UserControl.ToolTip>
<DataTemplate DataType="{x:Type Library:Event}">
<StackPanel>
<TextBlock FontWeight="Bold" Text="{Binding Name}" />
<TextBlock>
<TextBlock.Text>
<Binding Path="Kp" StringFormat="{}Kp: {0}m" />
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</UserControl.ToolTip>
</UserControl>
Couldn't you author a custom ValueConverter that returns the information you'd like to display for the type?
You could 'fancy this up' a bit to allow the converter to accept data templates like you're suggesting, but this will totally enable your scenario.
First, create the value converter. Pardon my quick code:
public class ToolTipConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
UIElement tip = null;
if (value != null)
{
// Value is the data context
Type t = value.GetType();
string fancyName = "Unknown (" + t.ToString() + ")";
// Can use IsInstanceOf, strings, you name it to do this part...
if (t.ToString().Contains("Person"))
{
fancyName = "My custom person type";
};
// Could create any visual tree here for the tooltip child
TextBlock tb = new TextBlock
{
Text = fancyName
};
tip = tb;
}
return tip;
}
public object ConvertBack(object o, Type t, object o2, CultureInfo ci)
{
return null;
}
}
Then instantiate it in your user control's resources (I defined the xmlns "local" to be this namespace and assembly):
<UserControl.Resources>
<local:ToolTipConverter x:Key="toolTipConverter" />
</UserControl.Resources>
And make sure the root visual of your user control binds its ToolTip property:
<Grid
ToolTip="{Binding Converter={StaticResource toolTipConverter}}"
Background="Blue">
<!-- stuff goes here -->
</Grid>
Although it's a really old post, I'll still post my answer, as I was facing the same problem today. Basically I ended up with putting all my tooltip templates into resourses, like the author of the question did. For this really to work there was a missing binding for the tooltip content and a resources section. With these in place, temlates do actually get applied.
<UserControl ...
<UserControl.ToolTip>
<Tooltip Content="{Binding}">
<Tooltip.Resources>
<DataTemplate DataType="{x:Type Type1}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type Type2}">
...
</DataTemplate>
</Tooltip.Resources>
</Tooltip>
</UserControl>

Categories