【发布时间】:2011-10-28 23:04:59
【问题描述】:
我目前有一个绑定到项目集合的 ListBox。由于集合很大,我们希望根据在 TextBox 上输入的文本过滤显示的项目。
我要问的是这是否可以仅使用 XAML 来实现,我不想修改项目的集合,我想根据过滤器修改每个项目的可见性。
希望清楚,
谢谢!
【问题讨论】:
我目前有一个绑定到项目集合的 ListBox。由于集合很大,我们希望根据在 TextBox 上输入的文本过滤显示的项目。
我要问的是这是否可以仅使用 XAML 来实现,我不想修改项目的集合,我想根据过滤器修改每个项目的可见性。
希望清楚,
谢谢!
【问题讨论】:
您可以使用CollectionViewSource 来应用过滤,另一个示例可以找到here 和here。
【讨论】:
像 CodeNaked 和 devdigital 告诉你 CollectionViewSource/CollectionView/ICollectionView 是实现目标的关键
这是一个 MVVM 模式,但这是一个仅与视图相关的问题,所以我不知道 在 ViewModel 上想要这段代码。
那不是正确的方式,因为视图只显示她得到的东西,但不应该修改 所以应该/必须是你的 ViewModel 来处理变化
所以现在有一些代码片段:
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 片段:
<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>
编辑我忘记了过滤器
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;
}
【讨论】:
您可以使用CollectionViewSource 来完成此操作。您不希望完全在 XAML 中执行此操作,因为如果过滤代码在您的视图模型中(假设为 MVVM 设计模式),测试起来会容易得多。
【讨论】:
无法仅在 XAML 中完成此操作。但是还有另外两种方式: 1) 使用转换器
<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) 更好更自然的方式是使用 CollectionView.Filter 属性。它不会修改基础集合。
var collectionView = CollectionViewSource.GetDefaultView(your_collection);
collectionView.Filter = filter_predicate
【讨论】:
XAML 真正做的唯一事情是以声明方式封装逻辑。使用markup extensions 可以做很多事情,这里有一个例子:
<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>
(请注意,这是可行的,但它会让每个 GUI 设计者绊倒,而且事件也没有 IntelliSense,因为它们通常不是通过元素语法设置的。)
这里有几个标记扩展,其中两个创建处理程序,一个创建一个动作:
扩展看起来像这样:
[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);
}
非常简单,只需遍历过滤器并应用它们。 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;
}
});
}
}
现在最后一个扩展有点复杂,因为它实际上需要做一些具体的事情:
[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>();
}
}
所有这些 sn-ps 都只是快速草稿,以展示如何解决这个问题。 (属性过滤器实现的草稿可以在this answer找到。)
【讨论】:
在collectin中项目的某些属性上使用数据触发器,您可以在xaml中完成所有操作。
【讨论】: