MVVM, two synchronized Grid Data - wpf

I want to do a control for adding and removing items from a two list (Selected and UnSelected), like this:
But I can't find a good way to do this; how can I use the GridData (or similar control like GridControl of Devexpress) for binding two List and modify it?
Problems:
I can't use ObservableCollection with this control
I can't bind the SelectedItems
If you have any suggestion or sample for some work, it will be a great help

Can you use two observable collections? One for selected and one for unselected. It seems to be the easiest way to implement such functionality.
public class MainViewModel
{
private readonly ObservableCollection<Item> _selectedItems = new ObservableCollection();
private readonly ObservableCollection<Item> _unselectedItems = new ObservableCollection();
public IEnumerable<Item> SelectedItems { get { return _selectedItems; } }
public IEnumerable<Item> UnselectedItems { get { return _unselectedItems; } }
private void UnselectItems()
{
MoveFromOneCollectionToAnother(_unselectedItems, _selectedItems, ...);
}
private void SelectItems()
{
MoveFromOneCollectionToAnother(_selectedItems, _unselectedItems, ...);
}
private void MoveFromOneCollectionToAnother(ICollection<Item> source, ICollection<Item> destination, IEnumerable<Item> itemsToMove)
{
foreach (var item in itemsToMove)
{
source.Remove(item);
destination.Add(item);
}
}
}

Related

WinForms binding Generic List - checkable business object to a Grid

We all like how easy it is to bind with WPF. Now I am back working with Winforms and I am looking for a nice way to bind my grid to a List of Checkable of BusinessObject (I am sticking with BindingList for Winforms). So I am essentially just adding a checkable to my business object.
I am using a grid as there will be multiple columns where the user would edit (in this scenario Name and Description on the business object) - as well as adding new objects to the grid and removing from it. Checked list box does not fit for this purpose as I want to edit columns.
For this I am using .NET 4.
I basically want to reduce the amount of UI code in the scenario so I am using a view model based approach which will populate the list. I want the user to be able to check a box alongside each of the business object properties.
Sure I can use inheritance, but if I want to apply the same mechanism against a lot of business objects (having lots of different screens where you check items in a list for the different business objects). Maybe this would be the way to go - but I have my doubts.
Now depending upon the choice of grid - I am using Infragistics - the functionality would hopefully be pretty similar conceptually.
I thought about wrapping the business object up in a Checkable generic class:
using System;
using System.Collections.Generic;
public class Checkable<T> : ModelBase
{
public Checkable(T value)
{
_value = value;
}
private T _value;
public T Value
{
get
{
return _value;
}
set
{
if (!EqualityComparer<T>.Default.Equals(_value, value))
{
_value = value;
OnPropertyChanged("Value");
}
}
}
private bool _checked;
public bool Checked
{
get { return _checked; }
set
{
if (_checked != value)
{
_checked = value;
OnPropertyChanged("Checked");
}
}
}
}
I have made up a business object for this scenario:
public class BusinessObject : ModelBase
{
public BusinessObject()
{
}
public BusinessObject(RepairType repairType)
{
_name = repairType.Name;
_id = repairType.Id;
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
private string _description;
public string Description
{
get { return _description; }
set
{
if (description != value)
{
description = value;
OnPropertyChanged("Description");
}
}
}
private int _id;
public int Id
{
get { return _id; }
set
{
if (_id != value)
{
_id = value;
OnPropertyChanged("Id");
}
}
}
}
Where ModelBase just implements the INotifyPropertyChanged:
public abstract class ModelBase : INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T field, T value, string propertyName = null)
{
if (object.Equals(field, value)) { return false; }
field = value;
OnPropertyChanged(propertyName);
return true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing)
{
if (disposing)
{
PropertyChanged = null;
}
}
}
So potentially for my grid datasource I would define:
// in view model
var datasource = new BindingList<Checkable<BusinessObject>>();
... populate list
grid.DataSource = viewmodel.DataSource;
So of course my scenario fails at the minute as Value is the BusinessObject reference which has the properties I want to bind to, and Checked is the property for a checkbox which I also want to bind to.
I am trying to kick start the old grey matter with some ideas on this. I don't really like writing code to define grid columns. However, the Infragistics grid has been ok for data binding directly to the BusinessObject at design time. Its possible to add an unbound column (checkbox for my scenario) and handle the checking/unchecking of items manually (which I might potentially have to do).
I am wondering if I am missing any neat tricks with Winform binding of late having missed out with Linq and Entity Framework when they appeared many years ago.

