WPF-Multiple Views on ObservableCollection - wpf

I have a viewmodel containing two CollectionViews defined.
One I am using for navigation and data entry/edit.
Another I want to use for filtering purpose and show the filteration in some Listview on the form.
I don't want the main view(used for DataEntry purpose) to get affected while I applying filteration on observablecollection.
Thanks in Advance!

As long as you're using separate collection views, changing one won't affect the other. That is the point of collection views - they're independent views on the same collection.

ok, Got it! and went ahead with the same idea. But when I did so, I get Error = "The calling thread cannot access this object because a different thread owns it.". Hence my filteration doesn't work.. Following is the code-
public ICollectionView Clients { get; set; } //Used for Data-navigation/modification
public ListCollectionView CodeView { get; set; } // to be used for filteration purpose on form.
string searchText = String.Empty;
public string CompanyCodeSearch
{
get { return searchText; }
set
{
try
{
searchText = value;
OnPropertyChanged("CompanyCodeSearch");
CodeView.Filter = new Predicate<object>(cmFilterData);
}
catch (Exception ex)
{
}
}
}
private bool cmFilterData(object item)
{
bool _filteredData = false;
try
{
var value = (item as cntClient);
if (value == null || value.CompanyCode == null)
return false;
_filteredData = value.CompanyCode.StartsWith(this.CompanyCodeSearch);
return _filteredData;
}
catch (Exception ex)
{
return false;
}
}

Related

ObservableCollection deep cloning

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

How to search in a lazy-loading WPF MVVM TreeView?

