I have a WPF application where I communicate with a second application via TCP sockets. Each message sent and received has a given length and information, which is wrapped into an object.
After handling the message, this object is added to an ObservableList, which is (oneway) bound to a CollectionViewSource. This CollectionViewSource is the Itemsource of a Datagrid. Additionally, the CollectionViewSource has a filter which can be set with several toggleButtons. When the connection is closed, the list should be cleared for further use after reconnecting.
I tested this setup and especially the filtering up to a few thousand entries and it seems to work fine. Except if I clear the ObservableCollection, the whole interface becomes unresponsive, slow and setting the filter will spike the processing power used up to 60%, even for 10 objects in the list.
Clearing the ObservableCollection doesn't seem to update the Interface, only overwriting the Collection with a new object.
I also tried to change properties in XAML, like binding mode or read only, but this does change nothing.
<CollectionViewSource x:Key="telegramList"
Source="{Binding Path=LogManager.TList, mode=OneWay }"
Filter="telegramFilter">
</CollectionViewSource>
<DataGrid ItemsSource="{Binding Source={StaticResource telegramList}}" IsReadOnly="True" IsSynchronizedWithCurrentItem="False" x:Name="
Edit: Here is the LogManager, and the functions accessing the list. LogTelegram() can be called from either a Socket Callback, or directly from the main thread, before the message object is queued for sending.
public class LogManager : INotifyPropertyChanged
{
private ObservableCollection<ImmutableTelegram> tList;
public ObservableCollection<ImmutableTelegram> TList { get { return tList; } set { tList = value; NotifyPropertyChanged("TList"); } }
public void logTelegram(Telegram512 telegram)
{
if (!Model.Instance.gui.Dispatcher.CheckAccess())
{
Model.Instance.gui.Dispatcher.Invoke(delegate
{
logTelegramDelegate(telegram);
});
}
else
{
logTelegramDelegate(telegram);
}
}
private void logTelegramDelegate(Telegram512 telegram)
{
if (telegram.TELETYPE == Telegram512.Types.XX)
TList.Add(new ImmutableTelegram(telegram));
if(TList != null)
TList.Add(new ImmutableTelegram(telegram));
}
public void clearTelegramLog()
{
TList = null;
}
public void createTelegramLog()
{
TList = new ObservableCollection<ImmutableTelegram>();
}
}
Here is also the filter function and one state changed listener for a toggle button.
private void dataGridFilter(object sender, FilterEventArgs e)
{
var obj = e.Item as Telegram512;
if (obj != null)
{
e.Accepted = true;
if (obj.TELETYPE == Telegram512.Types.LF && this.lfToggle.IsChecked == false)
{
e.Accepted = false;
return;
}
}
}
private void lfToggle_Click(object sender, RoutedEventArgs e)
{
if(telegramDataGrid.ItemsSource != null)
CollectionViewSource.GetDefaultView(telegramDataGrid.ItemsSource).Refresh();
}
Related
I have a function (Called OpenExcel())that opens an excel document and reads its values.
For the function to know what excel document to open, a string value must be provided. But in MVVM application with a barcode reader, the function will execute before the property has its value assigned. (I have tried with UpdateSourceTrigger=PropertyChanged in the Binding with no luck.)
I ended up using the PreviewTextInput Event of the TextBox, to manually assign to the property (which works).
My issue is that when I call the function from code-behind, the List in the View does not update with the values. (I have stepped through the function and checked that there are values there). However, when I call the function through Prism's ICommand, the list displays all the values.
All Relevant Code:
XAML:
<TextBox Text="{Binding Serial,Mode=TwoWay}" x:Name="serial_txt" PreviewKeyDown="serial_txt_PreviewKeyDown" PreviewTextInput="serial_txt_PreviewTextInput" />
<Button Command="{Binding GetFollower}"/>
<ListView ItemsSource="{Binding Parts}"/>
CodeBehind:
public ListViewModel view;
public LCMList()
{
InitializeComponent();
view = new ListViewModel();
}
private void serial_txt_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter) //scanned string is always followed by Enter input.
{
view.OpenExcel(sender); //Fails if called from here
}
}
private void serial_txt_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
view.Serial += e.Text;
}
ListViewModel:
public ICommand GetFollower
{
get { return new DelegateCommand<object>(OpenExcel); } //Succeeds if called from here
}
private string serial;
public string Serial
{
get { return serial; }
set
{
serial = value;
OnPropertyChanged("Serial");
}
}
private ObservableCollection<Part> parts;
public ObservableCollection<Part> Parts
{
get { return parts; }
set
{
parts = value;
SetProperty(ref parts, value);
OnPropertyChanged("Parts");
}
}
public void OpenExcel(object obj)
{
/* Find Excel document and read the applicable fields and put into partslist.
Needs Serial property as reference */
Parts = partslist;
}
The Parts property gets its values written to, but the list in View stays empty.
In Conclusion:
function updates the properties properly when called through prism and DelegateCommand, but not when called from CodeBehind.
instead of this
public LCMList()
{
InitializeComponent();
view = DataCotnex;
}
private void serial_txt_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter) //scanned string is always followed by Enter input.
{
view.OpenExcel(sender); //Fails if called from here
}
}
you can try that
private void serial_txt_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter) //scanned string is always followed by Enter input.
{
if (!(DataContext is ListViewModel vm)) return;
vm.OpenExcel(sender);
}
}
also don't forget to add datacontext in XAML
<Window.DataContext>
<local:ListViewModel />
</Window.DataContext>
I am using SelectionChanged event of ListBox, but it "doesn't work".
Here is repro:
public partial class MainWindow : Window
{
readonly List<Item> _items = new List<Item>
{
new Item(),
... // add 20 more, to have selected item outside of visible region
new Item(),
new Item { IsSelected = true },
new Item(),
};
void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
Debug.WriteLine($"Changed {e.AddedItems.Count}/{e.RemovedItems.Count}");
void button_Click(object sender, RoutedEventArgs e) =>
listBox.ItemsSource = listBox.ItemsSource == null ? _items : null;
}
public class Item
{
public bool IsSelected { get; set; }
}
and xaml:
<Grid>
<ListBox x:Name="listBox" SelectionChanged="listBox_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Button Content="Test" HorizontalAlignment="Right" VerticalAlignment="Bottom"
Margin="10" Click="button_Click" />
</Grid>
1. Disable virtualization
Add to list:
VirtualizingPanel.IsVirtualizing="False"
Clicking button will produce the output. Cool.
2. VirtualizationMode="Standard"
Remove that line, by default ListBox will use "Standard" virtualization:
Event is not triggered. I need to scroll to selected item to have event triggered.
3. VirtualizationMode="Recycling"
Change virtualization to:
VirtualizingPanel.VirtualizationMode="Recycling"
WTF? Even scrolling doesn't trigger event.
Question: How to get SelectionChanged event to work properly in the most performant mode without need to scroll as in "Standard" mode?
With virtualization, if an item doesn't have a container (ListBoxItem) associated with it, then there's no container to which that ItemContainerStyle is applied. That means your IsSelected binding won't be applied until the item is scrolled into view. Until that property is set, no selection change occurs, and SelectionChanged is not raised.
How to get SelectionChanged event to work properly in the most performant mode without need to scroll as in "Standard" mode?
It arguably *is* working properly. If you approach this from an MVVM angle, then you need not rely on events from UI elements. Track the item selection yourself, in your model. You could use a utility class like this:
public interface ISelectable
{
bool IsSelected { get; set; }
}
public class ItemEventArgs<T> : EventArgs
{
public T Item { get; }
public ItemEventArgs(T item) => this.Item = item;
}
public class SelectionTracker<T> where T : ISelectable
{
private readonly ObservableCollection<T> _items;
private readonly ObservableCollection<T> _selectedItems;
private readonly ReadOnlyObservableCollection<T> _selectedItemsView;
private readonly HashSet<T> _trackedItems;
private readonly HashSet<T> _fastSelectedItems;
public SelectionTracker(ObservableCollection<T> items)
{
_items = items;
_selectedItems = new ObservableCollection<T>();
_selectedItemsView = new ReadOnlyObservableCollection<T>(_selectedItems);
_trackedItems = new HashSet<T>();
_fastSelectedItems = new HashSet<T>();
_items.CollectionChanged += OnCollectionChanged;
}
public event EventHandler<ItemEventArgs<T>> ItemSelected;
public event EventHandler<ItemEventArgs<T>> ItemUnselected;
public ReadOnlyObservableCollection<T> SelectedItems => _selectedItemsView;
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewItems == null)
goto default;
AddItems(e.NewItems.OfType<T>());
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldItems == null)
goto default;
RemoveItems(e.OldItems.OfType<T>());
break;
case NotifyCollectionChangedAction.Replace:
if (e.OldItems == null || e.NewItems == null)
goto default;
RemoveItems(e.OldItems.OfType<T>());
AddItems(e.NewItems.OfType<T>());
break;
case NotifyCollectionChangedAction.Move:
break;
default:
Refresh();
break;
}
}
public void Refresh()
{
RemoveItems(_trackedItems);
AddItems(_items);
}
private void AddItems(IEnumerable<T> items)
{
foreach (var item in items)
{
var observableItem = item as INotifyPropertyChanged;
if (observableItem != null)
observableItem.PropertyChanged += OnItemPropertyChanged;
_trackedItems.Add(item);
UpdateItem(item);
}
}
private void RemoveItems(IEnumerable<T> items)
{
foreach (var item in items)
{
var observableItem = item as INotifyPropertyChanged;
if (observableItem != null)
observableItem.PropertyChanged -= OnItemPropertyChanged;
_trackedItems.Remove(item);
UpdateItem(item);
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender is T item)
UpdateItem(item);
}
private void UpdateItem(T item)
{
if (item?.IsSelected == true && _trackedItems.Contains(item))
{
if (_fastSelectedItems.Add(item))
{
_selectedItems.Add(item);
this.ItemSelected?.Invoke(this, new ItemEventArgs<T>(item));
}
}
else
{
if (_fastSelectedItems.Remove(item))
{
_selectedItems.Remove(item);
this.ItemUnselected?.Invoke(this, new ItemEventArgs<T>(item));
}
}
}
}
When you create your ObservableCollection of items, instantiate a SelectionTracker for that collection. Then subscribe to ItemSelected and ItemUnselected to handle individual selection changes, or alternatively subscribe to SelectedItems.CollectionChanged. If you don't care about being able to access SelectedItems as a collection, then you can get rid of _selectedItems and _selectedItemsView and avoid some list removal overhead.
[With VirtualizationMode="Recycling"] WTF? Even scrolling doesn't trigger event.
Well, that's a strange one. I see no reason why that should not work in this case, but I can perhaps see why it might not always work. In theory, as soon as the container is 'recycled' and its DataContext is assigned a new item, the IsSelected binding should update. If the container's previously assigned item had also been selected, that might not trigger a property change, and thus the event might not fire. But that doesn't seem to be the case in your example. Possibly a bug or unintended consequence of how recycling is implemented.
Don’t use IsSelected to manage selection.
I think the big takeaway here is that using ListBoxItem.IsSelected to *set* the selection is unreliable; it should only be trusted to reflect whether a given container is selected. It’s really intended for style and template triggers, so that they may know whether to render a container as selected or not. It was never meant to manage selection, and it’s a mistake to use it that way, because it represents the selection state of the container and not its associated data item. Thus, it only works in the most naïve and least performant scenario where every item is always associated with its own container (no virtualization).
I have a ComboBox that is bound to a property on my ViewModel (from hear on "VM".) When a user makes a selection on the ComboBox it properly updates the bound property in my VM. Within my UI code, I have subscribed to the PropertyChanged event on my VM.
As it should behave, when the user makes a selection within the ComboBox, my PropertyChanged event is correctly executing in my UI back-end code. When the UI code catches the change of this property, under certain selection conditions I need to halt the process and request the user for additional information. From the UI, I send them a dialog. If they cancel the dialog, I reset the value in the VM that is associated with the ComboBox controls SelectedValue.
This is what I've observed. When the operation is cancelled by the user, my VM property is being set to the new value. However, the ComboBox is still showing the text value of the original entry that has now changed. How can I force the ComboBox to update itself from within my PropertyChanged event? In this case, I think it's just a textual issue or numeric index change that's referencing the text data from the bound collection. The data is correct in the VM but the display value for the ComboBox is wrong.
EXAMPLE
ComboBox Details
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
Height="27" />
Sorry for the wordy properties on the VM, but that's to explain what's happening. My ListOfComboBoxDisplayObjects collection represents a set of enumerator values that are stored in the path within SelectedValuePath. The descriptive text for each value is pulled from the ListOfComboBoxDisplayObjects which is a special list strictly created for the UI. This basically pairs an enumeration value with a meaningful description.
ListOfComboBoxDisplayObjects Definition (from within VM)
Edit #1 - Added this definition to my example
private ObservableCollection<BindableEnumerationItem<Values>> _listOfComboBoxDisplayObjects;
public ObservableCollection<BindableEnumerationItem<Values>> ListOfComboBoxDisplayObjects
{
get { return _listOfComboBoxDisplayObjects; }
private set
{
if (value != _listOfComboBoxDisplayObjects)
{
_listOfComboBoxDisplayObjects= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ListOfComboBoxDisplayObjects)));
}
}
}
MySelectionIsAnEnumeration Definition (From within VM)
*Edit #1: Adding this code definition.
private Values_mySelectionIsAnEnumeration ;
public Values MySelectionIsAnEnumeration
{
get { return _mySelectionIsAnEnumeration; }
set
{
//Double-checked this-- value is different on the second-call to change this value, once the UI cancels the operation.
if (value != _mySelectionIsAnEnumeration)
{
_mySelectionIsAnEnumeration= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(MySelectionIsAnEnumeration )));
}
}
}
Pertinent Values Associated with ListOfComboBoxDisplayObjects
These values are generated in the ctor of the VM. They are fixed throughout the application.
Item #1
Text: "This is a Foo!"
Value: Values.Foo
Item #2:
Text: "Hi, I'm Bar."
Value: Values.Bar
Item #3:
Text: "This is Baz. I need to ask a question before I can be used."
Value: Values.Baz
PropertyChanged Event - From the UI Back-End
private void VM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "MySelectionIsAnEnumeration":
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if(!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
break;
}
}
After executing the above code, what I'm observing is that the VM.MySelectionIsAnEnumeration value is indeed changing to the proper value of Value.Foo when a user cancels the operation within AskAQuestionAndGetAResult(). However, after it's finished the ComboBox still reads "This is Baz. I need to ask a question before I can be used.", which is obviously the display value associated with Value.Baz.
How can I update both the underlying VM property AND the display text on the CombobBox to correctly show the valued that is now stored in VM.MySelectionIsAnEnumeration?
Edit #2
Below is the code efor my BindableEnumerationItem that I use within my Observable Collections for comboxes and list boxes. This is used throughout my application in simpler cases and has caused no issue. Please note, this is my actual, unaltered code. I've not renamed anything. My comboboxes can bind to each Item property for a type-safe property and DisplayText is the descriptor text.
public class BindableEnumerationItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private T _item;
public BindableEnumerationItem(T item, string displayText)
{
_item = item;
_displayText = displayText;
}
private string _displayText;
public string DisplayText
{
get { return _displayText; }
set
{
if (value != _displayText)
{
_displayText = value;
PropertyChanged(this, new PropertyChangedEventArgs("DisplayText"));
}
}
}
public T Item
{
get { return _item; }
set
{
_item = value;
PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
}
create an extension that will wire up the command from your viewmodel in xaml to the selector, which in this case is the combobox.
public partial class Extensions
{
public static readonly DependencyProperty SelectionChangedCommandProperty = DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
{
var element = s as Selector;
if (element != null)
{
element.SelectionChanged -= OnSelectionChanged;
if (e.NewValue != null)
{
element.SelectionChanged += OnSelectionChanged;
}
}
}));
public static ICommand GetSelectionChangedCommand(UIElement element)
{
return (ICommand)element.GetValue(SelectionChangedCommandProperty);
}
public static void SetSelectionChangedCommand(UIElement element, ICommand value)
{
element.SetValue(SelectionChangedCommandProperty, value);
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var element = sender as Selector;
var command = element.GetValue(SelectionChangedCommandProperty) as ICommand;
if (command != null && command.CanExecute(element.SelectedItem))
{
command.Execute(element.SelectedItem);
e.Handled = true;
}
}
}
Create the command in the viewmodel that handles the value changed event.
public ICommand EnumerationValueChangedCommand
{
get
{
return new Command(
() =>
{
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if (!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
});
}
}
And then bind using that extension. ext is the namespace for your extensions.
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
ext:Extensions.SelectionChangedCommand="{Binding EnumerationValueChangedCommand}"
Height="27" />
I'm trying to make some sort of wheel spinning. I have 5 customized text blocks, text file with the list of values (it may consist of 1-1000 items). After reading the file I have a 'List fileValues' with its values. I decided to create another 'List wheel' which will contain up to 5 elements at the time and is expected to be bind to text blocks.
When one presses a spin button, last element of 'wheel' is removed and new element from 'values' is added to the beginning of the 'wheel' list.
In order UI will be responsive to changes in the list, it is good to bind each element in the 'wheel' to corresponding text block on UI. But what I tried to do up to this moment didn't work.
Here is what I tried to do (the code is a little bit dirty, but I try to make it work firstly).
5 customized text blocks
<TextBlock Name="Value1" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value2" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value3" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value4" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value5" TextWrapping="WrapWithOverflow"/>
ObservableList which implements INotifyCollectionChanged interface
class ObservableList : INotifyCollectionChanged, IEnumerable
{
private readonly List<string> _valuesList;
public string First
{
get { return _valuesList.First(); }
}
public string Last
{
get { return _valuesList.Last(); }
}
public ObservableList()
{
this._valuesList = new List<string>();
}
public string this[Int32 index]
{
get
{
if (_valuesList.Count == 0 || index + 1 > _valuesList.Count)
{
return "------";
}
return _valuesList[index];
}
}
public void AddLast(string value)
{
_valuesList.Add(value);
OnNotifyCollectionChanged();
}
public void AddFirst(string value)
{
_valuesList.Insert(0, value);
OnNotifyCollectionChanged();
}
public void RemoveFirst()
{
_valuesList.RemoveAt(0);
OnNotifyCollectionChanged();
}
public void RemoveLast()
{
_valuesList.Remove(_valuesList.Last());
OnNotifyCollectionChanged();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void OnNotifyCollectionChanged()
{
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
public IEnumerator GetEnumerator()
{
return (_valuesList as IEnumerable).GetEnumerator();
}
}
XAML Code-behind
public partial class MainWindow : Window
{
private List<string> _values = new List<string>();
private ObservableList _uiValues = new ObservableList();
public MainWindow()
{
InitializeComponent();
Value1.DataContext = _uiValues[0];
Value2.DataContext = _uiValues[1];
Value3.DataContext = _uiValues[2];
Value4.DataContext = _uiValues[3];
Value5.DataContext = _uiValues[4];
}
private void LoadFileBtn_Click(object sender, RoutedEventArgs e)
{
//Loads text file and fills _values
}
private void SpinBtn_Click(object sender, RoutedEventArgs e)
{
InitUiTextBlocks();
//Spin simulation
}
private void InitUiTextBlocks()
{
_uiValues.Clear();
for (int i = 0; i < 5; ++i)
{
//Nothing appears on UI and CollectionChanged event is null
_uiValues.AddLast(_values.First());
_values.RemoveAt(0);
}
}
}
I tried to use 'ObservableCollection', but the effect is the same. Nothing appears on UI. In fact I can't imagine how to bind each of List element to specific Label. Is it even possible to do such binding?
In the XAML do something like:
<Label Name="some_name" Content="{Binding SomeStingProperty}"/>
and in the code behind, have a
public string SomeStringProperty {get; set;}
you can bind to a collection as well, and if it's an ObservableCollection it will update on change.
search for basic XAML binding otherwise :)
(on a side note, it's cleaner i think it the XAML, i personally don't like to do it in the code behind ...)
As a side note, and totally self promoting, here are 2 articles that will probably help:
Understanding selected value
The big mvvm template.
The second might be a bit over your head if you're a beginner, but should be worth reading nevertheless.
I'm writing a simple tool for troubleshooting computers. Basically its just a WPF Window with a ListBox bound to an ObservableCollection<ComputerEntry> where ComputerEntry is a simple class containing the computer host name, and Status. All the tool does is ping each compute name in the list, and if a response is received ComputerEntry.Status is updated to indicate the computer is connected to the network somewhere...
Pinging however can take some time, up to a couple seconds per computer depending on if it has to timeout or not. So I'm running the actual ping in a BackgroundWorker and using the ReportProgress method to update the UI.
Unfortunately the ObservableCollection does not seem raise the PropertyChanged event after the objects are updated. The collection does update with the new information, but the status never changes in the ListBox. Presumably because it does not know that the collection has changed.
[EDIT]
Per fantasticfix, the key here is: "The ObservableCollection fires just when the list gets changed (added, exchanged, removed)." Since I was setting the properties of the object instead of modifying it, the ObservableCollection was not notifying the list of the change -- it didn't know how. After implenting INotifyPropertyChanged everything works fine. Conversly, replacing the object in the list with a new updated instance will also fix the problem.
[/EDIT]
Btw I'm using C# 3.5 and I'm not in a position where I can add additional dependancies like TPL.
So as a simplified example [that won't compile without more work...]:
//Real one does more but hey its an example...
public class ComputerEntry
{
public string ComputerName { get; private set; }
public string Status { get; set; }
public ComputerEntr(string ComputerName)
{
this.ComptuerName = ComputerName;
}
}
//...*In Window Code*...
private ObservableCollection<ComputerEntry> ComputerList { get; set; }
private BackgroundWorker RefreshWorker;
private void Init()
{
RefreshWorker = new BackgroundWorker();
RefreshWorker.WorkerReportsProgress = true;
RefreshWorker.DoWork += new DoWorkEventHandler(RefreshWorker_DoWork);
RefreshWorker.ProgressChanged += new ProgressChangedEventHandler(RefreshWorker_ProgressChanged);
}
private void Refresh()
{
RefreshWorker.RunWorkerAsync(this.ComputerList);
}
private void RefreshWorker_DoWork(object sender, DoWorkEventArgs e)
{
List<ComputerEntry> compList = e as List<ComputerEntry>;
foreach(ComputerEntry o in compList)
{
ComputerEntry updatedValue = new ComputerEntry();
updatedValue.Status = IndicatorHelpers.PingTarget(o.ComputerName);
(sender as BackgroundWorker).ReportProgress(0, value);
}
}
private void RefreshWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ComputerEntry updatedValue = new ComputerEntry();
if(e.UserState != null)
{
updatedValue = (ComputerEntry)e.UserState;
foreach(ComputerEntry o in this.ComputerList)
{
if (o.ComputerName == updatedValue.ComputerName)
{
o.Status = updatedValue.Status;
}
}
}
}
Sorry for the jumble but its rather long with all the support code. Anyways, void Refresh() is called from a DispatcherTimer (which isn't shown), that starts RefreshWorker.RunWorkerAsync(this.ComputerList);.
I've been fighting this for a few days so I'm now to the point where I'm not actually attempting to modify the objects referenced in the ObservableCollection directly anymore. Hence the ugly looping through the ComputerList collection and setting the properties directly.
Any idea whats going on here and how I can fix it?
The observableCollection wont fire when you change properties of items which are inside of the collection (how should it even know that). The ObservableCollection fires just when the list gets changed (added, exchanged, removed).
If you want to detect the changes of the properties of the ComputerEntry the class has to Implement the INotifyPropertyChange interface (if you know MVVM, its like a lightweight MVVM pattern)
public class ComputerEntry : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void RaisePropertyChanged(String propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private String _ComputerName;
public String ComputerName
{
get
{
return _ComputerName;
}
set
{
if (_ComputerName != value)
{
_ComputerName = value;
this.RaisePropertyChanged("ComputerName");
}
}
}
}
Haven't used this in a long time, but don't you need something like INotifyPropertyChanged implemented?