I'm writing an application that reads data from a local db and display them in a listbox(I'm working in WPF).
I've a DVD object, where its properties are the columns of the db. This DVD object also implements INotifyPropertyChanged. "MyDVDs" is the table that refers to db. Once created these object, I create a class that inherits from ObservableCollection and takes data from "MyDVDs" in the constructor. However I don't need only to add, remove and update data from the listbox, but I also need to sort and filter them. Here is the code for ObservableCollection:
class ObservableDVD : ObservableCollection<DVD>
{
private ICollectionView collection;
public ObservableDVD(MyDVDs e)
{
foreach (DVD d in e.DVDs)
{
this.Add(d);
}
Collection = CollectionViewSource.GetDefaultView(this);
}
public ICollectionView Collection
{
get { return collection; }
private set { collection = value; }
}
}
I wanted to know, this is a good way?? Or can I do better?
In the MainWindow of the project, when Load_Window event fires, I assign the Collection property to listbox.ItemSource(in MainWindow code-behind I declare a private field that obviously refers to an ObservableDVD Object). I have some buttons that allow me to do the operations I tell you before.In the event headler of the buttons, I directly update and modify the ObservableDVD Object, not its property Collection. However, the Collection property also reflects those changes.
Why this behavior occurs?
It's ok for me, but I can't understand why it's happens. Is because of the notifications?
The property Collection has a reference to the view of the ObservableDVD. Being a reference means pointing to the same data in memory.
ObservableCollection Class Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
The Collection you are specifying is just a "view" of the ObservableDVD collection. Which means that both are really pointing to the same data in the memory, they're not 2 separate things. A "view" can be a subset of items when you apply filters to a collection, for instance.
Otherwise said, your ObservableDVD contains your "Data Table" for the entire dataset while the ICollectionView lets you manipulate which records/objects are visible to the user through custom logic.
Related
In order to get data binding in WinForms (to a DataGridView, for instance) to work anything like you'd hope and add/delete rows as the collection changes, you have to use a BindingList (or DataTable) instead of a generic List. The problem is, almost nobody has the first instinct to code with a BindingList instead of a List in their libraries.
The BindingList implements two events that the List doesn't have and these must be the difference in data binding action (also, a property to suppress the second event):
AddingNew
ListChanged
RaiseListChangedEvents
Similarly, the DataTable has two events which probably enable similar functionality:
RowDeleted
TableNewRow
EDIT: As the helpful SO community pointed out here and in another article, a List can be converted (maybe more accurately encapsulated?) by calling the correct BindingList constructor:
BindingList<MyType> MyBL = new BindingList<MyType>();
MyList.ForEach(x => MyBL.Add(x));
My situation is a little more complicated as illustrated by the code below.
EDIT Added INotifyPropertyChanged stuff that must exist in the real library.
public class RealString : INotifyPropertyChanged
{
private int _KnotCount = 0;
private List<KnotSpace> _KnotSpacings = new List<KnotSpace>();
public RealString()
{
KnotSpacings.Add(new KnotSpace());
}
public int KnotCount
{
get { return _KnotCount; }
set
{
int requiredSpacings = 0;
_KnotCount = value;
// Always one more space than knots
requiredSpacings = _KnotCount + 1;
if (requiredSpacings < KnotSpacings.Count)
{
while (requiredSpacings < KnotSpacings.Count)
{
KnotSpacings.Add(new KnotSpace());
}
}
else if (requiredSpacings > KnotSpacings.Count)
{
while (requiredSpacings > KnotSpacings.Count)
{
KnotSpacings.Remove(KnotSpacings.Last());
}
}
this.OnPropertyChanged(this, "KnotCount");
}
}
public List<KnotSpace> KnotSpacings { get => _KnotSpacings; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(object sender, string PropertyName)
{
if (this.PropertyChanged == null) return;
this.PropertyChanged(sender, new PropertyChangedEventArgs(PropertyName));
}
}
public class KnotSpace
{
private double _Spacing = 10;
public double Spacing { get => _Spacing; set => _Spacing = value; }
}
The things in the list are displayed in the UI, and the properties of the things in the list are modified in the UI, but the UI doesn't directly add/remove things from the list except by changing the KnotCount property. Wrapping the KnotSpacings property in a BindingList doesn't result in the BindingList updating when KnotSpacings is updated by changing the KnotCount property.
EDIT OK, more clarification...
BindingList BL = new BindingList<KnotSpace>(MyRealString.KnotSpacings);
DataGridView1.AutoGenerateColumns = true;
DataGridView1.DataSource = BL;
NumericUpDown1.DataBindings.Add("Value", MyRealString, "KnotCount", false, DataSourceUpdateMode.OnPropertyChanged);
The BindingList has no more success tracking the changes to the underlying List property (KnotSpacings) than the Windows controls. So data binding the controls to the BindingList doesn't accomplish much. BindingList works great if UI adds/removes items from the BindingList because it does the same operations in the underlying List. But then I would need to replicate the add/remove action and logic of the library in my UI and that's a breaking change in waiting.
EDIT Major changes made to my original post attempting to: (1) Clarify the problem. (2) Distinguish it as not a duplicate question (although one of the several questions was a dup). (3) Acknowledge the helpful efforts of others that would be lost if I deleted the post.
First Off, there is a better way to pass a List<T> to a BindingList<T>. BindingList<T> has a constructor that accepts a List<T> which copies the List's elements into the BindingList, like so:
List<int> myList = new List<int>();
BindingList<int> myBindingList = new BindingList<int>(myList);
But that's not your question, really. To answer your question simply - Correct, List<T> is not a good choice for two-way binding in WinForms. As List<T> does not have any events notifying for elements added, you can really only guarantee a one-way binding - data entry may work, but things break down when trying to refresh on, say, items being added to the List.
That said, you mention that these libraries are modifying a List<T> that you have access to during the modifications. I would argue that a good Library would use the Interface pattern to use, modify, and pass collections. Although List<T> and BindingList<T> are very different classes, they both implement IList<T>, ICollection<T>, and IEnumerable<T>. So any function which accepts any of those interfaces as a parameter would accept either a List<T> or a BindingList<T> (for example: public void DoSomethingWithCollection(IEnumerable<int> collection) could accept List<int>, BindingList<int>, or any other collection that implements IEnumerable<int>). The Interface pattern is a well-known standard at this point in C#'s lifespan, and though nobody's first instinct would be to use a BindingList<T> over a List<T>, their first instinct should absolutely be to use an IEnumerable<T> (or IList<T> or ICollection<T>) over a List<T>.
Where possible, it would be better for binding to pass your List to the BindingList's constructor, then never use the List again - instead, use the Add and Remove methods of the BindingList to manage it's internal collection.
If you use the BindingList<T> constructor that accepts an instance of IList<T>, then that instance is used to back the BindingList<T>, and changes in the IList<T> are reflected in the BindingList.
That's not the end of the story, however. WinForms databinding is structured in such a way that, the further away you get from simple, single-property 2-way binding, the more things you have to cover yourself.
For example, the INotifyPropertyChanged interface is implemented by classes that are used as a data source to notify of a change in a child property (like your KnotCount property).
For more complex scenarios, one would not use BindingList<T>, but would derive a class from it and override one or more of the data binding mechanisms. Ditto for the BindingSource class.
There is a lot of boilerplate behind the data binding mechanism, but almost every portion of it is open to derivation in order to customize the behavior. It is sometimes useful to draw out an object graph of the classes and interfaces used in data binding (lots of reading the documentation involved) to give yourself a good mental overview of the whole process.
ImageAlbums is an ICollectionView type and GlobalCollection.MyImageAlubms is an ObservableCollection<T> type.
ImageAlbums = CollectionViewSource.GetDefaultView(GlobalCollection.MyImageAlubms);
ImageAlbums.Filter = new Predicate<object>(this.FilterImageAlbumList);
In a view I'm using ImageAlbums for showing a filtered image list. I have filtered the list using FilterImageAlbumList method. The problem is I have used the GlobalCollection.MyImageAlubms in another place. In that view I have used the GlobalCollection.MyImageAlubms directly as source but in there the list are being showed as filtered also. I am also providing the filter method here, following code represents the filter method
private bool FilterImageAlbumList(object item)
{
AlbumModel albumMoel = (AlbumModel)item;
if(LOGIC_OF_FILTERING)
{
return false;
}
return true;
}
Is there any way to filter only ImageAlbums without affecting the GlobalCollection. FYI - I won't deep copy the Global Collection.
Your problem is caused by these two facts:
Each collection instance has only one default (instance of the) view, thus CollectionViewSource.GetDefaultView always returns the same instance for the same argument
WPF binding mechanism does not bind directly to a collection, but to its default collection view
So if you set a filter on the default view, its effects are visible wherever you bind to the collection.
If you want a separate instance of an ICollectionView your best bet is to instantiate it manually. For ObservableCollection<T> a good choice is ListCollectionView. So this should resolve your problems:
ImageAlbums = new ListCollectionView(GlobalCollection.MyImageAlubms);
I have problem with updating data from my datasource(database through entity fw) to wpf-windows. I generate files using entity framework, so i'm accesing data from datebase this way:
public partial class sampleWindow : Window
{
myEntity en = new myEntity();
public sampleWindow()
{
InitializeComponent();
Bind();
}
private void Bind()
{
var list = from o in en.table select o;
someDatagrid.ItemsSource = list.ToList();
}
This method, firstly, was adequate for my program, i was refreshing 'Bind' method after i was doing some operations on database, so the data in my datagrids or combos was fresh. The problem occurs when i was changing database in diffrent wpf-windows. I have read that I should implement observable interface and use load instead of itemsSource. I tried to do it but i'm begginer and my attempts faild miserably. Could someone tell me step by step, what i should do?
You need a Singleton to manage your data, combined with using an ObservableCollection to expose the data. When the collection is changed by any view, it will notify any subscribers to the observation and they will automatically update.
See: Example of bindable list in XAML app (first part)
Example of Singleton
You would want to use a singleton for the instance of your entity as The Sharp Ninja mentioned. His article in the link he posted does a good job of explaining. You will want to use an observable collection to bind your ItemSource to. When an item is added or removed from an Observable collection the UI is automatically notified. The problem you are going to have is that there is not a .ToObservableCollection()
extension method build in to .net so you will have to implement your own.
I use this extension method
public static ObservableCollection<T> ToObservableCollection<T>(
this IEnumerable<T> enumeration)
{
return new ObservableCollection<T>(enumeration);
}
So now your bind method can set your ItemSource to the observable collection
private void Bind()
{
var list = from o in en.table select o;
someDatagrid.ItemsSource = list.ToObservableCollection();
}
There are so many and better ways (MVVM pattern) to accomplish this than your approach. To keep it simple it can be accomplished this way:
//Gets the Load() extention method available for DbSet
using System.Data.Entity;
private void Bind()
{
myEntity.table.Load();
/*Local returns an obvervable collection convenient for data binding.
This is a synchronized local view of your data. It means any item added , deleted and updated will be reflected in your controls.*/
var obsColl = myEntity.table.Local;
someDatagrid.ItemsSource = obsColl;
}
I've been looking at the difference between a BindingList and an observablecollection and List. From what I've read, it seems like the BindingList is the only collection type that will notify if an object in it has one of its properties changed. I cannot get this to work.
I have a property on a ViewModel called Matches, which returns a BindingList created out of a list of CarMatch objects in another class. (Cars m_Cars = new Cars();) My DataGrid on the View is bound to this Matches property in the VM.
public BindingList<CarMatch> Matches
{
get
{
Return new BindingList<CarMatch>(m_Cars.Matches);
}
}
Now, in the code I change one of the CarMatch object's properties, say.. automaticTrans = true from false. Matches[0].automaticTrans = true. I want to see that change in the DataGrid. Without implementing INotifyPropertyChanged inside of the CarMatch class, is there a way to update the datagrid from the viewmodel? Using INotifyPropertyChanged on Matches does not seem to do it. There is something about this I just don't understand, and could use an example to look at.
CarMatch (not Matches) has to implement INotifyPropertyChanged. But consider using ObservableCollection unless you really need some of the additional scenarios offered by BindingList: with ObservableCollection, INotifyPropertyChanged comes for free. And, more importantly, BindingList doesn't scale well.
try
dataGrid.Items.Refresh();
but keep in mind that is a expensive call if you have lots of data and you call it several times in a short period of time.
I am trying to create a scheduler in WPF. I have a central static list in main window, which is initialized on load by a backgroundworker. There also is a dispatchtimer in mainwindow, whose interval is recalculated everytime the list changes.
public static List<ListViewcls> TODOdatalst = null;
public static void RefreshdblList()
{
if (ApplicationState.GetValue<bool>("dbDetected"))
{
TODOdatalst = DataAccess.ReadAllTODODataFromDataBase();
InitialiseDailyReminders();
}
}
Now there is another window which contains a listview which is bound to an observable collection, which is derived from the static list in mainwindow. whenever an item is added, updated or deleted from the list, the list in both the windows is manually refreshed via static functions.
private static readonly ObservableCollection<ListViewcls> TO_DOViewlst = new ObservableCollection<ListViewcls>();
public void RefreshView()
{
MainWindow.RefreshdblList();
if (MainWindow.TODOdatalst != null)
InitialiseListView(MainWindow.TODOdatalst);
else
InitialiseListView(DataAccess.ReadAllTODODataFromDataBase());
}
so is this approach proper ?? It works for now but suppose in the future, i have one more window which will also access the list but then i will be managing the refresh of data between 3 windows....that does not figure right. Anyone can suggest some better way, that i can keep a central repository and whenever it is updated all other lists get updated.
When one uses MVVM there is an option to use one ViewModel (the class which acquires and holds the data which is used as Data Contexts for a window/page(s)) between multiple windows and this scenario is common.
I would opt to share the instantiated ViewModel (or class which houses your data) between all windows and pass it in during window creation to have the window/page's data context bound to that one VM. That way the satellite window(s) are using the same data as the main and not having to do any update tricks as shown. Plus any new windows simply bind to the observable collection which everyone else is binding to.