I need a TreeView to represent some hierarchical data from multiple tables stored in a SQL Server CE database. Before, the data was stored in xml and was simple deserialized on startup and everything was good. Now I was asked to move data to a database and I've faced a several problems.
My first problem was that it takes quite a long time to retrieve many items from DB and build a TreeView ViewModel from this items (still not sure what is longer - to get items or to construct this tree). So I implemented lazy loading and now I'm getting items only when a TreeViewItem is expanding.
Now, I need to perform a text search over all the nodes, but to make it work, all nodes must be loaded.
I tried to load all of them but the UI freezes while the tree is loading. Doing this inside a BackgroundWorker is also impossible for me because the items are stored in an ObservableCollection and I'm getting "InvalidOperationException". Using Dispatcher helps with this but it is also freezes UI...
The excerpt from my TreeViewItem VM is below, if more code is needed please let me know. Maybe I am totally wrong with my design, so any comments are very appreciated!
public class TreeViewItemViewModel: DisplayableItem, IItemsHost
{
internal static DummyTreeViewItemViewModel _dummy = new DummyTreeViewItemViewModel();
public TreeViewItemViewModel(){}
public TreeViewItemViewModel(IDisplayableItem displayableItem)
{
Data = displayableItem;
}
public TreeViewItemViewModel(IDisplayableItem displayableItem, IDisplayableItem parent)
:this(displayableItem)
{
Parent = parent as TreeViewItemViewModel;
}
private TreeViewItemViewModel _parent;
public TreeViewItemViewModel Parent
{
get { return _parent; }
set { _parent = value; InvokePropertyChanged(new PropertyChangedEventArgs("Parent")); }
}
private IDisplayableItem _data;
public new IDisplayableItem Data
{
get { return _data; }
set { _data = value; InvokePropertyChanged(new PropertyChangedEventArgs("Data")); }
}
private bool _isSelected;
public new bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsSelected")); }
}
private bool _isEnabled=true;
public new bool IsEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsEnabled")); }
}
private bool _isVisible = true;
public new bool IsVisible
{
get { return _isVisible; }
set { _isVisible = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsVisible")); }
}
private void FillItems()
{
if (Items.Contains(_dummy))
{
Items.Remove(_dummy);
var itemshost = Data as IItemsHost;
if (itemshost != null)
{
_items = new ObservableCollection<IDisplayableItem>();
foreach (var item in itemshost.Items)//getting 'Items' actually requesting them from a database
{
var treeItem = new TreeViewItemViewModel(item, this);
_items.Add(treeItem);
}
InvokePropertyChanged(new PropertyChangedEventArgs("Items"));
}
}
}
protected bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if(value)
{
FillItems();
}
_isExpanded = value;
InvokePropertyChanged(new PropertyChangedEventArgs("IsExpanded"));
}
}
protected SObservableCollection<IDisplayableItem> _items = new SObservableCollection<IDisplayableItem>();
public SObservableCollection<IDisplayableItem> Items
{
get
{
var itemshost = Data as IItemsHost;
if (itemshost != null)
{
if (_items.Count == 0 && itemshost.Items.Count > 0)
_items.Add(_dummy);
}
return _items;
}
set { _items = value; InvokePropertyChanged(new PropertyChangedEventArgs("Items")); }
}
UPDATE: for those who would search for a similar solution - my problem was in my query method. I shouldn't open a new SQL Server CE connection each time I need to perform a query...
What about a new DB table that holds a flattened representation of the entire hierarchy, and have your search logic query this table? You'll obviously need to keep this table updated as you insert/update/delete records in the other tables.
Each record in the new table would need to include some information about where the item sits in the hierarchy, so that when you get the search results back you can load and populate just those tree nodes containing the "hits".
Since reading from database is being done asynchronously so the performance bottle-neck should be constructing View from ViewModel. I suggest the following method:
Read all essential Model data from database in one async call and store them in an object called SearchHelper.
Add a a simple property (Model.Id or Model's hash code) to every ViewModel that you create in order to find the equivalent view model of an specific model.
Create only visible ViewModels. (lazy loading for ViewModel only)
Use the SearchHelper to find matches for the search query, then using the Id or hash code of the results, you can easily locate their equivalent view models.
Please Consider:
Once loaded, SearchHelper does not update itself, so you might want to manually update it.
For this method to have optimal performance, try avoiding iteration of all nodes. instead, store the sequence of traced items (their index or Id) in order to find them in view model. if each Model item knows its parent, then the back-tracking should be easy.

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.

Binding ComboBox and ObservableCollection<KeyValue> in wpf

in My OpenViewModel i collect data:
private ObservableCollection<KeyValue> availableData;
public ObservableCollection<KeyValue> AvailableDatas
{
get { return availableData; }
set
{
if (value != availableData)
{
availableData= value;
NotifyOfPropertyChange("AvailableDatas");
}
}
}
method for collecting data:
public ObservableCollection<KeyValue> CollectData()
{
ConnectorClient client = null;
try
{
client = webservice.GetClient();
AvailableDatas = client.GetDatas();
client.Close();
}
catch (Exception ex)
{
webservice.HandleException(ex, client);
}
return AvailableDatas;
}
How to call the method CollectData in wpf and fill my COmboBox?
thx
You might simply call the method the first time the AvailableDatas property is accessed (e.g. from a binding in XAML):
private ObservableCollection<KeyValue> availableData;
public ObservableCollection<KeyValue> AvailableDatas
{
get
{
if (availableData == null)
{
availableData = CollectData();
}
return availableData;
}
set
{
if (value != availableData)
{
availableData = value;
NotifyOfPropertyChange("AvailableDatas");
}
}
}
Then you should change the CollectData method in a way that is does not also set the property:
public ObservableCollection<KeyValue> CollectData()
{
ConnectorClient client = null;
ObservableCollection<KeyValue> data = null;
try
{
client = webservice.GetClient();
data = client.GetDatas();
client.Close();
}
catch (Exception ex)
{
webservice.HandleException(ex, client);
}
return data;
}
You could override the OnActivated() event assuming you are using an IScreen implementation and load data in there, or just do it in the constructor or a custom Initialise method if you want to roll your own (or in the property accessor as someone has already said).
You can also use coroutines if you want some visual context for the user and a better tie in with CM actions
There is a nice simple implementation of a Loader class here which helps provide visual context to the user:
https://caliburnmicro.codeplex.com/wikipage?title=IResult%20and%20Coroutines&referringTitle=Documentation
This searches the visual tree for a BusyIndicator control and activates it whilst the content is loading e.g. ...
public class SomeViewModel : Screen
{
protected override void OnActivate()
{
RefreshData();
}
public void RefreshData()
{
Coroutine.BeginExecute(LoadData(), new ActionExecutionContext() { Target = this });
}
public IEnumerable<IResult> LoadData()
{
yield return Loader.Show("Loading Data...");
yield return new LoadSomeDataRoutine(client.GetDatas);
yield return Loader.Hide();
}
}
The reason to have a RefreshData method is that this also allows you to bind CM actions and allows the coroutine can grab more contextual information.
Obviously you have less need to worry about the async->sync benefits this gives in Silverlight because you are using WPF (but it still applies to async web service calls), however it still has many benefits and it also helps you to write reusable routines which become part of your application framework (e.g. some level of error handling/logging encapsulated in the IResult implementation etc)
You also mentioned filling the combobox... all you would need to do in CM is place a combobox on your control, and set it's Name property to the name of the property on your VM:
public class SomeViewModel : Screen
{
public ObservableCollection<MyObject> MyProperty { //blah blah... }
}
<UserControl .... blah>
<ComboBox x:Name="MyProperty" />
</UserControl>
This will fill the combobox with the items. You will still need to set the binding for SelectedItem/SelectedValue
I assume you know this already though - if not CM has some decent documentation:
https://caliburnmicro.codeplex.com/wikipage?title=Basic%20Configuration%2c%20Actions%20and%20Conventions&referringTitle=Documentation

Remove duplicates from listbox/combobox in WPF

I've seen answers mentioning something compact like this:here
List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
So I tried the following (syntax)
List<InfoControl> withDupes = (List<InfoControl>)listBox1.ItemsSource;
listBox1.ItemsSource = withDupes.Distinct().ToList();
but withDupes is null ? Perhaps I am retrieving the wrong data list. I added InfoControls one at a time.
Is there something else I should be implementing in InfoControl class? (Equal,hashCode)?
Thanks
Addendum 1: [ignore I should not be translating from Java :) ]
Also have (translated from a Java example, not sure it's 100% correct) declared in the InfoControl class..
public Boolean Equals(Object obj)
{ if (obj == this) { return true; }
if (!(obj is InfoControl)) { return false; }
InfoControl other = (InfoControl)obj;
return this.URL.Equals(other.URL); }
public int hashCode()
{ return this.URLFld.Content.GetHashCode(); }
Addendum 2:
When I try to use override based on the msdn link custom type example it says it is sealed :)
It does not seem distinct is stepping thru GetHashCode() and I am still getting the same listbox.items.count after distinct.
bool IEquatable<InfoControl>.Equals(InfoControl other)
{
if (Object.ReferenceEquals(other, null)) return false;
if (Object.ReferenceEquals(this, other)) return true;
return URL.Equals(other.URL);
}
public int GetHashCode(InfoControl obj)
{
return obj.URL.GetHashCode();
}
Addendum 3:
When I try override VS2010 says it is sealed? "cannot override inherited member 'System.Windows.DependencyObject.GetHashCode()' because it is sealed" what am I doing wrong?
public override int GetHashCode()
{
return URL.GetHashCode();
}
public string URL
{
get { return this.URLFld.Content.ToString() ; }
set
{
this.URLFld.Content = value;
}
}
.
Addendum 4:
public partial class InfoControl : UserControl
, IEquatable<YouTubeInfoControl>
{
private string URL_;
public string URL
{
get { return URL_; }
set
{
URL_ = value;
}
}
bool IEquatable<YouTubeInfoControl>.Equals(YouTubeInfoControl other)
{
if (Object.ReferenceEquals(other, null)) return false;
if (Object.ReferenceEquals(this, other)) return true;
return URL == other.URL;
}
public override int GetHashCode()
{
return URL.GetHashCode();
}
}
A ListBox's items can either be set via ListBox.Items or ListBox.ItemsSource, if you add items using listBox1.Items.Add this does not affect the ItemsSource which will stay null. In this case you should get your initial list from listBox1.Items.
If you're adding the InfoControl objects one at a time, the ItemSource of the listBox will remain set to NULL. You're better off binding a List to the listbox which will allow you to get the data back off of the ItemSource property later

Resources