WPF DependencyProperty chain notification

im exploring WPF world, i find a great example on the web about how to use binding on xml
http://www.codeproject.com/Articles/37854/How-to-Perform-WPF-Data-Binding-Using-LINQ-to-XML
Now im trying to extends this example: i want to create a "class in the middle" between the XElement and the UI and bind all togheder in a chain so, if i have a modification into the xml, then i have the property in the middle class updated then the UI updated too.
Here some code:
This is the class that wrap the XElement
public class XElementDataProvider : ObjectDataProvider
{
public XElementDataProvider()
{
ObjectInstance = XElement.Load(#"C:\MyFile.xml");
}
private static XElementDataProvider instance;
public static XElementDataProvider Instance
{
get
{
if (instance == null)
{
instance = new XElementDataProvider();
}
return instance;
}
}
}
This is the MiddleClass
public class MiddleClass : DependencyObject
{
XElementDataProvider xElementDataProvider;
XElement myxml;
public MiddleClass()
{
//here i get my dataprovider
xElementDataProvider = XElementDataProvider.Instance;
myxml = xElementDataProvider.Data as XElement;
//i bind my internal collection to the Elements...
Binding binding = new Binding("Elements[book]")
{
Source = myxml,
Mode = BindingMode.Default//here i cant use TwoWay, give me //back an exeption
};
BindingOperations.SetBinding(this, XBookListProperty, binding);
//just to have confirmation of the adding
myxml.Changed += new EventHandler<XObjectChangeEventArgs (myxml_Changed);
}
void myxml_Changed(object sender, XObjectChangeEventArgs e)
{
}
//i use a DependencyProperty to have also a change callback
public static readonly DependencyProperty XBookListProperty =
DependencyProperty.Register("XBookList", typeof(IEnumerable),
typeof(MiddleClass),
new PropertyMetadata(XBookPropertyChanged)
);
//here i have a notification only at start but no when i add a new book
private static void XBookPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MiddleClass middleClass = d as MiddleClass;
middleClass.XBookPropertyChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
private void XBookPropertyChanged(IEnumerable old, IEnumerable newValue)
{
}
//this is the propery i finally want to expose to the UI but im not able //to keep updated
public List<Book> bookList;
public List<Book> BookList
{
get
{
return bookList;
}
set
{
bookList = value;
}
}
//this is my internal list binded to the xml
private IEnumerable XBookList
{
get
{
return (IEnumerable)GetValue(XBookListProperty);
}
set
{
SetValue(XBookListProperty, value);
}
}
//here i try to add a book addind direcly to the xml//i expect a //notification of propery changed...but nothing
public bool AddBook(string name)
{
XElement newWorkSheet = new XElement("Book",
new XAttribute("Name", name)
);
myxml.Add(newWorkSheet);
return true;
}
Book is a class thar repersents a book, let say it has only a name propery for now.
The UI class misses but it should bind on public List<Book> BookList and show books names to the user in a ListBox
Enyone knows why i dont recive any notification...or what i have to do to keep the public List<Book> BookList synchronized with private IEnumerable<XBookList>?
OK, after many attempts, the only solution I found is this one:
to have notifications when something changes in the IEnumerable<XBookList> you need to clear it ad rebind after you modify it.
In this way you have a first, not used notification, about the clear and then another notification about the new set.
Then in the handler you can synchronize the new list with the old one.
public bool AddBook(string name)
{
XElement newWorkSheet = new XElement("Book",
new XAttribute("Name", name)
);
myxml.Add(newWorkSheet);
ClearValue(XBookListProperty);
Binding binding = new Binding("Elements[book]")
{
Source = myxml,
Mode = BindingMode.Default
};
BindingOperations.SetBinding(this, XBookListProperty, binding);
return true;
}

Communication between 2 ListBoxes in WPF

I want to build a WPF backend app for a shop. And one view should contain 2 listboxes. 1 for the items which one can buy and 1 for the categories.
I want to grey out items based on selection. Now more details:
So far my view model has an ObservableCollection<ShopItem>
and the class ShopItem has a price, title and a list of Categories
I want to bind ShopItems to 1 ListBox and the Distinct Category to another 2nd ListBox
Since a ShopItem can have multiple Categories I want to gray out all other categories beside the one that belong to the Selected ShopItem. So selection in my first listbox should control appereance in my 2nd listbox.
On the other hand side, when I select a category I would like to gray out all other ShopItems beside the ones that belong to that category. So again listbox 2 should also affect appereance in listbox 1.
With "grayed out" I mean the items should have another style.
I saw something about MultiTrigger that can swap out Template Styling based on conditions.
I am not sure if I can just bind my ObservableCollection<ShopItem> or would need to have two lists here. Do I need some pub/sub between the two lists. I would like to avoid to foreach over all elements in viewmodel each selection changes, any thoughts here?
I'm scratching my head how to solve this right now. Any suggestions would be great...
I am not sure you can get away with iterating over a collection as to change the list to grey out then each item in the list must notify of the change. The following is an example of how you could do it. Where there is IsSelected you can define a ValueConverter to change the font colour.
class ViewModel : ViewModelBase
{
//displayed on the first list
public ObservableCollection<ShopItemViewModel> Shops { get; private set; }
//displayed on the second list
public ObservableCollection<CategoryViewModel> AllCategories { get; private set; }
//when the user clicks an item on the first list
private ShopItemViewModel _selectedShop;
public ShopItemViewModel SelectedShop
{
get { return _selectedShop; }
set
{
_selectedShop = value;
RaisePropertyChanged("SelectedShop");
foreach (CategoryViewModel cat in AllCategories)
cat.Refresh();
}
}
//when the user clicks an item on the second list
private CategoryViewModel _selectedCat;
public CategoryViewModel SelectedCategory
{
get { return _selectedCat; }
set
{
_selectedCat = value;
RaisePropertyChanged("SelectedCategory");
foreach (ShopItemViewModel shops in Shops)
shops.Refresh();
}
}
}
class ShopItemViewModel : ViewModelBase
{
public ObservableCollection<CategoryViewModel> Categories { get; private set; }
public ShopItemViewModel(ViewModel vm)
{
_vm = vm;
}
private ViewModel _vm;
public void Refresh()
{
RaisePropertyChanged("IsSelected");
}
public bool IsSelected
{
get
{
if (_vm.SelectedCategory != null)
{
return Categories.Contains(_vm.SelectedCategory);
}
return true;
}
}
}
class CategoryViewModel : ViewModelBase
{
public CategoryViewModel(ViewModel vm)
{
_vm = vm;
}
private ViewModel _vm;
public string Title { get; set; }
public void Refresh()
{
RaisePropertyChanged("IsSelected");
}
public bool IsSelected
{
get
{
if (_vm.SelectedShop != null)
{
return _vm.SelectedShop.Categories.Contains(this);
}
return false;
}
}
}

DataGridViewColumn.DataPropertyName Property

I have a DataGridView control and I want to populate it with data.
I use DataSource property
// dgvDealAsset is DataGridView
private void DealAssetListControl_Load(object sender, EventArgs e)
{
dgvDealAssets.AutoGenerateColumns = false;
dgvDealAssets.DataSource = DealAssetList.Instance.Values.ToList();
}
Now problem number one. The class of my collection does not contain only simple types that I can map to columns using DataPropertyName. This is the class that is contained in collection.
class MyClass
{
public String Name;
MyOtherClass otherclass;
}
class MyOtherClass
{
public String Name;
}
Now I am binding properties of MyClass to columns
col1.DataPropertyName = "Name" // Ok
col2.DataPropertyName = "otherclass" // Not OK - I will have empty cell
The problem is that I want to display otherclass.Name field. But if I try to write
col2.DataPropertyName = "otherclass.Name"
I get empty cell.
I tried to manually set the column
private void DealAssetListControl_Load(object sender, EventArgs e)
{
dgvDealAssets.AutoGenerateColumns = false;
dgvDealAssets.DataSource = DealAssetList.Instance.Values.ToList();
// iterate through rows and set the column manually
foreach (DataGridViewRow row in dgvDealAssets.Rows)
{
row.Cells["Column2"].Value = ((DealAsset)row.DataBoundItem).otherclass.Name;
}
But this foreach cycle takes about minute to complete (2k elements). How to solve this problem?
DataGridView doesn't support databinding to child properties. For more info, check this post
I like the solution that uses the CellFormatting event.
Problem nr.1:
Try to do the following:
extend MyOtherClass from Object (this step might not be needed)
and override, or create, method ToString().
That should do it.
In case you want to use many child elements like this:
class MyClass
{
public int Id;
public MyOtherClass OtherClass;
}
class MyOtherClass
{
public string Name;
public int Number;
}
How about
1st solution
Set value for each cell in some event (mabye other one is better), manually, after setting datasource, for example:
private void dgv_CellFormatting( object sender, DataGridViewCellFormattingEventArgs e )
{
MyClass data = dgv.Rows[ e.RowIndex ].DataBoundItem as MyClass;
dgv.Rows[ e.RowIndex ].Cells[ "colName" ].Value = data.OtherClass.Name;
dgv.Rows[ e.RowIndex ].Cells[ "colNumber" ].Value = data.OtherClass.Number;
}
2nd solution
What about creating a DataTable from the data and then bind it?
I'd be thankful for any opinion ;-)
It sounds like the DataGridView's virtual mode would solve your problem. In virtual mode, the DataGridView will fire an event whenever it needs to display a cell. The event lets you populate the cell however you please. The advantage of virtual mode is the system only needs to pull the data that's actually being displayed, so there's no slow start-up time while you load everything.
private void my_init_function() {
datagridview.VirtualMode = true;
datagridview.CellValueNeeded += new System.Windows.Forms.DataGridViewCellValueEventHandler(datagridview_CellValueNeeded);
}
private void datagridview_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
e.Value = get_my_data(e.RowIndex, e.ColumnIndex);
}
The way of databinding a specific column of a datagrid to a child property of the datagrid's datasource is using the DataGridView.Column.Tag property, along with the ToString() override method inside the child object. It goes as follows:
public class Car
{
public int Id { get; set; }
public int Name { get; set; }
public string Colour { get; set; }
public Wheel Wheel { get; set; }
}
public class Wheel
{
public string WheelName { get; set; }
public override string ToString()
{
return WheelName;
}
}
public class Main
{
private void LoadGrid(List<Car> data)
{
this.dataGridView.Columns["Wheel"].Tag = "WheelName";
}
}

CollectionViewSource Filter not refreshed when Source is changed

I have a WPF ListView bound to a CollectionViewSource. The source of that is bound to a property, which can change if the user selects an option.
When the list view source is updated due to a property changed event, everything updates correctly, but the view is not refreshed to take into account any changes in the CollectionViewSource filter.
If I attach a handler to the Changed event that the Source property is bound to I can refresh the view, but this is still the old view, as the binding has not updated the list yet.
Is there a decent way to make the view refresh and re-evaluate the filters when the source changes?
Cheers
Updating the CollectionView.Filter based on a PropertyChanged event is not supported by the framework.
There are a number of solutions around this.
1) Implementing the IEditableObject interface on the objects inside your collection, and calling BeginEdit and EndEdit when changing the property on which the filter is based.
You can read more about this on the Dr.WPF's excellent blog here : Editable Collections by Dr.WPF
2) Creating the following class and using the RefreshFilter function on the changed object.
public class FilteredObservableCollection<T> : ObservableCollection<T>
{
public void RefreshFilter(T changedobject)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, changedobject, changedobject));
}
}
Example:
public class TestClass : INotifyPropertyChanged
{
private string _TestProp;
public string TestProp
{
get{ return _TestProp; }
set
{
_TestProp = value;
RaisePropertyChanged("TestProp");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
FilteredObservableCollection<TestClass> TestCollection = new FilteredObservableCollection<TestClass>();
void TestClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "TestProp":
TestCollection.RefreshFilter(sender as TestClass);
break;
}
}
Subscribe to the PropertyChanged event of the TestClass object when you create it, but don't forget to unhook the eventhandler when the object gets removed, otherwise this may lead to memory leaks
OR
Inject the TestCollection into the TestClass and use the RefreshFilter function inside the TestProp setter.
Anyhow, the magic here is worked by the NotifyCollectionChangedAction.Replace which updates the item entirely.
Are you changing the actual collection instance assigned to the CollectionViewSource.Source, or are you just firing PropertyChanged on the property that it's bound to?
If the Source property is set, the filter should be recalled for every item in the new source collection, so I'm thinking something else is happening. Have you tried setting Source manually instead of using a binding and seeing if you still get your behavior?
Edit:
Are you using CollectionViewSource.View.Filter property, or the CollectionViewSource.Filter event? The CollectionView will get blown away when you set a new Source, so if you had a Filter set on the CollectionView it won't be there anymore.
I found a specific solution for extending the ObservableCollection class to one that monitors changes in the properties of the objects it contains here.
Here's that code with a few modifications by me:
namespace Solution
{
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e != null) // There's been an addition or removal of items from the Collection
{
Unsubscribe(e.OldItems);
Subscribe(e.NewItems);
base.OnCollectionChanged(e);
}
else
{
// Just a property has changed, so reset the Collection.
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
protected override void ClearItems()
{
foreach (T element in this)
element.PropertyChanged -= ContainedElementChanged;
base.ClearItems();
}
private void Subscribe(IList iList)
{
if (iList != null)
{
foreach (T element in iList)
element.PropertyChanged += ContainedElementChanged;
}
}
private void Unsubscribe(IList iList)
{
if (iList != null)
{
foreach (T element in iList)
element.PropertyChanged -= ContainedElementChanged;
}
}
private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e);
// Tell the Collection that the property has changed
this.OnCollectionChanged(null);
}
}
}
Maybe a bit late to the party but just in case
You can also use CollectionViewSource.LiveSortingProperties
I found it through this blog post.
public class Message : INotifyPropertyChanged
{
public string Text { get; set; }
public bool Read { get; set; }
/* for simplicity left out implementation of INotifyPropertyChanged */
}
public ObservableCollection<Message> Messages {get; set}
ListCollectionView listColectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(Messages);
listColectionView.IsLiveSorting = true;
listColectionView.LiveSortingProperties.Add(nameof(Message.Read));
listColectionView.SortDescriptions.Add(new SortDescription(nameof(Message.Read), ListSortDirection.Ascending));
I found a relatively simple method to do this.
I changed the readonly ICollectionView property to get/set and added the raised property event:
Property TypeFilteredCollection As ICollectionView
Get
Dim returnVal As ICollectionView = Me.TypeCollection.View
returnVal.SortDescriptions.Add(New SortDescription("KeyName", ListSortDirection.Ascending))
Return returnVal
End Get
Set(value As ICollectionView)
RaisePropertyChanged(NameOf(TypeFilteredCollection))
End Set
End Property
Then to update, i just used:
Me.TypeFilteredCollection = Me.TypeFilteredCollection
This clearly won't work if you don't have somewhere to trigger that update though.

Resources