Using the standard listbox I would like to bind it to a set of objects and update a bound items collection to include those selected.
So I have something like:
_pContext = new BindingSource();
_pContext.DataSource = _gpContext;
_pContext.DataMember = "ParentEntities";
_AllChildrenListBox.DataSource = _container.ChildEntities;
_AllChildrenListBox.DataBindings.Add("MySelectedItems", _pContext, "ChildEntities", false);
_allChildrenListBox is the listbox. I created a new listbox type inheriting from ListBox so I could create an alternative SelectedItems property which would then encapsulate the logic to set/unset the items.
So the question in a nutshell is: In the above, ChildEntities is a collection of "ChildEntity" objects. My list box contains all possible ChildEntity objects and I want the elements in ChildEntities to be selected, and updated when selection changed.
I have found a way around this. It's a little ugly at the moment, but works. I can refine it later.
So first bit was the binding itself.
_AllChildrenListBox.DataBindings.Add("SelectedItems2", _pContext, "ChildEntities2", true);
The key here seemed to be setting the formatting parameter to true.
Next I needed something in SelectedItems2 that would do my dirty work. This is a mess, but works. Here's the full class:
public class MyListBox : ListBox
{
private IEnumerable<object> _original;
public IEnumerable<object> SelectedItems2
{
get
{
return UpdateSet();
}
set
{
SelectItems(value);
}
}
private IEnumerable<object> UpdateSet()
{
var listSource = _original as IListSource;
IList list = null;
if (listSource != null)
{
list = listSource.GetList();
}
var iList = _original as IList;
if (list == null && iList != null)
{
list = iList;
}
if (list == null)
{
return _original;
}
foreach (var item in SelectedItems)
{
if (!list.Contains(item))
{
list.Add(item);
}
}
foreach (var item in _original.ToList())
{
if (!SelectedItems.Contains(item))
{
list.Remove(item);
}
}
return _original;
}
private void SelectItems(IEnumerable<object> items)
{
_original = items;
var hashset = new HashSet<object>();
foreach (var item in items)
{
hashset.Add(item);
}
for(var i=0;i<Items.Count;i++)
{
SetSelected(i, hashset.Contains(Items[i]));
}
}
}
Essentially I'm breaking all the rules and assuming IEnumerable hides a more tasty list based interface, if it does it uses that to changes the underlying set.
Finally, I'm using EF (Entity Framework) and you can't call the setter of a collection property, so my entity currently looks like this: Notice how I'm duplicating all the list change operations which is totally unnecessary, but I did say this was just the first working go.
public partial class ParentEntity
{
public IEnumerable<object> ChildEntities2
{
get
{
return new List<object>(ChildEntities);
}
set
{
if (value == null)
{
return;
}
foreach (var item in ChildEntities.ToList().Where(item => !value.Contains(item)))
{
ChildEntities.Remove(item);
}
foreach (var item in value)
{
var cItem = item as ChildEntity;
if (cItem != null)
{
if (!ChildEntities.Contains(item as ChildEntity))
{
ChildEntities.Add(item as ChildEntity);
}
}
}
}
}
}
Related
I've implemented deep cloning of ObservableCollection in order to reset items to It's original state in editable Datagrid, via cancel button.
For this I have two collections - one ObservableCollection to bind Datagrid to It, and cloned List to re-initialize ObservableCollection to It's original state when needed.
My code works only first time I hit a cancel button, after that my cloned List has changes in It too.
Provided code is an example (mine is a bit longer), but It's 100% same as mine:
Model, which implements ICloneable:
public class EmployeeModel : ICloneable
{
public object Clone()
{
return MemberwiseClone();
}
public string NAME
{
get { return _name; }
set
{
if (_name != value)
{
CHANGE = true;
_name = value;
}
}
}
private string _name;
public string SURNAME
{
get { return _surname; }
set
{
if (_surname != value)
{
CHANGE = true;
_surname = value;
}
}
}
private string _surname;
///<summary>Property for tracking changes in model</summary>
public bool CHANGE { get; set; }
}
Viewmodel:
public ViewModel() : Base //Implements InotifyPropertyChanged
{
public ViewModel()
{
Task.Run(()=> GetData());
}
public ObservableCollection<EmployeeModel> Employees
{
get { return _employees; }
set { _employees = value; OnPropertyChanged();}
}
private ObservableCollection<EmployeeModel> _employees;
public List<EmployeeModel> Copy_employees
{
get { return _copy_employees; }
set { _copy_employees = value; OnPropertyChanged();}
}
private List<EmployeeModel> _copy_employees;
//Fetch data from DB
private async Task Get_data()
{
//Returns new ObservableCollection of type Employee
Employees = await _procedures.Get_employees();
if (Employees != null) //Now make a deep copy of Collection
{
Copy_employees = new List<EmployeeModel>();
Copy_employees = Employees.Select(s => (EmployeeModel)s.Clone()).ToList();
}
}
//My Command for canceling changes (reseting DataGrid)
//CanExecute happens, when model is changed - tracking via CHANGE property of EmployeeModel
public void Cancel_Execute(object parameter)
{
Employees.Clear(); //Tried with re-initializing too, but same result
foreach (var item in Copy_employees)// Reset binded ObservableCollection with old items
{
Employees.Add(item);
}
//Check if copied List really hasn't got any changes
foreach (EmployeeModel item in Copy_employees)
{
Console.WriteLine("Changes are " + item.CHANGES.ToString());
}
}
}
Output of cancel command:
1.) First time I hit cancel button:
// Changes are False
Every next time:
// Changes are True
So, as I see It from Console, my copied List get's updated when ObservableColection get's updated, even if It's not binded to DataGrid.
And It updates only a property which I changed, so List reflects ObservableCollection items.
How can I keep my original items of List<Employee>, and copy those into binded ObservableCollection anytime ?
When you return values, you do not return them, but write backing item references to the editable collection.
As a result, you have the same instances in both collections.
In the simplest case, when you return them, you also need to clone.
public void Cancel_Execute(object parameter)
{
Employees.Clear(); //Tried with re-initializing too, but same result
foreach (var item in Copy_employees)// Reset binded ObservableCollection with old items
{
Employees.Add((EmployeeModel)item.Clone());
}
//Check if copied List really hasn't got any changes
foreach (EmployeeModel item in Copy_employees)
{
Console.WriteLine("Changes are " + item.CHANGES.ToString());
}
}
Not relevant to the question, but I still advise you to use a slightly more user-friendly interface for cloneable:
public interface ICloneable<T> : ICloneable
{
new T Clone();
}
I have a ViewModel with an Observable Collection Property
public ObservableCollection<GeographicArea> CurrentSensorAreasList
{
get
{
return currentSensorAreasList;
}
set
{
if (currentSensorAreasList != value)
{
currentSensorAreasList = value;
OnPropertyChanged(PROPERTY_NAME_CURRENT_SENSOR_AREAS_LIST);
}
}
}
Then in my xaml i have a binding
ItemsSource="{Binding CurrentSensorAreasList}">
This Observable Collection is updated trought a method that can be call in the viewModel constructor or when a collectionchanged handler from another list gets called.
I just clear the list and then add a fewer new items. While debugging i see all my new items updated on the list. But the UI does not get updated.
When i regenerate the viewModel and then this update method gets call in the constructor the list gets updated in the UI.
Any ideas?? I don't know if the problem comes when i call the method from a handler.....
UPDATE #1
As requested i'm going the code when I update the list
I have tested two ways to do this update
private void UpdateList1()
{
if (globalAreaManagerList != null && OperationEntity != null)
{
CurrentSensorAreasList.Clear();
CurrentSensorAreasList.AddRange(globalAreaManagerList.Where(x => x != (OperationEntity as AreaManager)).SelectMany(areaRenderer => areaRenderer.AreaList));
//AddRange is an extension method.
}
}
private void UpdateList2()
{
if (globalAreaManagerList != null && OperationEntity != null)
{
CurrentSensorAreasList = new ObservableCollection<GeographicArea>(globalAreaManagerList.Where(x => x != (OperationEntity as AreaManager)).SelectMany(areaRenderer => areaRenderer.AreaList))
}
}
Both cases works when i call it from the constructor. Then I have Other Lists where the Areas changes, and i get notified via CollectionChanged Handlers.
private void globalAreaManagerList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (AreaManager newItem in e.NewItems)
{
newItem.AreaList.CollectionChanged += AreaList_CollectionChanged;
}
}
if (e.OldItems != null)
{
foreach (AreaManager oldItem in e.OldItems)
{
oldItem.AreaList.CollectionChanged -= AreaList_CollectionChanged;
}
}
UpdateList();
}
private void AreaList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UpdateList();
}
So when i use UpdateList1 seems to work more times but suddenly the Binding is broken and then this update does not show in the UI.
If you wish change exactly an instance of collection I recomend using DependecyProperty for that case.
Here is:
public ObservableCollection<GeographicArea> CurrentSensorAreasList
{
get { return (ObservableCollection<GeographicArea>)GetValue(CurrentSensorAreasListProperty); }
set { SetValue(CurrentSensorAreasListProperty, value); }
}
public static readonly DependencyProperty CurrentSensorAreasListProperty =
DependencyProperty.Register("CurrentSensorAreasList", typeof(ObservableCollection<GeographicArea>), typeof(ownerclass));
Where ownerclass - a name of class where you put this property.
But the better way is create only one instance of ObservaleCollection and then just change its items. I mean Add, Remove, and Clear methods.
I have the following implementation of RegionAdapter for a StackPanel but I need strict ordering of items I associate with a region can anyone help?
I want Views that Register themselves to the Region to be able to control there position maybe an index number of some sort
protected override void Adapt(IRegion region, StackPanel regionTarget)
{
region.Views.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
regionTarget.Children.Add(element);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (UIElement elementLoopVariable in e.OldItems)
{
var element = elementLoopVariable;
if (regionTarget.Children.Contains(element))
{
regionTarget.Children.Remove(element);
}
}
break;
}
};
}
How to tackle this greatly depends on whether the sorting refers to (a) the type of the view or (b) to the instance of the view. The former would be the case if you only wanted to specify that for example Views of type ViewA should be above Views of type ViewB. The latter is the case if you want to specify how several concrete instances of the same view type are sorted.
A. Sort type wise
On option is to implement a custom attribute, something like OrderIndexAttribute, which exposes an integer property:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public OrderIndexAttribute : Attribute
{
public int Index { get; }
public OrderIndexAttribute(int index)
{
Index = index;
}
}
Mark your view class with that attribute:
[OrderIndex(2)]
public ViewA : UserControl
{...}
Get the attribute of the type when adding the view to the region:
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
// Get index for view
var viewType = element.GetType();
var viewIndex= viewType.GetCustomAttribute<OrderIndexAttribute>().Index;
// This method needs to iterate through the views in the region and determine
// where a view with the specified index needs to be inserted
var insertionIndex = GetInsertionIndex(viewIndex);
regionTarget.Children.Insert(insertionIndex, element);
}
break;
B. Sort instance wise
Make your views implement an interface:
public interface ISortedView
{
int Index { get; }
}
On adding the view to the region, try casting the inserted view to the interface, read the index and then do the same as above:
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
// Get index for view
var sortedView = element as ISortedView;
if (sortedView != null)
{
var viewIndex = sortedView.Index;
// This method needs to iterate through the views in the region and determine
// where a view with the specified index needs to be inserted
var insertionIndex = GetInsertionIndex(viewIndex);
regionTarget.Children.Insert(insertionIndex, sortedView);
}
else
{ // Add at the end of the StackPanel or reject adding the view to the region }
}
I found Marc's "A. Sort type wise" case to be very helpful for my situation. I needed to sort the views into the region by using the OrderIndexAttribute and still be able to add a view if it did not actually have the OrderIndexAttribute.
As you will see below, I did this by keeping track of the view indexes in a List. The insertion index of a view without the attribute is defaulted to zero so that it sorts to the front(or top) of the StackPanel.
This original post is rather old, but maybe someone will stumble upon it as I did and will find my contribution to be helpful. Refactoring suggestions are welcome. :-)
public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel>
{
private readonly List<int> _indexList;
public StackPanelRegionAdapter(IRegionBehaviorFactory behaviorFactory)
: base(behaviorFactory)
{
_indexList = new List<int>();
}
protected override void Adapt(IRegion region, StackPanel regionTarget)
{
region.Views.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
var viewType = element.GetType();
// Get the custom attributes for this view, if any
var customAttributes = viewType.GetCustomAttributes(false);
var viewIndex = 0; // Default the viewIndex to zero
// Determine if the view has the OrderIndexAttribute.
// If it does have the OrderIndexAttribute, get its sort index.
if (HasThisAttribute(customAttributes))
{
viewIndex= viewType.GetCustomAttribute<OrderIndexAttribute>().Index;
}
// Get the insertion index
var insertionIndex = GetInsertionIndex(viewIndex);
regionTarget.Children.Insert(insertionIndex, element);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (UIElement elementLoopVariable in e.OldItems)
{
var element = elementLoopVariable;
if (regionTarget.Children.Contains(element))
{
regionTarget.Children.Remove(element);
}
}
break;
}
};
}
private static bool HasThisAttribute(IReadOnlyList<object> customAttributes)
{
// Determine if the view has the OrderIndexAttribute
if (customAttributes.Count == 0) return false;
for (var i = 0; i < customAttributes.Count; i++)
{
var name = customAttributes[i].GetType().Name;
if (name == "OrderIndexAttribute") return true;
}
return false;
}
private int GetInsertionIndex(in int viewIndex)
{
// Add the viewIndex to the index list if not already there
if (_indexList.Contains(viewIndex) == false)
{
_indexList.Add(viewIndex);
_indexList.Sort();
}
// Return the list index of the viewIndex
for (var i = 0; i < _indexList.Count; i++)
{
if (_indexList[i].Equals(viewIndex))
{
return i;
}
}
return 0;
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}
The answer from Marc and R.Evans helped me to create my own a little more generic RegionAdapter with the following improvements:
uses ViewSortHint to be compatible with Prism 6
Prism 7 / .Net 5 compatible
Helper Class for use in multiple Adapters
less code
Adapt method:
protected override void Adapt(IRegion region, StackPanel regionTarget)
{
region.Views.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
foreach (FrameworkElement item in e.NewItems)
{
regionTarget.Children.Insert(regionTarget.Children.GetInsertionIndex(item), item);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
foreach (UIElement item in e.OldItems)
{
if (regionTarget.Children.Contains(item))
{
regionTarget.Children.Remove(item);
}
}
};
}
Helper/Extension:
internal static int GetInsertionIndex(this IList items, in object newItem)
{
// Return the list index of the viewIndex
foreach (object item in items)
{
var currentIndex = item.GetType().GetCustomAttribute<ViewSortHintAttribute>()?.Hint ?? "0";
var intendedIndex = newItem.GetType().GetCustomAttribute<ViewSortHintAttribute>()?.Hint ?? "0";
if (currentIndex.CompareTo(intendedIndex) >= 0)
return items.IndexOf(item);
}
// if no greater index is found, insert the item at the end
return items.Count;
}
I have a WPF form which has many controls on it. Many (but not all) of these controls are databound to an underlying object. At certain times, such as when the Save button is pressed, I need to check all the validation rules of my controls. Is there a way to do this programatically, WITHOUT hard-coding a list of the controls to be validated? I want this to continue to work after another developer adds another control and another binding, without having to update some list of bindings to be refreshed.
In a nutshell, is there any way to retrieve the collection of all data bindings from a WPF window?
Try out my sample below. I haven't fully tested this so it may have issues. Also, performance may be questionable. Maybe others can help out to make it faster. But anyway, it seems to do the trick.
Note: A limitation to this, however, is that it may not pick up the bindings defined within Styles or DataTemplates. I'm not sure though. Needs more testing.
Anyway, the solution has three parts basically:
Use VisualTreeHelper to walk the entire visual tree.
For each item in the visual tree, get all dependency properties. Reference.
Use BindingOperations.GetBindingBase to get the binding for each property.
GetBindingsRecursive function:
void GetBindingsRecursive(DependencyObject dObj, List<BindingBase> bindingList)
{
bindingList.AddRange(DependencyObjectHelper.GetBindingObjects(dObj));
int childrenCount = VisualTreeHelper.GetChildrenCount(dObj);
if (childrenCount > 0)
{
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(dObj, i);
GetBindingsRecursive(child, bindingList);
}
}
}
DependencyObjectHelper class:
public static class DependencyObjectHelper
{
public static List<BindingBase> GetBindingObjects(Object element)
{
List<BindingBase> bindings = new List<BindingBase>();
List<DependencyProperty> dpList = new List<DependencyProperty>();
dpList.AddRange(GetDependencyProperties(element));
dpList.AddRange(GetAttachedProperties(element));
foreach (DependencyProperty dp in dpList)
{
BindingBase b = BindingOperations.GetBindingBase(element as DependencyObject, dp);
if (b != null)
{
bindings.Add(b);
}
}
return bindings;
}
public static List<DependencyProperty> GetDependencyProperties(Object element)
{
List<DependencyProperty> properties = new List<DependencyProperty>();
MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(element);
if (markupObject != null)
{
foreach (MarkupProperty mp in markupObject.Properties)
{
if (mp.DependencyProperty != null)
{
properties.Add(mp.DependencyProperty);
}
}
}
return properties;
}
public static List<DependencyProperty> GetAttachedProperties(Object element)
{
List<DependencyProperty> attachedProperties = new List<DependencyProperty>();
MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(element);
if (markupObject != null)
{
foreach (MarkupProperty mp in markupObject.Properties)
{
if (mp.IsAttached)
{
attachedProperties.Add(mp.DependencyProperty);
}
}
}
return attachedProperties;
}
}
Sample usage:
List<BindingBase> bindingList = new List<BindingBase>();
GetBindingsRecursive(this, bindingList);
foreach (BindingBase b in bindingList)
{
Console.WriteLine(b.ToString());
}
There is a better solution in .NET 4.5 and above:
foreach (BindingExpressionBase be in BindingOperations.GetSourceUpdatingBindings(element))
{
be.UpdateSource();
}
I'm using the MVVM design pattern, with a ListView bound to a ListCollectionView on the ViewModel. I also have several comboboxes that are used to filter the ListView. When the user selects an item from the combobox, the ListView is filtered for the selected item. Whenever I want to filter on top of what is already filtered, it undoes my previous filter like it never happened. The same is also true for removing a filter. Removing a filter for one combobox removes all filters and displays the original list. Is it possible to have multiple, separate filters on the same ListCollectionView?
Am I doing something wrong, or is this simply not supported? You can find a screen capture of my application here to see what I am trying to accomplish. Here's my code for filtering...
/// <summary>
/// Filter the list
/// </summary>
/// <param name="filter">Criteria and Item to filter the list</param>
[MediatorMessageSink("FilterList", ParameterType = typeof(FilterItem))]
public void FilterList(FilterItem filter)
{
// Make sure the list can be filtered...
if (Products.CanFilter)
{
// Now filter the list
Products.Filter = delegate(object obj)
{
Product product = obj as Product;
// Make sure there is an object
if (product != null)
{
bool isFiltered = false;
switch (filter.FilterItemName)
{
case "Category":
isFiltered = (product.Category.IndexOf(filter.Criteria, StringComparison.CurrentCultureIgnoreCase)) != -1 ? true : false;
break;
case "ClothingType":
isFiltered = (product.ClothingType.IndexOf(filter.Criteria, StringComparison.CurrentCultureIgnoreCase)) != -1 ? true : false;
break;
case "ProductName":
isFiltered = (product.ProductName.IndexOf(filter.Criteria, StringComparison.CurrentCultureIgnoreCase)) != -1 ? true : false;
break;
default:
break;
}
return isFiltered;
}
else
return false;
};
}
}
Every time you set Filter property you reset previous filter. This is a fact. Now how can you have multiple filters?
As you know, there are two ways to do filtering: CollectionView and CollectionViewSource. In the first case with CollectionView we filter with delegate, and to do multiple filters I'd create a class to aggregate custom filters and then call them one by one for each filter item. Like in the following code:
public class GroupFilter
{
private List<Predicate<object>> _filters;
public Predicate<object> Filter {get; private set;}
public GroupFilter()
{
_filters = new List<Predicate<object>>();
Filter = InternalFilter;
}
private bool InternalFilter(object o)
{
foreach(var filter in _filters)
{
if (!filter(o))
{
return false;
}
}
return true;
}
public void AddFilter(Predicate<object> filter)
{
_filters.Add(filter);
}
public void RemoveFilter(Predicate<object> filter)
{
if (_filters.Contains(filter))
{
_filters.Remove(filter);
}
}
}
// Somewhere later:
GroupFilter gf = new GroupFilter();
gf.AddFilter(filter1);
listCollectionView.Filter = gf.Filter;
To refresh filtered view you can make a call to ListCollectionView.Refresh() method.
And in the second case with CollectionViewSource you use Filter event to filter collection. You can create multiple event handlers to filter by different criteria. To read more about this approach check this wonderful article by Bea Stollnitz: How do I apply more than one filter? (archive)
Hope this helps.
Cheers, Anvaka.
Every time the user filters, your code is replacing the Filter delegate in your collection view with a new, fresh one. Moreover, the new one only checks the particular criteria the user just selected with a ComboBox.
What you want is a single filter handler that checks all criteria. Something like:
public MyViewModel()
{
products = new ObservableCollection<Product>();
productsView = new ListCollectionView(products);
productsView.Filter += FilterProduct;
}
public Item SelectedItem
{
//get,set omitted. set needs to invalidate filter with refresh call
}
public Type SelectedType
{
//get,set omitted. set needs to invalidate filter with refresh call
}
public Category SelectedCategory
{
//get,set omitted. set needs to invalidate filter with refresh call
}
public ICollection<Product> FilteredProducts
{
get { return productsView; }
}
private bool FilterProduct(object o)
{
var product = o as Product;
if (product == null)
{
return false;
}
if (SelectedItem != null)
{
// filter according to selected item
}
if (SelectedType != null)
{
// filter according to selected type
}
if (SelectedCategory != null)
{
// filter according to selected category
}
return true;
}
Your view can now just bind to the appropriate properties and the filtering will just work.