Фильтровать сетку данных WPF (DataTable) на основе значений из нескольких текстовых полей MVVM

#c# #wpf #mvvm

#c# #wpf #mvvm

Вопрос:

Я пытался настроить фильтр, который был бы основан на 4 текстовых полях. Если первое текстовое поле не пустое -> затем фильтровать на основе этого, если первое и второе текстовые поля не пусты, объедините фильтр из этих двух текстовых полей и т.д. Я ожидаю, что мой фильтр будет работать следующим образом http://www.tablefilter.com/auto-filter.html

Как вы можете видеть, я пробовал несколько вариантов, но я постоянно получаю сообщение об ошибке, приведенное ниже. Есть предложения, как заставить его работать?

Вот мой код:

     public void EnableRowFiltering()
    {
        StringBuilder sb = new StringBuilder();

        if (this.YRNROSearchKey != string.Empty)
        {
            sb.Append($"YRNRO LIKE '%{this.YRNROSearchKey}%' AND ");
        }
        if (this.HAKUNIMISearchKey != string.Empty)
        {
            sb.Append($"HAKUNIMI LIKE '%{this.HAKUNIMISearchKey}%' AND ");
        }
        if (this.GROUPSearchKey != string.Empty)
        {
            sb.Append($"KONSERNI LIKE '%{this.GROUPSearchKey}%' AND ");
        }
        if (this.BUSINESSIDSearchKey != string.Empty)
        {
            sb.Append($"LY LIKE '%{this.BUSINESSIDSearchKey}%' AND ");
        }

        // I have tried also this way without success 
        // this.MainDataTable.DefaultView.RowFilter = YRNRO   HAKUNIMI   GROUP   BUSINESSID;
        string YRNRO = string.IsNullOrEmpty(this.YRNROSearchKey) ? "" : $"YRNRO LIKE '{this.YRNROSearchKey}*'";
        string HAKUNIMI = string.IsNullOrEmpty(this.HAKUNIMISearchKey) ? "" : $" AND HAKUNIMI LIKE '{this.HAKUNIMISearchKey}*'";
        string GROUP = string.IsNullOrEmpty(this.GROUPSearchKey) ? "" : $" AND KONSERNI LIKE '{this.GROUPSearchKey}*'";
        string BUSINESSID = string.IsNullOrEmpty(this.BUSINESSIDSearchKey) ? "" : $" AND LY LIKE '{this.BUSINESSIDSearchKey}*'";

        this.MainDataTable.DefaultView.RowFilter = sb.ToString();
    }
  

System.Data.SyntaxErrorException: ‘Синтаксическая ошибка: отсутствует операнд после
‘И’ оператор.’

Это работает, но я должен предоставить все значения (заполнить все текстовые поля) для фильтрации:

   public void EnableRowFiltering()
  {
    this.MainDataTable.DefaultView.RowFilter = 
      $"YRNRO LIKE '{this.YRNROSearchKey}*' "   
      $"OR HAKUNIMI LIKE '{this.HAKUNIMISearchKey}*'"  
      $"OR KONSERNI LIKE '{this.GROUPSearchKey}*'"  
      $"OR LY LIKE '{this.BUSINESSIDSearchKey}*'";
  }
  

Ответ №1:

Последнее решение, безусловно, самое короткое из всех, поэтому я бы предпочел его.
Вам просто нужно заменить ИЛИ на И (моя ошибка).

 public void EnableRowFiltering()
{
  this.MainDataTable.DefaultView.RowFilter = 
    $"YRNRO LIKE '{this.YRNROSearchKey}*'"   
    $"AND HAKUNIMI LIKE '{this.HAKUNIMISearchKey}*'"  
    $"AND KONSERNI LIKE '{this.GROUPSearchKey}*'"  
    $"AND LY LIKE '{this.BUSINESSIDSearchKey}*'";
}
  

Строка выражения первого решения имеет завершающий AND оператор, который приводит к сообщению об ошибке. Добавление префикса к каждому выражению (операнду) с помощью оператора исправит это.

Обратите внимание, что начало % выражения должно быть удалено, чтобы сделать его выражением «начинается с». Наличие начального % и конечного % символов (оператор подстановки) означало бы «содержится в».

Поскольку и пустая строковая переменная приведет к выражению типа "Column LIKE '%' и поскольку ${null} возвращает "" (пустую строку), вы можете сократить код:

 public void EnableRowFiltering()
{
  StringBuilder sb = new StringBuilder();
  sb.Append($"YRNRO LIKE '{this.YRNROSearchKey}%'");
  sb.Append($"AND KONSERNI LIKE '{this.GROUPSearchKey}%'");
  sb.Append($"AND LY LIKE '{this.BUSINESSIDSearchKey}%'");

  this.MainDataTable.DefaultView.RowFilter = sb.ToString();
}
  

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

