Reputation: 363
So I understand my issue pretty well, I am just having issues developing a solution for it. Let's say I have an MVVM WPF application with a datagrid. The ItemSource is bound to a collection ObservableCollection. This is fed through a converter, as the data model is fairly complex, but the model complexity does not have anything to do with issue. I then need to use a Linq query on my observable collection, as I want it to only show certain items in that collection.
If I return the Linq query directly from my converter, I get the error "'EditItem' is not allowed for this view." I realize that that error occurs because the Linq query returns an IEnumerable collection, which is not supported with DataGrid TwoWay data binding.
However, if I return it as a new ObservableCollection, I get the error "Two-way binding requires Path or XPath." This makes sense because the new collection is not a property with accessors and mutators, and plus, we lose the data binding to the original source this way.
So the question here is how am I to return only the items that I need from the converter, in such a way that keeps my data binding to the original source and allows for two way binding?
I'm not sure if the following is necessary, but it may be worth noting a bit about the background of this application. This application Has dynamically Generated tabs in a tab control, and each tab item contains one of these DataGrids. These datagrids are populate with the same collection of objects, just with a different filter on each depending what tab it is contained in (this all ties back into the model). It is important that the converter is there as adding items to any of the datagrids will require a bit of extra coding.
Here is some code to demonstrate the issue.
The XAML DataGrid:
<DataGrid IsReadOnly="False">
<DataGrid.ItemsSource>
<MultiBinding Converter="{StaticResource MyObjectCollectionDataGridConverter}" UpdateSourceTrigger="PropertyChanged">
<Binding Path="StateManager.MyObjectCollection" Mode="TwoWay"/>
<Binding Mode="OneWay"/>
</MultiBinding>
</DataGrid.ItemsSource>
</DataGrid>
The C# Converter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string header = ((MyObjectType)values[1]).Name;
// Returning as a Linq query:
// return ((ObservableCollection<MyObject>)values[0]).Where(c => c.Type.Name == header);
// Returning as a new Observable Collection
return new ObservableCollection<MyObject>(((ObservableCollection<MyObject>)values[0]).Where(c => c.Type.Name == header));
}
Thank you for any help!!!
Edit
Ok, so I did find out that when using the Linq query approach, by calling the ToList() method, my databinding does hold for existing items (which makes sense because it is just referencing the old instance of the objects). However, when a new item is added via the datagrid the application breaks with the error "Two-way binding requires Path or XPath." (which also makes sense because it is not referencing the new list created, not the old observable collection). How do I make it so that I can still add items via the datagrid to reference the old observable collection over the new list?
Upvotes: 1
Views: 1420
Reputation: 1543
Since View is dictating what VM should look like your MyObject should also follow this rule.
You want to extend your MyObject class to contain flag whether item is visible or not.
This is how your RowViewModel might look:
public class MyObject : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
Here is how you would deal with it in converter:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string header = ((MyObject)values[1]).Name;
foreach (var item in (ObservableCollection<MyObject>)values[0])
item.IsEnabled = item.Name == header;
return values[0];
}
And of course you want to expose enabled flag in DataTemplate
xaml:
<DataGrid IsReadOnly="False" AutoGenerateColumns="False">
<DataGrid.ItemsSource>
<MultiBinding Converter="{StaticResource MyObjectCollectionDataGridConverter}" UpdateSourceTrigger="PropertyChanged">
<Binding Path="StateManager.MyObjectCollection" Mode="TwoWay"/>
<Binding Mode="OneWay"/>
</MultiBinding>
</DataGrid.ItemsSource>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="Hello" IsEnabled="{Binding IsEnabled}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Upvotes: 1