I have two classes: Employee and EmployeeGridViewAdapter. Employee is composed of several complex types. EmployeeGridViewAdapter wraps a single Employee and exposes its members as a flattened set of system types so a DataGridView can handle displaying, editing, etc.
I'm using VS's builtin support for turning a POCO into a data source, which I then attach to a BindingSource object. When I attach the DataGridView to the BindingSource it creates the expected columns and at runtime I can perform the expected CRUD operations. All is good so far.
The problem is the collection of adapters and the collection of employees aren't being synchronized. So all the employees I create an runtime never get persisted. Here's a snippet of the code that generates the collection of EmployeeGridViewAdapter's:
var employeeCollection = new List<EmployeeGridViewAdapter>();
foreach (var employee in this.employees)
{
employeeCollection.Add(new EmployeeGridViewAdapter(employee));
}
this.view.Employees = employeeCollection;
Pretty straight forward but I can't figure out how to synchronize changes back to the original collection. I imagine edits are already handled because both collections reference the same objects but creating new employees and deleting employees aren't happening so I can't be sure.
You could also considering using System.Collections.ObjectModel.ObservableCollection and wiring up it's CollectionChanged event. It could look something like this.
ObservableCollection<EmployeeAdapter> observableEmployees =
new ObservableCollection<EmployeeAdapter>();
foreach (Employee emp in employees)
{
observableEmployees.Add(new EmployeeAdapter(emp));
}
observableEmployees.CollectionChanged +=
(object sender, NotifyCollectionChangedEventArgs e) =>
{
ObservableCollection<EmployeeAdapter> views =
sender as ObservableCollection<EmployeeAdapter>;
if (views == null)
return;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (EmployeeAdapter view in e.NewItems)
{
if (!employees.Contains(view.Employee))
employees.Add(view.Employee);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (EmployeeAdapter view in e.OldItems)
{
if (employees.Contains(view.Employee))
employees.Remove(view.Employee);
}
break;
default:
break;
}
};
Code assumes the following using statements.
using System.Collections.ObjectModel;
using System.Collections.Specialized;
If you need the IList interface you could also use System.ComponentModel.BindingList and wire up it's ListChanged event. It could look like this.
BindingList<EmployeeAdapter> empViews = new BindingList<EmployeeAdapter>();
foreach (Employee emp in employees)
{
empViews.Add(new EmployeeAdapter(emp));
}
empViews.ListChanged +=
(object sender, ListChangedEventArgs e) =>
{
BindingList<EmployeeAdapter> employeeAdapters =
sender as BindingList<EmployeeAdapter>;
if (employeeAdapters == null)
return;
switch (e.ListChangedType)
{
case ListChangedType.ItemAdded:
EmployeeAdapter added = employeeAdapters[e.NewIndex];
if (!employees.Contains(added.Employee))
employees.Add(added.Employee);
break;
case ListChangedType.ItemDeleted:
EmployeeAdapter deleted = employeeAdapters[e.OldIndex];
if (employees.Contains(deleted.Employee))
employees.Remove(deleted.Employee);
break;
default:
break;
}
};
Code assumes the following using statement.
using System.ComponentModel;
The first problem seems to be that you are creating a new list and data binding to that. When you're adding elements these will be added to the collection but your original employee list remains unmodified.
To avoid this you should either provide a custom collection class that will migrate changes back to the underlying employee list, or wire up the appropriate events (to do the migration on insert/delete) before data binding to it.
To avoid a number of other problems with binding editable collections to grids, you should implement the data binding interfaces, as outlined below. The presence of these interfaces allows the visual controls to notify the underlying collection about actions such as "insert cancelled" (when users aborts entry of a new record), and similarly allows information to flow in the opposite direction (update UI when collection or individual entries change).
First, you'll want to implement at least IEditableObject, INotifyPropertyChanged and IDataErrorInfo on the individual items in a data-bound collection, which in your case would be the EmployeeGridViewAdaper class.
Additionally, you'd want your collection to implement ITypedList and INotifyCollectionChanged. The BCL contains a BindingList implementation which provides a good starting point for this. Recommend using this instead of the plain List.
I can recommend Data Binding with Windows Forms 2.0 for an exhaustive coverage of this topic.
Related
Using the Sitecore item:saved event handler, I am trying to add items in other languages. So if you say, add a item in one language, it should automatically add the item in the other given languages when saved.
Right now, I can get all the languages from the master database. The problem is when adding a new version of the new language, it triggers itself and therefore recursive adds "infinite" new elements until it crashes. How is it possible to bypass this?
public void OnItemSaved(object sender, EventArgs args)
{
Item savedItem = Event.ExtractParameter(args, 0) as Item;
if (savedItem.Versions.Count == 1)
{
// Allow only non null items and allow only items from the master database |
if (savedItem != null && savedItem.Database.Name.ToLower() == "master")
{
// Do some kind of template validation to limit only the items
if (savedItem.TemplateID == ID.Parse("{template id}"))
{
// Get all the installed languages from the master database
var installedLanguages = LanguageManager.GetLanguages(Database.GetDatabase("master"));
// Go through every language in the list
foreach (var language in installedLanguages)
{
// Copy item to the new language
if (savedItem.Language.Name != language.Name)
{
using (new LanguageSwitcher(language.Name))
{
// Save the new item, but it fails since it triggers itself (the event)
savedItem.Versions.AddVersion();
}
}
}
}
}
}
Try this code:
foreach (var language in installedLanguages)
{
if (savedItem.Language.Name != language.Name)
{
var otherLanguageItem = savedItem.Database.GetItem(savedItem.ID, language);
if (otherLanguageItem.Versions.Count == 0)
{
otherLanguageItem.Versions.AddVersion();
}
}
}
It's been quite a while since I have done any Sitecore work and I don't currently have access to a Sitecore instance for testing out a code sample, but one way to approach this would be to put a collection of updated item ids on the context. The approach (in pseudo code) could be inserted immediately after your template check (to keep less expensive checks earlier than more expensive ones) and would go something like:
- retrieve collection of updated items from context or create
- if collection contains current item id, return immediately
- add current item id to collection
I'd be happy to provide more clarity on how this would look syntactically if needed, but honestly if I write something in C# I prefer to be able to actually see it work
This question is a result of the fix to this problem. After getting the sort behavior to properly sort the ObservableCollection, I now notice that the first time the collection is added to, the CustomSorter handler fires with only the first item in it, and that same item is passed in as both parameters to the method. That is producing a duplicate item in the list.
Here are the pertinent parts of the view model code:
public ObservableCollection<PermissionItemViewModel> PermissionItems { get; private set; }
private void FetchRoleData()
{
PermissionItems.Clear();
if (SelectedRole != null)
{
using (var context = new myDataContext(new myDbFactory().GetConnectionString()))
{
foreach (PermissionsEnum permission in Enum.GetValues(typeof(PermissionsEnum)))
PermissionItems.Add(new PermissionItemViewModel(permission, SelectedRole[permission]));
}
}
}
All subsequent manipulations of that collection do not do this...it only happens the first time through the FetchRoleData method. Why?
EDIT:
Some additional information. The CustomSort property is set when the CollectionViewSource fires its Filter event (the only event it has AFAIK). I couldn't come up with any better trigger to get it set. The OnAttached override is too soon, as the View member of the CollectionViewSource is not set yet by that point. Catch 22, I guess. That is happening immediately after that first collection item is added. If this is due to the order in which things are being set, then are there any recommendations for a change?
I don't know how or where you're setting up the filter handler. Here's an example of how to set a custom sort on a CollectionViewSource when its View property changes. That's when you want to do it. This assumes that it's in the resources for a Window (or at least someplace where the Window can find it). But once you have cvs, wherever it comes from and however you got your mitts on it, the rest is the same.
public MainWindow()
{
InitializeComponent();
var cvs = FindResource("MyCollectionViewSource1") as CollectionViewSource;
var dpdView = DependencyPropertyDescriptor.FromProperty(
CollectionViewSource.ViewProperty, typeof(CollectionViewSource));
dpdView.AddValueChanged(cvs, (s, e) =>
{
if (cvs.View is ListCollectionView lvc)
{
lvc.CustomSort = new CustomSorter();
}
});
}
I'm baffled by your claim that the first item in the collection is being duplicated. No code you've shown, and no code I've given you, could have that effect. You'll have to share code that demonstrates that issue.
I'm trying to understand how to manage a transition between two collections in pivotviewer. Collections have the same images, it's just that one collection is processed. I want to have one collection vanishing into the other.
I know how to define different templates that vanishes one into the other by fixing the maxwidth. For example once you zoom over 300 px then you have your new template until you reach 500 px, etc. The code I use to bind the collection I've loaded in the code behind is like this:
<pv:PivotViewerItemTemplate x:Key="firstTemplate" MaxWidth="300">
<!-- template layout -->
<pv:PivotViewerMultiScaleSubImageHost CollectionSource="{Binding [VisualCollectionSource][0] }" ImageId="{Binding [VisualImageId][0]}" />
<!-- template layout -->
</pv:PivotViewerItemTemplate>
Is there a solution like this I can adopt? What's the best practice for it?
Here's an example of keeping the overlap between loaded CXML collections, instead of replacing the entire collection. Since there are animations when adding and removing objects, it looks pretty nice. Useful when requesting more/partial data from the server/backend. (Of course, this has nothing to do with "fading collections/items" when zooming.)
The most interesting code would be in KeepIntersection(this ICollection<PivotViewerItem> currentItems, CxmlCollectionSource newItems), which modifies the collection by adding and removing only the differences from the old and the new collections.
Based on Silverlight 5 PivotViewer's ObservableCollection as per Tony Champions's and Chris Arnold's tutorials/posts.
MainPageViewModel.cs
private void CxmlCollectionSource_StateChanged(object sender, CxmlCollectionStateChangedEventArgs e)
{
// TODO: check other states
switch (e.NewState)
{
case CxmlCollectionState.Loaded:
{
var collection = sender as CxmlCollectionSource;
Debug.Assert(collection != null, "collection != null");
// TODO: don't add/remove, replace the entire list after diffing
if (!this.pivotProperties.Any())
{
// TODO: diffing algorithm for properties, minimal changes
foreach (var pivotViewerProperty in collection.ItemProperties)
{
this.pivotProperties.Add(pivotViewerProperty);
}
}
this.pivotViewerItems.KeepIntersection(collection);
break;
}
}
}
ICollection{PivotViewerItem}Extensions.cs
namespace SilverlightPivotViewer.Extensions
{
#region Using directives
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls.Pivot;
#endregion
public static class ICollectionPivotViewerItemExtensions
{
#region Public Methods and Operators
public static void KeepIntersection(
this ICollection<PivotViewerItem> currentItems, CxmlCollectionSource newItems)
{
RemoveCurrentUniqueItems(currentItems, newItems);
AddNewUniqueItems(currentItems, newItems);
}
#endregion
#region Methods
private static void AddNewUniqueItems(ICollection<PivotViewerItem> currentItems, CxmlCollectionSource newItems)
{
IEnumerable<PivotViewerItem> onlyInNewCollection =
newItems.Items.Where(pivotViewerItem => currentItems.All(i => i.Id != pivotViewerItem.Id));
foreach (var pivotViewerItem in onlyInNewCollection)
{
currentItems.Add(pivotViewerItem);
}
}
private static void RemoveCurrentUniqueItems(
ICollection<PivotViewerItem> currentItems, CxmlCollectionSource newItems)
{
IEnumerable<PivotViewerItem> onlyInCurrentCollection =
currentItems.Where(pivotViewerItem => newItems.Items.All(i => i.Id != pivotViewerItem.Id));
// Need to produce a list, otherwise it will crash (concurrent looping and editing the IEnumerable, or something related)
var onlyInCurrentCollectionList = onlyInCurrentCollection.ToList();
foreach (var pivotViewerItem in onlyInCurrentCollectionList)
{
currentItems.Remove(pivotViewerItem);
}
}
#endregion
}
}
Coded custom diffing functions, but I'm sure someone has an awesome library doing just that.
Thought of adding diffing for facet categories too, but it's not built for it. I guess the recommended way is to make sure the client knows about all available categories, so they can be used for filtering.
What is the use of ObservableCollection in .net?
ObservableCollection is a collection that allows code outside the collection be aware of when changes to the collection (add, move, remove) occur. It is used heavily in WPF and Silverlight but its use is not limited to there. Code can add event handlers to see when the collection has changed and then react through the event handler to do some additional processing. This may be changing a UI or performing some other operation.
The code below doesn't really do anything but demonstrates how you'd attach a handler in a class and then use the event args to react in some way to the changes. WPF already has many operations like refreshing the UI built in so you get them for free when using ObservableCollections
class Handler
{
private ObservableCollection<string> collection;
public Handler()
{
collection = new ObservableCollection<string>();
collection.CollectionChanged += HandleChange;
}
private void HandleChange(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (var x in e.NewItems)
{
// do something
}
foreach (var y in e.OldItems)
{
//do something
}
if (e.Action == NotifyCollectionChangedAction.Move)
{
//do something
}
}
}
An ObservableCollection works essentially like a regular collection except that it implements
the interfaces:
INotifyCollectionChanged,
INotifyPropertyChanged
As such it is very useful when you want to know when the collection has changed. An event is triggered that will tell the user what entries have been added/removed or moved.
More importantly they are very useful when using databinding on a form.
From Pro C# 5.0 and the .NET 4.5 Framework
The ObservableCollection<T> class is very useful in that it has the ability to inform external objects
when its contents have changed in some way (as you might guess, working with
ReadOnlyObservableCollection<T> is very similar, but read-only in nature).
In many ways, working with
the ObservableCollection<T> is identical to working with List<T>, given that both of these classes
implement the same core interfaces. What makes the ObservableCollection<T> class unique is that this
class supports an event named CollectionChanged. This event will fire whenever a new item is inserted, a current item is removed (or relocated), or if the entire collection is modified.
Like any event, CollectionChanged is defined in terms of a delegate, which in this case is
NotifyCollectionChangedEventHandler. This delegate can call any method that takes an object as the first parameter, and a NotifyCollectionChangedEventArgs as the second. Consider the following Main()
method, which populates an observable collection containing Person objects and wires up the
CollectionChanged event:
class Program
{
static void Main(string[] args)
{
// Make a collection to observe and add a few Person objects.
ObservableCollection<Person> people = new ObservableCollection<Person>()
{
new Person{ FirstName = "Peter", LastName = "Murphy", Age = 52 },
new Person{ FirstName = "Kevin", LastName = "Key", Age = 48 },
};
// Wire up the CollectionChanged event.
people.CollectionChanged += people_CollectionChanged;
// Now add a new item.
people.Add(new Person("Fred", "Smith", 32));
// Remove an item.
people.RemoveAt(0);
Console.ReadLine();
}
static void people_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// What was the action that caused the event?
Console.WriteLine("Action for this event: {0}", e.Action);
// They removed something.
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
Console.WriteLine("Here are the OLD items:");
foreach (Person p in e.OldItems)
{
Console.WriteLine(p.ToString());
}
Console.WriteLine();
}
// They added something.
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
// Now show the NEW items that were inserted.
Console.WriteLine("Here are the NEW items:");
foreach (Person p in e.NewItems)
{
Console.WriteLine(p.ToString());
}
}
}
}
The incoming NotifyCollectionChangedEventArgs parameter defines two important properties,
OldItems and NewItems, which will give you a list of items that were currently in the collection before the event fired, and the new items that were involved in the change. However, you will want to examine these lists only under the correct circumstances. Recall that the CollectionChanged event can fire when
items are added, removed, relocated, or reset. To discover which of these actions triggered the event,
you can use the Action property of NotifyCollectionChangedEventArgs. The Action property can be
tested against any of the following members of the NotifyCollectionChangedAction enumeration:
public enum NotifyCollectionChangedAction
{
Add = 0,
Remove = 1,
Replace = 2,
Move = 3,
Reset = 4,
}
Explanation without Code
For those wanting an answer without any code behind it (boom-tish) with a story (to help you remember):
Normal Collections - No Notifications
Every now and then I go to NYC and my wife asks me to buy stuff. So I take a shopping list with me. The list has a lot of things on there like:
Louis Vuitton handbag ($5000)
Clive Christian’s Imperial Majesty Perfume ($215,000 )
Gucci Sunglasses ($2000)
hahaha well I"m not buying that stuff. So I cross them off and remove them from the list and I add instead:
12 dozen Titleist golf balls.
12 lb bowling ball.
So I usually come home without the goods and she's never pisssssssed off the thing is that she doesn't know about what i take off the list and what I add onto it; she gets no notifications.
The ObservableCollection - notifications when changes made
Now, whenever I remove something from the list: she get's a notification.
The observable collection works just the same way. If you add or remove something to or from it: someone is notified.
And when they are notified, then bunker down or run for cover! Of course, the consequences are customisable via an event handler.
Silly story, but hopefully you'll remember the concept now.
One of the biggest uses is that you can bind UI components to one, and they'll respond appropriately if the collection's contents change. For example, if you bind a ListView's ItemsSource to one, the ListView's contents will automatically update if you modify the collection.
EDIT:
Here's some sample code from MSDN:
http://msdn.microsoft.com/en-us/library/ms748365.aspx
In C#, hooking the ListBox to the collection could be as easy as
listBox.ItemsSource = NameListData;
though if you haven't hooked the list up as a static resource and defined NameItemTemplate you may want to override PersonName's ToString(). For example:
public override ToString()
{
return string.Format("{0} {1}", this.FirstName, this.LastName);
}
it is a collection which is used to notify mostly UI to change in the collection , it supports automatic notification.
Mainly used in WPF ,
Where say suppose you have UI with a list box and add button and when you click on he button an object of type suppose person will be added to the obseravablecollection and you bind this collection to the ItemSource of Listbox , so as soon as you added a new item in the collection , Listbox will update itself and add one more item in it.
class FooObservableCollection : ObservableCollection<Foo>
{
protected override void InsertItem(int index, Foo item)
{
base.Add(index, Foo);
if (this.CollectionChanged != null)
this.CollectionChanged(this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, item, index);
}
}
var collection = new FooObservableCollection();
collection.CollectionChanged += CollectionChanged;
collection.Add(new Foo());
void CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
{
Foo newItem = e.NewItems.OfType<Foo>().First();
}
ObservableCollection Caveat
Mentioned above (Said Roohullah Allem)
What makes the ObservableCollection class unique is that this
class supports an event named CollectionChanged.
Keep this in mind...If you adding a large number of items to an ObservableCollection the UI will also update that many times. This can really gum up or freeze your UI.
A work around would be to create a new list, add all the items then set your property to the new list. This hits the UI once. Again...this is for adding a large number of items.
I'm using a PagedCollectionView to bind an ObservableCollection<T> to a DataGrid in my Silverlight app. In this case, the source collection can incur an unbounded number of updates during its lifespan. It seems, however, that if I'm using a PropertyGroupDescription to group the elements in the DataGrid then I need to reapply that grouping using PagedCollectionView.GroupDescriptions.Add(...) every time the source collection is updated with an element that doesn't fit into an existing grouping. Is there any way to make the groupings refresh/recalculate automatically?
public ObservableCollection<DataItem> DataItems
{
get { return _dataItems; }
private set { _dataItems = value; }
}
public PagedCollectionView ItemsView
{
get { return _itemsView; }
private set { _itemsView = value; }
}
public MyGridControl()
{
// Initialize data members
DataItems = new ObservableCollection<DataItem>();
ItemsView = new PagedCollectionView(GridDataItems);
Loaded += new RoutedEventHandler(MyGridControl_Loaded);
}
private void MyGridControl_Loaded(object sender, RoutedEventArgs e)
{
_myGrid.ItemsSource = ItemsView;
}
public void RegisterDataItem(DataItem item)
{
DataItems.Add(item);
/* To get grouping to work like I want, I have to add this:
* ItemsView.GroupDescriptions.Clear();
* ItemsView.GroupDescriptions.Add(new PropertyGroupDescription("GroupName"));
*/
}
public void UnregisterError(DataItem item)
{
DataItem.Remove(item);
}
Thanks, and let me know if I can provide any additional information.
So, as far as I can tell, the grouping operation appears to be a once-off operation that is performed on the data as it exists at the time that the PagedCollectionView.GroupDescriptions collection is manipulated. It seems that the desired groupings do indeed need to be re-applied when the source collection is changed.
I did find one alternative approach for my specific case. Since I'm using an ObservableCollection for the source collection of the view, I can wire up something to the CollectionChanged event of that source collection wherein the PagedCollectionView.GroupDescriptions collection gets cleared and the desired GroupDescription is then reapplied. This doesn't seem totally compliant with good OO practices, nor is it usable if the source collection for the view doesn't implement INotifyCollectionChanged.
I'll leave this open a little longer in case anyone can offer another approach, otherwise I'll just concede.
Thanks again.
Looking at the code in Reflector, the Groupings are correctly processed but if you use view in a DataGrid with IsReadOnly == true you won't see the groupings reflected while changing the source collection.