如何在XamDataGrid中实现导致多列记录过滤的搜索?

Kev*_*vin 3 wpf infragistics xamdatagrid

我试图在XamDataGrid上实现搜索(Ctrl + F)功能.如何以编程方式在网格上调用记录筛选,搜索多列中的内容并仅显示与搜索匹配的列?

小智 9

DataPresenter中的记录过滤就是这样 - 根据某些指定的标准过滤记录的方法.通常,标准是通过其中一个内置的ui提供的 - 使用LabelIcons,它只是过滤值的下拉列表,或者通过FilterRecord,这是一个专用的特殊记录,显示每列的单元格以允许选择/进入运营商和价值.

也就是说,如果您愿意,可以在代码中操作记录过滤.所述FieldLayout暴露出RecordFilters集合,其中一个RecordFilter的提供了条件(即,匹配标准)和所述匹配应该执行领域.它也暴露在RecordManager之外,但这实际上是为了便于在分层情况下进行过滤,其中过滤条件对于子记录的每个"孤岛"是不同的.

由于您要搜索多个字段以获得相同的条件,因此您需要为FieldLayout的Fields集合中的每个Field创建一个RecordFilter(或者您希望将其应用到的Fields的任何子集).由于您要进行文本搜索,因此您可能希望使用比较条件,其中您使用的ComparisonOperator是包含的,值将是要搜索的文本.现在,因为如果在任何字段(您已为其创建了RecordFilter)中找到值,您希望匹配记录,您还需要将FieldLayoutSettings的RecordFiltersLogicalOperator属性设置为Or(默认情况下,这会解析为And,因为想要在所有条件与输入的值匹配时匹配记录.

因此,为此目的,以下是一个基本的附加行为(在这种情况下,您将在要过滤的DataPresenter上设置名为FilterText的属性).考虑到FilterText属性的文本值,此行为/属性将操作DefaultFieldLayout的RecordFilters.

public static class DataPresenterHelpers
{
    #region FilterText

    /// <summary>
    /// FilterText Attached Dependency Property
    /// </summary>
    public static readonly DependencyProperty FilterTextProperty =
        DependencyProperty.RegisterAttached("FilterText", typeof(string), typeof(DataPresenterHelpers),
            new FrameworkPropertyMetadata((string)null,
                new PropertyChangedCallback(OnFilterTextChanged)));

    /// <summary>
    /// Gets the text to be used to filter the DataPresenter on which the property was set.
    /// </summary>
    public static string GetFilterText(DependencyObject d)
    {
        return (string)d.GetValue(FilterTextProperty);
    }

    /// <summary>
    /// Sets the filter text on the DataPresenter that should be used to manipulate the RecordFilters of the specified DataPresenter
    /// </summary>
    public static void SetFilterText(DependencyObject d, string value)
    {
        d.SetValue(FilterTextProperty, value);
    }

    /// <summary>
    /// Handles changes to the FilterText property.
    /// </summary>
    private static void OnFilterTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var dp = d as DataPresenterBase;

        if (dp.DefaultFieldLayout != null)
        {
            dp.DefaultFieldLayout.RecordFilters.Clear();
            dp.DefaultFieldLayout.Settings.RecordFiltersLogicalOperator = LogicalOperator.Or;

            foreach (var field in dp.DefaultFieldLayout.Fields)
            {
                var filter = new RecordFilter();
                filter.Field = field;
                filter.Conditions.Add(new ComparisonCondition(ComparisonOperator.Contains, e.NewValue));
                dp.DefaultFieldLayout.RecordFilters.Add(filter);
            }
        }
    }

    #endregion //FilterText
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以执行以下操作,将TextBox的值连接到此附加属性.注意,您需要将local的xmlns映射定义为您将上述类放入的任何clr命名空间.

    <TextBox DockPanel.Dock="Top" x:Name="txtFilter" />
    <igDP:XamDataGrid 
        x:Name="grid" 
        BindToSampleData="True" 
        local:DataPresenterHelpers.FilterText="{Binding ElementName=txtFilter, Path=Text}">
    </igDP:XamDataGrid>
Run Code Online (Sandbox Code Playgroud)

现在问题的另一部分是仅显示包含匹配值的列.作为记录过滤器本身的一部分,这实际上是不可能的,因为记录过滤是基于某些指定的标准过滤掉记录,并且与隐藏/显示列/字段无关.也就是说,也许一种方法可以帮助最终用户理解匹配文本所在的位置,以突出显示单元格内的文本.

为了提供这样的功能,我定义了一个名为HighlightTextBlock的派生TextBlock .它暴露了几个属性:

  • RawText - 这是将显示的源文本.它不能使用Text属性,因为此元素将操纵TextBlock的Inlines,并且将设置Text属性,在单向绑定的情况下会破坏绑定,或者在双向绑定的情况下将值推回到源.
  • FilterText - 用于指示要在RawText中搜索的文本.
  • FilterTextComparisonType - 用于指示匹配的字符串比较(即区分大小写等).
  • FilterTextForeground - 用于突出显示匹配文本的前景.
  • FilterTextBackground - 用于突出显示匹配文本的背景.

这是该类的代码:

    public class HighlightTextBlock 
    : TextBlock
{
    #region Member Variables

    private DispatcherOperation _pendingUpdate; 

    #endregion //Member Variables

    #region Constructor
    static HighlightTextBlock()
    {
    }

    /// <summary>
    /// Initializes a new <see cref="HighlightTextBlock"/>
    /// </summary>
    public HighlightTextBlock()
    {

    } 
    #endregion //Constructor

    #region Base class overrides

    #region OnInitialized
    protected override void OnInitialized(EventArgs e)
    {
        if (_pendingUpdate != null)
            this.UpdateInlines(null);

        base.OnInitialized(e);
    }
    #endregion //OnInitialized 

    #endregion //Base class overrides 

    #region Properties

    #region FilterText

    /// <summary>
    /// Identifies the <see cref="FilterText"/> dependency property
    /// </summary>
    public static readonly DependencyProperty FilterTextProperty = DependencyProperty.Register("FilterText",
        typeof(string), typeof(HighlightTextBlock), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnCriteriaChanged)));

    /// <summary>
    /// Returns or sets the text that should be highlighted
    /// </summary>
    /// <seealso cref="FilterTextProperty"/>
    [Description("Returns or sets the text that should be highlighted")]
    [Category("Behavior")]
    [Bindable(true)]
    public string FilterText
    {
        get
        {
            return (string)this.GetValue(HighlightTextBlock.FilterTextProperty);
        }
        set
        {
            this.SetValue(HighlightTextBlock.FilterTextProperty, value);
        }
    }

    #endregion //FilterText

    #region FilterTextBackground

    /// <summary>
    /// Identifies the <see cref="FilterTextBackground"/> dependency property
    /// </summary>
    public static readonly DependencyProperty FilterTextBackgroundProperty = DependencyProperty.Register("FilterTextBackground",
        typeof(Brush), typeof(HighlightTextBlock), new FrameworkPropertyMetadata(Brushes.Yellow, new PropertyChangedCallback(OnCriteriaChanged)));

    /// <summary>
    /// Returns or sets the background of the matching text.
    /// </summary>
    /// <seealso cref="FilterTextBackgroundProperty"/>
    [Description("Returns or sets the background of the matching text.")]
    [Category("Behavior")]
    [Bindable(true)]
    public Brush FilterTextBackground
    {
        get
        {
            return (Brush)this.GetValue(HighlightTextBlock.FilterTextBackgroundProperty);
        }
        set
        {
            this.SetValue(HighlightTextBlock.FilterTextBackgroundProperty, value);
        }
    }

    #endregion //FilterTextBackground

    #region FilterTextComparisonType

    /// <summary>
    /// Identifies the <see cref="FilterTextComparisonType"/> dependency property
    /// </summary>
    public static readonly DependencyProperty FilterTextComparisonTypeProperty = DependencyProperty.Register("FilterTextComparisonType",
        typeof(StringComparison), typeof(HighlightTextBlock), new FrameworkPropertyMetadata(StringComparison.CurrentCultureIgnoreCase, new PropertyChangedCallback(OnCriteriaChanged)));

    /// <summary>
    /// Returns or sets the StringComparison when locating the FilterText within the RawText.
    /// </summary>
    /// <seealso cref="FilterTextComparisonTypeProperty"/>
    [Description("Returns or sets the StringComparison when locating the FilterText within the RawText.")]
    [Category("Behavior")]
    [Bindable(true)]
    public StringComparison FilterTextComparisonType
    {
        get
        {
            return (StringComparison)this.GetValue(HighlightTextBlock.FilterTextComparisonTypeProperty);
        }
        set
        {
            this.SetValue(HighlightTextBlock.FilterTextComparisonTypeProperty, value);
        }
    }

    #endregion //FilterTextComparisonType

    #region FilterTextForeground

    /// <summary>
    /// Identifies the <see cref="FilterTextForeground"/> dependency property
    /// </summary>
    public static readonly DependencyProperty FilterTextForegroundProperty = DependencyProperty.Register("FilterTextForeground",
        typeof(Brush), typeof(HighlightTextBlock), new FrameworkPropertyMetadata(Brushes.Black, new PropertyChangedCallback(OnCriteriaChanged)));

    /// <summary>
    /// Returns or sets the brushed used for the foreground of the matching text.
    /// </summary>
    /// <seealso cref="FilterTextForegroundProperty"/>
    [Description("Returns or sets the brushed used for the foreground of the matching text.")]
    [Category("Behavior")]
    [Bindable(true)]
    public Brush FilterTextForeground
    {
        get
        {
            return (Brush)this.GetValue(HighlightTextBlock.FilterTextForegroundProperty);
        }
        set
        {
            this.SetValue(HighlightTextBlock.FilterTextForegroundProperty, value);
        }
    }

    #endregion //FilterTextForeground

    #region RawText

    /// <summary>
    /// Identifies the <see cref="RawText"/> dependency property
    /// </summary>
    public static readonly DependencyProperty RawTextProperty = DependencyProperty.Register("RawText",
        typeof(string), typeof(HighlightTextBlock), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnCriteriaChanged)));

    /// <summary>
    /// Returns or sets the base string that will be displayed by the element.
    /// </summary>
    /// <seealso cref="RawTextProperty"/>
    [Description("Returns or sets the base string that will be displayed by the element.")]
    [Category("Behavior")]
    [Bindable(true)]
    public string RawText
    {
        get
        {
            return (string)this.GetValue(HighlightTextBlock.RawTextProperty);
        }
        set
        {
            this.SetValue(HighlightTextBlock.RawTextProperty, value);
        }
    }

    #endregion //RawText

    #endregion //Properties

    #region Methods

    #region OnCriteriaChanged
    private static void OnCriteriaChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var instance = d as HighlightTextBlock;

        if (instance._pendingUpdate == null)
        {
            instance._pendingUpdate = instance.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new SendOrPostCallback(instance.UpdateInlines), new object[] { null });
        }
    }
    #endregion //OnCriteriaChanged

    #region UpdateInlines
    private void UpdateInlines(object param)
    {
        _pendingUpdate = null;

        string filterText = this.FilterText;
        string text = this.RawText;
        var inlines = this.Inlines;

        try
        {
            inlines.Clear();

            if (string.IsNullOrEmpty(filterText))
            {
                inlines.Add(text);
                return;
            }

            var foreground = this.FilterTextForeground;
            var background = this.FilterTextBackground;
            var comparison = this.FilterTextComparisonType;
            var newInlines = new List<Inline>();
            int filterTextLen = filterText.Length;

            int start = 0;

            do
            {
                int end = text.IndexOf(filterText, start, comparison);

                string substr = text.Substring(start, (end < 0 ? text.Length : end) - start);
                newInlines.Add(new Run(substr));

                if (end < 0)
                    break;

                var run = new Run(text.Substring(end, filterTextLen));

                // note we could bind and not rebuild when the background/foreground 
                // changes but that doesn't seem likely to happen and would add more 
                // overhead than just referencing the value directly
                if (null != foreground)
                    run.Foreground = foreground;

                if (null != background)
                    run.Background = background;

                newInlines.Add(run);

                start = end + filterTextLen;
            } while (true);

            inlines.AddRange(newInlines);
        }
        finally
        {
            if (_pendingUpdate != null)
            {
                _pendingUpdate.Abort();
                _pendingUpdate = null;
            }
        }
    }
    #endregion //UpdateInlines

    #endregion //Methods
}
Run Code Online (Sandbox Code Playgroud)

