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

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?

Related

C#, Xamarin Forms - ObservableCollection of custom Object updates but does not fire property changed events

I have a collection that gets populated with produce. Once the collection is populated, a BindableLayout/DataTemplate bound to a StackLayout will display the items and the user will be prompted with the option to change the Price property of a stock item by typing into an Entry.
A user can type into the provided Entry box to change Price property of each StockInfo object in the collection, and the change WILL SUCCESSFULLY be applied to the Observable Collection, BUT it WILL NOT fire the setter/property changed event of the Observable Collection.
I need the property changed event to fire so that I can effectively execute other parts of my code, but since it won't the fire setter or property changed of the collection, it never gets the chance to tell other parts of my code to do things.
namespace Test
{
public class Testing : BaseContentPage, INotifyPropertyChanged
{
public class StockInfo : BaseContentPage, INotifyPropertyChanged
{
private string description;
public string Description
{
get => description;
set
{
description = value;
OnPropertyChanged();
}
}
private int price;
public int Price
{
get => price;
set
{
price = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<StockInfo> stockItems = new ObservableCollection<StockInfo>();
public ObservableCollection<StockInfo> StockItems
{
get => stockItems;
set
{
stockItems = value;
OnPropertyChanged();
OnPropertyChanged("SumPrices");
}
}
public double SumPrices
{
get
{
return StockItems.Sum(p => p.Price);
}
}
DataTemplate StockTemplate = new DataTemplate(() =>
{
return new StackLayout
{
Orientation = StackOrientation.Horizontal,
Children =
{
new Entry
{
}.Bind(Entry.TextProperty, path: "Description")
,
new Entry
{
Keyboard = Keyboard.Numeric
}.Bind(Entry.TextProperty, path: "Price")
}
};
});
public Testing()
{
BindingContext = this;
StockItems.Add(new StockInfo { Description = "Milk", Price = 20 });
StockItems.Add(new StockInfo { Description = "Cheese", Price = 15 });
Content = new StackLayout
{
Children =
{
new StackLayout
{
}.Invoke(layout => BindableLayout.SetItemTemplate(layout, StockTemplate))
.Bind(BindableLayout.ItemsSourceProperty, path: "StockItems")
,
new Label
{
}.Bind(Label.TextProperty, path: "SumPrices")
}
};
}
}
}
If I put a debugger stop line inside the get/set of the "Description" property in the StockInfo class and then type in the Entry, the debugger will pick it up and stop the program for debugging.
But if I put a debugger stop on a line some where in the set/get of the Observable Collection, the program will not stop inside of it.
*** Edits Below ***
I modified the code so that StockInfo now has a property that includes the price of a product. I also added a variable called SumPrices which will return the Sum of Price within StockItems using LINQ. The first time the page loads, the sum is calculated and the result is correct, but if I change the Entry box that the property is bound to for each object, it has no effect and the SumPrices variable never changes.
Ideally, I'd simply like for the Observable Collection to fire its setter/property change events whenever an Object's property within the collection is changed.
New Update Here
You cannot fire the setter of ObservableCollection when a property of an item in this collection has changed. I've searched so many info from the Internet and found a question similar to yours: ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged). Bob Sammers abstract and define a new FullyObservableCollection class and put forward a pretty robust solution, including some of the techniques in other answers. This new class could get notified when a property of item has been changed. I have tested it and worked well.
Simply used it like the following code:
private FullyObservableCollection<StockInfo> stockItems = new FullyObservableCollection<StockInfo>();
public FullyObservableCollection<StockInfo> StockItems
{
get => stockItems;
set
{
stockItems = value;
OnPropertyChanged();
}
}
public Testing ()
{
...
StockItems.ItemPropertyChanged += StockItems_ItemPropertyChanged;
...
}
private void StockItems_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(SumPrices));
}
Another workaround is what i've suggested in my previous answer, which is using TextChanged event handler.
In Datatemplate, add an EventHandler for entry:
new Entry
{
}.Bind(Entry.TextProperty, path: "Description",BindingMode.TwoWay)
.Invoke(entry=>entry.TextChanged+=Entry_TextChanged)
private void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
OnPropertyChanged(nameof(SumPrices));
}
For more info, you could refer to Xamarin.Forms - CollectionView sample
Hope it works for you.

