I have an ObservavebleColection bound to a listView. Basically, this collection must keep up with every change in the server and receives updates in string format.
My code parses the string and adds elements to the collection, but I'm having trouble finding a way to remove elements. How can I update the collection when an element is removed or changed on the server?
Here's my code:
public static ObservableCollection<TransactionDetails> offerList = new ObservableCollection<TransactionDetails>();
public async static Task<ObservableCollection<TransactionDetails>> getOfferList()
{
// Start getting Offers
string Offer = await BedpAPI_V1.getOfferList();
string[] splitedResponse = Offer.Split(new[] { "####" }, StringSplitOptions.RemoveEmptyEntries);
foreach (string response in splitedResponse) {
string[] splitedMessage = response.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
offer.TransactionID = Convert.ToInt32(splitedMessage[0]);
offer.Seller = splitedMessage[1];
offer.Cost = Convert.ToDouble(splitedMessage[2]);
offer.Duration = Convert.ToInt16(splitedMessage[3]);
offer.Delay = Convert.ToInt16(splitedMessage[4]);
offer.Capacity = Convert.ToDouble(splitedMessage[5]);
offer.Availability = Convert.ToDouble(splitedMessage[6]);
if (currentOffer <= offer.TransactionID)
{
offerList.Add(new TransactionDetails() { TransactionID = offer.TransactionID, Seller = offer.Seller, Cost = offer.Cost, Duration = offer.Duration, Delay = offer.Delay, Capacity = offer.Capacity, Availability = offer.Availability });
currentOffer++;
}
}
return offerList;
}
If your ListView bind with a ObservavebleColection<>, when you modify an element, you may fire a OnCollectionChanged event yourself.
Assume you modify an element using []:
public class MyCollection : ObservableCollection<MyClass>
{
public new MyClass this[int index]
{
get { return base[index]; }
set
{
base[index] = value;
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}
UPDATE:
Firstly, you may want to bind your collection like:
myCollection = MyFunction();
MyListView.ItemsSource = myCollection;
If you change an item in your collection like:
myCollection[index] = someNewItem;
Then you could update your listview like:
MyListView.Refresh();
But it is recommended to use some dynamic binding.
I solved the problem by clearing the collection on every call before parsing splitedResponse - as Clear() was throwing an unheld exception, had to handle it inside a throw catch block:
try
{
offerList.Clear();
}
catch (Exception ex) {
offerList.Clear();
Console.WriteLine(ex.Message);
}
Related
I am updating the property of the list items.
class Response
{
public string Name { get; set; }
public int Order { get; set; }
}
Here I want to update the Order of a List<Response> variable. As of now, I am looping through each item of the list and updating it.
List<Response> data = FromDb();
foreach (var item in data)
{
if(item.Name.Equals("A"))
{
item.Order=1;
}
if(item.Name.Equals("B"))
{
item.Order=2;
}
//Like this I have arround 20 conditions
}
The above code is working fine, but the problem is the Cognitive Complexity of the method is more than the allowed.
I tried something like below
data.FirstOrDefault(x => x..Equals("A")).Order = 1;
data.FirstOrDefault(x => x..Equals("B")).Order = 2;
//and more ...
In this code also null check is not in place, So if the searching string is not present in the list then again it will break.
If I add null check condition then again the complexity getting higher.
So here I want without any for loop or if, If I can update the Order of the list by using linq/lamda or anything else.
I don't know how you measure Cognitive Complexity and how much of it is allowed to be pushed out into other functions, but something like this makes the ordering quite declarative?
[Fact]
public void TestIt()
{
var data = FromDb().Select(SetOrder(
("A", 1),
("B", 2)
));
}
static Func<Response, Response> SetOrder(params (string Name, int Order)[] orders)
{
var orderByKey = orders.ToDictionary(x => x.Name);
return response =>
{
if (orderByKey.TryGetValue(response.Name, out var result))
response.Order = result.Order;
return response;
};
}
Addendum in response to comment:
In order to have a default value for unmatched names, the SetOrder could be changed to this:
static Func<Response, Response> SetOrder(params (string Name, int Order)[] orders)
{
var orderByKey = orders.ToDictionary(x => x.Name);
return response =>
{
response.Order =
orderByKey.TryGetValue(response.Name, out var result)
? result.Order
: int.MaxValue;
return response;
};
}
I have the following list of strings :
var files = new List<string> {"file0","file1","file2","file3" };
I would like to be able to add new files to this list, but if the inserted file is present in the list, I would like to insert custom value that will respect the following format $"{StringToBeInserted}"("{SomeCounter}
For instance : try to add "file0" and "file0" is already I would like to insert "file0(1)". If I try again to add "file0" ... I would like to insert with "file0(2)" and so on ... Also, I would like to provide a consistency, for instance if I delete "file0(1)" ... and try to add again "item0" ... I expect that "item0(1)" to be added. Can someone help me with a generic algorithm ?
I would use a HashSet<string> in this case:
var files = new HashSet<string> { "file0", "file1", "file2", "file3" };
string originalFile = "file0";
string file = originalFile;
int counter = 0;
while (!files.Add(file))
{
file = $"{originalFile}({++counter})";
}
If you have to use a list and the result should also be one, you can still use my set approach. Just initialize it with your list and the result list you'll get with files.ToList().
Well, you should create your own custom class for it, using the data structure you described and a simple class that includes a counter and an output method.
void Main()
{
var items = new ItemCountList();
items.AddItem("item0");
items.AddItem("item1");
items.AddItem("item2");
items.AddItem("item0");
items.ShowItems();
}
public class ItemCountList {
private List<SimpleItem> itemList;
public ItemCountList() {
itemList = new List<SimpleItem>();
}
public void DeleteItem(string value) {
var item = itemList.FirstOrDefault(b => b.Value == value);
if (item != null) {
item.Count--;
if (item.Count == 0)
itemList.Remove(item);
}
}
public void AddItem(string value) {
var item = itemList.FirstOrDefault(b => b.Value == value);
if (item != null)
item.Count++;
else
itemList.Add(new SimpleItem {
Value = value,
Count = 1
});
}
public void ShowItems() {
foreach (var a in itemList) {
Console.WriteLine(a.Value + "(" + a.Count + ")");
}
}
}
public class SimpleItem {
public int Count {get; set;}
public string Value {get; set;}
}
I'm currently looking for a way to monitor a list for an item to be added (although it may already be in the list) with a certain ID. The below example demonstrates what I want to do, however I was hoping there would be a neater approach to this.
var list = new List<string>();
var task = new Task<bool>(() =>
{
for (int i = 0; i < 10; i++)
{
if (list.Contains("Woft"))
return true;
Thread.Sleep(1000);
}
return false;
});
I would appreciate any suggestions, and can elaborate if required.
EDIT: I'm not sure how I would use a CollectionChanged handler for this, still not sure I'm describing the problem well enough, here is a more complete example.
class Program
{
private static List<string> receivedList = new List<string>();
static void Main(string[] args)
{
var thing = new item() { val = 1234, text = "Test" };
var message = new message() { id = "12", item = thing };
//This would be a message send
//in the receiver we would add the message id to the received list.
var task2 = new Task(() =>
{
Thread.Sleep(2000);
receivedList.Add(message.id);
});
task2.Start();
var result = EnsureReceived(thing, message.id);
if (result == null)
{
Console.WriteLine("Message not received!");
}
else
{
Console.WriteLine(result.text + " " + result.val);
}
Console.ReadLine();
}
//This checks if the message has been received
private static item EnsureReceived(item thing, string id)
{
var task = new Task<bool>(() =>
{
for (int i = 0; i < 10; i++)
{
if (receivedList.Contains(id))
return true;
Thread.Sleep(1000);
}
return false;
});
task.Start();
var result = task.Result;
return result ? thing : null;
}
}
class message
{
public string id { get; set; }
public item item { get; set; }
}
class item
{
public string text { get; set; }
public int val { get; set; }
}
It sounds like what you want to do is use an ObservableCollection and subscribe to the CollectionChanged event. That way, you'll have an event fire whenever the collection is modified. At that point, you can check if the collection has the element you're expecting.
Even better, the CollectionChanged event handler will receive NotifyCollectionChangedEventArgs, which contains a NewItems property.
See MSDN for more information.
You can implement own type, what intercept all operations to add items and exposes ReadOnlyList. Then create an event ItemAdded with added item value (you will have to rise up multiple events when adding a collection obviously). Subscribe to it, check, do whatever.
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.
I have the following code, which basically takes values from a database and populates a listview.
using (IDataReader reader = cmd.ExecuteReader())
{
lvwMyList.Items.Clear();
while (reader.Read())
{
ListViewItem lvi = lvwMyList.Items.Add(reader["Value1"].ToString());
lvi.SubItems.Add(reader["Value2"].ToString());
}
}
The problem that I have is that this is repeatedly executed at short intervals (every second) and results in the items in the listview continually disappearing and re-appearing. Is there some way to stop the listview from refreshing until it’s done with the updates? Something like below:
using (IDataReader reader = cmd.ExecuteReader())
{
lvwMyList.Items.Freeze(); // Stop the listview updating
lvwMyList.Items.Clear();
while (reader.Read())
{
ListViewItem lvi = lvwMyList.Items.Add(reader["Value1"].ToString());
lvi.SubItems.Add(reader["Value2"].ToString());
}
lvwMyList.Items.UnFreeze(); // Refresh the listview
}
Like this:
try
{
lvwMyList.BeginUpdate();
//bla bla bla
}
finally
{
lvwMyList.EndUpdate();
}
Make sure that you invoke lvwMyList.Items.Clear() after BeginUpdate if you want to clear the list before filling it.
This is my first time posting on StackOverflow, so pardon the messy code formatting below.
To prevent locking up the form while updating the ListView, you can use the method below that I've written to solve this issue.
Note: This method should not be used if you expect to populate the ListView with more than about 20,000 items. If you need to add more than 20k items to the ListView, consider running the ListView in virtual mode.
public static async void PopulateListView<T>(ListView listView, Func<T, ListViewItem> func,
IEnumerable<T> objects, IProgress<int> progress) where T : class, new()
{
if (listView != null && listView.IsHandleCreated)
{
var conQue = new ConcurrentQueue<ListViewItem>();
// Clear the list view and refresh it
if (listView.InvokeRequired)
{
listView.BeginInvoke(new MethodInvoker(() =>
{
listView.BeginUpdate();
listView.Items.Clear();
listView.Refresh();
listView.EndUpdate();
}));
}
else
{
listView.BeginUpdate();
listView.Items.Clear();
listView.Refresh();
listView.EndUpdate();
}
// Loop over the objects and call the function to generate the list view items
if (objects != null)
{
int objTotalCount = objects.Count();
foreach (T obj in objects)
{
await Task.Run(() =>
{
ListViewItem item = func.Invoke(obj);
if (item != null)
conQue.Enqueue(item);
if (progress != null)
{
double dProgress = ((double)conQue.Count / objTotalCount) * 100.0;
if(dProgress > 0)
progress.Report(dProgress > int.MaxValue ? int.MaxValue : (int)dProgress);
}
});
}
// Perform a mass-add of all the list view items we created
if (listView.InvokeRequired)
{
listView.BeginInvoke(new MethodInvoker(() =>
{
listView.BeginUpdate();
listView.Items.AddRange(conQue.ToArray());
listView.Sort();
listView.EndUpdate();
}));
}
else
{
listView.BeginUpdate();
listView.Items.AddRange(conQue.ToArray());
listView.Sort();
listView.EndUpdate();
}
}
}
if (progress != null)
progress.Report(100);
}
You don't have to provide an IProgress object, just use null and the method will work just as well.
Below is an example usage of the method.
First, define a class that contains the data for the ListViewItem.
public class TestListViewItemClass
{
public int TestInt { get; set; }
public string TestString { get; set; }
public DateTime TestDateTime { get; set; }
public TimeSpan TestTimeSpan { get; set; }
public decimal TestDecimal { get; set; }
}
Then, create a method that returns your data items. This method could query a database, call a web service API, or whatever, as long as it returns an IEnumerable of your class type.
public IEnumerable<TestListViewItemClass> GetItems()
{
for (int x = 0; x < 15000; x++)
{
yield return new TestListViewItemClass()
{
TestDateTime = DateTime.Now,
TestTimeSpan = TimeSpan.FromDays(x),
TestInt = new Random(DateTime.Now.Millisecond).Next(),
TestDecimal = (decimal)x + new Random(DateTime.Now.Millisecond).Next(),
TestString = "Test string " + x,
};
}
}
Finally, on the form where your ListView resides, you can populate the ListView. For demonstration purposes, I'm using the form's Load event to populate the ListView. More than likely, you'll want to do this elsewhere on the form.
I've included the function that generates a ListViewItem from an instance of my class, TestListViewItemClass. In a production scenario, you will likely want to define the function elsewhere.
private async void TestListViewForm_Load(object sender, EventArgs e)
{
var function = new Func<TestListViewItemClass, ListViewItem>((TestListViewItemClass x) =>
{
var item = new ListViewItem();
if (x != null)
{
item.Text = x.TestString;
item.SubItems.Add(x.TestDecimal.ToString("F4"));
item.SubItems.Add(x.TestDateTime.ToString("G"));
item.SubItems.Add(x.TestTimeSpan.ToString());
item.SubItems.Add(x.TestInt.ToString());
item.Tag = x;
return item;
}
return null;
});
PopulateListView<TestListViewItemClass>(this.listView1, function, GetItems(), progress);
}
In the above example, I created an IProgress object in the form's constructor like this:
progress = new Progress<int>(value =>
{
toolStripProgressBar1.Visible = true;
if (value >= 100)
{
toolStripProgressBar1.Visible = false;
toolStripProgressBar1.Value = 0;
}
else if (value > 0)
{
toolStripProgressBar1.Value = value;
}
});
I've used this method of populating a ListView many times in projects where we were populating up to 12,000 items in the ListView, and it is extremely fast. The main thing is you need to have your object fully built from the database before you even touch the ListView for updates.
Hopefully this is helpful.
I've included below an async version of the method, which calls the main method shown at the top of this post.
public static Task PopulateListViewAsync<T>(ListView listView, Func<T, ListViewItem> func,
IEnumerable<T> objects, IProgress<int> progress) where T : class, new()
{
return Task.Run(() => PopulateListView<T>(listView, func, objects, progress));
}
You can also try setting the visible or enabled properties to false during the update and see if you like those results any better.
Of course, reset the values to true when the update is done.
Another approach is to create a panel to overlay the listbox. Set it's left, right, height, and width properties the same as your listbox and set it's visible property to true during the update, false after you're done.