Programmatically WPF listbox.ItemsSource update - c#

I am doing a program like messenger that has all the contacts in a listbox with the relative states of the contacts.
Cyclic I get a xml with the contacts were updated over time, then updates the states within a class of binding called "Contacts".
The class Contacts has a filter to display only certain contacts by their state, "online, away, busy,.. " but not offline, for example ....
Some code:
public class Contacts : ObservableCollection<ContactData>
{
private ContactData.States _state = ContactData.States.Online | ContactData.States.Busy;
public ContactData.States Filter { get { return _state; } set { _state = value; } }
public IEnumerable<ContactData> FilteredItems
{
get { return Items.Where(o => o.State == _state || (_state & o.State) == o.State).ToArray(); }
}
public Contacts()
{
XDocument doc = XDocument.Load("http://localhost/contact/xml/contactlist.php");
foreach (ContactData data in ContactData.ParseXML(doc)) Add(data);
}
}
Update part:
void StatusUpdater(object sender, EventArgs e)
{
ContactData[] contacts = ((Contacts)contactList.Resources["Contacts"]).ToArray<ContactData>();
XDocument doc = XDocument.Load("http://localhost/contact/xml/status.php");
foreach (XElement node in doc.Descendants("update"))
{
var item = contacts.Where(i => i.UserID.ToString() == node.Element("uid").Value);
ContactData[] its = item.ToArray();
if (its.Length > 0) its[0].Data["state"] = node.Element("state").Value;
}
contactList.ListBox.ItemsSource = ((Contacts)contactList.Resources["Contacts"]).FilteredItems;
}
My problem is that when ItemsSource reassigns the value of the listbox, the program lag for a few seconds, until it has finished updating contacts UI (currently 250 simulated).
How can I avoid this annoying problem?
Edit:
I tried with Thread and after with BackgroundWorker but nothing is changed...
When i call Dispatcher.Invoke lag happen.
Class ContactData
public class ContactData : INotifyPropertyChanged
{
public enum States { Offline = 1, Online = 2, Away = 4, Busy = 8 }
public event PropertyChangedEventHandler PropertyChanged;
public int UserID
{
get { return int.Parse(Data["uid"]); }
set { Data["uid"] = value.ToString(); NotifyPropertyChanged("UserID"); }
}
public States State
{
get { return (States)Enum.Parse(typeof(States), Data["state"]); }
//set { Data["state"] = value.ToString(); NotifyPropertyChanged("State"); }
//correct way to update, i forgot to notify changes of "ColorState" and "BrushState"
set
{
Data["state"] = value.ToString();
NotifyPropertyChanged("State");
NotifyPropertyChanged("ColorState");
NotifyPropertyChanged("BrushState");
}
}
public Dictionary<string, string> Data { get; set; }
public void Set(string name, string value)
{
if (Data.Keys.Contains(name)) Data[name] = value;
else Data.Add(name, value);
NotifyPropertyChanged("Data");
}
public Color ColorState { get { return UserStateToColorState(State); } }
public Brush BrushState { get { return new SolidColorBrush(ColorState); } }
public string FullName { get { return Data["name"] + ' ' + Data["surname"]; } }
public ContactData() {}
public override string ToString()
{
try { return FullName; }
catch (Exception e) { Console.WriteLine(e.Message); return base.ToString(); }
}
Color UserStateToColorState(States state)
{
switch (state)
{
case States.Online: return Colors.LightGreen;
case States.Away: return Colors.Orange;
case States.Busy: return Colors.Red;
case States.Offline: default: return Colors.Gray;
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public static ContactData[] ParseXML(XDocument xmlDocument)
{
var result = from entry in xmlDocument.Descendants("contact")
select new ContactData { Data = entry.Elements().ToDictionary(e => e.Name.ToString(), e => e.Value) };
return result.ToArray<ContactData>();
}
}

I developed a similar software: a huge contact list with data (presence and other stuff) updating quite frequently.
The solution I used is different: instead of updating the whole itemssource everytime, that is quite expensive, implement a ViewModel class for each contact. The ViewModel class should implement INotifiyPropertyChanged.
At this point when you parse the XML, you update the ContactViewModel properties and this will trigger the correct NotifyPropertyChanged events that will update the correct piece of UI.
It might be expensive if you update a lot of properties for a lot of contacts at the same time, for that you can implement some kind of caching like:
contactViewModel.BeginUpdate()
contactViewModel.Presence = Presence.Available;
..... other updates
contactViewModel.EndUpdate(); // at this point trigger PropertyCHanged events.
Another point:
keep a separate ObservableCollection bound to the ListBox and never change the itemssource property: you risk losing the current selection, scrollposition, etc.
dynamically add/remove elements from the collection bound to the listbox.
Buon divertimento e in bocca al lupo :-)

Move the downloading and parsing of the contact status information to another thread.

The line where you assigning the ItemsSource I would put in another thread, but remember about invoking or you're gonna have irritating errors.

Related

Finding the items changed in Observable Collection

I have a class from a EF db context which I have displayed in a datagrid based on an ObservableCollection. The user can edit the the grid and this all displays fine.
However I now need to send the data back to the database. I do not want to send all the items in the collection to my save method, so can I find only the items that have been have change in the collection?
just as an idea (not professing this to be an ideal solution) i have run into a similar issue, looked around for potential solutions and none of those were exactly what i wanted.
i had to pass a collection to WPF DataGrid and it seemed to complain about using List, hence i turned to ObservableCollection
i did not want to work directly with the EF context for multiple reasons primarily because i wanted to grab items and pass them to intermediate transaction factory to be processed (business logic).
so decided to stick with ObservableCollection and instead make slight modification to the ViewModel since this i was free to do it.
my model ended up to look like this:
internal class databaseItemModel
{
int _id;
string _description;
decimal _price;
decimal _quantity;
decimal _cost;
bool _modified;
public databaseItemModel()
{
_modified = false;
}
public int id { get { return _id; } }
public bool modified { get { return _modified; } }
public string description { get { return _description; } set { _description = value; _modified = true; } }
public decimal price { get { return _price; } set { _price = value; _modified = true; } }
public decimal quantity { get { return _quantity; } set { _quantity = value; _modified = true; } }
public decimal cost { get { return _cost; } set { _cost = value; _modified = true; } }
public bool selected { get; set; }
public void setId(int _idvalue)
{
_id = _idvalue;
}
public decimal value
{
get { return price * quantity; }
}
public void setDescription(string _descr)
{
_description = _descr;
}
public void setPrice(decimal _pr)
{
_price = _pr;
}
public void setQuantity(decimal _qty)
{
_quantity = _qty;
}
public void setCost(decimal _cst)
{
_cost = _cst;
}
}
Basically, the plain idea behind it is that i would use functions to populate data rather than using properties direct and then pass the item to ObservableCollection which then would become the source for the DataGrid.ItemsSource
since DataGrid/ObservableCollection would work with properties - modified objects would be marked as modified and i would then be able to pick up the collection on exit and collect the modified items.
hope this is helpful.
You can use NotifyCollectionChangedAction to detect which items has been changed in the ObservableCollection
However, just Jens said, the best way would be let the EF handle it for you.
Cheers.
ObservableCollection<int> listOfObject = new ObservableCollection<int>() { 1, 2, 3, 4};
listOfObject.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(
delegate (object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
Console.WriteLine($"{e.NewItems[0]} just been added to the list at index = {e.NewStartingIndex}");
}
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)
{
Console.WriteLine($"Replace item {e.OldItems[0]} with {e.NewItems[0]}");
}
}
);
listOfObject.Add(1);
listOfObject[2] = 3;
listOfObject[3] = 1;
Output:
1 just been added to the list at index = 4
Replace item 3 with 3
Replace item 4 with 1

