WPF Localization: DynamicResource with StringFormat? - c#

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}}"/>

Related

Bind a Converter Parameter in re-usable User Control

I am trying to create a re-usable user control (for data entry) in which there are two text boxes and they are linked to each by an IValueConvertor.
The following XAML is the original, normal code. This is what I am trying to reproduce in a user control.
<WrapPanel>
<TextBlock Text="Length of Fence"/>
<TextBox Name="Metric" Width="50" Text="{Binding Path=LengthFence, Mode=TwoWay}"/>
<TextBlock Text="Meters"/>
<TextBox Text="{Binding ElementName=Metric, Path=Text, Converter={StaticResource MetersToInches}, StringFormat=N8}"/>
<TextBlock Text="Inches"/>
</WrapPanel>
and the code-behind for the IValueConvertor (in MainWindow.xaml) is
public class MetersToInches : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.ToString() == "")
return 0.0;
try
{
double meters = System.Convert.ToDouble(value);
var result = meters * 39.3701;
return result;
}
catch
{
// Catch errors when users type invalid expressions.
return 0.0;
}
}
public object ConvertBack(object value, Type targettype, object parameter, CultureInfo culture)
{
if (value.ToString() == "")
return 0.0;
try
{
double inches = System.Convert.ToDouble(value);
var result = inches * 0.0254;
return result;
}
catch
{
// Catch errors when users type invalid expressions.
return 0.0;
}
}
}
This is what this XAML looks like:
Now I have made a re-usable UserControl with three dependency properties Label for label string, Value for binding a property inside the ViewModel, and Units - a string property to show the input units.
<UserControl ...
x:Name="parent">
<StackPanel DataContext="{Binding ElementName=parent}">
<TextBlock Text="{Binding Path=Label}"/>
<TextBox Text="{Binding Path=Value}"/>
<TextBlock Text="{Binding Path=Units}"/>
</StackPanel>
However, this re-usable control can only tackle the first TextBox of the input. I do not know how to bind the IValueConvertor in the second TextBox. I need to do this because I want to bind other converters such as meters to feet, kg to pound, etc.
I have read that ConvertorParameter cannot be bound because it is not a dependency property and I am not sure if I can use multi-binding, mostly because I do not know how to use it properly Binding ConverterParameter.
I would be very grateful if you could show me how to do this or direct me to the appropriate link on StackOverflow or elsewhere that solves this problem. Or if there is a better way of doing this.
Many many thanks in advance.
First, don't bind the TextBoxes to each other (as in your original code at the begining of the question), instead, bind each TextBox to the same backing property, which, in your UserControl, is Value.
As for how to implement multiple bindings, you probably don't need a MultiBinding.
We have to pick a "standard" unit of measure to begin with- this will be the unit that will be actually stored in the property and in any database or file. I'll assume this standard unit will be meters (m). An IValueConverter can be used to convert between meters and some other unit of distance and back, using the ConverterParameter to specify which other unit to convert to/from.
Here's a good example to get you started.
public enum DistanceUnit { Meter, Foot, Inch, }
public class DistanceUnitConverter : IValueConverter
{
private static Dictionary<DistanceUnit, double> conversions = new Dictionary<DistanceUnit, double>
{
{ DistanceUnit.Meter, 1 },
{ DistanceUnit.Foot, 3.28084 },
{ DistanceUnit.Inch, 39.37008 }
};
//Converts a meter into another unit
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return conversions[(DistanceUnit)parameter] * (double)value;
}
//Converts some unit into a meter
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) { return 0; }
double v;
var s = value as string;
if (s == null)
{
v = (double)value;
}
else
{
if (s == string.Empty) { return 0; }
v = double.Parse(s);
}
if (v == 0) { return 0; }
return v / conversions[((DistanceUnit)parameter)];
}
}
The above has a few problems. I never check if parameter really is a DistanceUnit before using it, for example. But it works.
Here's an example of how I used it:
<StackPanel>
<StackPanel.Resources>
<local:DistanceUnitConverter x:Key="DistCon"/>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Distance, Converter={StaticResource DistCon}, ConverterParameter={x:Static local:DistanceUnit.Meter}}" MinWidth="20"/>
<TextBlock>m</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Distance, Converter={StaticResource DistCon}, ConverterParameter={x:Static local:DistanceUnit.Foot}}" MinWidth="20"/>
<TextBlock>ft</TextBlock>
</StackPanel>
</StackPanel>
The DistanceUnit enum and the internal conversions dictionary can be expanded with more units of measure. Alternatively, you can use a 3rd party library that already has all these included, like UnitsNet.
Not sure how you would like to bind mulitple converters in one single control. If i'm not wrong, you would like to build a control where when a user enters a particular value, you need to display it in different units. If this is the case, you can create a single converter with converterparameter as "m","cm","inch" etc and based on this you can return the result. Then in this case, you will have 4,5 controls and each will have same converter binding but different converter values. If this is not clear and you need further direction, please let know.
Multi Value binding
To answer your point 6, please see a sample multi binding converter and its implementation in xaml below. I have built a simple RolesFilter which will take different inputs from the xaml as object[] and since I already know what data is expected, i'm converting them in the converter.
public class RolesFilter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
try
{
FlipperObservableCollection<Role> _roles = (FlipperObservableCollection<Role>)values[0]; //Input
Department _dept_param = values[1] as Department;
bool _filter = (bool)values[2];
string _id = "NA";
if (values.Count() == 4 && values[3] is string) _id = (string)values[3] ?? "NA";
//If we need a filter, then without department, it should return empty results
if (!_filter) return _roles; //If no filter is required, then don't worry, go ahead with input values.
if (_dept_param == null) return new FlipperObservableCollection<Role>(); //If department is null, then
List<Role> _filtered_list = _roles.ToList().Where(p => p.department.id == _dept_param.id && p.id != _id)?.ToList() ?? new List<Role>();
return new FlipperObservableCollection<Role>(_filtered_list);
}
catch (Exception)
{
throw;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I'm using the multi value converter in the xaml as below. Here, i'm filtering an itemsource of a combo box based on another combobox and a check box. This is just an example and in your case, you can create a combo box with different Units values. Based on user selection, you can use the converter and return value to the textbox.
<ComboBox Height="30" SelectedItem="{Binding reports_to, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemsSource>
<MultiBinding Converter="{StaticResource roles_filter}">
<Binding Source="{StaticResource SingletonData__}" Path="roles" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="department" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
<Binding ElementName="cbx_filter" Path="IsChecked"/>
<Binding Path="id" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</ComboBox.ItemsSource>
<ComboBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding department.name}"/>
<TextBlock Text=" - "/>
<TextBlock Text="{Binding name}"/>
</WrapPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

