why does my ObservableCollection not filter - wpf

i have a WPF desktop app.
I load an ObservableCollection with a list of objects.
I use the ICollectionView object to 'wrap' a filter around this ObservableCollection .
I set the filter and refresh but it does nto work so:
public class DataFilters : ViewModelBase
{
private ICollectionView _UserMappedRolesView { get; set; }
private ObservableCollection<UserMappedRoles> _UserMappedRoles;
public ObservableCollection<UserMappedRoles> UserMappedRoles
{
get
{
_UserMappedRolesView = CollectionViewSource.GetDefaultView(_UserMappedRoles);
_UserMappedRolesView.Filter = UserMappedRolesFilter;
_UserMappedRolesView.Refresh();
return _UserMappedRoles;
}
set
{
_UserMappedRoles = value;
}
}
public void LoadUserMappedRoles()
{
var baseData = InformedWorkerBusinessService.UserMappedRoles.Get();
var modelData =
from data in baseData
select new UserMappedRoles
{
Enabled = 1,
Login = data.Login,
UserMappedRolesRef = data.UserMappedRolesRef,
UserRoleRef = data.UserRoleRef
};
_UserMappedRoles = new ObservableCollection<UserMappedRoles>(modelData);
}
public string Login { get; set; }
private bool UserMappedRolesFilter(object item)
{
UserMappedRoles UserMappedRole = item as UserMappedRoles;
if (UserMappedRole.Login== Login)
{
return true;
}
else
{
return false;
}
}
}
and my test script:
UI.InformedWorkerViewModel.Models.HeartBeat VM = new UI.InformedWorkerViewModel.Models.HeartBeat();
VM.CommonData.DataFilters = new UI.InformedWorkerViewModel.Models.DataFilters();
VM.CommonData.DataFilters.LoadUserMappedRoles();
var data = VM.CommonData.DataFilters.UserMappedRoles;
VM.CommonData.DataFilters.Login = "David";
var filtered = VM.CommonData.DataFilters.UserMappedRoles;
I know my data only contains oUserMappedRoles where the Login name is 'Andy' so, by setting the Login filter name to 'David' I expect to get no records back.
I have set breakpoints everywhere and everyline of code gets 'hit'.
Have I (obviously) implemented this wrong?
Thanks

It is the ICollectionView that gets filtered, not the ObservableCollection.
So you should bind to the ICollectionView property:
<ListBox ItemsSource="{Binding UserMappedRolesView}" DisplayMemberPath="Login" />
...or look for the filtered items in this one:
var filtered = VM.CommonData.DataFilters.UserMappedRolesView;
You also need to refresh the CollectionView whenever you want to re-apply the filter, i.e. whenever your Login property is set to a new value. Something like this:
public class DataFilters : ViewModelBase
{
private ICollectionView _UserMappedRolesView;
public ICollectionView UserMappedRolesView
{
get { return _UserMappedRolesView; }
set { _UserMappedRolesView = value; NotifyPropertyChanged(); }
}
private ObservableCollection<UserMappedRoles> _UserMappedRoles;
public ObservableCollection<UserMappedRoles> UserMappedRoles
{
get
{
return _UserMappedRoles;
}
set
{
_UserMappedRoles = value;
NotifyPropertyChanged();
UserMappedRolesView = CollectionViewSource.GetDefaultView(_UserMappedRoles);
UserMappedRolesView.Filter = UserMappedRolesFilter;
UserMappedRolesView.Refresh();
}
}
public void LoadUserMappedRoles()
{
var baseData = InformedWorkerBusinessService.UserMappedRoles.Get();
var modelData =
from data in baseData
select new UserMappedRoles
{
Enabled = 1,
Login = data.Login,
UserMappedRolesRef = data.UserMappedRolesRef,
UserRoleRef = data.UserRoleRef
};
UserMappedRoles = new ObservableCollection<UserMappedRoles>(modelData);
}
private string _login;
public string Login
{
get { return _login; }
set { _login = value; _UserMappedRolesView.Refresh(); }
}
private bool UserMappedRolesFilter(object item)
{
UserMappedRoles UserMappedRole = item as UserMappedRoles;
if (UserMappedRole.Login == Login)
{
return true;
}
else
{
return false;
}
}
}

Related

