The c# app that I'm working on uses an ObservableDictionary. The performance on this is not nearly fast enough to accommodate it's functionality. The ObservableDictionary is very rapidly being interacted with (Removing, Adding, and Updating elements) and has to sort every single time a change is made. Is there an alternative I can use to ObservableDictionary that would focus on performance and still be able to sort rapidly?
It's very simple to write your own that does not cause a rebinding each time an add / update or delete is done. We wrote our own because of this very reason. Essentially what we do is to disable the notification that a change has been made until all objects in the collection have been processed, and then we generate the notification. It looks something like this. As you can see, we use MvvmLight as our MVVM framework library. This will improve performance tremendously.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using FS.Mes.Client.Mvvm.MvvmTools.MvvmLight;
namespace FS.Mes.Client.Mvvm.MvvmTools
{
/// <summary>
/// Our implementation of ObservableCollection. This fixes one significant limitation in the .NET Framework implementation. When items
/// are added or removed from an observable collection, the OnCollectionChanged event is fired for each item. Depending on how the collection
/// is bound, this can cause significant performance issues. This implementation gets around this by suppressing the notification until all
/// items are added or removed. This implementation is also thread safe. Operations against this collection are always done on the thread that
/// owns the collection.
/// </summary>
/// <typeparam name="T">Collection type</typeparam>
public class FsObservableCollection<T> : ObservableCollection<T>
{
#region [Constructor]
/// <summary>
/// Constructor
/// </summary>
public FsObservableCollection()
{
DispatcherHelper.Initialize();
}
#endregion
#region [Public Properties]
/// <summary>
/// Gets or sets a property that determines if we are delaying notifications on updates.
/// </summary>
public bool DelayOnCollectionChangedNotification { get; set; }
#endregion
/// <summary>
/// Add a range of IEnumerable items to the observable collection and optionally delay notification until the operation is complete.
/// </summary>
/// <param name="items"></param>
/// <param name="delayCollectionChangedNotification">Value indicating whether delay notification will be turned on/off</param>
public void AddRange(IEnumerable<T> items, bool delayCollectionChangedNotification = true)
{
if (items == null)
throw new ArgumentNullException("items");
DoDispatchedAction(() =>
{
DelayOnCollectionChangedNotification = delayCollectionChangedNotification;
// Do we have any items to add?
if (items.Any())
{
try
{
foreach (var item in items)
this.Add(item);
}
finally
{
// We're done. Turn delay notification off and call the OnCollectionChanged() method and tell it we had a 'dramatic' change
// in the collection.
DelayOnCollectionChangedNotification = false;
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
});
}
/// <summary>
/// Clear the items in the ObservableCollection and optionally delay notification until the operation is complete.
/// </summary>
public void ClearItems(bool delayCollectionChangedNotification = true)
{
// Do we have anything to remove?
if (!this.Any())
return;
DoDispatchedAction(() =>
{
try
{
DelayOnCollectionChangedNotification = delayCollectionChangedNotification;
this.Clear();
}
finally
{
// We're done. Turn delay notification off and call the OnCollectionChanged() method and tell it we had a 'dramatic' change
// in the collection.
DelayOnCollectionChangedNotification = false;
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
});
}
/// <summary>
/// Override the virtual OnCollectionChanged() method on the ObservableCollection class.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
DoDispatchedAction(() =>
{
if (!DelayOnCollectionChangedNotification)
base.OnCollectionChanged(e);
});
}
/// <summary>
/// Makes sure 'action' is executed on the thread that owns the object. Otherwise, things will go boom.
/// </summary>
///<param name="action">The action which should be executed</param>
private static void DoDispatchedAction(Action action)
{
DispatcherHelper.CheckInvokeOnUI(action);
}
}
}
Related
I have a custom BindingList that I want create a custom AddRange method for.
public class MyBindingList<I> : BindingList<I>
{
...
public void AddRange(IEnumerable<I> vals)
{
foreach (I v in vals)
Add(v);
}
}
My problem with this is performance is terrible with large collections. The case I am debugging now is trying to add roughly 30,000 records, and taking an unacceptable amount of time.
After looking into this issue online, it seems like the problem is that the use of Add is resizing the array with each addition. This answer I think summarizes it as :
If you are using Add, it is resizing the inner array gradually as needed (doubling)
What can I do in my custom AddRange implementation to specify the size the BindingList needs to resize to be based on the item count, rather than letting it constantly re-allocate the array with each item added?
CSharpie explained in his answer that the bad performance is due to the ListChanged-event firing after each Add, and showed a way to implement AddRange for your custom BindingList.
An alternative would be to implement the AddRange functionality as an extension method for BindingList<T>. Based on on CSharpies implementation:
/// <summary>
/// Extension methods for <see cref="System.ComponentModel.BindingList{T}"/>.
/// </summary>
public static class BindingListExtensions
{
/// <summary>
/// Adds the elements of the specified collection to the end of the <see cref="System.ComponentModel.BindingList{T}"/>,
/// while only firing the <see cref="System.ComponentModel.BindingList{T}.ListChanged"/>-event once.
/// </summary>
/// <typeparam name="T">
/// The type T of the values of the <see cref="System.ComponentModel.BindingList{T}"/>.
/// </typeparam>
/// <param name="bindingList">
/// The <see cref="System.ComponentModel.BindingList{T}"/> to which the values shall be added.
/// </param>
/// <param name="collection">
/// The collection whose elements should be added to the end of the <see cref="System.ComponentModel.BindingList{T}"/>.
/// The collection itself cannot be null, but it can contain elements that are null,
/// if type T is a reference type.
/// </param>
/// <exception cref="ArgumentNullException">values is null.</exception>
public static void AddRange<T>(this System.ComponentModel.BindingList<T> bindingList, IEnumerable<T> collection)
{
// The given collection may not be null.
if (collection == null)
throw new ArgumentNullException(nameof(collection));
// Remember the current setting for RaiseListChangedEvents
// (if it was already deactivated, we shouldn't activate it after adding!).
var oldRaiseEventsValue = bindingList.RaiseListChangedEvents;
// Try adding all of the elements to the binding list.
try
{
bindingList.RaiseListChangedEvents = false;
foreach (var value in collection)
bindingList.Add(value);
}
// Restore the old setting for RaiseListChangedEvents (even if there was an exception),
// and fire the ListChanged-event once (if RaiseListChangedEvents is activated).
finally
{
bindingList.RaiseListChangedEvents = oldRaiseEventsValue;
if (bindingList.RaiseListChangedEvents)
bindingList.ResetBindings();
}
}
}
This way, depending on your needs, you might not even need to write your own BindingList-subclass.
You can pass in a List in the constructor and make use of List<T>.Capacity.
But i bet, the most significant speedup will come form suspending events when adding a range. So I included both things in my example code.
Probably needs some finetuning to handle some worst cases and what not.
public class MyBindingList<I> : BindingList<I>
{
private readonly List<I> _baseList;
public MyBindingList() : this(new List<I>())
{
}
public MyBindingList(List<I> baseList) : base(baseList)
{
if(baseList == null)
throw new ArgumentNullException();
_baseList = baseList;
}
public void AddRange(IEnumerable<I> vals)
{
ICollection<I> collection = vals as ICollection<I>;
if (collection != null)
{
int requiredCapacity = Count + collection.Count;
if (requiredCapacity > _baseList.Capacity)
_baseList.Capacity = requiredCapacity;
}
bool restore = RaiseListChangedEvents;
try
{
RaiseListChangedEvents = false;
foreach (I v in vals)
Add(v); // We cant call _baseList.Add, otherwise Events wont get hooked.
}
finally
{
RaiseListChangedEvents = restore;
if (RaiseListChangedEvents)
ResetBindings();
}
}
}
You cannot use the _baseList.AddRangesince BindingList<T> wont hook the PropertyChanged event then. You can bypass this only using Reflection by calling the private Method HookPropertyChanged for each Item after AddRange. this however only makes sence if vals (your method parameter) is a collection. Otherwise you risk enumerating the enumerable twice.
Thats the closest you can get to "optimal" without writing your own BindingList.
Which shouldnt be too dificult as you could copy the source code from BindingList and alter the parts to your needs.
I've written a DLL that does a bunch of stuff. One of the things it does is to search through a lot of incoming messages for a specific message, clean up the message, and raise an event when the message is found, and pass the data via the EventArgs.
Currently the DLL is working but the routine that searches for the message is slowing everything down and making the system slow to respond because there is so much data to go through.
I would like to move this searching to it's own thread, and once the message is found have it raise an event on the main thread and pass the message data to the main thread. I know how to make a thread to do the work, but I do not know how to make the thread raise the event on the main thread and pass the data to it. Could someone tell me how to do this, or point to an example of it?
I've tried creating a delegate and triggering an event, which works but when I try to pass the data I get a cross thread exception.
Some details that may be important. There most likely won't be a GUI at all and will probably remain a console application. Messages will always be coming in and the DLL has to continuously search them. The message the DLL is looking for may come as often as 1 couple times a second or it may be days between the messages.
Update for a real simple project illustrating what I would like to do. This code is rough cause I threw it together to test this out.
If you follow the code you will see that everything runs fine until the line in the CollectData function where:
StatusUpdated(this, null);
is called. At this point it causes the cross thread exception because of the UI thread and the data Collection thread. I know I can fix this by using an invoke on the UI thread, but as mentioned above this will most likely not be used with any sort of GUI (but it should be able to handle it). So the cross thread exception needs to be fixed in the BuildResultsManager class, I think in the function BRMgr_StatusUpdated. How do I get the data (in this case the value of currentVal) to the UI thread without changing any code in the MainWindow class.
You can run this by creating a solution with 2 projects, 1 for the first 2 files as a dll. The second as a wpf project, referencing the dll, and put a textbox named status on the form.
Main Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace BuildResultsManager
{
/// <summary>
/// Delegate to notify when data has changed
/// </summary>
/// <param name="sender">unused</param>
/// <param name="e">unused</param>
public delegate void StatusUpdatedEventHandler(object sender, EventArgs e);
/// <summary>
/// Connects to the PLC via PVI and reads in all the build results data, conditions it, updates values and reads/store into the database
/// </summary>
public class BuildResultsManager
{
#region events
// Change in The Build Results Manger Status
public event StatusUpdatedEventHandler StatusUpdated;
#endregion
#region Local variables
private Thread collectionThread;
private string statusMessage;
DataCollector dataCollection;
#endregion
#region Properties
/// <summary>
/// Current Status of the Build Results manager
/// </summary>
public String StatusMessage
{
get
{
return statusMessage;
}
private set
{
statusMessage = value;
if (StatusUpdated != null)
StatusUpdated(this, null);
}
}
#endregion
#region Constructors
/// <summary>
/// Default constructor
/// </summary>
public BuildResultsManager()
{
StatusMessage = "successfully initialized";
// start the thread that will collect all the data
dataCollection = new DataCollector();
dataCollection.StatusUpdated += new DCStatusUpdatedEventHandler(BRMgr_StatusUpdated);
collectionThread = new Thread(new ThreadStart(dataCollection.CollectData));
collectionThread.Start();
}
/// <summary>
/// EVent to handle updated status text
/// </summary>
/// <param name="sender">unused</param>
/// <param name="e">unused</param>
void BRMgr_StatusUpdated(object sender, EventArgs e)
{
StatusMessage = dataCollection.currentVal.ToString();
}
#endregion
}
}
The class that will be doing all of the thread work:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Windows.Threading;
using System.Threading;
namespace BuildResultsManager
{
/// <summary>
/// Delegate to notify when data has changed
/// </summary>
/// <param name="sender">unused</param>
/// <param name="e">unused</param>
public delegate void DCStatusUpdatedEventHandler(object sender, EventArgs e);
/// <summary>
/// Handles all of the data collection and conditioning
/// </summary>
class DataCollector
{
#region events
// Change in The Build Results Manger Status
public event DCStatusUpdatedEventHandler StatusUpdated;
#endregion
private const long updateInterval = 1000;
private Stopwatch updateTimer;
public int currentVal;
#region local variables
private bool shouldStop;
#endregion
/// <summary>
/// Default Constructor
/// </summary>
public DataCollector()
{
shouldStop = false;
updateTimer = new Stopwatch();
updateTimer.Start();
}
/// <summary>
/// Main task that listens for new data and collects it when it's available
/// </summary>
public void CollectData()
{
currentVal = 5;
while (!shouldStop && currentVal < 10)
{
if(updateTimer.ElapsedMilliseconds > updateInterval)
{
currentVal++;
if (StatusUpdated != null)
{
StatusUpdated(this, null);
}
//reset the timer
updateTimer.Restart();
}
}
}
/// <summary>
/// Asks the thread to stop
/// </summary>
public void RequestStop()
{
shouldStop = true;
}
}
}
Code behind for the wpf project:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region Local variables
private string BRMgrStatus;
private BuildResultsManager.BuildResultsManager BRMgr;
#endregion
public MainWindow()
{
InitializeComponent();
// create an instance of the build manager
BRMgr = new BuildResultsManager.BuildResultsManager();
if(BRMgr != null)
{
status.Text = BRMgr.StatusMessage;
BRMgr.StatusUpdated += new StatusUpdatedEventHandler(BRMgr_StatusUpdated);
}
}
/// <summary>
/// EVent to handle updated status text
/// </summary>
/// <param name="sender">unused</param>
/// <param name="e">unused</param>
void BRMgr_StatusUpdated(object sender, EventArgs e)
{
BuildResultsManager.BuildResultsManager brm;
brm = (BuildResultsManager.BuildResultsManager)sender;
status.Text = brm.StatusMessage;
}
}
If you don't use a GUI there's no need to execute things on the same thread, just protect non-thread-safe resources with a locking mechanism (lock, mutex, etc) to avoid concurrent access and you're good.
I'm newer to coding, as a warning not an excuse. I currently have a quick timer class 'wrapper' I use. While most of the time I've used it, it worked as expected, I now have an issue.
The code the timer calls back does not always have a set or even accurate estimate of how long it will take to execute that chunk of code start to finish. This leaves me with abnormally long timer update intervals to account for it, or, alternatively stop the timer, and start it again each time it 'ticks' to prevent over-lap.
I've found the first method unacceptable in my case. The second one sounded perfect, but when I tried it it slowed down the code a lot and could have easily ticked much more often after I reviewed it. I'm not sure why it slows it down so much.
Should I look at another way at doing this, besides timers, like some kind of manual control over the flow of events? This is for a simple game-update loop class. Here is said class in full below.
public class TimedUpdater<T>
{
#region Public Delegates/Events
/// <summary>
/// An event that is raised repeatedly at the Interval[in milliseconds] for this Instance.
/// </summary>
public event EventHandler<T> OnUpdate;
#endregion
#region Fields, Private Properties
private Timer Timer { get; }
private TimerCallback Callback { get; }
private T State { get; }
#endregion
#region Constructors, Destructors
/// <summary>
/// Initializes a new instance of the <see cref="TimedUpdater{T}" /> class.
/// </summary>
/// <param name="state">The state object to use for updating data.</param>
/// <param name="name">The unique name representing this instance.</param>
/// <param name="updateRateMs">The rate the the <code>OnUpdate</code> event is raised in milliseconds.</param>
public TimedUpdater(T state, string name, int updateRateMs)
{
Name = name;
Interval = updateRateMs;
State = state;
Callback += Process;
Timer = new Timer(Callback, State, Timeout.Infinite, Timeout.Infinite);
}
/// <summary>
/// Allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage
/// collection.
/// </summary>
~TimedUpdater()
{
Dispose();
}
#endregion
#region Public Properties, Indexers
/// <summary>
/// Gets or sets the interval in milliseconds.
/// </summary>
/// <value>The interval in milaseconds.</value>
public int Interval { get; set; }
/// <summary>
/// States if the updater is enabled.
/// </summary>
public bool IsEnabled { get; private set; }
/// <summary>
/// Gets the unique name that represents this instance.
/// </summary>
/// <value>The name.</value>
public string Name { get; }
#endregion
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Disable();
Timer.Dispose();
}
/// <summary>
/// Enables the updater.
/// </summary>
public void Enable()
{
Timer.Change(Interval, Interval);
IsEnabled = true;
}
/// <summary>
/// Disables the updater.
/// </summary>
public void Disable()
{
Timer.Change(int.MaxValue, Interval);
IsEnabled = false;
}
/// <summary>
/// Processes the specified object casted to the type.
/// </summary>
/// <param name="e">The e.</param>
protected virtual void Process(object e)
{
OnUpdate?.Invoke(this, (T) e);
}
}
Completely editing the earlier version, Can the following implementation be the Thread Safe List implementation. I just need to know whether it would truly thread safe or not, I know performance wise there would still be issues. Currently version is using ReaderWriterLockSlim, I have another implementation using the Lock, doing the same job
using System.Collections.Generic;
using System.Threading;
/// <summary>
/// Thread safe version of the List using ReaderWriterLockSlim
/// </summary>
/// <typeparam name="T"></typeparam>
public class ThreadSafeListWithRWLock<T> : IList<T>
{
// Internal private list which would be accessed in a thread safe manner
private List<T> internalList;
// ReaderWriterLockSlim object to take care of thread safe acess between multiple readers and writers
private readonly ReaderWriterLockSlim rwLockList;
/// <summary>
/// Public constructor with variable initialization code
/// </summary>
public ThreadSafeListWithRWLock()
{
internalList = new List<T>();
rwLockList = new ReaderWriterLockSlim();
}
/// <summary>
/// Get the Enumerator to the Thread safe list
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{
return Clone().GetEnumerator();
}
/// <summary>
/// System.Collections.IEnumerable.GetEnumerator implementation to get the IEnumerator type
/// </summary>
/// <returns></returns>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return Clone().GetEnumerator();
}
/// <summary>
/// Clone method to create an in memory copy of the Thread safe list
/// </summary>
/// <returns></returns>
public List<T> Clone()
{
List<T> clonedList = new List<T>();
rwLockList.EnterReadLock();
internalList.ForEach(element => { clonedList.Add(element); });
rwLockList.ExitReadLock();
return (clonedList);
}
/// <summary>
/// Add an item to Thread safe list
/// </summary>
/// <param name="item"></param>
public void Add(T item)
{
rwLockList.EnterWriteLock();
internalList.Add(item);
rwLockList.ExitWriteLock();
}
/// <summary>
/// Remove an item from Thread safe list
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool Remove(T item)
{
bool isRemoved;
rwLockList.EnterWriteLock();
isRemoved = internalList.Remove(item);
rwLockList.ExitWriteLock();
return (isRemoved);
}
/// <summary>
/// Clear all elements of Thread safe list
/// </summary>
public void Clear()
{
rwLockList.EnterWriteLock();
internalList.Clear();
rwLockList.ExitWriteLock();
}
/// <summary>
/// Contains an item in the Thread safe list
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool Contains(T item)
{
bool containsItem;
rwLockList.EnterReadLock();
containsItem = internalList.Contains(item);
rwLockList.ExitReadLock();
return (containsItem);
}
/// <summary>
/// Copy elements of the Thread safe list to a compatible array from specified index in the aray
/// </summary>
/// <param name="array"></param>
/// <param name="arrayIndex"></param>
public void CopyTo(T[] array, int arrayIndex)
{
rwLockList.EnterReadLock();
internalList.CopyTo(array,arrayIndex);
rwLockList.ExitReadLock();
}
/// <summary>
/// Count elements in a Thread safe list
/// </summary>
public int Count
{
get
{
int count;
rwLockList.EnterReadLock();
count = internalList.Count;
rwLockList.ExitReadLock();
return (count);
}
}
/// <summary>
/// Check whether Thread safe list is read only
/// </summary>
public bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// Index of an item in the Thread safe list
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public int IndexOf(T item)
{
int itemIndex;
rwLockList.EnterReadLock();
itemIndex = internalList.IndexOf(item);
rwLockList.ExitReadLock();
return (itemIndex);
}
/// <summary>
/// Insert an item at a specified index in a Thread safe list
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
public void Insert(int index, T item)
{
rwLockList.EnterWriteLock();
if (index <= internalList.Count - 1 && index >= 0)
internalList.Insert(index,item);
rwLockList.ExitWriteLock();
}
/// <summary>
/// Remove an item at a specified index in Thread safe list
/// </summary>
/// <param name="index"></param>
public void RemoveAt(int index)
{
rwLockList.EnterWriteLock();
if (index <= internalList.Count - 1 && index >= 0)
internalList.RemoveAt(index);
rwLockList.ExitWriteLock();
}
/// <summary>
/// Indexer for the Thread safe list
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T this[int index]
{
get
{
T returnItem = default(T);
rwLockList.EnterReadLock();
if (index <= internalList.Count - 1 && index >= 0)
returnItem = internalList[index];
rwLockList.ExitReadLock();
return (returnItem);
}
set
{
rwLockList.EnterWriteLock();
if (index <= internalList.Count - 1 && index >= 0)
internalList[index] = value;
rwLockList.ExitWriteLock();
}
}
}
Implementing a custom List<T> that encapsulates thread-safety is rarely worth the effort. You're likely best off just using lock whenever you access the List<T>.
But being in a performance intensive industry myself there have been cases where this becomes a bottleneck. The main drawback to lock is the possibility of context switching which is, relatively speaking, extremely expensive both in wall clock time and CPU cycles.
The best way around this is using immutability. Have all readers access an immutable list and writers "update" it using Interlocked operations to replace it with a new instance. This is a lock-free design that makes reads free of synchronization and writes lock-free (eliminates context switching).
I will stress that in almost all cases this is overkill and I wouldn't even consider going down this path unless you're positive you need to and you understand the drawbacks. A couple of the obvious ones are readers getting point-in-time snapshots and wasting memory creating copies.
ImmutableList from Microsoft.Bcl.Immutable is also worth a look. It's entirely thread-safe.
That is not threadsafe.
The method GetEnumerator() will not retain any lock after the enumerator has been returned, so any thread would be free to use the returned enumerator without any locking to prevent them from doing so.
In general, trying to create a threadsafe list type is very difficult.
See this StackOverflow thread for some discussion: No ConcurrentList<T> in .Net 4.0?
If you are trying to use some kind of reader/writer lock, rather than a simple locking scheme for both reading and writing, your concurrent reads probably greatly outnumber your writes. In that case, a copy-on-write approach, as suggested by Zer0, can be appropriate.
In an answer to a related question I posted a generic utility function that helps turning any modification on any data structure into a thread-safe and highly concurrent operation.
Code
static class CopyOnWriteSwapper
{
public static void Swap<T>(ref T obj, Func<T, T> cloner, Action<T> op)
where T : class
{
while (true)
{
var objBefore = Volatile.Read(ref obj);
var newObj = cloner(objBefore);
op(newObj);
if (Interlocked.CompareExchange(ref obj, newObj, objBefore) == objBefore)
return;
}
}
}
Usage
CopyOnWriteSwapper.Swap(ref _myList,
orig => new List<string>(orig),
clone => clone.Add("asdf"));
More details about what you can do with it, and a couple caveats can be found in the original answer.
My friend and I have been working on a game and we have a trace listener set up, here's some of the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
namespace GuiGame {
/// <summary>
/// A type of Trace Listener that sends its output to a ListBox.
/// </summary>
public class ListBoxTraceListener : TraceListener {
private ListBox listBox; // A reference to the listbox that we're writing to.
private string stringToAddToListBox = "";
private const char NEW_LINE = '\n';
/// <summary>
/// Parameterless constructor.
/// Do not want the generic default constructor to be used
/// as there is no way to set the ListBoxTraceListener's data.
/// This replaces the compiler's generic default constructor.
/// Pre: none
/// Post: ALWAYS throws an ArgumentException.
/// </summary>
/// <remarks>NOT TO BE USED!</remarks>
public ListBoxTraceListener() {
throw new ArgumentException("Parameterless constructor invalid.");
} // end ListBoxTraceListener constructor
/// <summary>
/// Constructor with initialising parameters.
/// Pre: the existence of a ListBox on a GUI form.
/// Post: initialised object.
/// </summary>
/// <param name="listBox">The ListBox that we're writing to.</param>
public ListBoxTraceListener(ListBox listBox) {
this.listBox = listBox;
}
/// <summary>
/// Automatically collects the outputs from all Trace.WriteLine statements.
/// Pre: none.
/// Post: the string s is displayed in the listBox.
/// </summary>
/// <param name="s"></param>
public override void WriteLine(string s) {
Write(s + NEW_LINE);
} //end WriteLine
/// <summary>
/// Automatically collects the outputs from all Trace.Write statements.
/// Pre: none.
/// Post: the string s is displayed in the listBox, once we receive a NEW_LINE.
/// </summary>
/// <param name="s"></param>
public override void Write(string s) {
stringToAddToListBox += s;
// If we have one or more complete lines
if (stringToAddToListBox.Contains (NEW_LINE)) {
// Split the string into multiple lines.
// If NEW_LINE is found at the beginning or end of the string,
// then the corresponding array element contains an empty string.
string[] lines = stringToAddToListBox.Split(NEW_LINE);
// Add all the lines to the listbox, except for the last one.
// When stringToAddToListBox has a new-line at the end,
// the last element in lines[] will be an empty string.
int highestLineNumber = lines.Length - 1;
for (int i = 0; i < highestLineNumber; i++) {
AddToListBox(lines[i]);
}
// Reset stringToAddToListBox to what remains. (May be an empty string).
stringToAddToListBox = lines[highestLineNumber];
}
} // end Write
/// <summary>
/// Adds a complete output-line to the ListBox.
/// Pre: none.
/// Post: the string listBoxLine is displayed in the listBox .
/// </summary>
/// <param name="listBoxLine"></param>
private void AddToListBox(string listBoxLine) {
Debug.Assert(listBox != null, "listBox != null");
listBox.Items.Add(listBoxLine);
} // end AddToListBox
}
}
At this stage we are just trying to use the trace listener to output some text on in the ListBox so we know it is working, so we have an event handler setup:
private void RollDiceButton_Click(object sender, EventArgs e)
{
}
We haven't been able to get any output from the trace listener. The Add method as the trace listener is not set up for that. Can anyone provide some suggestions please? I think maybe we are doing something really stupid and obvious that we have missed.
The most probable cause of your issue is that your application is single threaded (as most simple Windows applications are). This means that although you are sending messages to the listview to append a new element to the list, the message is not yet handled just because your original handler hasn't returned yet (the RollDiceButton_Click).
To work this around, you should force the list to refresh itself from within the current handler:
private void AddToListBox(string listBoxLine) {
Debug.Assert(listBox != null, "listBox != null");
listBox.Items.Add(listBoxLine);
// this would help?
listBox.Refresh();
} // end
If this doesn't help, try temporarily switching to unconditional processing of all pending events with
private void AddToListBox(string listBoxLine) {
Debug.Assert(listBox != null, "listBox != null");
listBox.Items.Add(listBoxLine);
// this would help?
Application.DoEvents();
} // end
and report back.