Update ListcollectionView based on current item of other ListCollectionView

I want to Update ListCollectionView in a listbox each time the Item of another ListCollection gets selected.
I have 2 ListViewCollection, SceneCollectionView and ShotCollectionView. I want to have the SceneCollection filtered based on a property SceneNumber in ShotCollectionView, but I can get the ShotCollectionView to update when I go from one item to the other in SceneCollectionView.
This is my ViewModel
public class ShotListViewModel : NotifyUIBase
{
public ListCollectionView SceneCollectionView { get; set; }
private Scenes CurrentScene
{
get { return SceneCollectionView.CurrentItem as Scenes; }
set { SceneCollectionView.MoveCurrentTo(value); RaisePropertyChanged(); }
}
private ObservableCollection<Shot> _allShots = new ObservableCollection<Shot>();
public ObservableCollection<Shot> AllShots
{
get { return _allShots; }
set { _allShots = value; RaisePropertyChanged();}
}
private ListCollectionView _allShotsCollection;
public ListCollectionView AllShotsCollection
{
get
{
if (_allShotsCollection == null)
{
_allShotsCollection = new ListCollectionView(this.AllShots);
_allShotsCollection.Filter = IsSceneNumber;
}
return _allShotsCollection;
}
}
private bool IsSceneNumber(object obj)
{
if (obj as Shot != null
&& (obj as Shot).SceneNumber == (SceneCollectionView.CurrentItem as Scene).SceneNumber)
{
return true;
}
return false;
}
public ShotListViewModel()
{
SceneCollectionView = Application.Current.Resources["SceneCollectionView"] as ListCollectionView;
GetShotList(); //Populates the AllShots Observable collection.
AddShotCommand = new RelayCommand(AddShot);
FilterShotsCommand = new RelayCommand(AddShot);
}
What am I missing here to make it work or is it better to use ICollectionViewLiveShaping. but I have no idea how to implement that
I don`t understand what you tried to do, but lets talk on an example :
Lets say we have
ListBox1 binded to ListBox1Items and
ListBox2 binded to ListBox2Items.
if you want to filter the data from ListBox2 you have to filter ListBox2Items. How to do that ? Is simple : ListBox1 has a property SelectedItem which you can bind to --- lets say --- ListBox1SelectedItem. Every time when selection change, in the setter of the ListBox1SelectedItem you can trigger a filter on ListBox2Items.
Hope you understand what I`ve explained.

Databound Listview not refreshing when set up with Parallel.Invoke

I have a Window with a TabControl in it. The TabControl contains 5 different TabItems. Each TabItem has its own ViewModel associated as its DataContext, while the Window has a DataContext that has all 5 TabItem's view models as properties. The problem I am having is setup. There is a noticeable lag when I launch the Window (from my MainWindow) and I have spent a good chunk of time refactoring my code and making it faster by running things in parallel, making fewer calls to the database and running Tasks on semi-expensive operations. Everything works great, except for one TabItem and its view model. For some reason, the view does not refresh itself properly.
For instance, I have a view model called DiaryDescriptionViewModel that takes in List<SectionViewModel> and does stuff with it, with the view bound to a result collection. It works fine. My troublesome view model is called DiaryPayItemEditorViewModel and it too takes in a List<SectionViewModel> and does stuff with it, with the view bound to a result collection. Neither view models perform the work on List<SectionViewModel> on worker threads or anything. However both view models are instanced and set up in parallel, which I wouldn't think be the root of the problem.
In my DiaryPayItemEditorViewModel, I have a ObservableCollection<DiaryPayItemDetailViewModel> that a ListView is data bound to. The ListView never displays the data, even though it exists. If I take all of my view model initialization code out of a Parallel.Invoke call, then it binds and displays the data.
My assumption here is that the view is initialized (this.InitializeComponents) before the DiaryPayItemEditorViewModel is fully set up, which should be fine. Since my view models all implement INotifyPropertyChanged, the view should be notified that changes have taken place. For the life of me, I can't figure this out.
The following is the applicable source for the view Window view model (DiaryEditorViewModel), the view model who uses the same collection and works with binding (DiaryDescriptionViewModel and its child DiaryDescriptionDetailsViewModel) and then my troublesome view model (DiaryPayItemEditorViewModel and its child DiaryPayItemDetailViewModel).
DiaryEditorViewModel.cs
public class DiaryEditorViewModel : BaseChangeNotify
{
private DiaryViewModel diary;
private Project project;
private DiaryDetailsViewModel diaryDetailsViewModel;
private DiaryDescriptionViewModel diaryDescriptionViewModel;
private DiaryPayItemEditorViewModel diaryPayItemsViewModel;
private DiaryEquipmentEditorViewModel diaryEquipmentEditorViewModel;
private DiaryLaborViewModel diaryLaborViewModel;
// This is the designated constructor used by the app.
public DiaryEditorViewModel(Project project, Diary diary, UserViewModel user)
: base(user)
{
// Instance a new diary view model using the provided diary.
this.diary = new DiaryViewModel(diary, user);
this.project = project;
// Setup the repositories we will use.
var repository = new ProjectRepository();
var contractorRepository = new ContractorRepository();
// Setup the temporary collections used by the repositories.
var contractors = new List<Contractor>();
var contractorViewModels = new List<ContractorViewModel>();
var projectSections = new List<Section>();
var bidItemCollection = new List<BidItem>();
var subItemCollection = new List<SubItem>();
var sectionViewModels = new List<SectionViewModel>();
var equipmentCategories = new List<EquipmentCategory>();
var equipmentFuelTypes = new List<EquipmentFuelType>();
var equipmentList = new List<Equipment>();
var equipmentViewModels = new List<EquipmentViewModel>();
Task.Run(() =>
{
Parallel.Invoke(
// Fetch contractors for selected project.
() =>
{
contractors.AddRange(contractorRepository.GetContractorsByProjectId(diary.ProjectId));
equipmentCategories.AddRange(contractorRepository.GetEquipmentCategories());
equipmentFuelTypes.AddRange(contractorRepository.GetEquipmentFuelTypes());
equipmentList.AddRange(contractorRepository.GetEquipmentByProjectId(this.Project.ProjectId));
// Reconstruct the contractor->Equipment->FuelType & Category relationship.
contractorViewModels.AddRange(
contractors.Select(contractor =>
new ContractorViewModel(
contractor,
equipmentList.Where(equipment =>
equipment.ContractorId == contractor.ContractorId).Select(e =>
new EquipmentViewModel(
e,
contractor,
equipmentCategories.FirstOrDefault(cat =>
cat.EquipmentCategoryId == e.EquipmentCategoryId),
equipmentFuelTypes.FirstOrDefault(f =>
f.EquipmentFuelTypeId == e.EquipmentFuelTypeId))))));
},
() =>
{
// Fetch all of the Sections, Bid-Items and Sub-items for the project
projectSections.AddRange(repository.GetSectionsByProjectId(project.ProjectId));
bidItemCollection.AddRange(repository.GetBidItemsByProjectId(project.ProjectId));
subItemCollection.AddRange(repository.GetSubItemsByProjectId(project.ProjectId));
// Reconstruct the Section->BidItem->SubItem hierarchy.
sectionViewModels.AddRange(
projectSections.Select(s =>
new SectionViewModel(project, s,
bidItemCollection.Where(b => b.SectionId == s.SectionId).Select(b =>
new BidItemViewModel(project, b,
subItemCollection.Where(si => si.BidItemId == b.BidItemId))))));
}
);
// Once the parallel invocations are completed, instance all of the children view models
// using the view model collections we just set up.
Parallel.Invoke(
// Fetch contractors for selected project.
() =>
this.DiaryDetailsViewModel = new DiaryDetailsViewModel(
project,
diary,
user),
() => // This view model works just fine, with same constructor signature.
this.DiaryDescriptionViewModel = new DiaryDescriptionViewModel(
project,
diary,
user,
sectionViewModels),
() =>
this.DiaryPayItemEditorViewModel = new DiaryPayItemEditorViewModel(
project,
diary,
user,
sectionViewModels),
() => // This view model does not notify the UI of changes to its collection.
this.DiaryEquipmentEditorViewModel = new DiaryEquipmentEditorViewModel(
project,
diary,
user,
contractorViewModels),
() =>
// For the Labor view, we just pass the Contractor model collection rather than the view model collection
// since the Labor view does not need any of the additional equipment information.
this.DiaryLaborViewModel = new DiaryLaborViewModel(
project,
diary,
user,
contractors));
});
}
public Project Project
{
get
{
return this.project;
}
set
{
this.project = value;
this.OnPropertyChanged();
}
}
public DiaryViewModel Diary
{
get
{
return this.diary;
}
set
{
this.diary = value;
this.OnPropertyChanged();
}
}
public DiaryDetailsViewModel DiaryDetailsViewModel
{
get
{
return this.diaryDetailsViewModel;
}
set
{
this.diaryDetailsViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryDescriptionViewModel DiaryDescriptionViewModel
{
get
{
return this.diaryDescriptionViewModel;
}
set
{
this.diaryDescriptionViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryPayItemEditorViewModel DiaryPayItemEditorViewModel
{
get
{
return this.diaryPayItemsViewModel;
}
set
{
this.diaryPayItemsViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryLaborViewModel DiaryLaborViewModel
{
get
{
return this.diaryLaborViewModel;
}
set
{
this.diaryLaborViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryEquipmentEditorViewModel DiaryEquipmentEditorViewModel
{
get
{
return this.diaryEquipmentEditorViewModel;
}
set
{
this.diaryEquipmentEditorViewModel = value;
this.OnPropertyChanged();
}
}
}
DiaryDescriptionViewModel
This view model works just fine, with its this.DiaryDescriptions collection being bound to properly and displayed in the ListView
public class DiaryDescriptionViewModel : BaseDiaryViewModel, IDataErrorInfo
{
private ObservableCollection<DiaryDescriptionDetailsViewModel> diaryDescriptions;
private DiaryDescriptionDetailsViewModel selectedDiaryDescription;
public DiaryDescriptionViewModel()
{
}
public DiaryDescriptionViewModel(Project project, Diary diary, UserViewModel user, List<SectionViewModel> sections)
: base(project, diary, user)
{
// Restore any previously saved descriptions.
var diaryRepository = new DiaryRepository();
List<DiaryDescription> descriptions = diaryRepository.GetDiaryDescriptionsByDiaryId(diary.DiaryId);
this.ProjectSections = sections;
// Reconstruct our descriptions
this.diaryDescriptions = new ObservableCollection<DiaryDescriptionDetailsViewModel>();
foreach (DiaryDescription description in descriptions)
{
SectionViewModel section = this.GetSectionContainingBidItemId(description.BidItemId);
BidItemViewModel bidItem = section.GetBidItem(description.BidItemId);
var details = new DiaryDescriptionDetailsViewModel(description, section, bidItem);
details.PropertyChanged += ChildViewModelPropertyChanged;
this.diaryDescriptions.Add(details);
}
this.diaryDescriptions.CollectionChanged += this.DiaryDescriptionsOnCollectionChanged;
this.IsDirty = false;
}
public ObservableCollection<DiaryDescriptionDetailsViewModel> DiaryDescriptions
{
get
{
return this.diaryDescriptions;
}
set
{
if (value != null)
{
this.diaryDescriptions.CollectionChanged -= this.DiaryDescriptionsOnCollectionChanged;
this.diaryDescriptions =
new ObservableCollection<DiaryDescriptionDetailsViewModel>(
value
.OrderBy(s => s.Section.Section)
.ThenBy(i => i.BidItem.BidItem.Number));
this.diaryDescriptions.CollectionChanged += this.DiaryDescriptionsOnCollectionChanged;
}
else
{
this.diaryDescriptions = new ObservableCollection<DiaryDescriptionDetailsViewModel>();
}
this.OnPropertyChanged();
}
}
public DiaryDescriptionDetailsViewModel SelectedDiaryDescription
{
get
{
return this.selectedDiaryDescription;
}
set
{
// Always unsubscribe from events before replacing the object. Otherwise we end up with a memory leak.
if (this.selectedDiaryDescription != null)
{
this.selectedDiaryDescription.PropertyChanged -= this.ChildViewModelPropertyChanged;
}
this.selectedDiaryDescription = value;
if (value != null)
{
// If the description contains a biditem DiaryId, then we go fetch the section and biditem
// associated with the diary description.
if (value.BidItemId > 0)
{
this.selectedDiaryDescription.Section = this.GetSectionContainingBidItemId(value.BidItemId);
this.selectedDiaryDescription.BidItem = this.selectedDiaryDescription.Section.GetBidItem(value.BidItemId);
}
// Subscribe to property changed events so we can set ourself to dirty.
this.selectedDiaryDescription.PropertyChanged += this.ChildViewModelPropertyChanged;
this.selectedDiaryDescription.IsDirty = false;
}
this.OnPropertyChanged();
this.IsDirty = false;
}
}
DiaryDescriptionDetailViewModel
Working child view model.
public class DiaryDescriptionDetailsViewModel : BaseChangeNotify
{
private readonly DiaryDescription diaryDescription;
private SectionViewModel section;
private BidItemViewModel bidItem;
public DiaryDescriptionDetailsViewModel(DiaryDescription description, SectionViewModel section = null, BidItemViewModel bidItem = null)
{
this.diaryDescription = description;
if (description.BidItemId > 0)
{
this.section = section;
this.bidItem = bidItem;
}
this.IsDirty = false;
}
public DiaryDescription Description
{
get
{
return this.diaryDescription;
}
}
public int BidItemId
{
get
{
return this.diaryDescription.BidItemId;
}
}
public BidItemViewModel BidItem
{
get
{
return this.bidItem;
}
set
{
this.bidItem = value;
this.diaryDescription.BidItemId = value.BidItem.BidItemId;
this.OnPropertyChanged();
}
}
public SectionViewModel Section
{
get
{
return this.section;
}
set
{
this.section = value;
this.OnPropertyChanged();
}
}
}
DiaryPayItemEditorViewModel
And finally, the view model who is not having its collection rendered to the view.
public class DiaryPayItemEditorViewModel : BaseDiaryViewModel, IDataErrorInfo
{
private ObservableCollection<DiaryPayItemDetailViewModel> diaryPayItemDetails;
private DiaryPayItemDetailViewModel selectedDiaryPayItemDetail;
private List<DiaryPayItem> allPayItemsForSelectedBidItem;
private decimal sumOfAllPayItemsForBidItem;
public DiaryPayItemEditorViewModel()
{
}
public DiaryPayItemEditorViewModel(Project project, Diary diary, UserViewModel user, List<SectionViewModel> sections)
: base(project, diary, user)
{
this.Initialize(project, sections);
this.IsDirty = false;
}
public ObservableCollection<DiaryPayItemDetailViewModel> DiaryPayItemDetails
{
get
{
return this.diaryPayItemDetails;
}
set
{
this.diaryPayItemDetails = value;
this.OnPropertyChanged();
}
}
public DiaryPayItemDetailViewModel SelectedDiaryPayItemDetail
{
get
{
return this.selectedDiaryPayItemDetail;
}
set
{
if (this.selectedDiaryPayItemDetail != null)
{
this.selectedDiaryPayItemDetail.PropertyChanged -= this.ChildViewModelPropertyChanged;
}
if (value != null)
{
value.PropertyChanged += this.ChildViewModelPropertyChanged;
}
this.selectedDiaryPayItemDetail = value;
this.OnPropertyChanged();
}
}
private void Initialize(Project project, List<SectionViewModel> sections)
{
var repository = new DiaryRepository();
var projectRepository = new ProjectRepository();
this.DiaryPayItemDetails = new ObservableCollection<DiaryPayItemDetailViewModel>();
this.ProjectSections = sections;
// Repository calls to the database.
List<DiaryPayItem> payItems = repository.GetDiaryPayItemsByDiaryId(this.Diary.DiaryId);
var sectionItems = projectRepository.GetSectionHierarchy(project.ProjectId);
// Temporary, needs to be refined.
foreach (var diaryPayItem in payItems)
{
var subItem = sectionItems.SubItems.FirstOrDefault(sub => sub.SubItemId == diaryPayItem.SubItemId);
var bidItems =
sectionItems.BidItems.Where(bid => bid.BidItemId == subItem.BidItemId)
.Select(
bid =>
new BidItemViewModel(project, bid,
sectionItems.SubItems.Where(sub => sub.BidItemId == bid.BidItemId)));
var section = new SectionViewModel(
project,
sectionItems.Sections.FirstOrDefault(s => bidItems.Any(bid => bid.BidItem.SectionId == s.SectionId)),
bidItems);
this.DiaryPayItemDetails.Add(
new DiaryPayItemDetailViewModel(
diaryPayItem,
section,
bidItems.FirstOrDefault(bid => bid.BidItem.BidItemId == subItem.BidItemId),
subItem));
}
}
DiaryPayItemDetailViewModel - Child view model to the troublesome view model
public class DiaryPayItemDetailViewModel : BaseChangeNotify
{
private DiaryPayItem diaryPayItem;
private SectionViewModel selectedSection;
private BidItemViewModel selectedBidItem;
private SubItem selectedSubItem;
public DiaryPayItemDetailViewModel(
DiaryPayItem diaryPayItem,
SectionViewModel section,
BidItemViewModel bidItem,
SubItem subItem)
{
this.DiaryPayItem = diaryPayItem;
this.SelectedSection = section;
this.SelectedBidItem = bidItem;
this.SelectedSubItem = subItem;
}
public DiaryPayItem DiaryPayItem
{
get
{
return this.diaryPayItem;
}
set
{
this.diaryPayItem = value;
this.OnPropertyChanged();
}
}
public SectionViewModel SelectedSection
{
get
{
return this.selectedSection;
}
set
{
this.selectedSection = value;
this.OnPropertyChanged();
}
}
public BidItemViewModel SelectedBidItem
{
get
{
return this.selectedBidItem;
}
set
{
this.selectedBidItem = value;
this.OnPropertyChanged();
}
}
public SubItem SelectedSubItem
{
get
{
return this.selectedSubItem;
}
set
{
this.selectedSubItem = value;
this.DiaryPayItem.SubItemId = value.SubItemId;
this.OnPropertyChanged();
}
}
XAML for the DiaryDescription Tab Item.
<ListView ItemsSource="{Binding Path=DiaryDescriptions}"
SelectedItem="{Binding Path=SelectedDiaryDescription}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Section.SectionName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
XAML for Diary Pay Items tab item.
<ListView Name="PayItemListView"
ItemsSource="{Binding Path=DiaryPayItemDetails}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=SelectedBidItem.BidItem.Description}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
BaseChangeNotify
Lastly, to show my INotifyPropertyChanged implementation, I present my base class. It wraps all calls to the event handlers in an Application.Current.Dispatcher.Invoke() action. This forces all event handler calls to be ran on the main thread so I don't have to worry about cross-thread issues in my inherited objects.
public class BaseChangeNotify : INotifyPropertyChanged
{
private bool isDirty;
private UserViewModel user;
public BaseChangeNotify()
{
}
public BaseChangeNotify(UserViewModel user)
{
this.user = user;
}
public event PropertyChangedEventHandler PropertyChanged;
public bool IsDirty
{
get
{
return this.isDirty;
}
set
{
this.isDirty = value;
this.OnPropertyChanged();
}
}
public UserViewModel User
{
get
{
return this.user;
}
}
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
// Perform the IsDirty check so we don't get stuck in a infinite loop.
if (propertyName != "IsDirty")
{
this.IsDirty = true; // Each time a property value is changed, we set the dirty bool.
}
if (this.PropertyChanged != null)
{
// Invoke the event handlers attached by other objects.
try
{
// When unit testing, this will always be null.
if (Application.Current != null)
{
Application.Current.Dispatcher.Invoke(() =>
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
}
else
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
catch (Exception)
{
throw;
}
}
}
If anyone could help me figure this out I would greatly appreciate it. I have been trying various things for the last two days and can't get it figured out. It's weird how one view model works fine, essentially performing the same kind of operation, and the other does not.
Thanks in advance.
The DiaryEditorViewModel is the view model to a DiaryEditorWindow. The DiaryPayItemEditorViewModel belongs to a user control that resides within the Window. Setting the data context in the XAML at the Window level, for the TabItem, resolved this issue. Setting the DataContext at the UserControl level caused view model to not bind properly.
I also tried setting the datacontext in the constructor, but that had the same issue. It would never bind. By setting the datacontext in the XAML of the TabItem associated with the troublesome view model, the problem was resolved. I don't understand why this is an issue. Since the view model fully implements the property changed event, I should be able to set the data context at any point, and adjust the values without a problem.
Eitherway, I have been able to solve this issue.

Forcing all WPF bound controls to update with GetBindingExpression().UpdateTarget()

I have a WPF application that includes ~50 controls that are bound to properties on my business object which implements INotifyPropertyChanged. Here's a quick snippet of my business object:
public class MyBusinessObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
// properties begin here
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
{
return;
}
_name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
// constructor, etc. not shown
}
I also have several validation rules that are used to validate the user input in these controls. I'm using command binding to prevent my user from saving the data if there are any validation errors. My application also includes a "Reset default values" button which, obviously, will reset the default value for all of the properties on my business object. This all works exactly as I'd like it to with one exception. If my user enters invalid data into one or more controls and then clicks the "Reset default values" button, the controls that contain invalid data don't always update as I'd expect. This happens because of the following code in my property setters:
if (_name == value)
{
return;
}
This code exists to prevent unnecessary property changed notifications from occurring when the value entered by my user in the bound UI control is the same value that the property is already set to. As an example, I have an IntegerUpDown control in my UI (this control is part of the Extended WPF Toolkit from Xceed). The default value of the property that my control is bound to is 10. My user deletes the value from the control and my validation rule is triggered which results in a validation error and the UI is updated appropriately with an error adorner, etc. The value of the property that this control is mapped to hasn't been changed so it's still set to 10. Now my user clicks the "Reset default values" button which will result in the default value (10) for the property being reset. However, the value for the property is already set to 10 so the short circuit logic in my setter will return instead of setting the property value.
So now, after my user clicks "Reset default values", I am also forcing an update on my binding target like this:
this.myIntegerUpDown.GetBindingExpression(Xceed.Wpf.Toolkit.IntegerUpDown.ValueProperty).UpdateTarget();
This solves my problem but only for this particular control. Is there any easy way to do this for all of my bound controls without having to specify each one? Thanks.
OnPropertyChanged(new PropertyChangedEventArgs(string.Empty));
This is intended to imply that ALL properties on that object have changed.
Could you do one of the following?
1) Reset the DataContext - Either recreate it, or re-set the property
var context = this.DataContext;
this.DataContext = null;
this.DataContext = context;
2) Loop through all properties programmatically via reflection and manually call OnPropertyChanged with the relevant property names.
var properties = typeof(ViewModel).GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var property in properties)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(property.Name));
}
You've mentioned validation and reset values, and of course the obvious one is to persist it.
Why don't you implement IEditableObject Interface on your entity that has three signature methods. BeginEdit(), CancelEdit() and EndEdit()
That way you can easily roll back your entity to the whatever you want, or validate it and lastly persist it. A good example is found here
Sample code
public class Customer : IEditableObject
{
struct CustomerData
{
internal string id ;
internal string firstName ;
internal string lastName ;
}
private CustomersList parent;
private CustomerData custData;
private CustomerData backupData;
private bool inTxn = false;
// Implements IEditableObject
void IEditableObject.BeginEdit()
{
Console.WriteLine("Start BeginEdit");
if (!inTxn)
{
this.backupData = custData;
inTxn = true;
Console.WriteLine("BeginEdit - " + this.backupData.lastName);
}
Console.WriteLine("End BeginEdit");
}
void IEditableObject.CancelEdit()
{
Console.WriteLine("Start CancelEdit");
if (inTxn)
{
this.custData = backupData;
inTxn = false;
Console.WriteLine("CancelEdit - " + this.custData.lastName);
}
Console.WriteLine("End CancelEdit");
}
void IEditableObject.EndEdit()
{
Console.WriteLine("Start EndEdit" + this.custData.id + this.custData.lastName);
if (inTxn)
{
backupData = new CustomerData();
inTxn = false;
Console.WriteLine("Done EndEdit - " + this.custData.id + this.custData.lastName);
}
Console.WriteLine("End EndEdit");
}
public Customer(string ID) : base()
{
this.custData = new CustomerData();
this.custData.id = ID;
this.custData.firstName = "";
this.custData.lastName = "";
}
public string ID
{
get
{
return this.custData.id;
}
}
public string FirstName
{
get
{
return this.custData.firstName;
}
set
{
this.custData.firstName = value;
this.OnCustomerChanged();
}
}
public string LastName
{
get
{
return this.custData.lastName;
}
set
{
this.custData.lastName = value;
this.OnCustomerChanged();
}
}
internal CustomersList Parent
{
get
{
return parent;
}
set
{
parent = value ;
}
}
private void OnCustomerChanged()
{
if (!inTxn && Parent != null)
{
Parent.CustomerChanged(this);
}
}
public override string ToString()
{
StringWriter sb = new StringWriter();
sb.Write(this.FirstName);
sb.Write(" ");
sb.Write(this.LastName);
return sb.ToString();
}
}
Wouldn't it be easier to just always call OnPropertyChanged regardless of whether its the same? How much of a performance boost does that give you?

Wp7 ListBox ItemSource ObservableCollection IndexOUtofRange and Items are not updated

I have the following issue: I am creating a Windows Phone 7 application and I am using a ListBox which is bound to an ObservableCollection people. The implementation of this you see below:
public class Person
{
private string _id { get; set; }
private string _name { get; set; }
public Person(string Id, string Name, string Title)
{
_id = Id;
_name = Name;
}
public string Id
{
get { return _id; }
set
{
_id = value;
FirePropertyChangedEvent("Id");
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
FirePropertyChangedEvent("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void FirePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The people Collection is filled with Person objects. They are created in the following function... listValues is my ListBox.
void svc_GetHierachyCompleted(object sender, HCMobileSvc.GetHierachyCompletedEventArgs e)
{
var data = e.Result.ToArray();
listValues.ItemsSource = null;
people.Clear();
int i = 0;
foreach(var item in data)
{
if (i == 0)
{
// Manager
mgrField1.Text = item[1].ToString();
mgrField2.Text = item[2].ToString();
i++;
}
else
{
// Untergebenen hinzufügen
people.Add(new Person(item[0].ToString(), item[1].ToString(), item[2].ToString()));
}
}
// Update List
listValues.ItemsSource = people;
}
Now I have a DataTemplate with two textblocks bound to both properties Id and Name. When the SelectionChanged event is fired I try to rebuild the entire list (so I call the function above again) using the following code:
string id = people[listValues.SelectedIndex].Id;
MessageBox.Show(id);
CreateHierachy(id);
The CreateHierachy just only queries a WebService which then goes into the method above. The problem is, as soon as I select a value in the ListBox I get the following error:
ArgumentOutOfRangeException {"\r\nParameter name: index"}
The error is caused by the line listValues.SelectedIndex.
I absolutely have no idea why that happens. What I know is that the MessageBox shows me the correct SelectedIndex value. What I also know is that when I remove the line people.Clear() that the error goes away but the ListBox does not get Updated.
Any ideas where the problem might be?
Thanks!!!
Bye,
WorldSignia
You should check here for SelectedIndex being >= 0:
if (listValues.SelectedIndex >= 0)
string id = people[listValues.SelectedIndex].Id;

Categories