Problems with BindlingList<T> and combo box - c#

I have got the following code:
private BindingList<INoun> _nouns;
private BindingList<INoun> Nouns
{
get
{
if ( _nouns == null )
{
_nouns = new BindingList<INoun>( _model.Feature.Nouns );
_nouns.Insert( 0, new Noun( -1, "Please select..." ) );
}
return _nouns;
}
}
public interface INoun
{
int Id;
string Text;
}
The Nouns property is bound to a ComboBox that adds a default entry Please select... to the BindingList.
The issue I am having here is that the Please select... entry is unexpectedly being added to the underlying_model.Feature.Nouns collection and I do not want this to happen.
Is there anyway I can add a Please select... default item to a ComboBox without it being added to the underlying collection?
Thanks

BindingList is just a wrapper, mainly to get notifications, around your _model.Feature.Nouns which remains as the underlying List of items (that's why you have AllowEdit, AllowNew, AllowRemove on BindingList) :
If you want to work on a brand new list (though I'm not sure it's the purpose of the BindingList), try :
_nouns = new BindingList<INoun>( _model.Feature.Nouns.Select(x=>x).ToList());

Related

to get current generic item item inside of CollectionChanged

I have a custom collection that inherits IEnumerable.
public class MyCollection<T> : IEnumerable<T>
{
private ObservableCollection<T> currentList = new ObservableCollection<T>();
public ObservableCollection<T> Items
{
get { return currentList; }
set { currentList = value; }
}
private List<T> deletedList = new List<T>();
private List<T> addedList = new List<T>();
public MyCollection(IEnumerable<T> currentList)
{
this.currentList = new ObservableCollection<T>(currentList);
Items.CollectionChanged+=Items_CollectionChanged;
}
}
I wish to track which items were added or deleted . Also , My Items is databound to a UI datagrid in WPF. The binding works fine if i bind to the Items instead of MyCollection . However , I want to track the added , removed items as below.
Am assuming the following event is caught when I add or remove any item from the UI.
As each row gets added to the DataGrid , I wish this even to be called to save the last added entry.
Now how do I get the current added or removed item in this ?
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
//this doesn't work ( I List is not assignable to IEnumerable T
deletedList.AddRange(e.OldItems);
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
//this doesnt work
addedList.AddRange(e.OldItems);
}
}
I only get the added or deleted items .
public ObservableCollection<T> GetAddedChanges()
{
var added = new ObservableCollection<T>();
addedList.ToList().ForEach(added.Add);
return added;
}
public ObservableCollection<T> GetDeletedChanges()
{
var deleted = new ObservableCollection<T>();
deletedList.ToList().ForEach(deleted.Add);
return deleted;
}
UPDATE :
The initial answer here worked i.e the Cast() option and I was able to get only the added members from my list and save them.
There is another issue . I am getting my server to return MyCollection . The sad thing is that my server when it de serializes to collection objects , it gets caught by the Added event of the Items_CollectionChanged , hence my added List faultily returns 2 items in added list always .
To solve this :
1) Subscribe to the event after server returns instead of MyCollection constructor. i.e Subscribe in another StartTracking( ).
2) Clear the added, deletd lists after getting the response from server .
Which of these would be better ? And are there any alternate solutions ?
This is the flow basically :
UI -- > OBservableCollection --> MyCollection ( where I track added /del elements ) --> Save to Server .. To fetch from server , I have my server side code return MyCollectionwith entries in it.
OldItems is not generic (it is IList not IList<T>), so you will need to cast the items.
Either:
foreach(T item in e.OldItems)
{
deletedList.Add(item);
}
Or:
deletedList.AddRange(e.OldItems.Cast<T>());
On tracking additions, you'll want to use NewItems. As an aside, this will not capture changes occurring during replace (e.g. c[0] = <new item>) or reset (e.g. after Clear is called).
I'd also note your GetAddedChanges could be simplified (And similarly GetDeletedChanges):
return new ObservableCollection<T>(addedList);
Though it's not clear why the return type would need to be ObservableCollection<T>.

ListBox Items Disappear

