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.
Related
I've been trying to make a Custom search field that, on the fly, should add objects to a list when typing.
But for some reason it only shows the list with an item when i hot reload.
CreateHerdPageViewModel
public class CreateHerdPageViewModel : ViewModelBase
{
private IHerdService herdService;
private string searchInput;
public string SearchInput { get => searchInput;
set {
SetProperty(ref searchInput, value);
RaisePropertyChanged(nameof(HerdSearchResults));
}
}
private List<Herd> herdSearchResults;
public List<Herd> HerdSearchResults
{
get => herdSearchResults;
set {
SetProperty(ref herdSearchResults, value);
}
}
private List<Herd> allHerds;
public List<Herd> AllHerds { get => allHerds; set => SetProperty(ref allHerds, value); }
public DelegateCommand SearchChrOrAddressCommand { get; set; }
public CreateHerdPageViewModel(INavigationService navigationService, IHerdService herdService)
: base(navigationService)
{
this.herdService = herdService;
SearchChrOrAddressCommand = new DelegateCommand(SearchChrOrAddress);
}
private void SearchChrOrAddress()
{
Herd herdMatch = new Herd();
for (int i = 0; i < AllHerds.Count; i++)
{
herdMatch = AllHerds[i];
}
if (herdMatch.ChrAddress.Area.Contains(SearchInput))
{
if (HerdSearchResults.Contains(herdMatch) == false)
{
HerdSearchResults.Add(herdMatch);
RaisePropertyChanged(nameof(HerdSearchResults));
}
}
}
public async override void OnNavigatedTo(INavigationParameters parameters)
{
base.OnNavigatedTo(parameters);
AllHerds = await herdService.GetHerds();
HerdSearchResults = new List<Herd>();
}
}
}
CreateHerdPage.Xaml
xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
xmlns:b="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
xmlns:CustomRenderer="clr-namespace:ChrApp.CustomRenderer">
<yummy:PancakeView
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="3"
CornerRadius="10">
<CustomRenderer:NoUnderlineEntry
x:Name="SearchField"
Style="{StaticResource UpdateEntry}"
Margin="0"
TextChanged="RemovceSearchIcon"
Text="{Binding SearchInput}">
<CustomRenderer:NoUnderlineEntry.Behaviors>
<b:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding SearchChrOrAddressCommand}"/>
</CustomRenderer:NoUnderlineEntry.Behaviors>
</CustomRenderer:NoUnderlineEntry>
</yummy:PancakeView>
As you can see, i've tried different approaches to make it recognize changes, but without luck.
Can someone enlighten me on what i'm missing?
Thanks in advance
So the issue was that I used an ordinary List, and not an ObservableCollection. So swithcing to this solved the issue.
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
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).UpdateSource(); txtPassword.GetBindingExpression(Infrastructure.AttachedProperty.PasswordAssistent.PasswordValue).UpdateSource(); 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/
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.
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.