WPF Binding.ValidationRules based on condition - c#

I currently have TextBox with a Binding.ValidationRules that work like;
<TextBox>
<Binding Path="MyID" NotifyOnValidationError="True" ValidatesOnDataErrors="True"
Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True"
NotifyOnTargetUpdated="True" Delay="100">
<Binding.ValidationRules>
<local:IDValidator ValidatesOnTargetUpdated="True" table="Items" />
</Binding.ValidationRules>
</Binding>
</TextBox>
And the custom ValidationRule:
public class IDValidator : ValidationRule
{
public string table { get; set; }
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
//Logic
}
}
Problem is under certain conditions I would want IDValidator to be the ValidationRule. Other times I may want say IDValidator2 to be the ValidationRule.
Now I couldn't find a way to accomplish this. So I figured hey why not send another value down to IDValidator and then handle it in the logic of Validate like this:
XMAL update:
<local:IDValidator ValidatesOnTargetUpdated="True" table="Items" testing="{Binding Path=test}" />
IDValidator update:
public string testing { get; set; }
Problem is that doesn't seem to like sending a bind value down. How can I accomplish this?

This is doable, but it is not very simple and has some gotchas that you may not expect. The underlying issue is that dynamic bindings can only be applied on objects that derive from DependencyObject. ValidationRule is not such an object. However, we can add a property to a custom ValidationRule that exposes a class that does derive from DependencyObject. An example will help explain:
public class IDValidator : ValidationRule
{
private IDValidatorRange _range;
public int MinLength { get; set; }
public int MaxLength { get; set; }
public IDValidatorRange Range
{
get { return _range; }
set
{
_range = value;
value?.SetValidator(this);
}
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
// Logic
}
}
Note the IDValidatorRange object returned from the Range property. You will need to create this class using DependencyProperties with a mechanism for updating the IDValidator rule properties. Here is an example of such a class:
public class IDValidatorRange : Freezable
{
public static readonly DependencyProperty MinLengthProperty = DependencyProperty.Register(
"MinLength", typeof (int), typeof (IDValidatorRange), new FrameworkPropertyMetadata(5, OnMinLengthChanged));
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register(
"MaxLength", typeof (int), typeof (IDValidatorRange), new FrameworkPropertyMetadata(10, OnMaxLengthChanged));
public void SetValidator(IDValidator validator)
{
Validator = validator;
if (validator != null)
{
validator.MinLength = MinLength;
validator.MaxLength = MaxLength;
}
}
public int MaxLength
{
get { return (int) GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
public int MinLength
{
get { return (int) GetValue(MinLengthProperty); }
set { SetValue(MinLengthProperty, value); }
}
private IDValidator Validator { get; set; }
private static void OnMaxLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var range = (IDValidatorRange) d;
if (range.Validator != null)
{
range.Validator.MaxLength = (int) e.NewValue;
}
}
private static void OnMinLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var range = (IDValidatorRange) d;
if (range.Validator != null)
{
range.Validator.MinLength = (int) e.NewValue;
}
}
protected override Freezable CreateInstanceCore()
{
return new IDValidatorRange();
}
}
You can see that I derived from Freezable instead of its ancestor DependencyObject because we will want to inherit a DataContext for our bindings. DependencyObject does not provide this but Freezable does.
Finally, we can put this all together in XAML like such:
<TextBox>
<TextBox.Resources>
<local:IDValidatorRange x:Key="ValidatorRange"
MaxLength="{Binding MaxLength}"
MinLength="{Binding MinLength}" />
</TextBox.Resources>
<TextBox.Text>
<Binding Delay="100"
Mode="TwoWay"
NotifyOnSourceUpdated="True"
NotifyOnTargetUpdated="True"
NotifyOnValidationError="True"
Path="ID"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:IDValidator Range="{StaticResource ValidatorRange}" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
One last gotcha here, because validation rules do not hold or inherit a DataContext this will prevent binding from working as expected if you try to declare everything inline with your rule. Instead, declare your bindable rule options as a resource and set the property on your custom rule using a StaticBinding.
This approach is a lot of work and a bit confusing. If your hands are not tied with the data context, I would recommend exploring other options. Using the INotifyDataErrorInfo interface on a view-model may be a more elegant approach to this problem.