I currently have a ListBox (called wafersListBox) bounded to an ArrayList of a certain object type (called wafers). When I want to add to the ListBox dynamically, I use the following code:
wafersListBox.DataSource = null;
wafersListBox.DataSource = wafers;
wafersListBox.Refresh();
This successfully changes the items in the ListBox, but all of the items disappear (they're still there and can be selected, but the user just can't see them).
Any ideas on how to fix this?
UPDATE:
This is my Wafer class:
public class Wafer
{
public string maID;
public string MID
{
get
{
return maID;
}
set
{
maID = value;
}
}
public Wafer(string m)
{
maID = m;
}
}
This is the code that I call, it adds a copy of the currently selected item to the listbox:
Wafer w = wafersListBox.SelectedItem as Wafer;
wafers.Add(w);
wafersListBox.DataSource = null;
wafersListBox.DisplayMember = "MID";
wafersListBox.DataSource = wafers;
wafersListBox.Refresh();
You should probably tell the wafersListBox what property to use as it's caption.
Do it like this;
wafersListBox.DisplayMember = "PropertyNameThatYouWantToShow";
Sorry - I'm not able to add an additional comment, which would have been preferable over writing a new "answer" but do you see any difference if you switch the position of the lines as below?
Wafer w = wafersListBox.SelectedItem as Wafer;
wafers.Add(w);
wafersListBox.DataSource = null;
wafersListBox.DataSource = wafers;
wafersListBox.DisplayMember = "MID";
wafersListBox.Refresh();
One other thing I just came across on a different SO posting (ListBox doesn't show changes to DataSource):
"There is also a bug in the list box which can cause this problem. If you set the SelectionMode to None this problem appears.
As a work around I set the selection mode to One and then back to None when updating the datasource."

EF Update is not Updating the GridView

I'm still in the learning Phase of WPF, EF and MVVM and now I got the following problem. I can delete and insert new items in my DataGridView but I don't know how to update my items.
All I do is select an emptyrow which already has a primary key and then I put the data into it. It's working (updating database) but the GridView is not refreshing. I Need to restart the program first to see my updated data.
My Execute Command to Update my Database. I'm in the ViewModel class
public void ExecuteUpdate(object obj)
{
try
{
SelectedIndex.Child_Update(new Farbe { FarbauswahlNr = SelectedIndex.FarbauswahlNr, Kurztext = SelectedIndex.Kurztext, Ressource = SelectedIndex.Ressource, Vari1 = SelectedIndex.Vari1, Vari2 = SelectedIndex.Vari2 });
//ListeAktualisieren --> Refreshing the List
ListeAktualisieren();
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
Here is my Refresh Method which SHOULD Refresh the GridView. I'm in the ViewModel class
public void ListeAktualisieren()
{
farbliste.ListeAktualisieren(db);
farbliste.Model = farbliste.Model.Concat(farbliste.Addlist).ToList();
Model = farbliste.Model;
farbliste.Addlist.Clear();
}
The method is calling my Business List which also got a Refresh Method. Reading from my database here. I'm in the Business List class
public void ListeAktualisieren(TestDBEntities db)
{
Model.Clear();
foreach (var item in db.Farben)
{
//Insert and delete working
add = new Farbe { FarbauswahlNr = item.FarbauswahlNr, Kurztext = item.Kurztext, Ressource = item.Ressource, Vari1 = Convert.ToBoolean(item.Var1), Vari2 = item.Vari2 };
Addlist.Add(add);
}
}
Model is the Source of my GridView which is not Refreshing changed data when Updated but is showing new data rows when inserting or deleting.
You need Observablecollections and Classes with implemented INotifyPropertyChanged. Add the new element to the Observablecollection by insert and raise the event propertychanged by a change.
The rest should be done by WPF.
Edit: The Sourcecollection for the DataGrid needs to be the Observablecollection.
Edit2: To be nice I put the result of the comments here ;-)
Each row of the DataGrid is an element of the collection. Each cell of one row listens to a PropertyChangedEvent of its element (the String is Casesensitive so be carefull). If the getter of the property isn't called after the propertychangedevent the binding didn't receive the event.
This piece of Code can help asure that you don't call with nonexistent strings:
private void VerifyPropertyName(string PropertyName)
{
if (string.IsNullOrEmpty(PropertyName))
return;
if (TypeDescriptor.GetProperties(this)(PropertyName) == null) {
string msg = "Ungültiger PropertyName: " + PropertyName;
if (this.ThrowOnInvalidPropertyName) {
throw new isgException(msg);
} else {
Debug.Fail(msg);
}
}
}
Try adding this to your binding section
ItemsSource="{Binding Path=Model, UpdateSourceTrigger= PropertyChanged"}

combobox selected value in c#

I am working on C#.net windows application. i am filling combobox on my winform by using follows.
cmbEMPType.DataSource = objEntityManager.EmployeeTypes();
cmbEMPType.DisplayMember = "EMPTypeName";
cmbEMPType.ValueMember = "EMPTypeId";
where objEntityManager.EmployeeTypes(); in the manager method that gets the List from Linq to sql server. this is working fine.
but as i select the item form combo box, and clicked the button then in the button click event i am getting cmbEMPType.SelectedValue as EmpType return type rather than its Id. why should this? I don't want to create one more EmpType object. need simple selected value. also can not keep faith with SelectedIndex. it may varies for item each time.
**Edited**
public List<EMPType> EmployeeTypes()
{
List<EMPType> EMPTypeList = null;
try
{
if (CommonDataObject.dataContext.EMPAllTypes.Any())
{
EMPTypeList = CommonDataObject.dataContext.EMPAllTypes.ToList();
}
return EMPTypeList;
}
catch
{
return EMPTypeList;
}
}
Edited
private void btnSave_Click(object sender, EventArgs e)
{
iEMPTypeId = cmbEMPType.SelectedValue;
}
here I must get inte. but asking of create the EMPType object.
This is the correct and expected behavior, you can't change it.
SelectedValue should return the type of the property, e.g. if EMPTypeId is integer it should return integer - please post more code so that we can try figuring out why you get different return value.
If by any chance you're using SelectedItem then have such code to get the ID:
int selectedID = (cmbEMPType.SelectedItem as EmpType).EMPTypeId;
To handle cases when there's nothing selected:
object oSelectedEmp = cmbEMPType.SelectedItem;
int selectedID = oSelectedEmp == null ? -1 : (oSelectedEmp as EmpType).EMPTypeId;
The problem is the sequence of your code. Please remove the first line code to the last line. You will get an int value (iEMPTypeId) from cmbEMPType.SelectedValue.
cmbEMPType.DisplayMember = "EMPTypeName";
cmbEMPType.ValueMember = "EMPTypeId";
cmbEMPType.DataSource = objEntityManager.EmployeeTypes();
iEMPTypeId = cmbEMPType.SelectedValue
Another option is to override the toString function in your EMPType class. As Edwin de Koning stated "If no ValueMember is specified it gives a ToString() representation."
Something like (I cant test it at the moment):
public override string ToString()
{
return this.ID;
}
You can check out this article: http://msdn.microsoft.com/en-us/library/ms173154(v=vs.80).aspx

Testing Custom Control derived from ComboBox

I've created a control derived from ComboBox, and wish to unit test its behaviour.
However, it appears to be behaving differently in my unit test to how it behaves in the real application.
In the real application, the Combobox.DataSource property and the .Items sync up - in other words when I change the Combobox.DataSource the .Items list immediately and automatically updates to show an item for each element of the DataSource.
In my test, I construct a ComboBox, assign a datasource to it, but the .Items list doesn't get updated at all, remaining at 0 items. Thus, when I try to update the .SelectedIndex to 0 in the test to select the first item, I recieve an ArgumentOutOfRangeException.
Is this because I don't have an Application.Run in my unit test starting an event loop, or is this a bit of a red herring?
EDIT: More detail on the first test:
[SetUp]
public void SetUp()
{
mECB = new EnhancedComboBox();
mECB.FormattingEnabled = true;
mECB.Location = new System.Drawing.Point( 45, 4 );
mECB.Name = "cboFind";
mECB.Size = new System.Drawing.Size( 121, 21 );
mECB.TabIndex = 3;
mECB.AddObserver( this );
mTestItems = new List<TestItem>();
mTestItems.Add( new TestItem() { Value = "Billy" } );
mTestItems.Add( new TestItem() { Value = "Bob" } );
mTestItems.Add( new TestItem() { Value = "Blues" } );
mECB.DataSource = mTestItems;
mECB.Reset();
mObservedValue = null;
}
[Test]
public void Test01_UpdateObserver()
{
mECB.SelectedIndex = 0;
Assert.AreEqual( "Billy", mObservedValue.Value );
}
The test fails on the first line, when trying to set the SelectedIndex to 0. On debugging, this appears to be because when the .DataSource is changed, the .Items collection is not updated to reflect this. However, on debugging the real application, the .Items collection is always updated when the .DataSource changes.
Surely I don't have to actually render the ComboBox in the test, I don't even have any drawing surfaces set up to render on to! Maybe the only answer I need is "How do I make the ComboBox update in the same way as when it is drawn, in a unit test scenario where I don't actually need to draw the box?"
Since you're simply calling the constructor, a lot of functionality of the combobox will not work. For example, the items will be filled when the ComboBox is drawn on screen, on a form. This does not happen when constructing it in a unit test.
Why do you want to write a unit test on that combobox?
Can't you seperate the logic which now is in the custom control? For example put this in a controller, and test that?
Why don't you test on the DataSource property instead of the Items collection?
I'm sure that Application.Run absence cannot affects any control's behavior
I'm having the same problem with a combo box where the items are data bound. My current solution is to create a Form in the test, add the combo box to the Controls collection, and then show the form in my test. Kind of ugly. All my combo box really does is list a bunch of TimeSpan objects, sorted, and with custom formatting of the TimeSpan values. It also has special behavior on keypress events. I tried extracting all the data and logic to a separate class but couldn't figure it out. There probably is a better solution but what I'm doing seems satisfactory.
To make testing easier, I created these classes in my test code:
class TestCombo : DurationComboBox {
public void SimulateKeyUp(Keys keys) { base.OnKeyUp(new KeyEventArgs(keys)); }
public DataView DataView { get { return DataSource as DataView; } }
public IEnumerable<DataRowView> Rows() { return (DataView as IEnumerable).Cast<DataRowView>(); }
public IEnumerable<int> Minutes() { return Rows().Select(row => (int)row["Minutes"]); }
}
class Target {
public TestCombo Combo { get; private set; }
public Form Form { get; private set; }
public Target() {
Combo = new TestCombo();
Form = new Form();
Form.Controls.Add(Combo);
Form.Show();
}
}
Here is a sample test:
[TestMethod()]
public void ConstructorCreatesEmptyList() {
Target t = new Target();
Assert.AreEqual<int>(0, t.Combo.DataView.Count);
Assert.AreEqual<int>(-1, t.Combo.SelectedMinutes);
Assert.IsNull(t.Combo.SelectedItem);
}
This solve some problems if target is ComboBox or any other control:
target.CreateControl();
but I was unable to set SelectedValue it has null value, my test working with two data sources for combo box, one as data source and second is binded to selevted value. With other controls everithing working fine. In the begining I was also creating form in tests, but there is problem when form on created on our build server while tests are executed.
I did a little hack to allow this in my custom derived combobox:
public class EnhancedComboBox : ComboBox
{
[... the implementation]
public void DoRefreshItems()
{
SetItemsCore(DataSource as IList);
}
}
The SetItemsCore function instructs the base combobox to load internal items with the provided list, it's what uses internally after the datasource changes.
This function never gets called when the control is not on a form, because there are lots of checks for CurrencyManagers and BindingContexts that are failing because this components, I believe, are provided by the parent form somehow.
Anyway, in the test, you have to call mECB.DoRefreshItems() just after the mECB.DataSource = mTestItems and everything should be fine if you only depend on the SelectedIndex and the Items property. Any other behavior like databinding is probably still not functional.

Categories