Updating a single ObservableCollection but show in 2 different ListViews - c#

Suppose I have one ObservableCollection that I insert, update and delete from.
public ObservableCollection<Item> AllItems {get;set;}
The Item class (which implements INotifyPropertyChanged) looks like this:
public class Item
{
public int Id {get;set;}
public int Kind {get;set;}
public string Text {get;set;}
}
I have this bound in my View in one ListView. Everything works, meaning if I delete or add or updates Items in the ObservableCollection from my ViewModel, they are updating in my ListView as expected.
But now I need to split the ListView in 2 ListViews in my View. The first ListView must display all items where Item.Kind=1. And the other ListView must have all items where Item.Kind=2.
I don't mind binding to two different ObservableCollections from my ViewModel, but I would really like a solution where I can just do:
var item = new Item { Id=22, Kind=1, Text="SomeText" };
AllItems.Add(item);
AllItems.First(x => x.Id==22).Text = "SomeOtherText";
AllItems.Remove(item);
And then these operations are automatically reflected in the 2 ListViews. So in the above example, the new Item is only viewed, updated and removed from the first ListView (where Item.Kind=1).
Is that possible?
EDIT: I should say that I have tried to use two CollectionViewSources with the same ObservableCollection as source. But that fails and does not seem to be the correct solution.

create two ListCollectionViews with different filters - one for each ListView
public ICollectionView Items_1 { get; private set; }
public ICollectionView Items_2 { get; private set; }
public ViewModel
{
Items_1 = new ListCollectionView(AllItems);
Items_1.Filter = o => (o as Item).Kind == 1;
Items_2 = new ListCollectionView(AllItems);
Items_2.Filter = o => (o as Item).Kind == 2;
}

Related

WPF Binding Datagrid to List of ObservableCollections of different types