Related

C# WPF Datagrid Validation Rule - Pass Parameters

I have a Point class as follows:
public class Point
{
public uint lID;
public double lX_Coordinate;
public Point()
{
ID = 0;
X_Coordinate = 0;
}
public uint ID
{
get
{ return lID; }
set
{
lID = value;
}
}
public double X_Coordinate
{
get
{ return lX_Coordinate; }
set
{
lX_Coordinate = value;
}
}
}
The Point class constitutes the members of the ObservableCollection PointList that belongs to class Geometry as follows:
public class Geometry
{
public ObservableCollection<Point> lPointList;
public double lLeftBoundary;
public Geometry()
{
lPointList = new ObservableCollection<Point>();
}
public ObservableCollection<Point> PointList
{
get
{ return lPointList; }
set
{ lPointList = value; }
}
public double LeftBoundary
{
get
{ return lLeftBoundary; }
set
{ lLeftBoundary = value; }
}
}
I then bind the contents of the PointList to a datagrid. There is a Validation Rule that checks the Point X_Coordinate against the LeftBoundary, which is passed on to the Validation Rule through a ValidationWrapper class as Dependency Objects.
The following is the XAML code:
</DataGridTextColumn>
<DataGridTextColumn Header="X" Width="60" >
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGridTextColumn.HeaderStyle >
<DataGridTextColumn.Binding>
<Binding Path="X_Coordinate" UpdateSourceTrigger="LostFocus" ValidatesOnExceptions="True" StringFormat="{}{0:N3}">
<Binding.ValidationRules>
<c:ValidationGeometryPointX>
<c:ValidationGeometryPointX.ValidationRuleWrapper>
<c:ValidationRuleWrapper LeftBoundary="{Binding Data.LeftBoundary, Source={StaticResource proxy}}" />
</c:ValidationGeometryPointX.ValidationRuleWrapper>
</c:ValidationGeometryPointX>
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
The following is the ValidationWrapper class:
public class ValidationGeometryPointX : ValidationRule
{
public ValidationGeometryPointX()
{
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
double PointCoordinate = 0;
try
{
if (((string)value).Length > 0)
PointCoordinate = Double.Parse((String)value);
}
catch
{
return new ValidationResult(false, "Illegal characters.");
}
if (PointCoordinate < this.ValidationRuleWrapper.LeftBoundary)
{
return new ValidationResult(false, "Point cannot be beyond the side boundaries.");
}
return ValidationResult.ValidResult;
}
public ValidationRuleWrapper ValidationRuleWrapper
{ get; set; }
}
The code for the window that displays the datagrid is the following:
public partial class GeometryWin : Window
{
public GeometryWin(Geometry Geometry1)
{
InitializeComponent();
base.DataContext = lGeometry;
datgrdGeometry.ItemsSource = lGeometry.PointList;
}
}
The above validation has been achieved successfully.
What I am now struggling to do is to pass the Point.ID parameter to the ValidationWrapper class so as to add another validation condition for X_Coordinate based on the ID. Any help would be much appreciated.
PS. For brevity certain parts of the code have been omitted.
Seems that the solution is to perform Row Validation, and pass the entire Item to the Validation Rule class. An interesting article is given here
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/how-to-implement-validation-with-the-datagrid-control?view=netframeworkdesktop-4.8

How to validate two properties which depend on each other?