那么您可以更改用于在其渲染模板中使用它的编辑器的模板.例如

<Window x:Class="WpfApplication6.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:igDP="http://infragistics.com/DataPresenter"
    xmlns:igEditors="http://infragistics.com/Editors"
    xmlns:local="clr-namespace:WpfApplication6"
    Title="MainWindow" Height="350" Width="525">
<DockPanel>
    <TextBox DockPanel.Dock="Top" x:Name="txtFilter" />
    <igDP:XamDataGrid 
        x:Name="grid" 
        BindToSampleData="True" 
        local:DataPresenterHelpers.FilterText="{Binding ElementName=txtFilter, Path=Text}">
        <igDP:XamDataGrid.Resources>
            <Style TargetType="igEditors:XamTextEditor">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="igEditors:XamTextEditor">
                            <Border x:Name="MainBorder"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                >
                                <local:HighlightTextBlock 
                                    Margin="{TemplateBinding Padding}"
                                    FilterText="{Binding Path=Host.DataPresenter.(local:DataPresenterHelpers.FilterText), RelativeSource={RelativeSource TemplatedParent}}"
                                    RawText="{TemplateBinding DisplayText}" 
                                    TextWrapping="{TemplateBinding TextWrapping}" 
                                    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                    TextAlignment="{TemplateBinding TextAlignmentResolved}"
                                  />
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </igDP:XamDataGrid.Resources>
    </igDP:XamDataGrid>
</DockPanel>
Run Code Online (Sandbox Code Playgroud)