How to convert enum to localized enum structure in WPF - c#

I have a question here to ask. I have an enum which at runtime shows in the UI. It has three values.
enum ExpiryOptions
{
Never,
After,
On
}
Now from the userControl when it loads its shows Never, After, on.
<ComboBox x:Name="accessCombo" Margin="5" Height="25" Width="80"
ItemsSource="{Binding Source={StaticResource ResourceKey=expiryEnum},
Converter={StaticResource enumtoLocalizeConverter}}"/>
In English its fine but the problem is, if the software is used as a localized settings the same strings appear. And not any localized strings.
In the converter I have a written a code like this
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
ExpiryOption[] myEnum = value; // This myEnum is having all the enum options.
// Now what shall I write here
//if I write a code like this
if(myEnum[0] == Properties.Resources.Never)
return Properties.Resources.Never;
else if(myEnum[1] == Properties.Resources.After)
return Properties.Resources.After;
else if(myEnum[2] == Properties.Resources.On)
return Properties.Resources.On;
}
then the enum in the UI fills with N E V E R (vertically) In English Language settings. Obviously the first string matches and fills with Never other two options are missing. Any suggestions and help is extremely needed.

You are always returning first enum value from converter i.e. string value Never which is char array hence you are seeing one item as single char in your comboBox.
Instead you should return string list:
List<string> descriptions = new List<string>();
foreach(ExpiryOption option in myEnum)
{
if(option == Properties.Resources.Never)
descriptions.Add(Properties.Resources.Never);
else if(option == Properties.Resources.After)
descriptions.Add(Properties.Resources.After);
else if(option == Properties.Resources.On)
descriptions.Add(Properties.Resources.On);
}
return descriptions;

You need to get the value passed into the ValueConverter to use it as follows.
[ValueConversion(typeof(ExpiryOptions), typeof(string))]
public class MyEnumConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ExpiryOptions option=
(ExpiryOptions)Enum.Parse(typeof(ExpiryOptions),value.ToString());
// Now that you have the value of the option you can use the culture info
// to change the value as you wish and return the changed value.
return option.ToString();
}
}

Assuming you have defined resources strings for Never, After, On as strings in class Properties as ExpiryOptionsNever, ExpiryOptionsAfter, ExpiryOptionsOn respectively(of course with strings you need) I would write this converter:
public class EnumConverter: IValueConverter{
public Dictionary<ExpiryOptions, string> localizedValues = new Dictionary<ExpiryOptions, string>();
public EnumConverter(){
foreach(ExpiryOptionsvalue in Enum.GetValues(typeof(ExpiryOptions)))
{
var localizedResources = typeof(Resources).GetProperties(BindingFlags.Static).Where(p=>p.Name.StartsWith("ExpiryOptions"));
string localizedString = localizedResources.Single(p=>p.Name="ExpiryOptions"+value).GetValue(null, null) as string;
localizedValues.Add(value, localizedString);
}
}
public void Convert(...){
return localizedValues[(ExpiryOptions)value];
}
}
This is essentially what user Blam suggested in the comments

Related

Limit the string length of TextBox when it is updated with Binding

I know that you can limit the input characters of TextBox from user by setting MaxLength property.
Is there a similar way to limit the number of characters shown in Text when the Text is updated with Binding? For example, when it is updated from Binding just show the first 5 characters and leave the rest?
Update:
Thanks for all the info, I got inspired by your recommendation and in the end did it with a converter. Here is how I did it, if someone wants to use it later.
public class StringLimiter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string val = value.ToString();
if (val.Length < 5)
return val;
else
return val.Substring(0, 5);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string val = value.ToString();
if (val.Length < 5)
return val;
else
return val.Substring(0, 5);
}
}
This should work:
Xaml:
<TextBox Text="{Binding TextToDisplay}" />
Code:
private const int maxLength = 5;
private string _textToDisplay = "Hello SO";
public string TextToDisplay
{
get
{
if(_textToDisplay.Length > maxLength)
{
return _textToDisplay.Substring(0, maxLength);
}
return _textToDisplay;
}
set
{
_textToDisplay = value;
RaiseProperyChanged();
}
}
I hope to understand you right. You could create a new Property in the ViewModel that returns only the first 5 chars of the text and set your binding to that property.
You might need to call PropertyChanged for the new Property when the text changes.
A simple but very flexible way of doing it would be to introduce a projected property in your Viewmodel that returns the first 5 characters of the original property and then bind your control to this property. Since you're only showing part of the property value, I assume that you don't want to write to this property from that TextBox. So make you projected property read-only too.