I have view model with 2 properties: A and B and I want to validate that A < B.
Below is my simplified implementation where I use custom validation rule. Since each property is validated independently, it lead to an anoying issue: if entered A value is invalid, than it stay so even after changing B, since validation of B doesn't know anything about A.
This can be seen on this demo:
A is invalid after entering 11, that's correct since 11 > 2. Changing B to 22 doesn't re-evalute A, I have to edit A to have validation passed.
What I want? I want that after enering 22 into B the red border (validation error) disappears and A = 11, B = 22 would be source values in view model.
How can I in B validation somehow force A validation after new B value is synchronized with source?
View model:
public class ViewModel : INotifyPropertyChanged
{
int _a;
public int A
{
get => _a;
set
{
_a = value;
OnPropertyChanged();
}
}
int _b;
public int B
{
get => _b;
set
{
_b = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged([CallerMemberName] string property = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
View:
<StackPanel>
<TextBox Margin="10" Text="{local:MyBinding A}" />
<TextBox Margin="10" Text="{local:MyBinding B}" />
</StackPanel>
View code:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel { A = 1, B = 2 };
}
Binding:
public class MyBinding : Binding
{
public MyBinding(string path) : base(path)
{
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
ValidationRules.Add(new MyValidationRule());
}
}
Validation rule:
public class MyValidationRule : ValidationRule
{
public MyValidationRule() : base(ValidationStep.ConvertedProposedValue, false) { }
public override ValidationResult Validate(object value, CultureInfo cultureInfo) => ValidationResult.ValidResult; // not used
public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingExpressionBase owner)
{
var binding = owner as BindingExpression;
var vm = binding?.DataItem as ViewModel;
switch (binding.ResolvedSourcePropertyName)
{
case nameof(vm.A):
if ((int)value >= vm.B)
return new ValidationResult(false, "A should be smaller than B");
break;
case nameof(vm.B):
if ((int)value <= vm.A)
return new ValidationResult(false, "B should be bigger than A");
break;
}
return base.Validate(value, cultureInfo, owner);
}
}
ValidationRules don't support invalidating a property when setting another property.
What you should do is to implement INotifyDataErrorInfo in your view model and raise the ErrorsChanged event whenever you want to refresh the validation status for a property.
There is an example available in the this TechNet article.

Stack OverFlow with Custom validator

I have implemented custom validator as following...
public class RquiredFiledValidation:ValidationRule
{
public string ErrorMessage { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (string.IsNullOrWhiteSpace(value.ToString()))
return new ValidationResult(false, ErrorMessage);
else
return new ValidationResult(true, null);
}
}
And Attached this with a text box as following...
<TextBox x:Name="txtLoging" Grid.Column="1" HorizontalAlignment="Stretch" Validation.ErrorTemplate="{x:Null}" VerticalAlignment="Center" Margin="0,40,30,0">
<Binding Path="Text" ElementName="txtLoging" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<Validate:RquiredFiledValidation ErrorMessage="Please Provide Login Name"></Validate:RquiredFiledValidation>
</Binding.ValidationRules>
</Binding>
</TextBox>
My problem is...
1) When I click directly on login button then the validation doesn't get fired
2) When I put a character in text box validation get fired but produced stack overflow error.
I have solve the first problem from code behind as below txtLoging.GetBindingExpression(TextBox.TextProperty).UpdateS‌​ource(); txtPassword.GetBindingExpression(Infrastructure.AttachedProp‌​erty.PasswordAssiste‌​nt.PasswordValue).Up‌​dateSource(); But how solve the same in MVVM
If you care about the MVVM pattern you should not validate your data using validation rules. Validation rules belong to the view and in an MVVM application the validation logic should be implemented in the view model or the model class.
What you should do is implement the INotifyDataErrorInfo interface: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifydataerrorinfo%28v=vs.110%29.aspx
Here is an example for you:
public class ViewModel : INotifyDataErrorInfo
{
private string _username;
public string Username
{
get { return _username; }
set
{
_username = value;
ValidateUsername();
}
}
private void ValidateUsername()
{
if (_username == "valid")
{
if (_validationErrors.ContainsKey("Username"))
_validationErrors.Remove(nameof(Username));
}
else if (!_validationErrors.ContainsKey("Username"))
{
_validationErrors.Add("Username", new List<string> { "Invalid username" });
}
RaiseErrorsChanged("Username");
}
private readonly Dictionary<string, ICollection<string>>
_validationErrors = new Dictionary<string, ICollection<string>>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
private void RaiseErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName)
|| !_validationErrors.ContainsKey(propertyName))
return null;
return _validationErrors[propertyName];
}
public bool HasErrors
{
get { return _validationErrors.Count > 0; }
}
}
<TextBox Text="{Binding Username, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />
Please refer to the following blog post for more information about the broad picture of how data validation in WPF works and some comprehensive samples on how to implement it.
Data validation in WPF: https://blog.magnusmontin.net/2013/08/26/data-validation-in-wpf/