This could probably be solved from different angles, but I'm kind of stuck right now.
I want to have a Datagrid, which can add Data of different types into lists in the backend.
I have a list of ObservableCollections, which all have different types of elements in it (I'm using MVVM and I have a couple of different model classes). In my frontend, I have a DataGrid, which is supposed to display the data of the lists depending on which value is selected in a Combobox. It also has to happen dynamicly and can't use fixed grids, as things are added and removed constantly.
I got the Binding to work, so it displays the lists if there are any entries by making the lists the type ObservableCollection.
However, if they are empty, the column headers will disappear and thus, the user cannot enter a new row of the correct type, which is necessary.
This is what I use right now to initiate the Collections:
foreach (var lvm in ListOfValues)
{
Type listType = lvm.GetListType();
string lvmName = listType.Name;
// Create ObservableCollection
ObservableCollection<object> observableCollection = new ObservableCollection<object>
{
// add a basic line to signal the list type - THIS IS A WORKAROUND
Activator.CreateInstance(listType),
};
TemplateEntries.Add(new TemplateEntry { ListName = lvmName, Values = observableCollection });
}
Each TemplateEntry from the class contains the before named list of data. The TemplateEntries Property is just an ObservableCollection of TemplateEntries:
public ObservableCollection<TemplateEntry> TemplateEntries { get; set; } = new ObservableCollection<TemplateEntry>();
public class TemplateEntry : ObservableObject
{
public string ListName { get; set; }
public ObservableCollection<object> Values { get; set; }
public int Count { get { return Values.Count; } }
}
And in XAML I just have this line so far to display it:
<DataGrid Grid.Column="0" Grid.Row="1" Margin="5,5,5,5" AutoGenerateColumns="True" Name="Grid" CanUserAddRows="True" ItemsSource="{Binding ValuesToDisplay}" />
So my question: How can I remove the Activator.CreateInstance Line workaround and signal the datagrid which type it should use?
After some time, I managed to figure it out myself. The solution was to use the non-generic type IList instead of ObservableCollection for the Values of the TemplateEntry class, then cast the ObservableCollection into it (after creating it using Reflection). The DataGrid will accept this.
foreach (var lvm in ListModel.ListVMs)
{
Type listType = lvm.GetListType();
string lvmName = listType.Name;
Type collectionType = typeof(ObservableCollection<>).MakeGenericType(listType);
// Create ObservableCollection
IList observableCollection = (IList)Activator.CreateInstance(collectionType);
TemplateEntries.Add(new TemplateEntry { ListName = lvmName, Values = observableCollection });
}

WPF C# How can I use a common ObservableCollection in two ViewModels?

I got a question about how can I use a common ObservableCollection in two ViewModels in WPF C#?
I got two views, first view consist of a a observablecollection of categories used in a combobox in some form. The second view is a window which enable adding, editing and removal of categories. Now I'm retrieving the categories for both views separately, but I want to combine and use a common observable collection, in order to get the new changes when I added, change or remove categories in the second view.
Both Views and viewmodels is controlled by a mainviewmodel:
public viewModel1 viewModel1 { get; set; }
public ViewModel2 ViewModel2 { get; set; }
public MainViewModel()
{
this.InitializeCommands();
this.viewModel1 = new viewModel1();
this.ViewModel2 = new ViewModel2();
this.ViewModel2.OnChangedCategory += (s, e) =>
{
this.viewModel1.Categories = GetCategories();
};
}
ViewModel for View1:
public ObservableCollection<Categories> GetCategories
{
get
{
if (this._getCategories == null)
{
this._getCategories = methods.GetCategories();
}
return this._getCategories ;
}
set
{
if (this._getCategories != value)
{
this._getCategories = value;
this.OnPropertyChanged("GetCategories");
}
}
}
ViewModel for View2:
public ObservableCollection<Categories> GetCategories
{
get
{
if (this._getCategories == null)
{
this._getCategories = methods.GetCategories();
}
return this._getCategories ;
}
set
{
if (this._getCategories != value)
{
this._getCategories = value;
this.OnPropertyChanged("GetCategories");
}
}
}
In theory, they are both identical, just two different observable collection in two viewmodels, I want to use the same observable collection in both views, if it's in the first or second ViewModel doesn't matter. But it makes maybe more sense if it's in viewmodel 2. Let's say if I decide have a common categories observable collection in ViewModel 2, how can Viewmodel 1 use this observable collection from ViewModel 2 and still be in sync upon changes?
How can I do this?
You could make it a resource and then any code could get at it.
The sample associated with this article has two pairs of usercontrols share two such collections and sounds fairly similar to your requirement.
https://social.technet.microsoft.com/wiki/contents/articles/29859.wpf-tips-bind-to-current-item-of-collection.aspx

WPF XAML DataGrid populating sub DataGrid

I am new to WPF and XAML. Sorry if this seems simple, but I have a set of classes:
Public class Master()
{
public int Id {get;set;}
public List<Student> Students { get; set; }
}
Public class Student()
{
public int Id {get;set;}
public string Name { get; set; }
}
I wish to display them in a datagrid, so I have created and configured a datagrid control on my page.
I have then bound my classes above using:
dataGrid.ItemsSource = result.Master.ToList();
This provides me with the list, but what I am trying to do is also display the student collection against each master row. At the moment all I get is (Collection) populated in the student record of the datagrid.
To move from comments to an answer:
I assume you know how to make another datagrid, since you already have 1 with correct bindings. Just copy paste another one and move it to one side.
For this other datagrid, let's call it dataGrid2.
On the first datagrid: add this to your xaml:
SelectionChanged="DataGrid_SelectionChanged"
and in code behind:
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selected = dataGrid.SelectedItem as Master;
dataGrid2.ItemsSource = selected.Students.ToList();
}
I haven't included everything, such as error checking and empty selection handling, etc. So you will need to implement it.

Binding combobox to another combobox