How to keep my items in datagrid in sequence order when item from middle is deleted WPF C#

I am loading items from database to my DataGrid on this way (on form load):
dataGridArticles.ItemsSource = null;
dataGridArticles.ItemsSource = listCurrentArticles;
And I got ability to add item to my datagrid list, and also I have ability to remove them by pressing DEL key <datagrid automatically removes it from a list>.
But When I am entering item to my list which is source to datagrid I am also setting OrdinalNumber for each item, for example when I adding new item to a list I am doing next thing:
article.OrdinalNumber = GetCurrentlyMaxOrdinalNumber()+1;
so if I'm adding items one after other, ordinals numbers will be 1,2,3,4,5... and so on, something like this (marked red) :
My stored procedure which is returning max ordinal number from articles table:
CREATE DEFINER=`root`#`localhost` PROCEDURE `ArticleGetNextCodeNumber`()
BEGIN
Select ifnull(max(OriginalArticleCode),0)
from articles;
END
And this is what is happening when I am adding new item to my list:
I am setting next ordinal numbers and some other attributes for new article:
private void btnSaveChangesOnArticle_Click(object sender, RoutedEventArgs e)
{
try
{
if (newItem)
{
Article newArticle = new Article();
newArticle.ArticleCode = txtArticleCode.Text.Trim();
newArticle.OriginalArticleCode = Convert.ToInt32(ArticlesController.Instance.GetNextArticleCode()) + 1; // Here I'm setting new article ordinal number
newArticle.Title = txtArticleTitle.Text.Trim();
newArticle.Price = Convert.ToDecimal(txtPrice.Text.Trim());
var lastInserted = ArticlesController.Instance.Save(newArticle);
MessageBox.Show("Operation Successful.");
listCurrentArticles.Add(lastInserted);
dataGridArticles.ItemsSource = null;
dataGridArticles.ItemsSource = listCurrentArticles;
}
}
}
}
And I am wondering right now, when user press DELETE KEY to remove some item from my datagrid,how can I reorganize my ordinal numbers, because if I add 5 items for example to my datagrid, and if user delete second item, than my order might look like this: 1 3 4 5.. insted of 1 2 3 4
So my question is guys, how can i reorganize that, when something changes to keep it sequenced all the time ( so maybe then I dont need to keep ordinal number in database, because it is not important to me, I am using it just because of better UI look, to show to user how many items he added to that DataGrid).
Thanks a lot,
Cheers!
You could replace the OrdinalNumber DataGridTextColumn with a DataGridTemplateColumn and use a converter to return the value of the GetIndex() method of the parent DataGridRow container.
You need to bind to both the parent DataGridRow and the Count property of the Items property of the DataGrid itself for the binding to get refreshed when the number of items changes, i.e. when an item is added or removed.
This should be pretty simple to implement. You create a class that implements the IMultiValueConverter interface:
namespace WpfApplication3
{
public class RowNumberConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return ((values[0] as DataGridRow).GetIndex() + 1).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
And use it like this:
<DataGrid x:Name="dataGridArticles" AutoGenerateColumns="False" xmlns:local="clr-namespace:WpfApplication3">
<DataGrid.Resources>
<local:RowNumberConverter x:Key="RowNumberConverter" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="#" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource RowNumberConverter}">
<Binding Path="." RelativeSource="{RelativeSource AncestorType=DataGridRow}" />
<Binding Path="Items.Count" RelativeSource="{RelativeSource AncestorType=DataGrid}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- + the rest of your columns -->
</DataGrid.Columns>
</DataGrid>
Check out this related question that might solve your problem.
Column/Row index in a DataGrid column
Your best option is to remove OrdinalNumber property (Since you do not need it, as you said) and use DataGrid.RowHeaderTemplate with a converter. Here is a working example:
MainWindow
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
listCurrentArticles = new ObservableCollection<Article>() { new Article { Text = "k" }, new Article { Text = "l" } };
}
public ObservableCollection<Article> listCurrentArticles { get; set; }
Xaml
<Window.Resources>
<local:HeaderConverter x:Key="headerConverter"/>
</Window.Resources>
<Grid>
<DataGrid DataContext="{Binding}" ItemsSource="{Binding listCurrentArticles}">
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<TextBlock MinWidth="25" TextAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource headerConverter}">
<Binding Path="ItemsSource" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=DataGrid}" />
<Binding Path="Item" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
</DataGrid>
</Grid>
Converter
public class HeaderConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
ObservableCollection<Article> list = values[0] as ObservableCollection<Article>;
Article obj = values[1] as Article;
int ind = list.IndexOf(obj);
if (ind == -1)
return "+";
else
return (ind + 1).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Unset dependency on binding to dependency property

