Paging ListBox with ReactiveUI and Caliburn.Micro - wpf

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=", "")); }
}

Related

Unsubscribe from SceneView draw calls when Edited Unity PropertyDrawer Array element got deleted

I'm making an editor in PropertyDrawer using SceneView.duringSceneGui. So it involves subscribing to SceneView.duringSceneGui when a property needs to draw stuff in SceneView and unsubscribing when it's gone. However I have no idea how to know if edited array element was removed from an array. It still exists in the memory and SceneView.duringSceneGui subscribed method is still there. I need to know when to stop editing and unsubscribe from it.
I guess I need to implement some context object, to store property value, edited object, PropertyDrawer and that subscription method should be there, to be able to unsubscribe exactly that editor... Although there may be only one editor running at once.
Does anybody found that out? Couldn't find anything with PropertyDrawers and array elements being deleted or removed.
TL.DR. Does Unity has an event to tell that PropertyDrawer's array element was removed or is there a simple or neat way to figure this out?
So, while making an example, I've solved my problem by getting property value every SceneView draw call and on catching an exception or if that value isn't edited stopped editor. I've added [NonSerialized] to _IsInEditMode to fix a new issue that I caught at the last moment, so that one is crucial.
Not sure if it's the best way to do that. If anybody will ever need to make a SceneView editor for some class, here's the example that works on arrays and lists also. Just separate it into 3 files and put them in respective folders, like Editor/ for MyClassDrawer.
using InspectorSerializedUtility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MyScript : MonoBehaviour
{
public MyClass field;
public List<MyClass> list = new List<MyClass>();
}
[Serializable]
public class MyClass
{
#if UNITY_EDITOR
[NonSerialized]
public bool _IsInEditMode;
#endif
public Vector3 position;
public void Reset()
{
position = Vector3.zero;
#if UNITY_EDITOR
_IsInEditMode = false;
#endif
}
}
[CustomPropertyDrawer(typeof(MyClass))]
public class MyClassDrawer : PropertyDrawer
{
public MyClass value;
public Transform targetTransform;
private Tool internalTool;
bool editorStarted {
get => value?._IsInEditMode ?? false;
set {
if (this.value != null)
this.value._IsInEditMode = value;
}
}
private SerializedProperty currentProperty;
private SerializedProperty drawerProperty;
private static MyClassDrawer currentlyEditedDrawer;
string editorButtonText(bool isInEditMode) => isInEditMode ? "Stop Editing" : "Start Editing";
Color editorButtonColor(bool isInEditMode) => isInEditMode ? Color.red + Color.white / 2f : Color.white;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
//Debug.Log("OnGUI");
drawerProperty = property;
targetTransform = ((Component)property.serializedObject.targetObject).transform;
var val = property.GetValue<MyClass>();
GUI.color = editorButtonColor(val._IsInEditMode);
var toggle = GUI.Toggle(position, val._IsInEditMode, editorButtonText(val._IsInEditMode), "Button");
if (toggle != val._IsInEditMode)
{
if (toggle && currentlyEditedDrawer != null && currentlyEditedDrawer.editorStarted)
currentlyEditedDrawer.StopEditor();
value = val;
currentlyEditedDrawer = this;
if (toggle)
StartEditor();
else
StopEditor();
}
GUI.color = Color.white;
}
public void OnDrawScene(SceneView sv) => OnDrawScene();
public void OnDrawScene()
{
//Debug.Log("OnDrawScene");
MyClass value = null;
try
{
value = currentProperty.GetValue<MyClass>();
if (!value._IsInEditMode)
{
StopEditor();
return;
}
} catch
{
StopEditor();
return;
}
var m = Handles.matrix;
Handles.matrix = targetTransform.localToWorldMatrix;
if (Tools.current == Tool.Move)
{
internalTool = Tool.Move;
Tools.current = Tool.None;
}
if (internalTool == Tool.Move)
{
var pos = Handles.PositionHandle(value.position, Quaternion.identity);
if (value.position != pos)
{
Undo.RecordObject(targetTransform, "position changed");
value.position = pos;
}
}
Handles.matrix = m;
}
public void StartEditor()
{
currentProperty = drawerProperty;
editorStarted = true;
Debug.Log("StartEditor");
Subscribe();
CallAllSceneViewRepaint();
}
private void Subscribe()
{
Unsubscribe();
SceneView.duringSceneGui += OnDrawScene;
Selection.selectionChanged += StopEditor;
EditorSceneManager.sceneClosed += StopEditor;
AssemblyReloadEvents.beforeAssemblyReload += StopEditor;
}
public void StopEditor(Scene s) => StopEditor();
public void StopEditor()
{
Tools.current = internalTool;
editorStarted = false;
Unsubscribe();
currentProperty = null;
CallAllSceneViewRepaint();
}
private void Unsubscribe()
{
SceneView.duringSceneGui -= OnDrawScene;
Selection.selectionChanged -= StopEditor;
EditorSceneManager.sceneClosed -= StopEditor;
AssemblyReloadEvents.beforeAssemblyReload -= StopEditor;
}
private void CallAllSceneViewRepaint()
{
foreach (SceneView sv in SceneView.sceneViews)
sv.Repaint();
}
}
namespace InspectorSerializedUtility
{
/// <summary>
/// https://gist.github.com/douduck08/6d3e323b538a741466de00c30aa4b61f
/// </summary>
public static class InspectorSeriallizedUtils
{
public static T GetValue<T>(this SerializedProperty property) where T : class
{
try
{
if (property.serializedObject.targetObject == null) return null;
}
catch
{
return null;
}
object obj = property.serializedObject.targetObject;
string path = property.propertyPath.Replace(".Array.data", "");
string[] fieldStructure = path.Split('.');
Regex rgx = new Regex(#"\[\d+\]");
for (int i = 0; i < fieldStructure.Length; i++)
{
if (fieldStructure[i].Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldStructure[i].Where(c => char.IsDigit(c)).ToArray()));
obj = GetFieldValueWithIndex(rgx.Replace(fieldStructure[i], ""), obj, index);
}
else
{
obj = GetFieldValue(fieldStructure[i], obj);
}
}
return (T)obj;
}
public static bool SetValue<T>(this SerializedProperty property, T value) where T : class
{
object obj = property.serializedObject.targetObject;
string path = property.propertyPath.Replace(".Array.data", "");
string[] fieldStructure = path.Split('.');
Regex rgx = new Regex(#"\[\d+\]");
for (int i = 0; i < fieldStructure.Length - 1; i++)
{
if (fieldStructure[i].Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldStructure[i].Where(c => char.IsDigit(c)).ToArray()));
obj = GetFieldValueWithIndex(rgx.Replace(fieldStructure[i], ""), obj, index);
}
else
{
obj = GetFieldValue(fieldStructure[i], obj);
}
}
string fieldName = fieldStructure.Last();
if (fieldName.Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldName.Where(c => char.IsDigit(c)).ToArray()));
return SetFieldValueWithIndex(rgx.Replace(fieldName, ""), obj, index, value);
}
else
{
Debug.Log(value);
return SetFieldValue(fieldName, obj, value);
}
}
private static object GetFieldValue(string fieldName, object obj, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
return field.GetValue(obj);
}
return default(object);
}
private static object GetFieldValueWithIndex(string fieldName, object obj, int index, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
object list = field.GetValue(obj);
if (list.GetType().IsArray)
{
return ((object[])list)[index];
}
else if (list is IEnumerable)
{
return ((IList)list)[index];
}
}
return default(object);
}
public static bool SetFieldValue(string fieldName, object obj, object value, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
field.SetValue(obj, value);
return true;
}
return false;
}
public static bool SetFieldValueWithIndex(string fieldName, object obj, int index, object value, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
object list = field.GetValue(obj);
if (list.GetType().IsArray)
{
((object[])list)[index] = value;
return true;
}
else if (value is IEnumerable)
{
((IList)list)[index] = value;
return true;
}
}
return false;
}
}
}

