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

时间:2011-10-21 19:52:48

标签: wpf infragistics xamdatagrid

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

4 个答案:

答案 0 :(得分:9)

DataPresenter 中的记录过滤就是这样 - 根据某些指定条件过滤记录的方法。通常情况下,标准是通过其中一个内置的ui提供的 - 使用 LabelIcons ,它只是过滤值的下拉列表,或者通过 FilterRecord 来实现与每列的单元格一起显示的特殊记录,以允许选择/输入运算符和值。

据说如果你愿意,可以在代码中操作记录过滤。 FieldLayout 会公开 RecordFilters 集合,其中RecordFilter提供条件(即匹配条件)和应执行匹配的字段。它也暴露在 RecordManager 之外,但这实际上是为了便于在分层情况下进行过滤,其中过滤条件对于子记录的每个“孤岛”都是不同的。

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

因此,为此目的,以下是一个基本的附加行为(在本例中是一个名为 FilterText 的属性,您将在要过滤的DataPresenter上设置该属性)。考虑到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
}

然后,您可以执行以下操作,将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>

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

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

  • RawText - 这是将要显示的源文本。它不能使用Text属性,因为此元素将操作TextBlock的Inlines,并且将设置Text属性,该属性在单向绑定的情况下会破坏绑定,或者在双向绑定的情况下将值推回到源。 / LI>
  • 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
}

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

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

答案 1 :(得分:1)

我将解决此问题的方法是将代码添加到搜索按钮单击事件(或其他一些触发机制)并直接在代码中执行搜索,使用linq或记录比较记录,无论哪种方法最适合您的情况(有助于了解如何填充列和行)。

获得具有适当结果的记录和/或列集后,隐藏没有匹配的列。为了简化流程,我将创建一个应该隐藏的列集合,然后有一个处理每个列的方法。该方法将搜索列数据,并在第一个匹配的符号处,从要隐藏的列列表中删除该列,并停止处理列中的记录。

在流程结束时,您的列表将包含要隐藏的列列表,然后您可以使用这些列来执行实际隐藏。

这个机制也可以扩展为隐藏没有任何匹配的行,假设有多行。我会支持这一点,方法是在处理第一列时隐藏每个没有匹配的行,然后在发现匹配的后续列中取消隐藏它们。

答案 2 :(得分:1)

感谢您的分享。我也会分享。 我用这个例子来处理一个更简单的案例。 我只想在一列上设置过滤器。 这是:

Imports Infragistics.Windows.DataPresenter
Imports Infragistics.Windows.Controls

Public Class DataPresenterFilter

Public Shared Function GetTitleFilter(ByVal element As DependencyObject) As String
    If element Is Nothing Then
        Throw New ArgumentNullException("element")
    End If
    Return element.GetValue(TitleFilter)
End Function

Public Shared Sub SetTitleFilter(ByVal element As DependencyObject, 
        ByVal value As String)
    If element Is Nothing Then
        Throw New ArgumentNullException("element")
    End If
    element.SetValue(TitleFilter, value)
End Sub

Public Shared ReadOnly TitleFilter As  _
        DependencyProperty = DependencyProperty.RegisterAttached("TitleFilter", _
    GetType(String), GetType(DataPresenterFilter), _
    New FrameworkPropertyMetadata(String.Empty, 
    New PropertyChangedCallback(AddressOf OnTitleFilterChanged)))

Private Shared Sub OnTitleFilterChanged(d As DependencyObject, 
        e As DependencyPropertyChangedEventArgs)
    Dim dp As DataPresenterBase = CType(d, DataPresenterBase)

    If (Not dp.DefaultFieldLayout Is Nothing) Then
        Dim Filter As RecordFilter = New RecordFilter()
        Filter.FieldName = "Title"
        Filter.Conditions.Add(
            New ComparisonCondition(ComparisonOperator.Contains, e.NewValue))
        dp.DefaultFieldLayout.RecordFilters.Remove(
            dp.DefaultFieldLayout.RecordFilters.Item("Title"))
        dp.DefaultFieldLayout.RecordFilters.Add(Filter)
    End If
End Sub

End Class

和XAML:

xmlns:Inv="clr-namespace:InventoryApp"

<TextBox Height="23" Margin="0,2,0,2" x:Name="tbTitle" />

<igDP:XamDataPresenter Name="xamDataPresenterPublicationCollection" 
    DataSource="{Binding Source={StaticResource PublicationCollectionViewSource},
    UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="True"
    ActiveDataItem="{Binding Path=PublicationModel, Mode=OneWay}" 
    Inv:DataPresenterFilter.TitleFilter="{Binding ElementName=tbTitle, Path=Text}"/>

这对我来说很有效。

答案 3 :(得分:0)

我一直在寻找一个非常基本的解决方案,所以我将分享对我有用的东西。这是我的C#代码隐藏代码,用于将过滤器应用于列:

grdConnectionManagerEntries.FieldLayouts[0].RecordFilters.Clear();  // Clear any existing filters before applying this one.
grdConnectionManagerEntries.FieldLayouts[0].RecordFilters.Add(new RecordFilter(new Field("Name")));
grdConnectionManagerEntries.FieldLayouts[0].RecordFilters[0].Conditions.Add(new Infragistics.Windows.Controls.ComparisonCondition(Infragistics.Windows.Controls.ComparisonOperator.Contains, connectionName));

这里 grdConnectionManagerEntries 是我的XamDataGrid,我有一个名为 Name 的Field(即列),我正在添加一个 Contains 过滤器它将按给定的 connectionName 过滤。

由于某些原因,当我使用 Contains 比较运算符时,过滤器文本仅显示在XamDataGrid的UI中;我猜是因为那是该列的默认过滤器运算符。所以有些东西我做得不对,但是因为我想使用Contains运算符,所以它已经为我的目的而工作。