How do I set "OnPropertyChanged" on Properties from Model?

I Have Two ComboBoxes for example Country and State where State ComboBox's ItemSource depends on Selected Country of First ComboBox.
Country and State Properties are defined in separate CountryModel(CountryID,CountryName) and StateModel(CountryID,StateID,StateName).
Now there is the Model "UserModel" is like :
public class UserModel:ViewModelBase
{
private string _userName;
public string UserName
{
get { return _userName; }
set { _userName = value; OnPropertyChanged("UserName"); }
}
private long _stateID;
public long StateID
{
get { return _stateID; }
set { _stateID = value; OnPropertyChanged("StateID"); }
}
private int _countryID;
public int CountryID
{
get { return _countryID; }
set { _countryID = value; OnPropertyChanged("CountryID"); }
}
}
It has a Service "UserModelService" for populating :
public class UserModelService
{
public UserModelService()
{
}
public ObservableCollection<UserModel> GetUserList()
{...}
public ObservableCollection<CountryModel> GetCountryList()
{...}
public ObservableCollection<StateModel> GetStateList()
{...}
}
Now The UserViewModel is Like:
public class UserViewModel:ViewModelBase,IPageViewModel
{
UserModelService modelService;
public UserViewModel()
{
modelService = new UserModelService();
GetData();
}
private void GetData()
{
UserList = modelService.GetUserList();
CountryList = modelService.GetCountryList();
StateList = modelService.GetStateList();
}
private UserModel _currentUser=new UserModel();
public UserModel CurrentUser
{
get { return _currentUser; }
set
{
if (value == _currentUser) return;
_currentUser = value; OnPropertyChanged("CurrentUser");
}
}
private ObservableCollection<UserModel> _userList;
public ObservableCollection<UserModel> UserList
{
get { return _userList; }
set
{
if (value == _userList) return;
_userList = value;
OnPropertyChanged("UserList");
}
}
private ObservableCollection<CountryModel> _countryList;
public ObservableCollection<CountryModel> CountryList
{
get { return _countryList; }
set { _countryList = value; OnPropertyChanged("CountryList"); }
}
private ObservableCollection<StateModel> _stateList;
public ObservableCollection<StateModel> StateList
{
get { return _stateList; }
set
{
_stateList = value;
OnPropertyChanged("StateList");
}
}
}
Now the Country and State ComboBox's ItemSource is binded to UserViewModels "CountryList" and "StateList" respectively. In this scenario how to I repopulate StateList OnPropertyChange of CurrentUser.CountryID?
EDIT: I Solved it by introducing separate property for Selected Country and OnPropertyChanged CurrentUser.CountryID=selectedCountry. Thanks
Handle the PropertyChanged event for the current user:
public UserModel CurrentUser
{
get { return _currentUser; }
set
{
if (value == _currentUser)
return;
if (_currentUser != null)
_currentUser.PropertyChanged -= OnCurrentUserPropertyChanged;
_currentUser = value;
if (_currentUser != null)
_currentUser.PropertyChanged += OnCurrentUserPropertyChanged;
OnPropertyChanged("CurrentUser");
}
}
private void OnCurrentUserPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(UserModel.CountryID))
{
UserModel userModel = (UserModel)sender;
var states = ...; //get states by userModel.CountryID
StateList = states;
}
}

Winforms: re-binding when the ViewModel changes

