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)
Related
This question already has answers here:
C# WPF MVVM Binding not updating
(1 answer)
WPF: What can cause a binding source to not be updated?
(2 answers)
Closed 5 years ago.
To give a short description of my problem, I have a label which I want to show the contents of an ObservableCollection list in the format of "Item1, Item2, Item3, etc".
So my XAML looks like this. Code shortened for readability.
<Window.Resources>
<c:ListToString x:Key="ToList"></c:ListToString>
</Window.Resources>
<Label x:Name="yaxisTxt" Content="{Binding Path=YAxisVariables, Converter={StaticResource ToList}}">
YAxisVariables is the ObservableCollection. My VM behind looks like this, again shortened.
class ChartVM : INotifyPropertyChanged
{
ObservableCollection<string> _yaxisvars;
public ObservableCollection<string> YAxisVariables
{
get
{
return (_yaxisvars);
}
set
{
_yaxisvars = value;
OnPropertyChanged("YAxisVariables");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropName));
}
}
}
And my converter looks like this. I want the label to show "Drag variable" if the ObservableCollection is empty, and to show the values of the collection if it's not empty.
public class ListToString : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is ObservableCollection<string>)
{
ObservableCollection<string> vars = (ObservableCollection<string>)value;
if (vars.Count > 0)
{
string series = null;
foreach (string var in vars)
{
if (series != null)
series = series + ", " + var;
else
series = var;
}
series.Trim(',');
series.Trim(' ');
return series;
}
else
return "Drag variable";
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
My problem is that when the form is loaded and the list is empty, the converter fires correctly and the label displays "Drag variable", however when the list is updated and the property changes, the converter does not fire.
Am I missing something?
With collections there are 3 kinds of bindings/change notification you need:
Changes to the collection (add, remove). That is the only ones ObservableCollection takes care off
Change notification on the property exposing the collection. OC are notoriously bad to bulk-modify while exposed. So when you want to do bulk modifications, it is better to build it in the View Model, then expose it when fully build.
Change notification for every property of the class you hold in the ObservableCollection
Other things of notice:
You hardcoded the property name string. C# had syntax added so you whould never have to do that. You should definitely be using [CallerMemberName] and similar things to make your code refactoring safe and avoid mispellings.
Actually nothing ever binds to a simple collection. WPF elements only ever bind to CollectionViews. But if you give them a random collection, they do not hesistate to make a CollectionView from it. For advanced things (changing/tracking selected elements, filtering, sorting) you have to take control of that step and make the CollectionView yourself: https://wpftutorial.net/DataViews.html
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;
}
I need to set background color of my controls, depending on a punch of ruler. So, I am trying to use converters to do that.
In my XAML:
<TextBox Background="{Binding Converter={StaticResource BackgroundConverter}, ConverterParameter='UserName'}">
In my converter I find for a rule for "UserName". But I use the entire binding object for that:
object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
var person = (value as PersonBase).Person;
if (person.state == editing)
return GetRulesFor(parameter);
else
return Brushes.Silver;
It works in the first time screen is showing, but I need to update these properties when user edit form, cancel, etc.
How can I set my binding for this to happen ?
You can make a new property in the viewmodel and call it State, and return the person.state. When you change the state of the person object is changed just call OnPropertyChanged("State").
public class YourViewModel : INotifyPropertyChanged
{
// ... your code here
public StateObjectType State
{
get {return person.state;}
}
// when you modify the person state just call OnPropertyChanged("State")
}
In your view bind the textbox
<TextBox Background="{Binding State,Converter={StaticResource BackgroundConverter}, ConverterParameter='UserName'}">
This will make your code works.
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
Tearing my hair out here! I have this type-converter:
class CouponBarcodeToVisibilityConverterColumn : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (DesignerProperties.IsInDesignMode)
{
if ((string)parameter == "123456")
{
return Visibility.Visible;
}
return Visibility.Hidden;
}
if (value == null)
{
return Visibility.Visible;
}
var barcodesWanted = ((string)parameter).Split(System.Convert.ToChar("_"));
var actualBarcode = (string)value;
return barcodesWanted.Any(barcodeWanted => barcodeWanted == actualBarcode) ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
I have a UserControl with the following Resources section:
<UserControl.Resources>
<converters:CouponBarcodeToVisibilityConverterColumn x:Key="CouponBarcodeToVisibilityConverter1"/>
</UserControl.Resources>
I have a model called Bet, it looks like this:
public class Bet : INotifyPropertyChanged
{
//Lots of other stuff
private string _barcode;
public string Barcode
{
get { return _barcode; }
set
{
if (value == _barcode) return;
_barcode = value;
OnPropertyChanged("Barcode");
}
}
//Lots of other stuff
}
In the ViewModel which is the DataContext of my user control I have an Observable Collection of Bet. Back to my user control, I have a stack panel, the data context of which is the aforementioned Observable Collection.
Inside the Stack Panel I have a DataGrid, the ItemsSource property is simply {Binding}, deferring the binding up the tree as it were.
Inside my DataGrid I have this column:
<DataGridCheckBoxColumn x:Name="IsEwColumn" Binding="{Binding Wagers[0].IsEw,UpdateSourceTrigger=PropertyChanged}" Header="Each Way" Visibility="{Binding Path=Barcode, Converter={StaticResource CouponBarcodeToVisibilityConverter1}, ConverterParameter=123456}" Width="Auto"/>
The other element of the binding works perfectly (the checkbox is ticked whenever it is supposed to be) but my type converter is not. The breakpoint doesn't even get hit. The Barcode property inside Bet is definitely equal to 123456.
What have I missed?
What you have here is a list of bets for the items source of the data grid.
If you think about it
Bet1 could evaluate to visible when passed via type converter.
Bet2 could evaluate to visible when passed via type converter.
Bet3 could evaluate to collapsed when passed via type converter.
How would the datacolumn be both visible and collapsed at the same time.
You can't bind to visibility like that, unless you had an overall variable on the list or something that it could bind to.