Why is my converter not firing once the property is updated? [duplicate] - c#

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

Related

IsSynchronizedWithCurrentItem not working when ListView is bound to an ICollectionView with a Converter

I have the an ICollectionView in my ViewModel:
public class ContactsPanelViewModel : ViewModelBase
{
private ICollectionView accountContactsView;
public ICollectionView AccountContactsView
{
get => accountContactsView;
set => NotifyPropertyChangedAndSet(ref accountContactsView, value);
}
public ContactsPanelViewModel()
{
AccountContactsView = CollectionViewSource.GetDefaultView(G.AccountContacts); //G.AccountContacts is an IEnumarable<Contact> object
AccountContactsView.SortDescriptions.Add(new SortDescription("FullName_LastNameFirst", ListSortDirection.Ascending));
AccountContactsView.CurrentChanged += NewContactSelected;
AccountContactsView.MoveCurrentToFirst();
}
}
This is the list it is bound to:
<ListView
x:Name="ContactList"
HorizontalAlignment="Left"
ItemsSource="{Binding Path=AccountContactsView, Converter={StaticResource ContactCardConverter}}"
IsSynchronizedWithCurrentItem="True"/>
And this is the converter I created:
public class ContactCardConverter : IValueConverter
{
public object Convert(object Value, Type TargetType, object Parameter, CultureInfo Culture)
{
if (Value is ListCollectionView)
{
List<ContactCard> contactCards = new List<ContactCard>(); //ContactCard is a UserControl
ListCollectionView data = Value as ListCollectionView;
if (data.Count > 0 && data.GetItemAt(0) is Contact)
{
foreach(Contact contact in data)
{
contactCards.Add(new ContactCard(contact));
}
return contactCards;
}
else
{
return null;
}
}
else
{
return null;
}
}
public object ConvertBack(object Value, Type TargetType, object Parameter, CultureInfo Culture)
{
if (Value is ContactCard)
{
return (Value as ContactCard).Contact;
}
else
{
return null;
}
}
}
The issue is that because I am using a converter the CurrentItem in my ICollectionView isn't being synchronized when the ListView selection changes. The converter is definitely the issue since removing the converter makes it work perfectly.
I added in a ConvertBack function thinking it would call that if there was a converter used but it didn't work.
I could theoretically make the ICollectionView of the type ContactCard (which is a UserControl), but that doesn't seem like it is how it should be done since from what I understand the ViewModel shouldn't be dependent on how the View will look. This would also add some complecity as I would need to keep the ICollectionView in sync with my actual collection of Contact objects.
What is the proper way to synchronize a ListView and an ICollectionView when the ListView uses a converter?
The proper way is don't use a converter.
You shouldn't convert your viewmodel-CollectionView to the List(as in your case) or another CollectionView in converter, because otherwise in case you created a CollectionView in converter you will synchronize your view (and IsSynchronizedWithCurrentItem works good) against in converter created collection. List can't be synchronized, because it does not implement IColectionView and has not CurrentItem.
The proper way is don't use a converter and put ContactCard-UserControl to the ListView.ItemTemplate.
<ListView.ItemTemplate>
<DataTemplate>
<youCustomCtlNameSpace:ContactCard Contact="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
If you mandatory wants to use a converter, then IsSynchronizedWithCurrentItem makes no sense and you have to synchronize V and VM by yourself via binding with converter to ListView.SelectedItem and your Contact have to hold reference to the UI-object(It's MVVM conform, if you have no dependencies to View see: Is MVVM pattern broken?). But as I already noted it's not a preferred way!

Ways to bind Enums to WPF controls like Combobox, TabHeader etc

In my program (MVVM WPF) there are lot of Enumerations, I am binding the enums to my controls in the view.
There are lot of ways to do it.
1) To bind to ComboBoxEdit(Devexpress Control). I am using ObjectDataProvider.
and then this
<dxe:ComboBoxEdit ItemsSource="{Binding Source={StaticResource SomeEnumValues}>
This works fine but in TabControl header it doesn't.
2) So, I thought of using IValueConverter that didnt worked either.
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (!(value is Model.MyEnum))
{
return null;
}
Model.MyEnum me = (Model.MyEnum)value;
return me.GetHashCode();
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
on XAML:
<local:DataConverter x:Key="myConverter"/>
<TabControl SelectedIndex="{Binding Path=SelectedFeeType,
Converter={StaticResource myConverter}}"/>
3) The third way of doing this is to make a behavior dependency property
Something like this
public class ComboBoxEnumerationExtension : ComboBox
{
public static readonly DependencyProperty SelectedEnumerationProperty =
DependencyProperty.Register("SelectedEnumeration", typeof(object),
typeof(ComboBoxEnumerationExtension));
public object SelectedEnumeration
{
get { return (object)GetValue(SelectedEnumerationProperty); }
set { SetValue(SelectedEnumerationProperty, value); }
}
I want to know what is the best way to handle enumerations and binding to it. Right now I am not able to bind tabheader to the enums.
Here's a nicer way of doing it:
On your model, put this Property:
public IEnumerable<string> EnumCol { get; set; }
(Feel free to change the name to whatever suits you, but just remember to change it everywhere)
In the constructor have this (or even better, put it in an initialization method):
var enum_names = Enum.GetNames(typeof(YourEnumTypeHere));
EnumCol = enum_names ;
This will take all the names from your YourEnumTypeHere and have them on the property you'll be binding to in your xaml like this:
<ListBox ItemsSource="{Binding EnumCol}"></ListBox>
Now, obviously, it doesn't have to be a ListBox, but now you're simply binding to a collection of strings, and your problem should be solved.

Combobox selection change in XAML

I have a Combobox and two buttons in my UserControl. Is it possible to set those button to change the selected index of the Combobox directly in XAML?
I have done this by two approaches:
Code-behind
private void nextBut_Click(object sender, RoutedEventArgs e)
{
combo.SelectedIndex++;
}
private void prevBut_Click(object sender, RoutedEventArgs e)
{
combo.SelectedIndex--;
}
Or by binding commands to those buttons and define that command in my ModelView.
I have another question about XAML and I really don't know if ask a different question or use this opportunity that you are already reading me! I'm sure it has to be straightforward (at least for WPF gurus around here):
I have a ItemsControl that holds that UserControl, but there may be several or none (because you can create more, or delete). I want a Checkbox outside that is enabled or not depending if there are or not elements in my ItemsContol (disable if there is nothing). I think this can be done with Command Validation but looks difficult to me as I'm new in this world. This also could be done with codebehind but I would like to avoid it. (Like defining a bool property bound to that Checkbox, as write something like if(myItems.Count==0)
I'd rather bind the SelectedItem property to some property in the ViewModel, and bind these buttons to some Commands in the ViewModel. This way keep the state data (selectedItem) in the ViewModel, and can use that to perform any additional logic required, removing the need for code behind.
For the CheckBox, I'd rather put a bool property in the ViewModel, and notify that whenever you add/remove items.
public bool HasItems {get {return Items.Any(); } }
public void AddItem()
{
//...Add Items
NotifyPropertyChanged("HasItems");
}
public void RemoveItem()
{
//...Remove Item
NotifyPropertyChanged("HasItems");
}
This removes the need for an additional converter.
For the checkbox issue, it comes under the generic issue of converting a quantity into a bool. A canonical WPF answer would be bind the checkbox IsChecked property to the collection and route it through an IValueConverter. Here is a converter to do just that...
public class QuantityToBoolConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
try
{
IEnumerable items = value as IEnumerable;
if (items != null)
{
return items.OfType<object>().Any();
}
}
catch
{
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return null;
}
}
To deploy it, you would need to declare it in your Xaml as a resource...
<Window.Resources>
<converters:QuantityToBoolConverter x:Key="QuantityToBoolConverter"/>
</Window.Resources>
And then bind the IsChecked property to your items and declare the converter...
<ListBox Name="mylb">
</ListBox>
<CheckBox IsChecked="{Binding ElementName=mylb, Path=ItemsSource, Converter={StaticResource
QuantityToBoolConverter}}"></CheckBox>
For the combobox SelectedIndex issue, you can check out the CollectionViewSource docs. Here You can manipulate this in your ViewModel to move the current selection. And there's tons of sample code to examine. Knowing the ICollectionView will serve you well as a WPF developer.