I'm working on a Winforms ReactiveUI app and I have a UserControl that implements IViewFor:
public partial class CustomView : UserControl, IViewFor<CustomViewModel>
{
public CustomViewModel ViewModel { get; set; }
object IViewFor.ViewModel {
get { return ViewModel; }
set { ViewModel = value as CustomViewModel; }
}
public CustomView()
{
InitializeComponent();
this.Bind(ViewModel, x => x.SomeBindingList, x => x.DataGridBindingSource.DataSource);
}
}
In the calling control, I set the ViewModel with:
customView.ViewModel = new CustomViewModel(model)
However, when data changes, customView.ViewModel is re-assigned (using the same code above) but it does not automatically re-bind. I'm assuming that's because ViewModel has no PropertyChanged event.
I could implement INotifyPropertyChanged on CustomView, but I was wondering - is there a convenience method/ReactiveUI way of doing this?
I think you are on the right track with passing a new model instead of replacing the ViewModel. I wasn't sure of your exact requirements, but here is an example that might help. Selecting a new user changes the list of contacts in the CustomView's DataGridView.
Item
public class Item
{
public string Name { get; set; }
public int Value { get; set; }
public Item(string name, int value)
{
Name = name; Value = value;
}
}
MainViewModel
public class MainViewModel : ReactiveObject
{
int _userId;
public int UserId
{
get { return _userId; }
set { this.RaiseAndSetIfChanged(ref _userId, value); }
}
ObservableAsPropertyHelper<User> _user;
public User User => _user.Value;
ObservableAsPropertyHelper<List<Item>> _userList;
public List<Item> UserList => _userList.Value;
public ReactiveCommand<User> LoadUser { get; protected set; }
public ReactiveCommand<List<Item>> LoadUserList { get; protected set; }
public MainViewModel()
{
LoadUser = ReactiveCommand.CreateAsyncObservable(_ => LoadUserImp(UserId));
_user = LoadUser.ToProperty(this, x => x.User, null);
LoadUserList = ReactiveCommand.CreateAsyncObservable(_ => LoadUserListImp());
_userList = LoadUserList.ToProperty(this, x => x.UserList, new List<Item>());
// Listens for change to UserId and loads new User.
this.WhenAnyValue(x => x.UserId).Where(id => id > 0).InvokeCommand(LoadUser);
}
private IObservable<User> LoadUserImp(int userId)
{
User user;
if (userId == 1)
{
user = new User { Id = 1, Name = "Bob" };
}
else
{
user = new User { Id = 2, Name = "Jane" };
}
return Observable.Return(user);
}
private IObservable<List<Item>> LoadUserListImp()
{
Item item1 = new Item("Bob", 1);
Item item2 = new Item("Jane", 2);
List<Item> items = new List<Item> { item1, item2 };
return Observable.Return(items);
}
}
MainView
public partial class MainView : Form, IViewFor<MainViewModel>
{
public MainViewModel ViewModel { get; set; }
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = value as MainViewModel; }
}
public MainView()
{
InitializeComponent();
List<Item> items = new List<Item>();
UserComboBox.DataSource = items;
UserComboBox.DisplayMember = "Name";
UserComboBox.ValueMember = "Value";
// Two way binding.
this.Bind(ViewModel, vm => vm.UserId, v => v.UserComboBox.SelectedValue);
// One way binding.
this.OneWayBind(ViewModel, vm => vm.UserList, v => v.UserComboBox.DataSource);
// Per Paul Betts: Invoking this in the VM constructor means that your VM class becomes more difficult to test,
// because you always have to mock out the effects of calling [LoadUserList],
// even if the thing you are testing is unrelated.
// Instead, I always call these commands in the View.
this.WhenAnyValue(v => v.ViewModel.LoadUserList)
.SelectMany(x => x.ExecuteAsync())
.Subscribe();
// This is where I would change your model, in this case the user in the CustomView.
this.WhenAnyObservable(v => v.ViewModel.LoadUser)
.Subscribe(user => customView1.ViewModel.User = user);
ViewModel = new MainViewModel();
customView1.ViewModel = new CustomViewModel();
}
}
CustomViewModel
public class CustomViewModel : ReactiveObject
{
ObservableAsPropertyHelper<List<Person>> _contacts;
public List<Person> Contacts => _contacts.Value;
User _user;
public User User
{
get { return _user; }
set { this.RaiseAndSetIfChanged(ref _user, value); }
}
public ReactiveCommand<List<Person>> LoadContacts { get; protected set; }
public CustomViewModel()
{
LoadContacts = ReactiveCommand.CreateAsyncObservable(_ => LoadContactsImp(User.Id));
_contacts = LoadContacts.ToProperty(this, x => x.Contacts, new List<Person>());
this.WhenAnyValue(vm => vm.User.Id).InvokeCommand(LoadContacts);
}
private IObservable<List<Person>> LoadContactsImp(int userId)
{
List<Person> contacts;
if (userId == 1)
{
contacts = new List<Person>()
{
new Person() { Id = 1, FirstName = "John", LastName = "Jones" },
new Person() { Id = 2, FirstName = "Beth", LastName = "Johnson" },
};
}
else
{
contacts = new List<Person>()
{
new Person() { Id = 1, FirstName = "Dave", LastName = "Smith" },
new Person() { Id = 2, FirstName = "Elizabeth", LastName = "Bretfield" },
};
}
return Observable.Return(contacts);
}
}
CustomView
public partial class CustomView : UserControl, IViewFor<CustomViewModel>
{
public CustomViewModel ViewModel { get; set; }
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = value as CustomViewModel; }
}
public CustomView()
{
InitializeComponent();
this.OneWayBind(ViewModel, vm => vm.Contacts, v => v.Contacts.DataSource);
}
}
Why the CustomView constructor is called "BridgeGeometryView"?
Are you using ReactiveList or ReactiveBindingList for SomeBindingList in the ViewModel?
Also, I recommend to not set the binding directly, use WhenActivated after InitializeComponent() method:
public BridgeGeometryView()
{
InitializeComponent();
this.WhenActivated(() =>
{
this.Bind(ViewModel, x => x.SomeBindingList, x => x.DataGridBindingSource.DataSource);
});
}

