I am learning C#, WPF and XAML and at this point I am targeting internationalization of the product.
I can define the string that I want to translate in Resources.resx, and I would like to be able to manipulate the strings when I use them in my XAML files. Let's make an example, supposing to have a label like this
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.username}" />
In Resources.resx I define name username with value username and the label gets the correct value.
Suppose now that I want to display another label, but this time I want to display the text Username, with capital u. The immediate solution would be defining name Username value Username, but I am getting a duplicated resource. In other templating systems, such as in django, I can use the initial resource and and I can apply a filter to modify the string, but I am not able to achieve this in C#.
Is there a way to manypulate static resources in C# and XAML, for example to apply a converter that capitalizes the first letter of the string?
Binding has a Converter property. So you can do bind to the static resource and use a converter to modify the value:
<Window.Resources>
<local:StrToLowerValueConverter x:Key="strToLowerCnv"/>
</Window.Resources>
<Label Grid.Column="0" Grid.Row="0" Content="{Binding Source={x:Static p:Resources.username}, Mode=OneWay, Converter={StaticResource strToLowerCnv}}" />
And the converter itself:
public class StrToLowerValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value as string)?.ToLower() ?? value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Related
This question already has answers here:
Binding ConverterParameter
(3 answers)
Closed 4 years ago.
I am trying to bind the text of a textblock based on two things -
Object ShoppingList
Object Items(which is a property of the ShoppingList object. Type is List).
I want to invoke the converter as I want the text to be dependent on change in the value of either of the above.The only way that I could think of was this way as shown below. But this is not possible as I cannot bind the ConverterParameter to the object ShoppingList as it is not a dependency property .
<TextBlock
Margin="5"
TextWrapping="Wrap"
Text="{Binding Items, Converter={StaticResource ABCDConverter}, ConverterParameter="???" />
Below is the converter I had written
Convert(Items obj, object par, xyz culture)
{
if (obj != null && par!=null)
{
var parameter = (ShoppingList)par;
// Different Logic to determine the string to be returned
}
return string.Empty;
}
In simple words, how can I invoke the converter based on changes in either of Items or ShoppingList
Sounds like you're looking for MultiBinding, paired with an IMultiValueConverter.
That being said, I would strongly suggest that you determine the needed value in your ViewModel since you already have all the needed properties there and you know when and how they change. Once you have your derived property, just use regular binding to bind to it in the View. Using MultiBindings will generally tend to break your separation of concerns as you will end up adding specific logic to the converter that really should be in the ViewModel. One case where I did find MultiBindings indispensable was in a group header template (i.e. for a DataGrid). It would be pretty well impossible to get the values from the ViewModel as you don't know how many groups you have or what they will contain at runtime as there are just too many moving parts. In this case, MultiBindings were great.
On the other hand, if you are just targeting a specific element that you know about at design time, you should generally avoid using MultiBinding for the reasons stated above. In addition, MutliBinding are quite verbose compared to regular Bindings, making your XAML harder to read which creates a greater potential for bugs and limits future extensibility and maintainability.
But if you must, here's a simple example:
XAML
<Window x:Class="testapp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:testapp"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MyMultiConverter x:Key="multiTextConverter"/>
</Window.Resources>
<Grid>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource multiTextConverter}">
<Binding Path="someProp"/>
<Binding Path="someOtherProp" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</Window>
Converter
public class MyMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
string ret = null;
if(values.Count() > 1)
{
string value1 = values[0] as string;
string value2 = values[1] as string;
ret = value1 + value2;
}
return ret;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
}
}
Now, if you're binding to a List, and, assuming you care when items are added or removed, you will rather need to use an ObservableCollection as it implements the INotifyCollectionChanged Interface see here.But this is not quite enough, as the binding will not be subscribing to that event. You will need to listen to the change in collection in your DataContext (ViewModel?). Then create some dummy property, and when the collection changes, just increment the dummy property (with INotifyPropertyChanged of course) and use that property in the multibinding as a third binding. This way, the TextBlock text will get updated if either a property changes, or your list changes.
If you don't care about the changes in the collection, only when you assign a whole new collection, then the sample provided will work as is.
I need to change some functionality in a WPF app we've written. We use MVVM Light to implement MVVM. Whenever we've needed to pass some parameters to a method we've used MVVM Light's Messenger class. I've got to pass 3 parameters to a method, but I thought I'd try doing this without using the Messenger class, but instead I hoped I could do it using the RelayCommand() method. I did a search and found this post here on SO, from some years ago. But at least to me, I think this won't work as it's using just 1 type; string in this case. After making some trials and realizing that I'd done it wrong, I decided I could probably create a class with the 3 values I need in it as properties of the class, put it into Models folder and use
new RelayCommand<MyClass>()
So, first question, just to verify that I've got the right idea, I think I would do something like this:
new RelayCommand<MyClass>((mc) => MyMethod(mc.Prop_A, mc.Prop_B, mc.Prop_C)
Is that correct?
Assuming the answer to the above is yes, then how do I actually pass parameters to this when I bind to it in the XAML? This command is going to be associated with a button on the window/page, so I'll be using the Button's Command property. How do I actually pass in the values for the MyClass instances Prop_A, Prop_B and Prop_C?
So, first question, just to verify that I've got the right idea, I
think I would do something like this:
new RelayCommand<MyClass>((mc) => MyMethod(mc.Prop_A, mc.Prop_B, mc.Prop_C)
This is correct.
Assuming the answer to the above is yes, then how do I actually pass
parameters to this when I bind to it in the XAML? This command is
going to be associated with a button on the window/page, so I'll be
using the Button's Command property. How do I actually pass in the
values for the MyClass instances Prop_A, Prop_B and Prop_C?
This will actually depend on where would Prop_A, Prop_B and Prop_C come from. If these properties are already inside your view model, then there is no need for you to pass parameters using XAML.
new RelayCommand<MyClass>((mc) => MyMethod(mc.Prop_A, mc.Prop_B, mc.Prop_C)
will change to
new RelayCommand<object>((param) =>
{
// param is not used.
var mc = this.MC; // assuming your view model holds the mc value
MyMethod(mc.Prop_A, mc.Prop_B, mc.Prop_C);
});
We must make sure that when we load our view model, we have everything we need. Else, use an IoC to fetch whatever it is you need to.
Binding a parameter to your command is often useful for something like a calculator app where you want to pass the button value to your command such as 0 - 9.
<Button Grid.Row="0" Grid.Column="1" Content="7" Command="{Binding PerformAction}" CommandParameter="7"/>
I would want to stay away from defining classes in your view. For the separation of concern, the view should only know of the properties to be bounded to and not the models.
So, first question, just to verify that I've got the right idea, I think I would do something like this:
new RelayCommand<MyClass>((mc) => MyMethod(mc.Prop_A, mc.Prop_B, mc.Prop_C)
Is that correct?
Yes, it is.
How do I actually pass in the values for the MyClass instances Prop_A, Prop_B and Prop_C?
Simply create an instance of the class that holds the parameters inside your xaml as command parameter:
<Button Command="{Binding Command}">
<Button.CommandParameter>
<local:MyClass Prop_A="a value" Prop_B="b value" Prop_C="c value" />
</Button.CommandParameter>
</Button>
There is another approach to do this by using IMultiValueConverter:
class MultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
foreach (var item in values)
{
//process the properties passed in and you will need to unbox those parameters
}
return new object();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And then in xaml (Button code):
<Button Content="Test" Style="{StaticResource contextMenuAware}" Command="{Binding MultiParameterCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MultiValueConverter}">
<Binding Path="Items"/>
<!-- Pass more Bindings here or static values -->
</MultiBinding>
</Button.CommandParameter>
</Button>
Here is the code for inclusion of the converter:
xmlns:converter="clr-namespace:SO_app.Converters"
And then in you Window Resources tag:
<converter:MultiValueConverter x:Key="MultiValueConverter"/>
This way you can use Binding when passing parameters without implementing DependencyProperties.
Here is how I did it:
Your CommandRelay object takes object as parameter, you convert that object to object[] then each element to its own object.
private RelayCommand<object> _YourCommand;
public RelayCommand<object> YourCommand
{
get
{
return _YourCommand ?? (_YourCommand = new RelayCommand<object>(p =>
{
var values = (object[]) p;
int item1 = int.Parse(values[0].ToString());
string item2 = values[1].ToString();
double item3 = double.Parse(values[2].ToString());
}));
}
}
Then, in xaml (Of course, your Paths in Binding must be valid references to your binded objects)
<Button Command="{Binding YourCommand}">
<Button.CommandParameter>
<MultiBinding>
<Binding Path="Item1"/>
<Binding Path="Item2"/>
<Binding Path="Item3"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
I have my localized text resources in a separate project in the solution, AppResources. This allows a paid translator to work on resource translations without having the entire application source code.
In my Window, I have the namespace declared:
xmlns:viewProperties="clr-namespace:AppResources.Properties;assembly=AppResources"
And example control:
<TextBlock VerticalAlignment="Center"
Margin="3"
Text="{x:Static viewProperties:Resources.TextBlockUserName}" />
This is all working properly. I change the culture, the appropriate resource is used. I'm trying to maintain the benefit that the keys in viewProperties:Resources are available through autocomplete in the XAML, so we know it's there.
I am hoping to add some kind of converter here to intercept the resource string value and either do one of 2 things:
Pass through the acquired resource string value (e.g. culture is Japanese, send that localize text through)
Display the name of the resource key that is being requested from the control
The reason for this is to provide an application option to go into "translation mode" and instead of displaying the translated resource text like OK, Cancel, Save, etc. the application would display the key names (e.g. Button_OK, Button_Cancel, File_Save or whatever the key names were). This is handy to know the context of what the translator is trying to understand.
Anyway, the converter I'm trying to use works, but is not ideal:
public class TranslationKeyInterceptor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//Can do this, but this is horrible
string resourceName = value.ToString().Replace("x:Static viewProperties:Resources.", string.Empty);
string resourceString = string.Empty;
if (AppOptions.DisplayLanguageKeys)
{
resourceString = resourceName;
}
else
{
resourceString = AppResources.Properties.Resources.ResourceManager.GetString(resourceName);
}
return resourceString;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
The value it is getting is the entire path coming from the XAML, so I can't parse out just the key name.
Also the XAML becomes a little messier because I need to
<TextBlock VerticalAlignment="Center"
Margin="3"
Text="{x:Static viewProperties:Resources.TextBlockUserName,
Converter={StaticResource Converter_Translate}}" />
Where
<wpfTools:TranslationKeyInterceptor x:Key="Converter_Translate"/>
is declared in my Window's Resources.
So my question is either:
Can I pass into the converter the actual string property that exists in AppResources.Properties.Resources somehow to maintain autocomplete in the XAML and use that name to perform my option's logic?
What is the best way to accomplish this "text interception" and either let the resource text through or capture who is asking for in in XAML and instead return the requested key's name?
I have an enumeration that has values like
HomeRun, StolenBase, FirstBase, etc.
I want to display these values in a combobox, with a space inserted before the capital letters, so it will display as 'Home Run', 'Stolen Base', etc.
I already have code that can do the formatting for me, and I have added that code to the 'Convert' method of an IValueConverter implementation.
My question is, where do I need to use this converter (in the xaml) such that not only the dropdown, but also the displayed value, will have this formatting? Do I need to implement ConvertBack as well?
I am well aware of setting 'descriptions' for the enumeration and using the popular EnumToDescriptionConverter, but I'd rather stay away from that.
I'm not sure if there is a better way, but you can achieve what you want using an ItemTemplate
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentPresenter
Content="{Binding Converter={StaticResource baseballEnumConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
This will display the converted value in your ComboBox.
The SelectedValue of the ComboBox will still be the Enum value. You won't need to implement ConvertBack.
[updated] The key point of my answer is that the enum values are converted totally. I think this way is eaier than the coverting each enum value.[/updated]
Where do I need to use this converter (in the xaml) such that not only the dropdown, but also the displayed value, will have this
formatting?
At Binding ItemsSource of ComboBox(ItemsSource="{Binding Source={x:Null}, Converter={StaticResource converter}}"), Please check the following code.
Do I need to implement ConvertBack as well?
No, you don't., because at runtime you cannot modify the enumeration, and even though it can do, you cannot change ItemsSource of ComboBox in VIEW, which means Binding Mode is OneWay.
XAML
<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MyEnumConverter x:Key="converter"/>
</Window.Resources>
<StackPanel>
<ComboBox ItemsSource="{Binding Source={x:Null}, Converter={StaticResource converter}, Mode=OneWay}"></ComboBox>
</StackPanel>
</Window>
Code
public enum MyEnum
{
HomeRun, StolenBase, FirstBase
}
[ValueConversion(typeof(object), typeof(List<string>))]
public class MyEnumConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var names = Enum.GetNames(typeof (MyEnum)).ToArray();
//Add some code to support the thing you want to do(add blank in front of Capital...)
return names;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
If you want the selected value of the ComboBox to be converted back to an enum then you will need to implement ConvertBack.
I'd personally go with the description attribute pattern that you mentioned, because
the obvious questions have already been asked, and
You aren't limited to simply inserting spaces at uppercase letters - you can use whatever description you want.
But assuming you want to go with this pattern, you just need to write your converter correctly. I'd suggest something like this:
// Convert method
var formattedNames = new List<string>();
foreach (var value in Enum.GetValues(typeof(Things)))
{
// Format is a method to convert the enum value to the display string
var formattedName = Format(value);
formattedNames.Add(formattedName);
}
// return a list of strings that you can bind to
return formattedNames;
// ConvertBack method
// Unformat is a method to revert the display string back to the enum value
var value = Unformat(formattedValue);
return Enum.Parse(typeof(Things), value);
You could also create a simple class to hold both the display value and the enum, and then set the DisplayPath property on the combo box appropriately
class DisplayEnum
{
public string DisplayValue { get;set; }
public MyEnum ActualValue { get;set; }
}
<ComboBox DisplayMemberPath=DisplayValue ...
Edit
I realise that this won't work because the ConvertBack is attempting to convert a string to an enum, but the actual binding set is a List<string>. I'll leave it here because it is a start in the right direction.
I believe you'd need two converters
to convert the enum type into a set of enum values, and
to convert an enum value to a string. This second converter should implement the ConvertBack method.
As I pointed out, if you don't implement ConvertBack then you won't be able to bind the SelectedValue back to your enum Property on your ViewModel.
You will need to make a dictionary or some other lookup structure that maps the Enum value to the string representation.
There is an hint that you can use as a start :
http://geekswithblogs.net/jawad/archive/2005/06/24/EnumDropDown.aspx
I developped my own enum binding helpers starting with that idea.
I am building an application that can be used by many users. Each user is classified to one of the next Authentication levels:
public enum AuthenticationEnum
{
User,
Technitian,
Administrator,
Developer
}
Some controls (such as buttons) are exposed only to certain levels of users.
I have a property that holds the authentication level of the current user:
public AuthenticationEnum CurrentAuthenticationLevel { get; set; }
I want to bind this property to the 'Visibilty' property of some controls and pass a parameter to the Converter method, telling it what is the lowest authentication level that is able to see the control.
For example:
<Button Visibility="{Binding Path=CurrentAuthenticationLevel, Converter={StaticResource AuthenticationToVisibility}, ConverterParameter="Administrator"}"/>
means that only 'Administrator' and 'Developer' can see the button.
Unfortunately, the above code passes "Administrator" as a string. Of course I can use switch/case inside the converter method and convert the string to AuthenticationEnum. But this is ugly and prone to maintenance errors (each time the enum changes - the converter method would require a change also).
Is there a better way to pass a nontrivial object as a parameter?
ArsenMkrt's answer is correct,
Another way of doing this is to use the x:Static syntax in the ConverterParameter
<Button ...
Visibility="{Binding Path=CurrentAuthenticationLevel,
Converter={StaticResource AuthenticationToVisibility},
ConverterParameter={x:Static local:AuthenticationEnum.Administrator}}"/>
And in the converter
public class AuthenticationToVisibility : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
AuthenticationEnum authenticationEnum = (AuthenticationEnum)parameter;
//...
}
}
User
(AuthenticationEnum)Enum.Parse(typeof(AuthenticationEnum),parameter)
to parse string as enumerator