why does my ObservableCollection not filter

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

Cross threading exception accessing viewmodel property set from code behind

I have a WPF MVVM app that displays drug information in a telerik grid view. I want to do some filtering and paging in the view model but I keep getting cross-thread exceptions. The initial load works fine. I can change pages/page size without a problem. When I filter the grid, I create an IEnumerable of FilterDescriptors in the code behind and then set it on the Filters property of the view model. At this point, I get the cross-thread exception when doing the query using the filters. I have tried everything I can think of but can't get it to work.
public partial class DrugEdit : Page
{
public DrugEdit()
{
InitializeComponent();
}
private void RadGridView1_Filtered(object sender, Telerik.Windows.Controls.GridView.GridViewFilteredEventArgs e)
{
DrugEditViewModel vm = this.DataContext as DrugEditViewModel;
Telerik.Windows.Controls.RadGridView gv = sender as Telerik.Windows.Controls.RadGridView;
if (gv.FilterDescriptors == null || gv.FilterDescriptors.Count == 0)
vm.Filters = null;
else
{
List<FilterDescriptor> filters = (vm.Filters==null? new List<FilterDescriptor>(): vm.Filters.ToList());
foreach (FilterDescriptor r in e.Removed)
{
var fnd = filters.FirstOrDefault(x => x.Member == r.Member);
if (fnd == null) continue;
filters.Remove(fnd);
}
foreach (FilterDescriptor a in e.Added)
{
var fnd = filters.FirstOrDefault(x => x.Member == a.Member);
if (fnd == null)
{
filters.Add(new FilterDescriptor(a.Member, a.Operator, a.Value, false, a.MemberType));
}
else
{
fnd.Operator = a.Operator;
fnd.Value = a.Value;
}
}
vm.Filters = filters;
}
}
}
public class DrugEditViewModel
: ViewModelBase
{
private Data.DBContext ctx = new Data.DBContext();
private List<FilterDescriptor> FiltersValue = new List<FilterDescriptor>();
public List<FilterDescriptor> Filters
{
get { return FiltersValue; }
set
{
SetPropertyValue((() => Filters), ref FiltersValue, value);
RecordCount = 0;
LoadData();
}
}
private int PageValue = 0;
public int Page
{
get { return PageValue; }
set
{
SetPropertyValue((() => Page), ref PageValue, value);
LoadData();
}
}
private int PageSizeValue = 20;
public int PageSize
{
get { return PageSizeValue; }
set
{
SetPropertyValue((() => PageSize), ref PageSizeValue, value);
Page = 0;
}
}
private int RecordCountValue;
public int RecordCount
{
get { return RecordCountValue; }
set
{
SetPropertyValue((() => RecordCount), ref RecordCountValue, value);
}
}
private ObservableCollection<Models.Drug> DrugsValue;
public ObservableCollection<Models.Drug> Drugs
{
get { return DrugsValue; }
set
{
SetPropertyValue((() => Drugs), ref DrugsValue, value);
}
}
#endregion
#region Methods
private void LoadData()
{
if (ctx == null)
ctx = new Data.DBContext();
//load record count if we don't have it.
if (RecordCount == 0)
{
IsBusy = true;
Task.Run(() =>
{
if (Filters == null || Filters.Count() == 0)
{
RecordCount = ctx.Set<Entities.Drug>().Count();
}
else
{
RecordCount = ctx.Set<Entities.Drug>().Where(Filters).Count();
}
IsBusy = false;
LoadData();
});
return;
}
IsBusy = true;
Task.Run(() =>
{
if (Filters == null || Filters.Count() == 0)
currentPage = (from d in ctx.Query<Entities.Drug>()
orderby d.NDC
select d)
.Skip(PageSize * Page)
.Take(PageSize).ToList();
else
currentPage = (from d in ctx.Query<Entities.Drug>()
orderby d.NDC
select d)
.Where(Filters)
.Skip(PageSize * Page)
.Take(PageSize)
.ToIList() as List<Entities.Drug>;
Drugs = new ObservableCollection<Models.Drug>((from c in currentPage
select new Models.Drug(c)));
IsBusy = false;
});
}
#endregion
}