I have a problem with binding combobox to another combobox. I'm trying to dynamically pass a parameter (id) from first combobox to the method of initiating second combobox. For example, if I selected the first item in first combobox, then second combobox will initialize with parameter that selected from first combobox.
XAML:
<ComboBox Name="ItServiceCmbox" ItemsSource="{Binding ItServiceMetricsNames}" DisplayMemberPath="ServiceName" SelectedValuePath="ServiceId" />
<ComboBox Name="MetricCmbox" ItemsSource="{Binding SelectedItem.MetricId, ElementName=ItServiceCmbox}" DisplayMemberPath="MetricName" SelectedValuePath="MetricId"/>
C#:
public partial class MainWindow : Window
{
readonly MetricsValuesHelper _metricsValuesHelper = new MetricsValuesHelper(new Repository());
public static int SelectedService;
public static int SelectedMetric;
public ObservableCollection<ItServiceMetricsNames> ItServiceMetricsNames { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
SelectedService = Convert.ToInt32(ItServiceCmbox.SelectedItem);
ItServiceMetricsNames = new ObservableCollection<ItServiceMetricsNames>();
ItServiceMetricsNames.Add(new ItServiceMetricsNames()
{
ServiceId = _metricsValuesHelper.GetServiceId(),
ServiceName = _metricsValuesHelper.GetServiceName(),
MetricId = _metricsValuesHelper.GetMetricId(SelectedService),
MetricName = _metricsValuesHelper.GetMetricName(SelectedService)
});
}
}
And ItServiceMetricsNames class:
public class ItServiceMetricsNames
{
public List<int> ServiceId { get; set; }
public List<string> ServiceName { get; set; }
public List<int> MetricId { get; set; }
public List<string> MetricName { get; set; }
}
Is it possible? Thanks for any answers!
This is a messy, naive implementation I did last year that seemed to work. There's definitely a better way out there. Instead of trying to do any actual binding in my xaml I made event handlers. You may create event handlers for ComboBoxes that are triggered whenever the sending ComboBox loses focus, closes it's DropDown, changes selection, etc.
If you want one ComboBox dependent on another, you may make the dependent ComboBox disabled until a selection is made in the independent ComboBox. Once a selection is made, you populate and enable the dependent ComboBox with the appropriate data.
Event handlers in your code will look something like this:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Independent ComboBox is the sender here
ProcessComboBoxes(sender as ComboBox);
}
The ProcessComboBoxes method will look different depending on what you're trying to do. But, essentially, it will identify the target/dependent ComboBox that you want to conditionally populate -- do this either with a Dictionary that maps from ComboBox to ComboBox or something you find suiting. After identifying the target, you will clear any items previously added, and then repopulate with your new ones. Below is a method in pseudocode (practically).
private void ProcessComboBoxes(ComboBox senderBox)
{
ComboBox dependentBox = lookupDependent[senderBox];
var itemType = itemTypes[senderBox.selectedIndex];
var listOfItemsNeeded = lookupItemsByType[itemType];
dependentBox.Items.Clear();
foreach (string item in listOfItemsNeeded){
dependentBox.Items.Add(item);
}
dependentBox.IsEnabled = true;
}
Don't forget to add your eventhandlers to your xaml. Make sure to pay close attention to the call hierarchy of events and determine when exactly you want your dependent ComboBox to be repopulated.

How to Bind Listbox in WPF to a generic list?

i'm having trouble getting a clear answer for this.
I have a Static class (DataHolder) that holds a static list with a complex type (CustomerName and CustomerID properties).
I want to bind it to a ListBox in WPF but add another item that will have the word "All" for future drag and drop capablilities.
Anyone?
Create a ViewModel Class you can databind to! The ViewModel can reference the static class and copy the items to its own collection and add the all item to it.
Like this
public class YourViewModel
{
public virtual ObservableCollection<YourComplexType> YourCollection
{
get
{
var list = new ObservableCollection<YourComplexType>(YourStaticClass.YourList);
var allEntity = new YourComplexType();
allEntity.Name = "all";
allEntity.Id = 0;
list.Insert(0, allEntity);
return list;
}
}
}
Note, sometimes, you need empty Items. Since WPF can't databind to null values you need to use the same approach to handle it. The empty business entity has been a best practice for it. Just google it.
That "All" item has to be part of the list you bind your ListBox against. Natuarally you can not add that item to the DataHolder list because it holds items of type Customer (or similar). You could of course add a "magic" Customer that always acts as the "All" item but that is for obvious reasons a serious case of design smell (it is a list of Customers after all).
What you could do, is to not bind against the DataHolder list directly but introduce a wrapper. This wrapper would be your ViewModel. You would bind your ListBox agains a list of CustomerListItemViewModel that represents either a Customer or the "All" item.
CustomerViewModel
{
string Id { get; private set; }
string Name { get; set; }
public static readonly CustomerViewModel All { get; private set; }
static CustomerViewModel()
{
// set up the one and only "All" item
All = new CustomerViewModel();
All.Name = ResourceStrings.All;
}
private CustomerViewModel()
{
}
public CustomerViewModel(Customer actualCustomer)
{
this.Name = actualCustomer.Name;
this.Id = actualCustomer.Id;
}
}
someOtherViewModel.Customers = new ObservableCollection<CustomerViewModel>();
// add all the wrapping CustomerViewModel instances to the collection
someOtherViewModel.Customers.Add(CustomerViewModel.All);
And then in your Drag&Drop code somewhere in the ViewModel:
if(tragetCustomerViewModelItem = CustomerViewModel.All)
{
// something was dropped to the "All" item
}
I might have just introduced you to the benefits of MVVM in WPF. It saves you a lot of hassle in the long run.
If you use binding than the data provided as the source has to hold all of the items, ie. you can't databind and then add another item to the list.
You should add the "All" item to the DataHolder collection, and handle the 'All' item separately in your code.

Categories