Filter Method of ICollectionView and Properly Binding and ObservableCollection in MVVM

I'm getting stuck into MVVM in WPF and I have setup an ObservableCollection and an ICollectionView. The ICollectionView is set as the ItemsSource of a DataGrid, and the model is a type of Job.
I've setup getters and setter for both of the collections however when I am setting a Filter on the ICollectionView instead of the Job being filtered by the SearchString they're just replicated over and over again, leading me to believe that they way I have the collections setup is totally wrong.
Here is how the two collections are get/set:
public ObservableCollection<Job> AllJobs
{
get
{
foreach (var job in _allJobsList)
_allJobs.Add(job);
return _allJobs;
}
set
{
if (_allJobs == value) return;
OnPropertyChanged("AllJobs");
}
}
public ICollectionView AllJobsView
{
get
{
_allJobsView = CollectionViewSource.GetDefaultView(AllJobs);
return _allJobsView;
}
set
{
if (_allJobsView == value)
{
return;
}
_allJobsView = value;
OnPropertyChanged("AllJobsView");
}
}
Now I have a stringcalled SearchString that is bound to a TextBox.Text. When the text changes I do the following:
public string SearchString
{
get => _searchString;
set
{
if (_searchString == value) return;
_searchString = value;
FilterJobs();
OnPropertyChanged("SearchString");
}
}
private void FilterJobs()
{
AllJobsView.Filter = x =>
{
var viewJob = x as Job;
return viewJob != null && viewJob.Number.Contains(_searchString);
};
}
Now when the page first loads, there are the correct Jobs loaded into the DataGrid. However, as soon as the user types the Jobs are duplicated if the Job.Number does contain the SearchString. How am I able to setup the collections so that I can appropriately set a filter?
The problem is in the getter of your ObservableCollection. Every time you "get" the collection, you are adding every item to the collection all over again.
Your code:
get
{
foreach (var job in _allJobsList)
_allJobs.Add(job);
return _allJobs;
}
Instead, it should be:
get
{
return _allJobs;
}
The setter of your ObservableCollection is also missing the "setter" (private field = value) code:
set
{
if (value != _allJobs)
{
_allJobs = value;
OnPropertyChanged("AllJobs");
}
}
Your Property AllJobs would then be:
private ObservableCollection<Job> _allJobs;
public ObservableCollection<Job> AllJobs
{
get
{
return _allJobs;
}
set
{
if (value != _allJobs)
{
_allJobs = value;
OnPropertyChanged("AllJobs");
}
}
}
The initialization of your collection should be someplace else (and not in the getter of your property), like in the constructor of the ViewModel or/and in a method that a command calls after the user asks for a refresh of the collection.
For example, if your VieModel is called MyViewModel and your List<Job> is called _allJobsList, you can initialize your collection like so:
public MyViewModel()
{
//fill the _allJobsList first, getting from a database for example: _allJobsList = GetJobs();
//and then create an observable collection from that list
AllJobs = new ObservableCollection<Job>(_allJobsList);
}

UWP XAML Combobox ItemsSource management from another thread