mvvm how to pass data from one view model to another view model

I have One View which has one Data grid with radio Button , onchecking radio Box , the selected row should go to other View Screen Textbox
here is my first ViewModel
public class CampaignSearchResultsViewModel : ViewModelBase
{
public CampaignSearchResultsViewModel(List<Lead> obj)
{
foreach(Lead lead in obj)
{
SelectedLead = lead;
}
}
public CampaignSearchResultsViewModel()
{
this.Commands.Add("CheckedCommand", new ActionCommand<Lead>(CheckIt));
Commands.Add("OutboundSelect", new ActionCommand<Object>(OutboundSelection));
_leads = new ObservableCollection<Lead>();
}
public ICommand OutboundSelect
{
get
{
return Commands["OutboundSelect"];
}
}
public void OutboundSelection(Object obj)
{
}
private void CheckIt(Lead lead)
{
SelectedLead = lead;
LeadViewModel lmv = new LeadViewModel(this);
}
#region Private
private ObservableCollection<Lead> _leads;
public bool IsChecked { get; set; }
private ICommand _checkedCommand;
private object _testProperty;
private Lead _selectedLead;
private ICollectionView icv;
#endregion
private ICommand _checkedRadioCommand;
private bool _inboundChecked;
#region Properties
public ObservableCollection<Lead> Leads
{
get { return _leads; }
set
{
_leads = value;
FirePropertyChanged("Leads");
}
}
public Lead SelectedLead
{
get { return _selectedLead; }
set { _selectedLead = value; }
}
public ICommand CheckedCommand
{
get
{
return Commands["CheckedCommand"];
}
}
public bool InboundChecked
{
get
{
return _inboundChecked;
}
private set
{
if (_inboundChecked != value)
{
_inboundChecked = value;
FirePropertyChanged("InboundChecked");
}
}
}
#endregion
}
i have to map SelectedLead to the other view model i have pass info to SearchCampaignMembers() method , how
public partial class LeadViewModel : ViewModelBase
{
public void SearchCampaignMembers()
{
_service.Load(_service.SearchCampaignMembersQuery(Entity.FirstName, Entity.LastName), lo =>
{
if (!lo.HasError)
{
ListLead = lo.Entities.ToList();
_savedLeadStatusId = Entity.LeadStatusId;
EntitySet = _service.Leads;
if (ListLead.Count == 1)
{
if (Entity != null)
{
IsVendorLead = Entity.LeadTypeId == Lookups.LeadType.VendorLead;
//Lead Update History
EntityQuery<LeadUpdateHistory> historyquery = null;
historyquery = _service.GetLeadUpdateHistoryByLeadIdQuery(Entity.LeadId);
_service.Load(historyquery, l =>
{
if (!l.HasError)
{
EntityHistory = _service.LeadUpdateHistories;
}
}, null);
//Lead Assignment
EntityQuery<LeadsAssignment> assignmentquery = null;
assignmentquery = _service.GetLeadsAssignmentByLeadIdQuery(Entity.LeadId);
_service.Load(assignmentquery, l =>
{
if (!l.HasError)
{
EntityAssignment = _service.LeadsAssignments;
}
}, null);
if (Entity.LeadTypeId == Lookups.LeadType.PhoneLead)
{
IsInboundLead = Entity.VendorId == null;
IsOutboundLead = Entity.VendorId != null;
}
else
{
IsInboundLead = false;
IsOutboundLead = false;
}
//SelectTimeToCall(Entity);
if (IsOutboundLead)
SelectedCampaign = Entity.LeadCampaigns.FirstOrDefault().Campaign;
else
SelectCampaign(Entity);
OperationsListener listener = new OperationsListener();
listener.Completed += (s, args) =>
{
CompleteInitializing();
//SwitchTab(param.InitialTab);
Action action = () =>
{
SelectDealer(Entity);
};
//GetDealerRecommendation(Entity.Address.ZipCode, action);
SelectStatus(Entity);
//if (callback != null)
// callback();
};
LoadLookupData(listener);
listener.Start();
}
}
else if (ListLead.Count >= 1)
{
CampaignSearchResultsViewModel vm = new CampaignSearchResultsViewModel();
foreach (Lead lead in ListLead)
{
vm.Leads.Add(lead);
ObservableCollection<Lead> abc;
abc = new ObservableCollection<Server.DataAccess.Lead>();
}
ViewController.OpenDialog("SearchCampaignResults", vm, r =>
{
});
}
else if (ListLead.Count == 0)
{
ViewController.OpenDialog("NoResults", (r) =>
{
});
}
}
else
{
//if (callback != null)
// callback();
}
}, null);
}
}
If you use MVVM Light Toolkit, see Messenger class see this answer for sample.

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