Xamarin Forms ListView Data Binding with two objects

I'm creating weather app with forecast. I have created ListView with TextCell as entries.
I want to format test inside cell to XXX YY where:
XXX is value
YY is unit
I have observable collection declared in ContentPage and it is my ItemSource, I have another important property, weatherUnit.
private ObservableCollection<ForecastData> forecast = new ObservableCollection<ForecastData>();
private Unit weatherUnit { get; set; }
I'm creating Data template in constructor and setting everything up:
public WeatherFormsAppPage()
{
InitializeComponent();
var forecastWeatherDataTemplate = new DataTemplate(typeof(TextCell));
forecastWeatherDataTemplate.SetBinding(TextCell.TextProperty, "mainData.Temperature");
forecastWeatherDataTemplate.SetBinding(TextCell.DetailProperty, "date");
ForecastView.ItemsSource = forecast;
ForecastView.ItemTemplate = forecastWeatherDataTemplate;
}
How I can add to TextCell.TextProperty binding formatting to be temperature and weatherUnit. Temperature is double and weather unit have Extension that return String. Right now, only Temperature value is shown properly and date as detail:
You can create a readonly property that concats the values for you and then bind to that
public string WeatherData
{
get
{
return $"{Temperature} {Unit}";
}
}
binding
forecastWeatherDataTemplate.SetBinding(TextCell.TextProperty, "mainData.WeatherData ");
I also like David's approach. Having a get-only property in your JSON class is nothing to worry about. As you don't want to go that way, you can also write a converter class and add that to your binding.
public class StringToFormattedTempConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is string))
return value;
return $"{(string)value} \u00B0CC";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And then add it to the binding like this.
forecastWeatherDataTemplate.SetBinding(TextCell.TextProperty, new Binding("mainData.Temperature", BindingMode.Default, new StringToFormattedTempConverter(), null));

IValueConverter and Metric/Imperial units