Filter ListBox by text got from a TextBox in WPF? [duplicate]

I currently have a ListBox binded to a collection of items. As the collection is big we want to filter the items being shown based on the text entered on a TextBox.
What I'm asking is if this is possible to implement using only XAML, I don't want to modify the collection of items, I would like to modify the Visibility of each of the items based on the filter.
Hope its clear,
thanks!
Like CodeNaked and devdigital told you CollectionViewSource/CollectionView/ICollectionView
are the keys to your goal
It's a MVVM patter but this is a View only related problem so I don't
want this code at the ViewModel.
thats not the right way because the View only shows what she get´s but shouldn´t modifi
so it should/must be your ViewModel who handel changes
so now some code snips:
public class myVM
{
public CollectionViewSource CollViewSource { get; set; }
public string SearchFilter
{
get;
set
{
if(!string.IsNullOrEmpty(SearchFilter))
AddFilter();
CollViewSource.View.Refresh(); // important to refresh your View
}
}
public myVM(YourCollection)
{
CollViewSource = new CollectionViewSource();//onload of your VM class
CollViewSource.Source = YourCollection;//after ini YourCollection
}
}
Xaml Snip:
<StackPanel>
<TextBox Height="23" HorizontalAlignment="Left" Name="tB" VerticalAlignment="Top"
Width="120" Text="{Binding SearchFilter,UpdateSourceTrigger=PropertyChanged}" />
<DataGrid Name="testgrid" ItemsSource="{Binding CollViewSource.View}"/>
</StackPanel>
Edit i forgot the Filter
private void AddFilter()
{
CollViewSource.Filter -= new FilterEventHandler(Filter);
CollViewSource.Filter += new FilterEventHandler(Filter);
}
private void Filter(object sender, FilterEventArgs e)
{
// see Notes on Filter Methods:
var src = e.Item as YourCollectionItemTyp;
if (src == null)
e.Accepted = false;
else if ( src.FirstName !=null && !src.FirstName.Contains(SearchFilter))// here is FirstName a Property in my YourCollectionItem
e.Accepted = false;
}
You can use the CollectionViewSource to apply filtering, another example can be found here and here.
You can do this with a CollectionViewSource. You wouldn't want to do this completely in XAML, as it would be much easier to test this if the filtering code is in your view model (assuming an MVVM design pattern).
There is no way to accomplish this in XAML only. But there are other two ways:
1) using converter
<TextBox x:Name="text"/>
<ListBox Tag="{Binding ElementName=text}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Visibility" Value="{Binding RelativeSource={RelativeSource AncestorType=ListBox},Path=Tag, Converter={StaticResource filterLogicConverter}}"/>
</Style>
</ListBox.ItemContainerStyle>
<LixtBox/>
2) better and more natural way is to use CollectionView.Filter property. It doesn't modify an underlying collection.
var collectionView = CollectionViewSource.GetDefaultView(your_collection);
collectionView.Filter = filter_predicate
The only thing XAML really does is encapsulating logic in a declarative fashion. Using markup extensions you can do quite a lot, here's an example:
<StackPanel>
<StackPanel.Resources>
<CollectionViewSource x:Key="items" Source="{Binding Data}">
<CollectionViewSource.Filter>
<me:Filter>
<me:PropertyFilter PropertyName="Name"
RegexPattern="{Binding Text, Source={x:Reference filterbox}}" />
</me:Filter>
</CollectionViewSource.Filter>
</CollectionViewSource>
</StackPanel.Resources>
<TextBox Name="filterbox" Text="Skeet">
<TextBox.TextChanged>
<me:ExecuteActionsHandler ThrowOnException="false">
<me:CallMethodAction>
<me:CallMethodActionSettings MethodName="Refresh"
TargetObject="{Binding Source={StaticResource items}}" />
</me:CallMethodAction>
</me:ExecuteActionsHandler>
</TextBox.TextChanged>
</TextBox>
<!-- ListView here -->
</StackPanel>
(Note that this works but it will trip every GUI designer, also there is no IntelliSense for the events as they usually are not set via element syntax.)
There are several markup extensions here of which two create handlers and one creates an action:
FilterExtension
ExecuteActionsHandlerExtension
CallMethodActionExtension
The extensions look like this:
[ContentProperty("Filters")]
class FilterExtension : MarkupExtension
{
private readonly Collection<IFilter> _filters = new Collection<IFilter>();
public ICollection<IFilter> Filters { get { return _filters; } }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new FilterEventHandler((s, e) =>
{
foreach (var filter in Filters)
{
var res = filter.Filter(e.Item);
if (!res)
{
e.Accepted = false;
return;
}
}
e.Accepted = true;
});
}
}
public interface IFilter
{
bool Filter(object item);
}
Quite straightforward, just loops through filters and applies them. Same goes for the ExecuteActionsHandlerExtension:
[ContentProperty("Actions")]
public class ExecuteActionsHandlerExtension : MarkupExtension
{
private readonly Collection<Action> _actions = new Collection<Action>();
public Collection<Action> Actions { get { return _actions; } }
public bool ThrowOnException { get; set; }
public ExecuteActionsHandlerExtension()
{
ThrowOnException = true;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new RoutedEventHandler((s, e) =>
{
try
{
foreach (var action in Actions)
{
action.Invoke();
}
}
catch (Exception)
{
if (ThrowOnException) throw;
}
});
}
}
Now the last extension is a bit more complicated as it actually needs to do something concrete:
[ContentProperty("Settings")]
public class CallMethodActionExtension : MarkupExtension
{
//Needed to provide dependency properties as MarkupExtensions cannot have any
public CallMethodActionSettings Settings { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new Action(() =>
{
bool staticCall = Settings.TargetObject == null;
var argsCast = Settings.MethodArguments.Cast<object>();
var types = argsCast.Select(x => x.GetType()).ToArray();
var args = argsCast.ToArray();
MethodInfo method;
if (staticCall)
{
method = Settings.TargetType.GetMethod(Settings.MethodName, types);
}
else
{
method = Settings.TargetObject.GetType().GetMethod(Settings.MethodName, types);
}
method.Invoke(Settings.TargetObject, args);
});
}
}
public class CallMethodActionSettings : DependencyObject
{
public static readonly DependencyProperty MethodNameProperty =
DependencyProperty.Register("MethodName", typeof(string), typeof(CallMethodActionSettings), new UIPropertyMetadata(null));
public string MethodName
{
get { return (string)GetValue(MethodNameProperty); }
set { SetValue(MethodNameProperty, value); }
}
public static readonly DependencyProperty TargetObjectProperty =
DependencyProperty.Register("TargetObject", typeof(object), typeof(CallMethodActionSettings), new UIPropertyMetadata(null));
public object TargetObject
{
get { return (object)GetValue(TargetObjectProperty); }
set { SetValue(TargetObjectProperty, value); }
}
public static readonly DependencyProperty TargetTypeProperty =
DependencyProperty.Register("TargetType", typeof(Type), typeof(CallMethodActionSettings), new UIPropertyMetadata(null));
public Type TargetType
{
get { return (Type)GetValue(TargetTypeProperty); }
set { SetValue(TargetTypeProperty, value); }
}
public static readonly DependencyProperty MethodArgumentsProperty =
DependencyProperty.Register("MethodArguments", typeof(IList), typeof(CallMethodActionSettings), new UIPropertyMetadata(null));
public IList MethodArguments
{
get { return (IList)GetValue(MethodArgumentsProperty); }
set { SetValue(MethodArgumentsProperty, value); }
}
public CallMethodActionSettings()
{
MethodArguments = new List<object>();
}
}
All of these snippets are just quick drafts to demonstrate how one could approach this. (A draft for the property filter implementation can be found in this answer.)
Use a data trigger on some property of the item in the collectin and you can do it all in xaml.

Listbox isn't showing my first item

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
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.

Categories

Resources