WPF<-> entity binding - c#

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;
}

Related

BindingList vs List - WinForms Data Binding

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.

Filtering issue with ICollectionView

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);

WPF Share and Update lists between different windows

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.

ObservableCollection and CollectionView

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.

Binding ItemsSource to an EntityFramework ObjectSet<T> does not generate content

Our application is in use of several TreeViewItem and DataGrid controls in XAML whose ItemsSource properties are bound to Entity Framework ObjectSet<Entity> collections. The problem is that the UI controls are behaving as if the ObjectSets are empty.
The EF ObjectContext is enclosed within a singleton class in a static class:
public static class BusinessData
{
public static readonly BizDataSource Source = BizDataSource.Instance;
}
public class BizDataSource : INotifyPropertyChanged
{
private BusinessEntitiesObjectContext _context = ...;
and the ObjectSets are being returned from readonly properties within the singleton:
public IEnumerable<Employee> RetiredEmployees
{
get {
return (from it in _context.Employees where it.Status == "Retired" select it);
}
}
and finally, the ItemsSource is data bound to the collection, with INotifyPropertyChanged serving to update the UI when its known that the data source is updated:
<TreeViewItem x:Name="PART_TVI" Header="Retired Employees"
ItemsSource="{Binding
Source={x:Static local:BusinessData.Source},
Path=RetiredEmployees}"
/>
Debug stepping has revealed that the binding is correctly providing an IEnumerable which resolves to the business objects, but it seems like the control isn't iterating upon it. For example, if I add this code to the window:
PART_TVI.ItemsSource = BusinessData.Source.RetiredEmployees;
The same behavior occcurs as with XAML Binding: nothing. However:
PART_TVI.ItemsSource = BusinessData.Source.RetiredEmployees.ToArray();
Ah hah! Now, we have generated content in our TreeViewItem. But, why was this necessary in the first place?
My guess is that: in the case of IEnumerable, you pass the expression to the ItemsSource, NOT a SOURCE. When you called: ToArray() which means that, it will execute the expression and return the actual data for the ItemsSource.
In summary, the first case: You tell the WPF how to get the ItemsSource. The second case: You pass the actual data source.

Categories