I wish for my ListBox to update the old values with new values rather than simply adding more and more lines to the ListBox like it does at the moment. However, I'm not sure where to look to implement something that can handle this.
My current code looks like this:
private void DisplayText(string rawData)
{
textArduinoData.Text = rawData;
string[] sortedData = rawData.Split(';');
for (int i = 0; i < sortedData.Length; i++)
{
listPortData.Items.Add(sortedData[i].ToString());
}
}
Could someone please point me in the right direction to implementing this update feature? Any advice would be much appreciated.
You need to manage the process. It is easy in concept but depending on how much data is needed to be processed, it could get slow quickly. Steps
Create a specialized token class which implements to INotifyPropertyChanged.
Have an ObservableCollection hold the class items from #1. The observable collection notifies the ListBox when an item is added or removed. This will allow your code to add items one at a time. (Solves 1 problem)
To solve the next problem of data changing: Have a property named Text, on the class in #1 which will hold the data, provide a property change notification.
In the list box bind to the list of items created in step 1 and specify to bind to the Text. Use of a data template for the listbox will allow you to bind to the Text property of the list's instance.
Provide the heuristics/ smarts to read incoming data and find the associated data in the observable collection from step 2. When found change the Text property of the existing data to the new and the binding of that list item will change accordingly.
You could check if the ListBox contains the string using the IndexOf method and then update the existing string (or simply do nothing) or add a new one depending on whether you get an index other than the default value of -1 back:
private void DisplayText(string rawData)
{
textArduinoData.Text = rawData;
string[] sortedData = rawData.Split(';');
int index;
for (int i = 0; i < sortedData.Length; i++)
{
if ((index = listPortData.Items.IndexOf(sortedData[i])) == -1)
{
listPortData.Items.Add(sortedData[i]);
}
}
}
Related
I have an object that has some attributes from the list selected - let's say a Promotion that can have 0 to X communication channels. To display/edit this information I am using a listbox with option SelectionMode==MultiExtended.
But in some cases it is behaving strangely
I have Promotion with 2 communication channels selected (first and last out of three channels),
I click on a second channel (that previously was the only unselected channel) and know it shows, that 1st and 2nd channels are selected (I placed a check at the beginning of the listbox SelectedIndexChanged event - and it shows that SelectedItems.Count==2, although I clicked on a single item not holding Ctrl or Shift keys) and in this case SelectedIndexChanged event is triggered twice in all other cases it is triggered just once
This happens only after the first time I open this dialogform, if I manually select 1st and 3rd item of Channels, and then click on the 2nd item - then it works properly
Screencast of a problem in action
http://screencast.com/t/lVs0e9oau
This is how I load list of all possible channels into listbox
foreach (var ct in Promotion_operations.Configuration.PromoCommunicationTypes)
{
KeyValuePair<string, PromotionCommunicationType> nct =
new KeyValuePair<string, PromotionCommunicationType>(ct.Name, ct);
communications.Add(nct);
}
PromotionCommunicationList.DataSource = communications; //Promotion_operations.Configuration.PromoCommunicationTypes;
PromotionCommunicationList.DisplayMember = "Key";
PromotionCommunicationList.ValueMember = "Value";
This is how I load selecteditems based on Promotion's data
private void LoadSelectedCommunicationsList(ListBox lstbox, List<PromotionCommunication> communications)
{
lstbox.SelectedItems.Clear();
foreach (var ct in communications)
{
for (int j = 0; j < lstbox.Items.Count; j++)
{
if (ct.CommunicationType.Id == ((KeyValuePair<string, PromotionCommunicationType>)lstbox.Items[j]).Value.Id)
{
lstbox.SelectedItems.Add(lstbox.Items[j]);
}
}
}
}
What could be the cause of this behaviour?
that clicking on one previously unselected list selects both - newly selected item and first item of the list?
Your PromotionCommunicationList and HistoryCommunicationList are sharing the same reference to your list of objects as DataSource. That said, they have the same BindingContext and share the same CurrencyManager. CurrencyManager is remembering selected items of your ListBox control and that's where your conflict is created because he's saving selected items of both of your ListBoxes. You already found the solution for your problem because new CurrencyManager is created when you set "different" list (the copy of your original one) as DataSource. Another possible solution would be the creation of new BindingContext for one of your ListBox controls.
You can try this out:
PromotionCommunicationList.DataSource = communications;
(..)
HistoryCommunicationList.BindingContext = new BindingContext(); // Add this
HistoryCommunicationList.DataSource = communications;
It should solve your problem. For more information about BindingContext check this link on MSDN.
I found the cause of the problem, though I don't really understand why it caused such a behaviour (if someone will answer that question, I will accept it as an answer to this question)
I had 2 listbox-es in my form and both of them where using the same collection as a Datasource, BUT!!! SelectedItems was selected using code (acctually it seems that in winforms it is not possible to databind listbox's selecteditems)
INITIALLY My code was:
PromotionCommunicationList.DataSource = communications;
(..)
HistoryCommunicationList.DataSource = communications;
Corrected version is:
PromotionCommunicationList.DataSource = communications.ToList();
(..)
HistoryCommunicationList.DataSource = communications.ToList();
I know that ToList() makes a copy, but I don't understand what's wrong with having the same collection as DataSource for list items of 2 listbox-es? Why does this have an impact on SelectedItems collection?
My requirement is to hide specific rows in a ListView. Since there is no inbuilt functionality I was using the ListView_DrawItem Event to hide the specific rows. I was able to hide the rows but the issue is there is a balnk space coming if i add a row post to the hidden row.
Please find the below code:
ListViewItem LVI = listView1.Items.Add("1");
LVI.SubItems.Add("Srikanth");
ListViewItem LVI1 = listView1.Items.Add("2");
LVI1.SubItems.Add("Suresh");
private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
if (e.Item.Text != "2")
{
e.DrawDefault = true;
}
}
Output Looks Like
1 Srikanth
3 Sandy
Would like to remove the space b/w 1 & 3 record
Any help is appreciated
You are not really hiding the items, that is plain to see. Instead, you are offering to draw them yourself, and passing some off to default rendering. The items are there regardless.
My approach would be to manipulate the collection, adding only those items that the list box needs to deal with.
Depending on the data you have at hand, keep a local copy of the full list, in order, such as in a generic List<YourDataClass>. Also, keep a list of 'hidden' indexes.
Respect the order of List<T> and loop across it, only adding new ListViewItem objects for those indexes not present in the 'hidden' list.
private List<String[]> _listItems = new List<String[]>();
private List<Int32> _hiddenIndexes = new List<Int32>();
private void UpdateCollection()
{
listBox.Items.Clear();
for (Int32 i = 0; i < _listItems.Count; i++)
{
if (!_hiddenIndexes.Contains(i))
listBox.Items.Add(new ListBoxItem(_listItems[i]));
}
}
Beware that if this happens to be a list with a very large number of items, it might not be the most efficient approach.
I am trying to figure out how to update a Object's property in a ListView after render. For an example, let's say that the ListView is DataBound to a collection of Employees. Each row displays the information of the employee. After the table is loaded, I needed to say "If an employee name = [RON] then change it to text [RONALD]".
I currently was thinking I could foreach the ListViewDataItems in the ListView, and go from there, but am stuck. Any help would be appreciated.
foreach(ListViewDataItem entry in lvProjectModeratorEntries.Items)
{
//I need to find the div where the firsName is
//displayed, and run my logic to update it.
}
I also thought I would get it through entry.DataItem but am stuck at that point.
It sounds like you could use the INotifyPropertyChanged Interface on your Employee model. In your listview item template, bind the text to the Employee's property that you wish to display. Then you can change your Employee objects.
to find each item in a Listview you can loop like this
for (int i = 0; i < lvProjectModeratorEntries.Items.Count; i++)
{
int ii = 1;
MessageBox.Show(lvProjectModeratorEntries.Items[i].SubItems[ii].Text);
ii++;
}
Using your own code:
foreach (ListViewItem item in lvTest.Items)
{
if (item.Text == "John")
item.Text = "John is gone";
}
That should give you a start on how to do things, yet I don't recommend it. There are more elegant and code-sustainable solutions. Have you thought about binding the listview to a List and make all the necessary business logic in the List, instead of the actual view?
Ok, this has been a head scratcher for me. I have a ListBox I am binding to a linq query like so:
private IQueryable<Feed> _feeds;
public IQueryable<Feed> Feeds
{
get
{
if (_feeds == null)
{
var feedsQuery = from f in _db.Feed orderby f.Title select f;
_feeds = feedsQuery;
}
return _feeds;
}
}
public Options()
{
InitializeComponent();
this.DataContext = Feeds;
}
(For the record I've also tried List, instead of IQueryable)
Everything shows up great and I have a databound form that allows you to edit a record and all of those changes work just fine, the modified data shows up in the list.
The problem comes with I add an item. Nothing shows up in the list. The data goes into the database fine, but the only way to see the data is closing and restarting my app. I'm using the code below as an example:
Feed feed = new Feed()
{
ID = Guid.NewGuid(),
Url = "http://www.test.com",
Title = "Test"
};
_db.Feed.InsertOnSubmit(feed);
_db.SubmitChanges();
_db.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
(with or without the _db.Refresh nothing happens)
What's going on?
You are doing everything right, you jus need to use ObservableCollection. This will notify the ListBox about any changes in the list and refresh it automatically.
From MSDN
In many cases the data that you work
with is a collection of objects. For
example, a common scenario in data
binding is to use an ItemsControl
such as a ListBox, ListView, or
TreeView to display a collection of
records.
P.S. you don't need a db refresh
Unless notified otherwise, the ListBox only iterates once over its ItemsSource. Your query is only being run once.
The query object doesn't know when the database changes (and Refresh doesn't help; see below)--it's up to you to know (or anticipate) that and to rerun relevant queries at the appropriate times.
Stan R mentions ObservableCollection. That's fine, but simply storing the result of your query in an ObservableCollection won't solve the problem unless you do some work to update the collection yourself when the database changes. This means rerunning the query and manually adding new items and removing deleted items from the collection. (You could alternatively just rerun the query and set the entire result back in to the ListBox, but that means a whole new set of items will be created--not very performant, and maybe not what you want for other reasons.)
As an aside, your call to DataContext.Refresh is probably not doing what you think it is. From the docs:
This method is useful after an optimistic concurrency error to bring items into a state for another attempt. It updates the state of the primitive fields and properties on the objects.
Okay. I'm not positive this is 100% the correct way to use the ObservableCollection, but this seems to work:
private ObservableCollection<Feed> _feeds;
public ObservableCollection<Feed> Feeds
{
get
{
if (_feeds == null)
{
var feedsQuery = from f in _db.Feed orderby f.Title select f;
_feeds = new ObservableCollection<Feed>();
foreach (var item in feedsQuery)
{
_feeds.Add(item);
}
}
return _feeds;
}
}
And add my item:
Feed feed = new Feed()
{
ID = Guid.NewGuid(),
Url = "http://www.test.com",
Title = "Test"
};
_db.Feed.InsertOnSubmit(feed);
_db.SubmitChanges();
// manually update the list
Feeds.Add(feed);
It took me a little while to figure out I had to update the list manually (thanks Ben), but it all seems to work. Sorting would be nice, but I'll worry about that another time.
So lets say I have these classes:
public class Person
{
public string Name { get; set; }
}
public class PersonCollection : ObservableCollection<Person> { }
And lets say I have a ListView whose ItemsSource is bound to a PersonCollection. Now lets say I have this code:
public void AddPeople()
{
Person p = new Person() { Name = "Someone" };
MyPersonCollection.Add(p);
MyPersonCollection.Add(p);
MyPersonCollection.Add(p);
}
So now I have a ListView with three items in which all three items are references to the SAME object. So now I select lets say items with index 0 and 2 in the ListView.
The ListView.SelectedItems property will say I have ONE item selected since both visually selected items are the SAME object.
So how can I get the visually selected items so I can remove the items at indices 0 and 2, without removing the item at index 1?
In WinForms there is the ListBox.SelectedIndices property that would be useful here, but we don't have that in WPF, unfortunately...
You could iterate through the ListViewItems using ItemContainerGenerator.ContainerFromIndex, check ListViewItem.IsSelected and then remove them by index. However, this doesn't play well with virtualization because ContainerFromIndex could return null if you scroll away from the item and it gets virtualized.
The code would look something like this:
for(int ixPerson = myListView.Items.Count - 1; ixPerson >= 0; ixPerson--)
{
ListViewItem personItem = myListView.ItemContainerGenerator.ContainerFromIndex(ixPerson);
if (personItem.IsSelected)
{
mySourcePersonCollection.RemoveAt(ixPerson);
}
}
There are cases where this makes sense, adding people to a queue where appearing more than once is desirable for instance. For this case it seems like WPF is designed poorly. Is it possible to manually iterate between all items in the collection and check their selection state?
I think there's something wrong with your model! Whatever it is you are trying to achieve, I would try and find a more robust way of doing it.