【问题标题】:Filter items of a ListBox based on the text of a TextBox using only XAML in WPF在 WPF 中仅使用 XAML 根据 TextBox 的文本过滤 ListBox 的项目
【发布时间】:2011-10-28 23:04:59
【问题描述】:

我目前有一个绑定到项目集合的 ListBox。由于集合很大,我们希望根据在 TextBox 上输入的文本过滤显示的项目。

我要问的是这是否可以仅使用 XAML 来实现,我不想修改项目的集合,我想根据过滤器修改每个项目的可见性。

希望清楚,

谢谢!

【问题讨论】:

    标签: c# .net wpf xaml


    【解决方案1】:

    您可以使用CollectionViewSource 来应用过滤,另一个示例可以找到herehere

    【讨论】:

    【解决方案2】:

    像 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;
    }
    

    【讨论】:

      【解决方案3】:

      您可以使用CollectionViewSource 来完成此操作。您不希望完全在 XAML 中执行此操作,因为如果过滤代码在您的视图模型中(假设为 MVVM 设计模式),测试起来会容易得多。

      【讨论】:

      • 这是一个 MVVM 模式,但这是一个仅与视图相关的问题,所以我不想在 ViewModel 上使用此代码。
      • 正如 Yurec 所说,没有真正的方法可以仅在视图中执行此操作,您也不想这样做。这是视图模型的完美应用。
      • 可能你是对的,我将问题转移到 ViewModel。
      【解决方案4】:

      无法仅在 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
      

      【讨论】:

        【解决方案5】:

        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,因为它们通常不是通过元素语法设置的。

        这里有几个标记扩展,其中两个创建处理程序,一个创建一个动作:

        • 过滤器扩展
        • ExecuteActionsHandlerExtension
        • CallMethodActionExtension

        扩展看起来像这样:

        [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找到。

        【讨论】:

          【解决方案6】:

          在collectin中项目的某些属性上使用数据触发器,您可以在xaml中完成所有操作。

          【讨论】:

          • 但后来我将仅视图问题移至数据域。我的意思是,模型不需要知道有关过滤的任何信息。
          • 我认为您不了解数据触发器的工作原理。但是,再次阅读您的问题后,我同意这不起作用,因为数据触发值不适用于数据绑定。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-02-24
          • 1970-01-01
          • 1970-01-01
          • 2011-01-21
          • 1970-01-01
          • 2012-04-16
          相关资源
          最近更新 更多