I am planning to implement code that will use an IValueConverter to display units of measurement in either metric or imperial based on a boolean the user sets. The issue I am facing is that the data stored in my database is required to always be in metric. I'm using Entity Framework database first if that makes a difference.
So the situation I am wondering about is this: if a user has chosen to display data in imperial units, but then alters one of the fields, and saves it - how do I ensure that it is saved properly in metric? From what I've gathered by researching online it looks like that would be part of the converter's ConvertBack method? Right now I call Textbox.GetBindingExpression(TextBox.TextProperty).UpdateSource(); to save the data, and I don't understand how to make that work since, as I understand, it just grabs the value in the TextBox and saves it. Am I right, or if there is a ConvertBack method will that be called in order to get the TextProperty?
Also, generally speaking, am I going about this the correct way (i.e. using an IValueConverter to alter the display)? To be honest I am in way over my head on this project, but I have deadlines fast approaching, and have a dire need to do this right. Any help would be greatly appreciated.
Thanks.
So you have some model, and you want to edit some properties of that model using TextBox and ValueConverter. Let's take some dummy value converter, which will multiply value by 10, and in ConvertBack will divide value by 10
public class MyConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value == null || value == DependencyProperty.UnsetValue)
return null;
return (decimal) value*10;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
if (value == null || value == DependencyProperty.UnsetValue)
return 0;
decimal d;
if (decimal.TryParse((string) value, out d))
return d/10;
return 0;
}
}
Now some test model
public class MyModel : INotifyPropertyChanged {
private decimal _someValue;
public decimal SomeValue
{
get { return _someValue; }
set
{
if (value == _someValue) return;
_someValue = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And then you have your textbox
<TextBox Width="150" Height="20" Text="{Binding SomeValue, Mode=TwoWay, Converter={StaticResource myConverter}, UpdateSourceTrigger=PropertyChanged}" />
If you use UpdateSourceTrigger=PropertyChanged, on each user edit, the value will be passed to your ConvertBack method, and the result will be assigned to SomeValue of your model.
When user presses save button, you just save your model to database using whichever method you have for that. You don't need to explicitly update properties of your model, because they are already up-to-date. You can also use UpdateSourceTrigger=LostFocus (default one). Then whenever field loses focus - property in your model will be synchronized with it (again, through ConvertBack)

C# WPF value converter receiving entire collection instead of single item as value parameter?

I have a C# WPF 4.51 application. On one of my XAML forms I have a list box that has its ItemsSource property bound to a property in my main ViewModel that is of type Collection. When the Collection was of type string, everything worked fine and I saw the collection of strings in the list box.
But then I changed the type in the Collection to a class named ObservableStringExt. The class has two fields: StrItem that contains the string I want displayed in the list box, and IsSelected, a supporting field. I then created a value converter to extract the StrItem field and return it.
However, when I look at the targetType passed to the Convert() method of the value converter I see a type of IEnumerable. Given that the Count property in that parameter matches the number of list items expected, it looks like the Convert() method is receiving a reference to the entire Collection instead of ObservableStringExt, the type of each item in the Collection. This of course is a problem. What is causing this? I have done this sort of thing many times in Windows Phone and WinRT (windows store apps) many times without trouble.
Here is the code for the value converter:
public class ObservableStringExtToStrItem : IValueConverter
{
// The targetType of the value received is of type IEnumerable, not ObservableStringExt.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is ObservableStringExt)
return (value as ObservableStringExt).StrItem;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Below is the XAML code for the list box. Note Commands_FrequentyUsed is a property of type ObservableCollectionWithFile found in the main view model, which is the data context for the entire form:
<ListBox x:Name="listFrequentlyUsedCommands"
Width="278"
Height="236"
Margin="30,103,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding Commands_FrequentyUsed.Collection,
Converter={StaticResource ObservableStringExtToStrItem}}" />
Here is the code for the class that contains the Collection that the list box binds to and the class the Collection contains:
public class ObservableStringExt
{
public string StrItem { get; set;}
public bool IsSelected{ get; set; }
}
public class ObservableCollectionWithFile : BaseNotifyPropertyChanged
{
public const string CollectionPropertyName = "Collection";
private ObservableCollection<ObservableStringExt> _observableCollection = new ObservableCollection<ObservableStringExt>();
public ObservableCollection<ObservableStringExt> Collection
{
get { return _observableCollection; }
private set { SetField(ref _observableCollection, value); }
}
} // public class ObservableCollectionWithFile
I just had the same problem. Not sure how it should normally work, but changing the converter to also convert list of items helped (I found this easier than creating a separate converter for List)
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var list = value as IEnumerable<ObservableStringExt>;
if (list != null)
{
return list.Select(x => Convert(x, typeof(string), null, culture));
}
if (value is ObservableStringExt)
return (value as ObservableStringExt).StrItem;
}

C# WPF manipulate a bound string to replace everything before the last / and add then a different prefix

I am just learning Datagrids and had a question I could not find a clear answer to.
How would I be able to manipulate a bound string to replace everything before the last / and add then a different prefix.
This will be to show an image in a Datagrid.
For example what is bound to img variable is
img: "/img_banner/testBanner.jpg"
What finally needs to be output is this:
http://www.testsite.com/img_thumnail/testBanner.jpg
So far, I have found information about adding text to a bound string but I would like to remove part of the String.
Here is what I got so far:
<Image Source="{Binding img, StringFormat=http://www.testsite.com/img_thumnail{0}}" Height="40"/>
Is there any way to easily trim everything before the last / ?
You can use an IValueConverter:
public class MyUrlConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == null)
{
return null;
}
var urlString = value as string;
//now do whatever you want to do with the string
//then return it
return urlString;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
then add it to your resources
<Window.Resources>
<local:MyUrlConverter x:Key="conv"></local:MyUrlConverter>
</Window.Resources>
and use it as:
<Image Source="{Binding img,Converter={StaticResource ResourceKey=conv}}"></Image>
note that if the logic dependant on your view model then you better do your thing in the getter or the setter of your img property

Categories