Paging ListBox with ReactiveUI and Caliburn.Micro

I'm trying to implement a paging mechanism for a listbox using Caliburn.Micro.ReactiveUI with a call to EF using ".Skip(currentPage).Take(pageSize)". I'm new to ReactiveUI and Reactive in general. I'm sure this is supposed to be easy.
I've got a single "SearchParameters" class which I needs to be observed and the search function needs to execute when any of the properties on the SearchParameters object changes.
You can see from the commented-out code that I've tried to define the class as a ReactiveObject as well. The current implementation though is with CM's PropertyChangedBase. The individual properties are bound textboxes in my view using CM's conventions:
public class SearchParameters : PropertyChangedBase
{
private string _searchTerm;
public string SearchTerm
{
get { return _searchTerm; }
set
{
if (value == _searchTerm) return;
_searchTerm = value;
NotifyOfPropertyChange(() => SearchTerm);
}
}
private int _pageSize;
public int PageSize
{
get { return _pageSize; }
set
{
if (value == _pageSize) return;
_pageSize = value;
NotifyOfPropertyChange(() => PageSize);
}
}
private int _skipCount;
public int SkipCount
{
get { return _skipCount; }
set
{
if (value == _skipCount) return;
_skipCount = value;
NotifyOfPropertyChange(() => SkipCount);
}
}
//private string _searchTerm;
//public string SearchTerm
//{
// get { return _searchTerm; }
// set { this.RaiseAndSetIfChanged(ref _searchTerm, value); }
//}
//private int _pageSize;
//public int PageSize
//{
// get { return _pageSize; }
// set { this.RaiseAndSetIfChanged(ref _pageSize, value); }
//}
//private int _skipCount;
//public int SkipCount
//{
// get { return _skipCount; }
// set { this.RaiseAndSetIfChanged(ref _skipCount, value); }
//}
}
"SearchService" has the following method which needs to execute when any one of SearchParameter's values change:
public async Task<SearchResult> SearchAsync(SearchParameters searchParameters)
{
return await Task.Run(() =>
{
var query = (from m in _hrEntities.Departments select m);
if (!String.IsNullOrEmpty(searchParameters.SearchTerm))
{
searchParameters.SearchTerm = searchParameters.SearchTerm.Trim();
query = query.Where(
x => x.Employee.LastName.Contains(searchParameters.SearchTerm) || x.Employee.FirstName.Contains(searchParameters.SearchTerm)).Skip(searchParameters.SkipCount).Take(searchParameters.PageSize);
}
return new SearchResult
{
SearchTerm = searchParameters.SearchTerm,
Matches = new BindableCollection<DepartmentViewModel>(query.Select(x => new DepartmentViewModel{ Department = x }).Skip(searchParameters.SkipCount).Take(searchParameters.PageSize))
};
});
}
Here's how I've tried to wire all of this up in MainViewModel's ctor and where Rx gets hazy for me:
public class MainViewModel : ReactiveScreen
{
private SearchParameters _searchParameters;
public SearchParameters SearchParameters
{
get { return _searchParameters; }
set
{
if (value == _searchParameters) return;
_searchParameters = value;
NotifyOfPropertyChange(() => SearchParameters);
}
}
{
public void MainViewModel()
{
var searchService = new SearchService();
//default Skip and PageSize values
SearchParameters = new Services.SearchParameters { SkipCount = 0 , PageSize = 10};
var searchParameters = this.ObservableForProperty(x => x.SearchParameters)
.Value()
.Throttle(TimeSpan.FromSeconds(.3));
var searchResults = searchParameters.SelectMany(parameters => searchService.SearchAsync(parameters));
var latestMatches = searchParameters
.CombineLatest(searchResults,
(searchParameter, searchResult) =>
searchResult.SearchTerm != searchParameter.SearchTerm
? null
: searchResult.Matches)
.Where(matches => matches != null);
_departmentViewModels = latestMatches.ToProperty(this, x => x.DepartmentViewModels);
searchParameters.Subscribe(x => Debug.WriteLine(x));
}
}
In the above example the call to SearchAsync doesn't execute. It seems that changes to SearchParameter's properties aren't being observed.
Can anyone tell me what I'm doing wrong here?
Here's how I ended up doing this although I'd be interested in hearing other solutions if anyone has suggestions. I'm not sure if this is the best way but it works:
First, I defined a computed property in my SearchParameters class that returns a string and reevaluates anytime CurrentPage, SkipCount and PageSize are updated from the View:
public string ParameterString
{
get { return String.Format("SearchTerm={0}|SkipCount={1}|PageSize={2}", SearchTerm, SkipCount, PageSize); }
}
Next, in my MainViewModel ctor I simply observe the computed rather than attempting to react to SearchTerm, SkipCount and PageSize individually (which my original question was asking how to do):
var searchTerms = this
.ObservableForProperty(x => x.SearchParameters.ParameterString)
.Value()
.Throttle(TimeSpan.FromSeconds(.3));
var searchResults = searchTerms.SelectMany(parameters => SearchService.SearchAsync(parameters));
var latestMatches = searchTerms
.CombineLatest(searchResults,
(searchTerm, searchResult) =>
searchResult.SearchTerm != searchTerm
? null
: searchResult.Matches)
.Where(matches => matches != null);
Finally, in my SearchService I parse the parameter string to get the current values:
var parameters = searchParameters.Split('|');
var searchTerm = "";
var skipCount = 0;
var pageSize = 0;
foreach (var parameter in parameters)
{
if (parameter.Contains("SearchTerm="))
{searchTerm = parameter.Replace("SearchTerm=", "");}
else if (parameter.Contains("SkipCount="))
{ skipCount = Convert.ToInt32(parameter.Replace("SkipCount=", "")); }
else if (parameter.Contains("PageSize="))
{ pageSize = Convert.ToInt32(parameter.Replace("PageSize=", "")); }
}

BindingList<> (master) with a composed BindingList<> (child) reference

I have a situation where a BindingList<> represents a collection of POCOs that have sub-collections of similar nature, Here is a sample code of two such POCOs and their respective lists:
The DirectoryTypePoco
public class DirectoryTypePoco : IBasePoco
{
public DirectoryTypePoco()
{
}
public DirectoryTypePoco(Int16 directoryTypeId, String directoryImplementation, String directoryDescription, DirectoryDefinitionPocoList directoryDefinition)
{
DirectoryTypeId = directoryTypeId;
DirectoryImplementation = directoryImplementation;
DirectoryDescription = directoryDescription;
DirectoryDefinition = directoryDefinition;
}
public Int16 DirectoryTypeId { get; set; }
public String DirectoryImplementation { get; set; }
public String DirectoryDescription { get; set; }
public DirectoryDefinitionPocoList DirectoryDefinition { get; set; }
public object GenerateEntity(GenericRepository repository, params object[] parameters)
{
var lastMaxEntityId = repository.GetQuery<DirectoryType>().Select(select => #select.DirectoryTypeId).DefaultIfEmpty().Max();
var newEntity = new DirectoryType
{
DirectoryTypeId = (short)(lastMaxEntityId + 1),
DirectoryImplementation = this.DirectoryImplementation,
DirectoryDescription = this.DirectoryDescription
};
return newEntity;
}
}
And the BindingList<DirectoryTypePoco>:
public class DirectoryTypePocoList : BindingList<DirectoryTypePoco>
{
public DirectoryTypePocoList()
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryType>();
foreach (var r in query)
{
Add(new DirectoryTypePoco(r.DirectoryTypeId, r.DirectoryImplementation, r.DirectoryDescription, new DirectoryDefinitionPocoList(r.DirectoryTypeId)));
}
}
}
public DirectoryTypePocoList(short directoryTypeId)
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryType>(where => where.DirectoryTypeId == directoryTypeId);
foreach (var r in query)
{
Add(new DirectoryTypePoco(r.DirectoryTypeId, r.DirectoryImplementation, r.DirectoryDescription, new DirectoryDefinitionPocoList(r.DirectoryTypeId)));
}
}
}
}
The second object: DirectoryDefinitionPoco
public class DirectoryDefinitionPoco : IBasePoco
{
public DirectoryDefinitionPoco()
{
}
public DirectoryDefinitionPoco(Int16 directoryTypeId, Byte parameterId, String parameterName, String parameterValidation, Boolean encryptionRequired, PocoChangeType changeType = PocoChangeType.None)
{
DirectoryTypeId = directoryTypeId;
ParameterId = parameterId;
ParameterName = parameterName;
ParameterDescription = parameterName;
ParameterRequired = false;
ParameterValidation = parameterValidation;
EncryptionRequired = encryptionRequired;
}
public Int16 DirectoryTypeId { get; set; }
public Byte ParameterId { get; set; }
public String ParameterName { get; set; }
public String ParameterDescription { get; set; }
public String ParameterValidation { get; set; }
public Boolean ParameterRequired { get; set; }
public Boolean EncryptionRequired { get; set; }
public object GenerateEntity(GenericRepository repository, params object[] parameters)
{
var masterId = (short) parameters[0];
var lastMaxEntityId = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == masterId).Select(select => #select.ParameterId).DefaultIfEmpty().Max();
var newEntity = new DirectoryDefinition
{
DirectoryTypeId = (short)parameters[0],
ParameterId = (byte)(lastMaxEntityId + 1),
ParameterName = this.ParameterName,
ParameterDescription = this.ParameterDescription,
ParameterValidation = this.ParameterValidation,
ParameterRequired = this.ParameterRequired,
EncryptionRequired = this.EncryptionRequired
};
return newEntity;
}
}
And BindingList<DirectoryDefinitionPoco>:
public class DirectoryDefinitionPocoList : BindingList<DirectoryDefinitionPoco>
{
public DirectoryDefinitionPocoList(short directoryTypeId)
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == directoryTypeId);
foreach (var r in query)
{
Add(new DirectoryDefinitionPoco(r.DirectoryTypeId, r.ParameterId, r.ParameterName, r.ParameterValidation, r.EncryptionRequired));
}
}
}
public List<DirectoryDefinition> GetSourceQuery()
{
List<DirectoryDefinition> result;
using (var repository = new GenericRepository(new PWRDbContext()))
{
result = repository.GetQuery<DirectoryDefinition>().ToList();
}
return result;
}
public List<DirectoryDefinition> GetSourceQuery(short directoryTypeId)
{
List<DirectoryDefinition> result;
using (var repository = new GenericRepository(new PWRDbContext()))
{
result = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == directoryTypeId).ToList();
}
return result;
}
}
On the form, I load the data into the grid through a BindingSource component. The child rows are added properly and the data is valid.
Here is the issue: I'm able to add new DirectoryTypePoco but when try to add a DirectoryDefinitionPoco, in the code, the the DirectoryDefinitionPocoobject that I get has a zero for it's parent object. In the above picture, the Test5.dll234 is a DirectoryTypePoco with DirectoryTypeId = 8 and all child under it are ok except the new one I create. What am I suppose to do to make sure I have Master-Child relation in this case?
Ok. It seems that there are two thing I should have noticed in my design.
The individual child Poco needs to know the parent Poco through a reference.
The DevExpress Grid has methods that allow for retrieving the attached data to a parent row while in the child view' particular row.
The first part is straightforwards: add a new property in the child poco of parent poco type.
This however, in my case, doesn't solve my issue as when I visually add a new row on the grid, the default constructor is invoked and it takes no parameters and hence the parent poco reference will remain NULL and the Ids (numeric) will be defaulted to 0
The second point helped fix my issue completely. I was able to conjure up an extension method for the XtraGrid's GridView as follows:
public static class DevExpressGridHelper
{
public static IBasePoco GetPocoFromSelectedRow(this BaseView view)
{
return (IBasePoco)view.GetRow(((GridView)view).FocusedRowHandle);
}
public static IBasePoco GetParentPocoFromSelectedRow(this GridView view)
{
if (view.ParentView !=null)
{
// return (IBasePoco)(view.ParentView).GetRow(((GridView)(view.ParentView)).FocusedRowHandle);
return (IBasePoco)((GridView)view.ParentView).GetFocusedRow();
}
return null;
}
}
And used it as follows:
private void GridMain_Level_1_RowUpdated(object sender, RowObjectEventArgs e)
{
var view = sender as GridView;
if (view == null)
{
return;
}
var pocoObject = e.Row as DirectoryDefinitionPoco;
if (pocoObject == null)
{
return;
}
var parentPocoObject = view.GetParentPocoFromSelectedRow();
if (parentPocoObject == null)
{
return;
}
if (view.IsNewItemRow(e.RowHandle))
{
Create(pocoObject, parentPocoObject);
}
else
{
Update(pocoObject);
}
}