Context
On the network are servers that advertise their names with UDP at regular intervals.
The datagrams come in on port 1967 and contain a string like this:
UiProxy SomeServerMachineName
New entries are added, existing entries are updated and stale entries age out of an observable collection that serves as the ItemsSource of a XAML combo box.
This is the combo box
<ComboBox x:Name="comboBox" ItemsSource="{Binding Directory}" />
and this is the supporting code. Exception handlers wrap everything dangerous but are here omitted for brevity.
public class HostEntry
{
public string DisplayName { get; set;}
public HostName HostName { get; set; }
public DateTime LastUpdate { get; set; }
public HostEntry(string displayname, HostName hostname)
{
DisplayName = displayname;
HostName = hostname;
LastUpdate = DateTime.Now;
}
public override string ToString()
{
return DisplayName;
}
}
HostEntry _selectedHost;
public HostEntry SelectedHost
{
get { return _selectedHost; }
set
{
_selectedHost = value;
UpdateWriter();
}
}
async UpdateWriter() {
if (_w != null)
{
_w.Dispose();
_w = null;
Debug.WriteLine("Disposed of old writer");
}
if (SelectedHost != null)
{
_w = new DataWriter(await _ds.GetOutputStreamAsync(SelectedHost.HostName, "1967"));
Debug.WriteLine(string.Format("Created new writer for {0}", SelectedHost));
}
}
ObservableCollection<HostEntry> _directory = new ObservableCollection<HostEntry>();
public ObservableCollection<HostEntry> Directory
{
get { return _directory; }
}
private async void _ds_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
{
if (_dispatcher == null) return;
await _dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
var dr = args.GetDataReader();
var raw = dr.ReadString(dr.UnconsumedBufferLength);
var s = raw.Split();
if (s[0] == "UiProxy")
{
if (_directory.Any(x => x.ToString() == s[1]))
{ //update
_directory.Single(x => x.ToString() == s[1]).LastUpdate = DateTime.Now;
}
else
{ //insert
_directory.Add(new HostEntry(s[1], args.RemoteAddress));
}
var cutoff = DateTime.Now.AddSeconds(-10);
var stale = _directory.Where(x => x.LastUpdate < cutoff);
foreach (var item in stale) //delete
_directory.Remove(item);
}
});
}
The collection starts empty.
The UpdateWrite method called from the setter of SelectedHost destroys (if necessary) and creates (if possible) a DataWriter around a DatagramSocket aimed at the address described by the value of SelectedHost.
Goals
Automatically select when a value is added and the list ceases to be empty.
The list can also become empty. When this happens the selection must return to null with a selected index of -1.
As things stand, the list is managed and it is possible to interactively pick a server from the list.
At the moment I am setting SelectedHost like this but I am sure it could be done with binding.
private void comboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedHost = comboBox.SelectedItem as HostEntry;
}
The setter method for SelectedHost calls CreateWriter which manages the object used elsewhere to send data to the selected host. I've called this from the setter because it must always happen right after the value changes, and at no other time. It's in a method so it can be async.
I could move it to the SelectionChanged handler but if I do that then how can I guarantee order of execution?
Problem
I get errors when I try to programmatically set the selection of the combo box. I am marshalling onto the UI thread but still things aren't good. What is the right way to do this? I've tried setting SelectedIndex and SelectedValue.
I get errors when I try to programmatically set the selection of the combo box.
How are you doing it? In code-behind this should work so long as the collection you are bound to has an item at that index:
myComboBox.SelectedIndex = 4;
but I am sure it could be done with binding
Yes it can, looks like you forgot to implement INotifyPropertyChanged. Also since you are using UWP there is a new improved binding syntax Bind instead of Binding learn more here: https://msdn.microsoft.com/en-us/windows/uwp/data-binding/data-binding-in-depth
<ComboBox x:Name="comboBox" ItemsSource="{Binding Directory}"
SelectedItem="{Binding SelectedHost}" />
public event PropertyChangedEventHandler PropertyChanged;
HostEntry _selectedHost;
public HostEntry SelectedHost
{
get { return _selectedHost; }
set
{
_selectedHost = value;
RaiseNotifyPropertyChanged();
// What is this? propertys should not do things like this CreateWriter();
}
}
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Custom Label does not show the Text string