I am trying to bind to a dependency property of my user control from my user control however it doesn't seem to be working as the converter keeps throwing an unset dependency property error
The dependency property
public DateTime? DisplayedDate
{
get { return (DateTime?)base.GetValue(DisplayedDateProperty); }
set { base.SetValue(DisplayedDateProperty, value); }
}
public static readonly DependencyProperty DisplayedDateProperty =
DependencyProperty.Register("DisplayedDate", typeof(DateTime?), typeof(SideBarUser), new FrameworkPropertyMetadata()
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
});
The XAML Binding
<UserControl.Resources>
<sys:Int32 x:Key="Test">1</sys:Int32>
<Converters:DateCountConverter x:Key="DateCountConverter"/>
</UserControl.Resources>
<TextBlock DataContext="{Binding RelativeSource={RelativeSource Self}}"
TextAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource DateCountConverter}">
<Binding Path="DisplayedDate" />
<Binding Source="{StaticResource Test}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
And finally the part it's failing at in the coverter
DateTime date = (DateTime)values[0];
All together yields
System.InvalidCastException
Specified cast is not valid.
at System.Windows.Data.MultiBindingExpression.TransferValue()
at System.Windows.Data.MultiBindingExpression.Transfer()
at System.Windows.Data.MultiBindingExpression.UpdateTarget(Boolean includeInnerBindings)
at System.Windows.Data.MultiBindingExpression.AttachToContext(Boolean lastChance)
at System.Windows.Data.MultiBindingExpression.MS.Internal.Data.IDataBindEngineClient.AttachToContext(Boolean lastChance)
at MS.Internal.Data.DataBindEngine.Task.Run(Boolean lastChance)
at MS.Internal.Data.DataBindEngine.Run(Object arg)
at MS.Internal.Data.DataBindEngine.OnLayoutUpdated(Object sender, EventArgs e)
at System.Windows.ContextLayoutManager.fireLayoutUpdateEvent()
at System.Windows.ContextLayoutManager.UpdateLayout()
at System.Windows.UIElement.UpdateLayout()
at System.Windows.Interop.HwndSource.SetLayoutSize()
at System.Windows.Interop.HwndSource.set_RootVisualInternal(Visual value)
at System.Windows.Interop.HwndSource.set_RootVisual(Visual value)
at MS.Internal.DeferredHwndSource.ProcessQueue(Object sender, EventArgs e)
I cannot seem to get this to work for the life of me. Am I missing something? When debugging with another instance of Visual Studio it comes up that it is an Unset Dependency property
Edit:
When I comment out everything and just have
<TextBlock Text="{Binding Path=DisplayedDate, RelativeSource={RelativeSource Self}}" />
It works just fine displaying the display date. My confusion level is too great to cope with right now
EDIT EDIT:
Converter code
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DateTime? date = (DateTime?)values[0];
//ToDo move most of the logic inside AppointmentsViewModel class to handle date filtering
AppointmentsViewModel MyAppointments = new AppointmentsViewModel();
String Count;
int SelectionType = (int)values[1];
//Note To Self Make Enum
switch (SelectionType)
{
case 0:
Count = MyAppointments.Appointments.Where(x => date != null && x.Beginning.HasValue && date.HasValue
&& x.Beginning.Value.Month == date.Value.Month
&& x.Beginning.Value.Year == date.Value.Year ).Count().ToString();
break;
case 1:
Count = MyAppointments.Appointments.Where(x => date != null && x.Test.HasValue && date.HasValue
&& x.Test.Value.Month == date.Value.Month
&& x.Test.Value.Year == date.Value.Year).Count().ToString();
break;
//ETC
default:
Count = MyAppointments.Appointments.Where(x => date != null && x.End.HasValue
&& date.HasValue && x.End.Value.Month == date.Value.Month
&& x.End.Value.Year == date.Value.Year).Count().ToString();
break;
}
return Count;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
There are several problems with your code. I had to make some assumptions here, so hopefully I'm correct.
The converter
Your converter assumes that it will get 2 values of certain types. You want to be a little careful with that. Especially the first value, which comes from a binding, might be DependencyProperty.UnsetValue if the binding has not been set yet.
So, you probably want to check if the values are correct before you start doing the actual conversion, for example:
if (values.Length != 2 || !(values[0] is DateTime?)|| !(values[1] is int))
{
return DependencyProperty.UnsetValue;
}
You should not have your converter throw exceptions, because they are treated as uncaught run-time exceptions and will terminate your application unless you have some global exception handler (see this question).
The control
Now, I assume that your DisplayedDate property is defined on your UserControl. If so, then this line:
<TextBlock DataContext="{Binding RelativeSource={RelativeSource Self}}"
will set the DataContext to this TextBlock, so when you later go to retrieve the DisplayedDate property, it will not be found. You can fix this in 2 ways:
1) You use the ancestor-lookup binding:
"{Binding RelativeSource={RelativeSource AncestorType=local:UserControl1}}"
Of course, replace local:UserControl1 with the namespace and name of your control.
2) You define the content of the UserControl as a Template instead, then use {RelativeSource TemplatedParent}, which will point to the "owner" of the template, in this case, your UserControl:
<UserControl.Template>
<ControlTemplate>
<TextBlock DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
TextAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource DateCountConverter}">
<Binding Path="DisplayedDate" />
<Binding Source="{StaticResource Test}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</ControlTemplate>
</UserControl.Template>
Just put this in your XAML instead of the <TextBlock>...</TextBlock> part.
Maybe there are some other issues, but testing with a simple converter this worked for me.

How to display dynamic culture formatted number in a WPF UserControl

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>

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