Reputation: 396
I'm having a small problem with WPF and binding my listbox/listview to a custom list.
This list has the IEnumerator & IEnumerable interfaces implemented. If I bind my control to those, I never get to see the first object of that list.
When I a gridview, it does show my first object. So for some reason the listbox/listview are doing different things to enumerate my custom list.
My binding is for both setup the exact same way, using a public property on my ViewModel.
The Binding ( My PersonObject has a public property ProjectList, which gets the custom list i'm talking about ).
public Person Person
{
get
{
return this._person;
}
set
{
if (this._person != value)
{
this._person = value;
RaisePropertyChanged("Person");
}
}
}
The XAML:
<ListBox ItemsSource="{Binding Path=Person.ProjectList,UpdateSourceTrigger=PropertyChanged}" AlternationCount="2" ItemContainerStyle="{StaticResource CustomListBoxItemStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1}">
<Binding Path="Name" />
<Binding Path="Number" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1}">
<Binding Path="StartDate" />
<Binding Path="EndDate" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The Customlist class:
public class ProjectList : DeletableSupport, IEnumerator, IEnumerable
{
private IList<Project> _pList;
private int _position;
public ProjectList()
{
this._pList = new ActivatableList<Project>();
this._position = -1;
}
public void Add(Project p)
{
Activate(ActivationPurpose.Write);
this._pList.Add(p);
}
public void Remove(Project p)
{
Activate(ActivationPurpose.Write);
this._pList.Remove(p);
}
public int Count()
{
Activate(ActivationPurpose.Read);
return this._pList.Count();
}
public bool Contains(Project p)
{
Activate(ActivationPurpose.Read);
return this._pList.Contains(p);
}
public Project this[int i]
{
get
{
Activate(ActivationPurpose.Read);
return this._pList[i];
}
set
{
Activate(ActivationPurpose.Write);
this._pList[i] = value;
}
}
public static ProjectList operator +(ProjectList pList, Project p)
{
pList.Add(p);
return pList;
}
public static ProjectList operator -(ProjectList pList, Project p)
{
pList.Remove(p);
return pList;
}
#region IEnumerable Members
public IEnumerator GetEnumerator()
{
Activate(ActivationPurpose.Read);
return (IEnumerator)this;
}
#endregion
#region IEnumerator Members
public object Current
{
get
{
try
{
Activate(ActivationPurpose.Read);
return this._pList[_position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
public bool MoveNext()
{
Activate(ActivationPurpose.Read);
this._position++;
if (_position < this._pList.Count)
{
return true;
}
else
{
return false;
}
}
public void Reset()
{
Activate(ActivationPurpose.Write);
_position = -1;
}
#endregion
}
The Activate is from db4o, the ActivatableList implements IList
Upvotes: 1
Views: 261
Reputation: 7538
I'm going to hazard a guess here, just to test my psychic debugging skills :)
ListBox/ListView are using IEnumerable.Any() (or some equivalent) to test whether the list is empty. That's returning true, so it then uses your IEnumerable implementation to actually iterate through the list.
Notice that it hasn't called reset on your class, which means that the first element that was retrieved by the Any() call will be skipped.
Typically calling GetEnumerator on an IEnumerable will return a new instance of IEnumerator for your class, but your actual list implementation contains all the state for the enumerator. Have you considered what would happen if you were to pass this list to your ListBox a second time? I don't think you'd see anything at all.
Ok, so how can this be fixed? Well, given the code that you have, you could just call Reset() whenever someone calls GetEnumerator(). However the implementation that you have isn't thread-safe (maybe not a problem now, but who knows how it will be used in the future?). If you really don't want to use something like ObservableCollection to store your list of items, I would at least have a look at returning a separate IEnumerator instance from the GetEnumerator method, with all the state for the enumeration process held there.
Upvotes: 3