3

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

4

4 回答 4

9

DataPresenter中的记录过滤就是这样 - 一种基于某些指定条件过滤记录的方法。通常,该标准是通过内置 ui 之一提供的 - 要么使用LabelIcons,它只是过滤值的下拉列表,要么通过FilterRecord,它是一个专用的特殊记录,显示每列的单元格以允许选择/输入运算符和值。

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

由于您想在多个字段中搜索相同的条件,因此您需要为FieldLayout 的Fields集合中的每个字段(或您希望应用它的任何字段子集)创建一个RecordFilter 。由于您想要进行文本搜索,因此您可能希望使用 ComparisonCondition,其中您使用的 ComparisonOperator 是 Contains,值是要搜索的文本。现在,如果在任何字段(已为其创建 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
}

然后,您可以执行以下操作以将 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 的内联,并且将设置 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
}

因此,您可以更改您正在使用的编辑器的模板,以便在他们的渲染模板中使用它。例如

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

于 2011-10-31T16:31:40.223 回答
1

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

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

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

假设有多行,该机制也可以扩展为隐藏没有任何匹配的行。我会通过在处理第一列时隐藏每一行不匹配的行来支持这一点,然后为找到匹配的后续列取消隐藏它们。

于 2011-10-31T01:24:41.950 回答
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}"/>

这对我来说非常有效。

于 2011-12-23T18:33:48.270 回答
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的字段(即列) ,我正在向它添加一个Contains过滤器,它将按给定的connectionName过滤。

出于某种原因,当我使用包含比较运算符时,过滤器文本仅显示在 XamDataGrid 的 UI 中;我猜是因为那是该列上的默认过滤器运算符。所以有些事情我做的不太对,但是因为我想使用 Contains 运算符,所以它已经为我的目的工作了。

于 2013-07-25T22:57:09.243 回答