Adding an item to a bound WPF ListBox - c#

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.

Related

Updating ListBox With New COM Port Data

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]);
}
}
}

Using a Model With a ComboBox

I'm having trouble accessing the ID of a model I am adding to a ComboBox. At the moment I have a model, a preset, that contains an ID and Text. I create a list of presets through OleDB and then filter the list down before adding the contents to a ComboBox. Here is the code that does this;
var ps = new PresetService();
List<PresetModel> presetList = ps.HandleGetPresets();
List<PresetModel> filteredList = presetList.Where(filteredPreset => filteredPreset.PresetReferenceFoxPro == 3).ToList();
try
{
foreach (PresetModel preset in filteredList)
{
presetComboBox.Items.Add(preset.PresetText);
}
}
catch (Exception ex)
{
var hEs = new HandleExceptionService();
hEs.HandleException(ex.ToString());
return false;
}
Here you can see that I am adding the preset's text to the ComboBox. The issue comes however when someone wants to add a Company using one of the presets. In this case I am actually not interested in the text, I just want to add the preset's ID to the database against a company instead.
Using SelectedItem is not allowing me to access the ID, it returns a null exception. This is what I've triedl
var selectedPreset = presetComboBox.SelectedItem as PresetModel;
var presetIDToAdd = selectedPreset.PresetID;
I assume that this does not work because I have simply added the preset's Text value to the ComboBox and not it's ID. How can I add preset to a ComboBox, only display it's text but access it's ID when needed?
The problem here is that you are adding the PresetText to the ComboBox, not the PresetModel itself:
presetComboBox.Items.Add(preset.PresetText);
On top of that, you are then trying to cast the SelectedItem to a PresetModel, but it's actually the PresetText.
I would suggest using the following method.
Firstly, add the whole object to the ComboBox, like this:
presetComboBox.Items.Add(preset);
You can then define an ItemTemplate on your ComboBox to display the PresetText, or, to make things easier, just set the DisplayMemberPath:
<ComboBox ...
DisplayMemberPath="PresetText"/>
This will allow you to cast the SelectedItem to a PresetModel, but also still displaying the PresetText property in the ComboBox.

Multi value ListBox automatically selects undesired item

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?

How to delete an items inside a list nested by a field

I have a problem with a deletion in a list.
Imagine this 2 records like this.
Item1: ID = 1001, Name="A", Dependency = 1, Order=1.00
Item2: ID = 2354, Name="B", Dependency = 1, Order=1.01
They are linked in some way by the dependency and visual effect in the web page is like an element nested by another one. I've been trying to delete this record in the front-end. Database level is ok. This thing is in order to avoid the database roundtrip I used to delete the element from my collection and do a databind refresh. But now, with my element nested I don't know have to do it.
I tried this in a rush without seeing the problem of list modification.
this.Entity.Items.Remove(this.CurrentItem);
foreach(Item item in this.Entity.Items)
{
if(item.Dependency == this.CurrentItem.Depedency){
this.Entity.Items.Remove(item);
}
}
myRepeater.DataSource = this.Entity.Items
myRepeater.DataBind();
Then I did
this.Entity.Items.ToList().RemoveAll(x => x.Depedency == this.CurrentItem.Dependency);
myRepeater.DataSource = this.Entity.Items
myRepeater.DataBind();
But ToList() recreates a new list and this do not remove anything in the same list
Items is a collection (List) inside the Entity
Not allow to modify entities structures nor databases, please.
You can use a for loop instead of a for each.
Also, you can try
myRepeater.DataSource = this.Entity.Items.ToList().RemoveAll(x => x.Depedency == this.CurrentItem.Dependency);

DataGrid: Dyanmically adding rows

I am new to windows application. I need to add rows in the DataGrid dynamically which has person data. when i do the following is see only the last person in the last row. i see rows populating but with no data. If i do a break on the first fetch i do get the right one. But something is wrong. Any ideas
foreach (var p in personList)
{
gvAdminSummary.Rows.Add(new DataGridViewRow());
gvAdminSummary.Rows[gvAdminSummary.Rows.Count-1].Cells[0].Value = p.FName;
gvAdminSummary.Rows[gvAdminSummary.Rows.Count - 1].Cells[1].Value = p.LName;
gvAdminSummary.Rows[gvAdminSummary.Rows.Count - 1].Cells[2].Value = p.PNo;
}
The DataGridRowView.Add method accepts string arrays:
gvAdminSummary.Rows.Add( { p.FName, p.LName, p.PNo });
Likely, though, there's a better solution for you in binding the grid directly to your person list.
This may not be the right approach. Create a BindingSource and bind a collection of your objects to it. Then bind the BindingSource to the Grid's data source. Make sure your objects implement INotifyPropertyChanged. This way, whenever, you add an object to the collection, or change a property within your object, it'll automatically reflect in the grid.
I don't know about DataGridView, but if you want to stick to inserting data into the control directly, why not use ListView instead? It has an API more suited to your current needs or way of doing things.
Either
gvAdminSummary.Datasource = persons;
gvAdminSummary.databind();
Or
foreach (var p in personList)
{
DataGridViewRow dr = new DataGridViewRow();
dr.cells.add(new datagridcell()) etc.. populate cells
gvAdminSummary.Rows.add(dr);
}

Categories

Resources