Filter a collection with LINQ vs CollectionView

I want to filter a ObservableCollection with max 3000 items in a DataGrid with 6 columns. The user should be able to filter in an "&&"-way all 6 columns.
Should I use LINQ or a CollectionView for it? LINQ seemed faster trying some www samples. Do you have any pro/cons?
UPDATE:
private ObservableCollection<Material> _materialList;
private ObservableCollection<Material> _materialListInternal;
public MaterialBrowserListViewModel()
{
_materialListInternal = new ObservableCollection<Material>();
for (int i = 0; i < 2222; i++)
{
var mat = new Material()
{
Schoolday = DateTime.Now.Date,
Period = i,
DocumentName = "Excel Sheet" + i,
Keywords = "financial budget report",
SchoolclassCode = "1",
};
_materialListInternal.Add(mat);
var mat1 = new Material()
{
Schoolday = DateTime.Now.Date,
Period = i,
DocumentName = "Word Doc" + i,
Keywords = "Economical staticstics report",
SchoolclassCode = "2",
};
_materialListInternal.Add(mat1);
}
MaterialList = CollectionViewSource.GetDefaultView(MaterialListInternal);
MaterialList.Filter = new Predicate<object>(ContainsInFilter);
}
public bool ContainsInFilter(object item)
{
if (String.IsNullOrEmpty(FilterKeywords))
return true;
Material material = item as Material;
if (DocumentHelper.ContainsCaseInsensitive(material.Keywords,FilterKeywords,StringComparison.CurrentCultureIgnoreCase))
return true;
else
return false;
}
private string _filterKeywords;
public string FilterKeywords
{
get { return _filterKeywords; }
set
{
if (_filterKeywords == value)
return;
_filterKeywords = value;
this.RaisePropertyChanged("FilterKeywords");
MaterialList.Refresh();
}
}
public ICollectionView MaterialList { get; set; }
public ObservableCollection<Material> MaterialListInternal
{
get { return _materialListInternal; }
set
{
_materialListInternal = value;
this.RaisePropertyChanged("MaterialList");
}
}
Using ICollectionView gives you automatic collection changed notifications when you call Refresh. Using LINQ you'll need to fire your own change notifications when the filter needs to be re-run to update the UI. Not difficult, but requires a little more thought than just calling Refresh.
LINQ is more flexible that the simple yes/no filtering used by ICollectionView, but if you're not doing something complex there's not really any advantage to that flexibility.
As Henk stated, there shouldn't be a noticable performance difference in the UI.
For an interactive (DataGrid?) experience you should probabaly use the CollectionView. For a more code-oriented sorting, LINQ.
And with max 3000 items, speed should not be a (major) factor in a UI.
How about both? Thomas Levesque built a LINQ-enabled wrapper around ICollectionView.
Usage:
IEnumerable<Person> people;
// Using query comprehension
var query =
from p in people.ShapeView()
where p.Age >= 18
orderby p.LastName, p.FirstName
group p by p.Country;
query.Apply();
// Using extension methods
people.ShapeView()
.Where(p => p.Age >= 18)
.OrderBy(p => p.LastName)
.ThenBy(p => p.FirstName)
.Apply();
Code:
public static class CollectionViewShaper
{
public static CollectionViewShaper<TSource> ShapeView<TSource>(this IEnumerable<TSource> source)
{
var view = CollectionViewSource.GetDefaultView(source);
return new CollectionViewShaper<TSource>(view);
}
public static CollectionViewShaper<TSource> Shape<TSource>(this ICollectionView view)
{
return new CollectionViewShaper<TSource>(view);
}
}
public class CollectionViewShaper<TSource>
{
private readonly ICollectionView _view;
private Predicate<object> _filter;
private readonly List<SortDescription> _sortDescriptions = new List<SortDescription>();
private readonly List<GroupDescription> _groupDescriptions = new List<GroupDescription>();
public CollectionViewShaper(ICollectionView view)
{
if (view == null)
throw new ArgumentNullException("view");
_view = view;
_filter = view.Filter;
_sortDescriptions = view.SortDescriptions.ToList();
_groupDescriptions = view.GroupDescriptions.ToList();
}
public void Apply()
{
using (_view.DeferRefresh())
{
_view.Filter = _filter;
_view.SortDescriptions.Clear();
foreach (var s in _sortDescriptions)
{
_view.SortDescriptions.Add(s);
}
_view.GroupDescriptions.Clear();
foreach (var g in _groupDescriptions)
{
_view.GroupDescriptions.Add(g);
}
}
}
public CollectionViewShaper<TSource> ClearGrouping()
{
_groupDescriptions.Clear();
return this;
}
public CollectionViewShaper<TSource> ClearSort()
{
_sortDescriptions.Clear();
return this;
}
public CollectionViewShaper<TSource> ClearFilter()
{
_filter = null;
return this;
}
public CollectionViewShaper<TSource> ClearAll()
{
_filter = null;
_sortDescriptions.Clear();
_groupDescriptions.Clear();
return this;
}
public CollectionViewShaper<TSource> Where(Func<TSource, bool> predicate)
{
_filter = o => predicate((TSource)o);
return this;
}
public CollectionViewShaper<TSource> OrderBy<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, true, ListSortDirection.Ascending);
}
public CollectionViewShaper<TSource> OrderByDescending<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, true, ListSortDirection.Descending);
}
public CollectionViewShaper<TSource> ThenBy<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, false, ListSortDirection.Ascending);
}
public CollectionViewShaper<TSource> ThenByDescending<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, false, ListSortDirection.Descending);
}
private CollectionViewShaper<TSource> OrderBy<TKey>(Expression<Func<TSource, TKey>> keySelector, bool clear, ListSortDirection direction)
{
string path = GetPropertyPath(keySelector.Body);
if (clear)
_sortDescriptions.Clear();
_sortDescriptions.Add(new SortDescription(path, direction));
return this;
}
public CollectionViewShaper<TSource> GroupBy<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
string path = GetPropertyPath(keySelector.Body);
_groupDescriptions.Add(new PropertyGroupDescription(path));
return this;
}
private static string GetPropertyPath(Expression expression)
{
var names = new Stack<string>();
var expr = expression;
while (expr != null && !(expr is ParameterExpression) && !(expr is ConstantExpression))
{
var memberExpr = expr as MemberExpression;
if (memberExpr == null)
throw new ArgumentException("The selector body must contain only property or field access expressions");
names.Push(memberExpr.Member.Name);
expr = memberExpr.Expression;
}
return String.Join(".", names.ToArray());
}
}
Credit:
http://www.thomaslevesque.com/2011/11/30/wpf-using-linq-to-shape-data-in-a-collectionview/
Based on a visual complexity and number of items there really WILL be a noticable performance difference since the Refresh method recreates the whole view!!!
You need my ObservableComputations library. Using this library you can code like this:
ObservableCollection<Material> MaterialList = MaterialListInternal.Filtering(m =>
String.IsNullOrEmpty(FilterKeywords)
|| DocumentHelper.ContainsCaseInsensitive(
material.Keywords, FilterKeywords, StringComparison.CurrentCultureIgnoreCase));
MaterialList reflects all the changes in the MaterialListInternal collection. Do not forget to add the implementation of the INotifyPropertyChanged interface to Material class, so that MaterialList collection reflects the changes in material.Keywords property.

Resources