Как реализовать поиск, который вызывает фильтрацию записей по нескольким столбцам в XamDataGrid? - PullRequest
3 голосов
/ 21 октября 2011

Я пытаюсь реализовать функцию поиска (Ctrl + F) в XamDataGrid. Как программно вызвать фильтрацию записей в сетке, которая выполняет поиск по контенту в нескольких столбцах и отображает только столбцы, которые соответствуют запросу?

Ответы [ 4 ]

9 голосов
/ 31 октября 2011

Фильтрация записей в DataPresenter - это всего лишь средство фильтрации записей на основе определенных критериев. Обычно этот критерий предоставляется с помощью одного из встроенных пользовательских интерфейсов - либо с помощью LabelIcons , который представляет собой просто раскрывающийся список отфильтрованных значений, либо с помощью FilterRecord , который представляет собой специальную специальную запись. это отображается с ячейками для каждого столбца, чтобы позволить выбрать / ввести оператор и значение.

При этом фильтрацией записей можно манипулировать в коде, если хотите. FieldLayout предоставляет коллекцию RecordFilters , где RecordFilter предоставляет условия (то есть критерии соответствия) и поле, для которого должно быть выполнено сравнение. Он также отображается в RecordManager , но это действительно облегчает фильтрацию в иерархических ситуациях, когда критерии фильтрации различны для каждого «острова» дочерних записей.

Поскольку вы хотите искать несколько полей по одним и тем же критериям, вам нужно будет создать RecordFilter для каждого Поле в коллекции Поля FieldLayout (или любое подмножество полей, к которому вы хотите применить это). Поскольку вы хотите выполнить текстовый поиск, вы, вероятно, захотите использовать ComparisonCondition, где используемый вами ComparisonOperator - Contains, а значением будет текст для поиска. Теперь, так как вы хотите, чтобы запись соответствовала, если значение найдено в любом из полей (для которых вы создали RecordFilter), вам также нужно будет установить свойство FieldLayoutSettings * RecordFiltersLogicalOperator на Или (по умолчанию это разрешается на А, поскольку обычно требуется сопоставить запись, когда все критерии соответствуют введенным значениям).

Таким образом, ниже приведено базовое присоединенное поведение (в данном случае свойство с именем FilterText , которое вы бы указали в DataPresenter для фильтрации). Это поведение / свойство будет манипулировать RecordFilters элемента DefaultFieldLayout с учетом текстового значения свойства FilterText.

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 к этому присоединенному свойству. Обратите внимание, что вам нужно будет определить отображение xmlns для local как пространства имен 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>

Теперь другая часть вашего вопроса касалась только отображения столбцов, которые содержали совпадающие значения. Это на самом деле невозможно, поскольку часть самой записи фильтруется, поскольку фильтрация записей заключается в фильтрации записей на основе определенных критериев и не имеет отношения к скрытию / отображению столбцов / полей. При этом, возможно, одним из способов помочь конечному пользователю понять, где находится соответствующий текст, было бы выделить этот текст в ячейках.

Чтобы обеспечить такую ​​функциональность, я определил производный TextBlock с именем HighlightTextBlock . Он предоставляет несколько свойств:

  • RawText - это исходный текст, который будет отображаться. Он не может использовать свойство Text, потому что этот элемент будет манипулировать Inlines of 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>

1 голос
/ 23 декабря 2011

Спасибо, что поделились.Я тоже поделюсьЯ использовал этот пример для обработки более простого случая.Я просто хотел установить фильтр для одного столбца.Вот оно:

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}"/>

Это прекрасно работает для меня.

1 голос
/ 31 октября 2011

Я бы решил эту проблему, добавив код к событию нажатия кнопки поиска (или к какому-либо другому механизму триггера) и выполнив поиск непосредственно в коде, используя linq или сравнение записей по записям, что лучше всего работает в вашемситуация (это помогло бы узнать, как заполняются столбцы и строки).

Как только у вас будет набор записей и / или столбцов, которые имеют соответствующие результаты, спрячьте столбцы, в которых нет попаданий.Чтобы упростить процесс, я хотел бы создать коллекцию столбцов, которые должны быть скрыты, а затем иметь метод, который обрабатывает каждый столбец.Метод будет выполнять поиск в данных столбца и по знаку первого совпадения удалит столбец из списка столбцов, которые будут скрыты, и прекратит обработку записей в столбце.

В конце процесса,ваш список будет содержать список скрываемых столбцов, который вы затем сможете использовать для фактического скрытия.

Этот механизм также может быть расширен для скрытия строк, которые не имеют совпадений, при условии, что их несколькострок.Я бы поддержал это, скрыв каждую из строк, у которых нет совпадений, при обработке первого столбца, а затем скрыл их для последующих столбцов, в которых было найдено совпадение.

0 голосов
/ 26 июля 2013

Я искал очень простое решение, поэтому я поделюсь тем, что сработало для меня.Вот мой код кода 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, у меня есть поле (то есть столбец) с именем Имя, и я добавляю к нему фильтр Contains , который будет фильтровать по указанному имя_соединения .

По какой-то причине текст фильтра отображается только впользовательский интерфейс XamDataGrid при использовании оператора сравнения Contains ;Я предполагаю, потому что это оператор фильтра по умолчанию в этом столбце.Так что есть кое-что, что я делаю не совсем правильно, но так как я все равно хотел использовать оператор Contains, это сработало для моих целей.

...