I needed to make my own label to hold some value, that is diferent from the value displayed to user
public class LabelBean : Label {
private string value;
public LabelBean(string text = "", string value = ""): base() {
base.Text = text;
this.value = value;
}
public string Value {
get { return value; }
set { this.value = value; }
}
}
but now id in the form constructor I replace the control with my class
this.lbAttributeType = new LabelBean();
and later after the form is created, but before it is shown I set the text through setter
(this.lbAttributeType as LabelBean).Value = value;
this.lbAttributeType.Text = Transform(value);
but in the form I have always "label1" text... what is wrong with it?
thanks
UPDATE
I added the solution here to find it easier:
public class MyLabel : Label {
public MyLabel()
: base() {
}
public string Value {
set {
this.Text = value;
}
}
}
and the form with Widnows.Forms.Label label1 control
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
this.Controls.Remove(this.label1);
this.label1 = new MyLabel();
this.Controls.Add(this.label1);
(this.label1 as MyLabel).Value = "oh";
}
}
the bug was in the Controls.Remove and Controls.Add,
thanks all for their time :)
My guess is because, since you're doing the work in the constructor, the InitializeComponent code, automatically generated by the designer, is overwriting the control instance, as it's most likely called after your initialisation.
If the class is part of the project, you will find it on the toolbox; meaning you can simply drag and drop your new control on the form in place of the existing one - this is what you should do.
This ensures that the designer-generated property is of your LabelBean type, and not simply Label.
Also - you should consider changing your Value setter as demonstrated by WoLfulus (+1 there)
Update
In response to the comment you put on WoLfulus' answer - here's a couple of alternatives:
1) If the form is the 'clever' bit here - consider just writing a helper method in it, and setting the value of the label through it, leveraging the Tag property:
public void SetLabelBean(Label target, string value)
{
Label.Tag = value;
Label.Text = Transform(value);
}
public string GetLabelBean(Label target)
{
return target.Tag as string;
}
2) Continue using your sub-classed LabelBean type (adding it via the designer as I've already mentioned) - but use an abstraction to give it access to the form's Transform method:
public interface ITransformProvider
{
string Transform(string);
}
Make your form class implement this interface, with the Transform method you elude to.
Now, in your LabelBean class:
public ITransformProvider Transformer
{
get{
//searches up the control hierarchy to find the first ITransformProvider.
//should be the form, but also allows you to use your own container controls
//to change within the form. The algorithm could be improved by caching the
//result, invalidating it if the control is moved to another container of course.
var parent = Parent;
ITransformProvider provider = parent as ITransformProvider;
while(provider == null){
parent = parent.Parent;
provider = parent as ITransformProvider;
}
return provider;
}
}
And then, finally, using WoLfulus' code, but slightly changed, you can do this:
public string Value
{
get
{
return value;
}
set
{
this.value = value;
var transformer = Transformer;
if(transformer != null) this.Text = transformer.Transform(value);
}
}
That, I think, addresses your issues with that answer.
Try this:
Create a new delegate outside the label class:
public delegate string LabelFormatDelegate( string val );
Add this to your label class:
public LabelFormatDelegate ValueFormatter = null;
public string Value
{
get
{
return value;
}
set
{
this.value = value;
if (this.ValueFormatter != null)
{
this.Text = this.ValueFormatter(value); // change the label here
}
else
{
this.Text = value;
}
}
}
Place a new common label to your form (lets name it "label1")
Goto to Form1.Designer.cs and search for "label1" declaration.
Rename the "Label" type to your own label type (Ex: "MyLabel")
Change the initialization code of label on InitializeComponent function on designer code to match the new type "MyLabel"
Example:
this.label1 = new Label();
Change to:
this.label1 = new MyLabel();
In the Form_Load event, specify the format function:
this.label1.ValueFormatter = new LabelFormatDelegate(this.Transform);
Notes: You'll need to remove the "Text" setter call too from here:
(this.lbAttributeType as LabelBean).Value = value;
// this.lbAttributeType.Text = Transform(value);
This will keep your value/text in sync but remember not to set "Text" property by hand.
I agree with WoLfulus and Andreas Zoltan and would add a symmetrical functionality to Text if there exists a unambiguous reverse transformation:
public string Value
{
get { return value; }
set
{
if (this.value != value) {
this.value = value;
this.Text = Transform(value);
}
}
}
public override string Text
{
get { return base.Text; }
set
{
if (base.Text != value) {
base.Text = value;
this.value = TransformBack(value);
}
}
}
Note the if checks in order to avoid an endless recursion.
EDIT:
Assigning your label to lbAttributeType is not enough. You must remove the old label from the Controls collection before the assignment and re-add it after the assignment.
this.Controls.Remove(lbAttributeType); // Remove old label
this.lbAttributeType = new LabelBean();
this.Controls.Add(lbAttributeType); // Add new label
Your form was still displaying the old label! Why did I not see it earlier?

Programmatically WPF listbox.ItemsSource update

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.

Categories