Поскольку и пустая строковая переменная приведет к выражению типа "Column LIKE '*' и поскольку ${null} возвращает "" (пустую строку), вы можете сократить код:

 public void EnableRowFiltering()
{ 
  string YRNRO = $"YRNRO LIKE '{this.YRNROSearchKey}*'";
  string HAKUNIMI = $" AND HAKUNIMI LIKE '{this.HAKUNIMISearchKey}*'";
  string GROUP = $" AND KONSERNI LIKE '{this.GROUPSearchKey}*'";
  string BUSINESSID = $" AND LY LIKE '{this.BUSINESSIDSearchKey}*'";

  this.MainDataTable.DefaultView.RowFilter = YRNRO   HAKUNIMI   GROUP   BUSINESSID;
}
  

После исправления и улучшения всех трех решений все они были сведены в основном к одному и тому же решению.

Комментарии:

1. Еще раз спасибо! Первое решение выглядит аккуратно, но оно потерпит неудачу, если одно из текстовых полей будет пустым? С оператором AND ни одно из предоставленных решений не работает. Однако, если я заменю все И на ИЛИ в вашем первом решении, фильтр сработает, но в этом случае все текстовые поля не должны быть пустыми. Поэтому я должен ввести некоторые данные для всех них.

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

3. Имеют ли текстовые поля какое-либо начальное значение? Не могли бы вы, пожалуйста, показать привязки из текстового поля для просмотра модели?

4. Рассмотрим оба логических оператора и полученные ими результаты: И будет действовать как многоступенчатое или многоуровневое сито. Первое выражение выдаст результат. Второе выражение выдаст результат, основанный на предыдущем наборе результатов. При использовании OR каждый фильтр работает как несколько параллельных сит для исходного набора данных. Первое выражение выдает результат. Второе выражение добавляет свой результат на основе исходного набора к набору результатов первого выражения. Из вашего примера похоже, что вы хотели использовать сложенное сито (логическое И).

5. Честно говоря, я бы не рекомендовал Rx в этом сценарии. Просто настройте привязку данных и установите Binding.UpdateSourceTrigger значение PropertyChanged . По умолчанию для текстового поля установлено значение LostFocus .

Ответ №2:

Вы должны обработать AND оператор. Вы не можете добавить его в любое время, это зависит от того, сколько условий (фильтров) установлено.

Предположим, что для определения переменной с именем int counter установлено значение, равное нулю во время запуска, и увеличивается каждый раз, когда устанавливается фильтр, и уменьшается при каждой очистке фильтра, ваш код должен быть изменен на это:

 public void EnableRowFiltering()
    {
        StringBuilder sb = new StringBuilder();
        var logicalOperator = counter > 0 ? " AND " : string.Empty;

        if (YRNROSearchKey != string.Empty)
        {
            logicalOperator = counter   > 0 ? " AND " : string.Empty;
            sb.Append($"{logicalOperator}YRNRO LIKE '%{YRNROSearchKey}%'");
        }
        if (HAKUNIMISearchKey != string.Empty)
        {
            logicalOperator = counter   > 0 ? " AND " : string.Empty;
            sb.Append($"{logicalOperator}HAKUNIMI LIKE '%{HAKUNIMISearchKey}%'");
        }
        if (GROUPSearchKey != string.Empty)
        {
            logicalOperator = counter   > 0 ? " AND " : string.Empty;
            sb.Append($"{logicalOperator}KONSERNI LIKE '%{GROUPSearchKey}%'");
        }
        if (BUSINESSIDSearchKey != string.Empty)
        {
            logicalOperator = counter   > 0 ? " AND " : string.Empty;
            sb.Append($"{logicalOperator}LY LIKE '%{BUSINESSIDSearchKey}%'");
        }

        this.MainDataTable.DefaultView.RowFilter = sb.ToString();
    }
  

Ответ №3:

Я бы выбрал ReactiveExtensions (особенно из RxUI):

 // written without IDE
var text1 = this.WhenAnyValue(x => x.YRNROSearchKey).Select(x => {
       if(string.IsNullOrEmpty(x))
          return null;
       return $"YRNRO LIKE '%{x}%";
     }); // get observable to monitor changes
var text2 = ...

var filterObservable = Observable.CombineLatest(
                new []{text1, text2, text3} , 
                    (textParts) => {
                         return string.Join(" AND ",  textParts.Where(x => !string.IsNullOrEmpty(x)));
                    }
             )
            .Throttle(TimeSpan.FromMilliseconds(80));

filterObservable.ObserveOnDispatcher().Subscribe(f => this.MainDataTable.DefaultView.RowFilter = f); 
  

Мне действительно нравится Rx, поэтому я бы сделал это именно так — возможно, вы можете использовать в качестве шаблона и решить уведомление об изменении каким-либо другим способом, но построение фильтра должно работать.

RxUI также поставляется с DyanmicData — отличной библиотекой для манипулирования данными, это в основном наблюдаемый LINQ

Комментарии:

1. Довольно много для объединения 4 строк.

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

3. @KrzysztofSkowronek Что такое WhenAnyValue — не компилируется? В конце не хватает одной скобки .Throttle(TimeSpan.FromMilliseconds(80)))

4. Также они могут быть добавлены для того, чтобы кому-то было интересно то же самое using System.Reactive.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks;

5. this.WhenAnyValue является ли метод расширения из RxUI для реализации объектов, INotifyPropertyChanged где this в этом случае является моделью представления