Why isn't my type converter working

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.

Displaying data differntly across viewmodels, without changing model data

Another problem has been bugging me lately, and I am quite sure it is my lacking WPF/MVVM skills that prevents me from seing the obvious. I am sure the solution is a simple one, however, I am unsure how to implement it.
I am developing in WPF/C# and I am using the MVVM designpattern.
For simplicity, I am boiling the question down to it's most basic components.
The scenario:
I have a Model, only containing an integer.
I have a Parent ViewModel, displaying this integer.
I have two child viewmodels, incorparated in the parent viewmodel, both displaying this integer. On one of the viewmodel, I have a Command, incrementing the value of the integer with 1. The value is changed in the model, which implements the INotifyPropertyChanged therefore making the second ViewModel aware of the change, so it can update accordingly.
So far everything works fine.
However, I am interested in a new feature, and I cannot get it to work. Say, that I on my second viewmodel want to display the integer, but I want to transform the way the data is displayed. This should be done, however, without changing the data in the model. Should the data in the model change, the transformed data will change accordingly.
As an example, lets assume that the integer is 5. On the second viewmodel, I want to display the integer + 2, meaning 7.
Then the data is changed to 6 from the first viewmodel, meaning that the property on the second viewmodel changes to 8 automatically.
How is this implemented?
A few codepieces, to illustrate what the system looks like so far:
The Model:
public DataModel()
{
data = new Data();
data.Value = 2;
}
public Data data { get; set; }
And the data Class:
public class Data : INotifyPropertyChanged
{
private int m_Value;
public int Value
{
get { return m_Value; }
set
{
if (m_Value != value)
{
m_Value = value;
OnPropertyChanged("Value");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
The mainviewmodel
public class MainViewModel : ViewModelBase
{
readonly DataModel _dataModel;
public MainViewModel()
{
_dataModel = new DataModel();
ViewModel1 = new 1ViewModel(this);
ViewModel2 = new 2ViewModel(this);
}
public 1ViewModel ViewModel1 { get; set; }
public 2ViewModel ViewModel2 { get; set; }
public Data Data
{
get { return _dataModel.data; }
}
}
And here is the way the ChildViewmodels binds themselves to the Data object
public class 1ViewModel : ViewModelBase
{
private MainViewModel _mainViewModel;
public 1ViewModel(MainViewModel mainViewModel)
{
_mainViewModel = mainViewModel;
}
public Data Number
{
get { return _mainViewModel.data; }
}
}
And on the view1, i have bound the Number property like this
<TextBlock Text="{Binding Path=Number.Value}" />
Again, I want to be able to create a second property on the viewmodel, which displays the transformed data, based on, but without changing the original data, and which updates together with the data being updated.
Preferably, it should be a kind of converter-method, which converts the data to the new data.
Hope you are able to help.
The ViewModel should hold the data ready for display but not knowledgeable on the way in which it will be displayed. If you want to change the way in which the data is displayed in your View, for that you would use a Converter. This would allow you to use the same ViewModel for multiple Views and have different appearances.
In addition wrapping a ViewModel inside a ViewModel is not necessarily the way you want to go. A ViewModel generally has an associated View. If you have no view for the ViewModel, treat the data as typical classes and proceed with your single ViewModel.
Use an IValueConverter:
public class AddConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (int)value + (int)parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (int)value - (int)parameter;
}
}
And in your XAML, add the resource:
And update your binding:
I used a ConverterParameter here, but you can hardcode the value if so desired. Might want to add some checks to your converter as well, as it will throw an exception if the types are not correct.

Categories