#c# #wpf #xaml
#c# #wpf #xaml
Вопрос:
Я создал инструмент для базы данных, который показывает элементы с определенными параметрами фильтрации. Однако, прочитав еще немного о WPF и C #. Я использовал это https://www.codeproject.com/Articles/683429/Guide-to-WPF-DataGrid-formatting-using-bindings руководство по изменению моего приложения для решения ItemCollectionViewSource
Я использовал это раньше для фильтрации:
public void FilterSetup()
{
try
{
string qry = null;
_conditions["name"] = null;
if (!string.IsNullOrEmpty(BusinessIDSearch.Text))
{
qry = string.Format("LY Like '{0}%'", BusinessIDSearch.Text);
}
if (!string.IsNullOrEmpty(NameSearch.Text))
{
if (!string.IsNullOrEmpty(qry))
qry = " AND ";
qry = string.Format("HAKUNIMI Like '%{0}%'", NameSearch.Text);
}
if (!string.IsNullOrEmpty(GroupSearch.Text))
{
if (!string.IsNullOrEmpty(qry))
qry = " AND ";
qry = string.Format("KONSERNI Like '{0}%'", GroupSearch.Text);
}
if (!string.IsNullOrEmpty(LiinosIDSearch.Text))
{
if (!string.IsNullOrEmpty(qry))
qry = " AND ";
qry = string.Format("YRNRO Like '{0}%'", LiinosIDSearch.Text);
}
_conditions["name"] = qry;
UpdateFilter();
//LiinosFICount.Content = DataGrid1.Items.Count;
}
catch (Exception)
{
throw;
}
}
Затем это:
private void UpdateFilter()
{
try
{
var activeConditions = _conditions.Where(c => c.Value != null).Select(c => "(" c.Value ")");
DataView dv = DataGrid1.ItemsSource as DataView;
dv.RowFilter = string.Join(" AND ", activeConditions);
}
catch (Exception)
{
//MessageBox.Show(ex.Message);
}
}
Однако, похоже, теперь мне нужен другой подход ItemCollectionViewSource
.
Вот часть XAML для текстового поля:
<TextBox Style="{StaticResource TextBox_Style}" x:Name="GroupSearch" HorizontalAlignment="Left" Margin="414,121,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="90" Height="20" TextChanged="GroupSearch_TextChanged"/>
<TextBox Style="{StaticResource TextBox_Style}" x:Name="NameSearch" HorizontalAlignment="Left" Margin="130,121,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="279" Height="20" TextChanged="NameSearch_TextChanged"/>
<TextBox Style="{StaticResource TextBox_Style}" Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}" x:Name="LiinosIDSearch" HorizontalAlignment="Left" Margin="20,121,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="105" Height="20"/>
<TextBox Style="{StaticResource TextBox_Style}" x:Name="BusinessIDSearch" HorizontalAlignment="Left" Margin="509,121,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="192" Height="20" TextChanged="BusinessIDSearch_TextChanged"/>
Вот XAML для DataGrid:
<DataGrid Margin="0,146,0,0" Background="{x:Null}" BorderBrush="{x:Null}"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False" IsReadOnly="True"
HorizontalGridLinesBrush="#FF377A6C" VerticalGridLinesBrush="#FF377A6C"
DataContext="{StaticResource ItemCollectionViewSource}"
ItemsSource="{Binding}"
AutoGenerateColumns="False" FontFamily="Arial Nova" Foreground="White" >
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" >
<Setter Property="Background" Value="{Binding YRNRO, Converter={StaticResource LiinosIDToBackgroundConverter}}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Resources>
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="#377A6C" />
<Setter Property="Foreground" Value="White" />
<Setter Property="MinHeight" Value="20" />
</Style>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="#5AC37E"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding YRNRO}">
<DataGridTextColumn.Header>
<TextBlock Text="LIINOS ID" FontWeight="Bold" TextAlignment="Left"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding HAKUNIMI}">
<DataGridTextColumn.Header>
<TextBlock Text="SEARCH NAME" FontWeight="Bold" TextAlignment="Left"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding KONSERNI}">
<DataGridTextColumn.Header>
<TextBlock Text="GROUP" FontWeight="Bold" TextAlignment="Left"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding LY}">
<DataGridTextColumn.Header>
<TextBlock Text="BUSINESS ID" FontWeight="Bold" TextAlignment="Left"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
I have tried several tutorials without success. Does anybody have any kind of tutorial to suggest or maybe provide an answer with solution I can implement to other TextBoxes as well? The tricky part that I have several TextBoxes and I need to combine filter query based on values in TextBoxes.
As you can see I have tried to apply Binding here:
<TextBox Style="{StaticResource TextBox_Style}" Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}" x:Name="LiinosIDSearch" HorizontalAlignment="Left" Margin="20,121,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="105" Height="20"/>
I have got this from some tutorial and tried to modify it to my needs without success:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows.Data;
namespace Liinos_inspector_FilterTest
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _filter1 = "";
private string _filter2 = "";
private string _filter3 = "";
public ViewModel()
{
var List = MainProcess.CustomersInLiinos.AsEnumerable()
.GroupBy(x => x.Field<string>("HAKUNIMI"))
.Where(x => x.Count() > 1)
.SelectMany(x => x)
.ToList();
//ItemList = new ObservableCollection<Items>(List);
ItemView = (CollectionView)CollectionViewSource.GetDefaultView(List);
ItemView.Filter = TextFilter;
}
private bool TextFilter(object obj)
{
var data = obj as Items;
if (data != null)
{
return data.Text1.StartsWith(_filter1) amp;amp; data.Text2.StartsWith(_filter2) amp;amp; data.Text3.StartsWith(_filter3);
}
return false;
}
private void NotifyPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public ObservableCollection<Items> ItemList { get; set; }
public CollectionView ItemView { get; set; }
public string Filter1
{
get { return _filter1; }
set
{
_filter1 = value;
NotifyPropertyChanged("Filter1");
ItemView.Refresh();
}
}
public string Filter2
{
get { return _filter2; }
set
{
_filter2 = value;
NotifyPropertyChanged("Filter2");
ItemView.Refresh();
}
}
public string Filter3
{
get { return _filter3; }
set
{
_filter3 = value;
NotifyPropertyChanged("Filter3");
ItemView.Refresh();
}
}
}
public class Items
{
public string Text1 { get; set; }
public string Text2 { get; set; }
public string Text3 { get; set; }
}
}
Редактировать:
В настоящее время я загружаю данные при нажатии кнопки в DataGrid:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
if (MainProcess.CheckForVPNInterface() == true)
{
if (MainProcess.Customers != null)
{
ProgressBar.IsIndeterminate = true;
CollectionViewSource itemCollectionViewSource;
itemCollectionViewSource = (CollectionViewSource)(FindResource("ItemCollectionViewSource"));
itemCollectionViewSource.Source = await LoadMainTableDataAsync();
ProgressBar.IsIndeterminate = false;
}
}
else
{
string caption = "VPN connection missing";
MessageBox.Show("Please, check your VPN connection!", caption,
MessageBoxButton.OK,
MessageBoxImage.Exclamation);
}
}
РЕДАКТИРОВАТЬ 2:
Вот LoadMainTableDataAsync
метод, который я использовал:
public Task<DataView> LoadMainTableDataAsync()
{
return Task.Run(() =>
{
MainProcess.MergedTable();
return MainProcess.Customers.DefaultView;
});
}
Здесь выполняется объединение таблиц данных:
public static DataTable Customers = new DataTable();
public static void MergedTable()
{
var t1 = ConnectAndRetriveDatatatableS(); // t1
var t2 = ConnectAndRetriveDatatatableF(); // t2
Customers = t1.Copy();
Customers.Merge(t2);
}
Комментарии:
1. Я думаю, вам нужно предоставить более подробную информацию или нужно больше подумать об этом. Если вы хотите запросить способ реализации фильтра, вам потребуется дополнительная информация. Фильтрация может быть довольно сложной. Вам нужна концепция: например, фиксированный ключ, предоставляемый выпадающим списком или пользовательскими поисковыми запросами пользователя. Какие правила есть у этого запроса (языка)? Соответствует ли каждое текстовое поле атрибуту элемента данных? И тогда каковы правила фильтрации: существуют ли правила приоритета в отношении текстовых полей?
2. Как объединяются входные данные фильтра (логически), должны ли быть найдены все ключи, чтобы вернуть действительный результат, или каждый ключ обрабатывается индивидуально? Если вы пытаетесь исправить текущий фильтр, тогда нужно знать, что именно не так или каковы текущие результаты фильтрации по сравнению с ожидаемыми результатами. В настоящее время ВСЕ предикаты должны быть истинными, чтобы фильтр прошел успешно. Неясно, чего вы ожидаете и каков текущий результат.
3. @BionicCode Я ожидаю, что он будет работать так, как он работал раньше с
FilterSetup
в моем вопросе. Поэтому, если конкретное текстовое поле не пустое, добавьте его значение в запрос. Итак, если 1 текстовое поле не пустое, используйте его значение, затем добавьте второе, третье и четвертое, используя ту же логику. Нет необходимости проверять все «не пусто», но строить запрос на основе значений текстового поля.4. @BionicCode если вы имеете в виду, что должна быть какая-то проверка для данных, введенных в TexBox, тогда нет необходимости проверять. Все значения text = string.
5. Нет, я спрашивал о приоритете различных значений текстового поля. Если все значения должны совпадать, чтобы отфильтровать элемент, или если достаточно одного совпадения. В любом случае, я привел базовый пример. Вам просто нужно настроить фильтр в соответствии с вашими требованиями.
Ответ №1:
Вам не нужен CollectionViewSource
для фильтрации коллекции. Просто привяжитесь к коллекции вашей модели представления. В WPF коллекции используются через ICollectionsView
под капотом. Каждая коллекция возвращает представление по умолчанию. Элементы управления работают с этим представлением автоматически. Чтобы изменить порядок, группировку или фильтрацию, вы изменяете текущее представление коллекции вместо фактического экземпляра коллекции. Также не устанавливайте DataContext
явно для CollectionViewSource
. Привязать непосредственно к нему.
В качестве примечания, поскольку вы привязываетесь к элементам модели, Items
следует реализовать INotifyPropertyChanged
, иначе вы спровоцируете утечки памяти.
Не обрабатывайте ProgressBar
в своей модели представления. Установите bool
свойство и привяжите его к ProgressBar.Visibility
.
Не показывайте MessageBox
или любой пользовательский диалог из модели представления, чтобы обработать исключение. Скорее создайте значение полного пользовательского исключения или исключения оболочки (с исходными исключениями в качестве внутреннего исключения) и обработайте его в своем представлении, например, показав диалоговое окно взаимодействия.
Не обрабатывайте доступ к базе данных или любым другим данным (модели) в вашем представлении. Скорее реализуйте ICommand
и назначьте его кнопке, чтобы заменить обработчик событий и выполнить операции в вашей модели представления. См. Документы Microsoft: Логика передачи команд для простой реализации RelayCommand
.
Это простой пример того, как фильтровать коллекцию, используя значение коллекции по умолчанию ICollectionView
, включая предлагаемые улучшения. Вам необходимо настроить фактическую логику фильтра в соответствии с вашими требованиями:
VpnInterfaceException.cs
class VpnInterfaceException : Exception
{
public VpnInterfaceException(string message) : base(message)
{
}
public VpnInterfaceException(string message, Exception innerException) : base(message, innerException)
{
}
}
Item.cs
class Item : INotifyPropertyChanged
{
private string text;
public string Text
{
get => this.text;
set
{
this.text = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Item> Items { get; set; }
public ICommand LoadMainTableDataCommand => new RelayCommand(ExecuteLoadMainTableDataAsync);
// Binding source for the TextBox
private string searchKey;
public string SearchKey
{
get => this.searchKey;
set
{
this.searchKey = value;
OnPropertyChanged();
// Refresh the ICollectionView to update the filter expression
CollectionViewSource.GetDefaultView(this.Items).Refresh();
}
}
private bool hasProgress;
public bool HasProgress
{
get => this.hasProgress;
set
{
this.hasProgress = value;
OnPropertyChanged();
}
}
public ViewModel()
{
this.Items = new ObservableCollection<Item>();
EnableItemsFiltering();
}
public void EnableItemsFiltering()
{
// Assign the filter expression which is executed when items are added
// or the 'ICollectionView.Refresh()' was called
CollectionViewSource.GetDefaultView(this.Items).Filter = FilterPredicate;
}
// The filter expression.
// Returns 'true' to include the current item.
private bool FilterPredicate(object item)
=> string.IsNullOrWhiteSpace(this.SearchKey)
|| ((item as Item)?.Text.StartsWith(this.SearchKey, StringComparison.OrdinalIgnoreCase) ?? false);
private async Task ExecuteLoadMainTableDataAsync(object commandParameter)
{
if (MainProcess.CheckForVPNInterface())
{
if (MainProcess.Customers != null)
{
this.HasProgress = true;
IEnumerable<Item> resultItems = await LoadMainTableDataAsync();
this.Items = new ObservableCollection<Item>(resultItems);
EnableItemsFiltering();
this.HasProgress = false;
}
}
else
{
// Throw an exception since the operation cannot be completed unexpectedly.
// The view can catch this exception to execute the error handling.
throw new VpnInterfaceException("Please, check your VPN connection!");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
MainWindow.xaml.cs
partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Handle specific view model exceptions for user interaction
this.Dispatcher.UnhandledException = (sender, args) =>
{
// Only handle selected exceptions, that can be handled through user interaction
if (args.Exception is VpnInterfaceException exception)
{
args.Handled = true;
string caption = "VPN connection missing";
// Convert exception message to user friendly message
MessageBox.Show(exception.Message,
caption,
MessageBoxButton.OK,
MessageBoxImage.Exclamation);
}
};
}
}
Главное окно.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<StackPanel>
<ProgressBar IsIndeterminate="True"
Visibility="{Binding HasProgress, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button Command="{Binding LoadMainTableDataCommand}"
Content="Load Data" />
<TextBox Text="{Binding SearchKey}" />
<DataGrid ItemsSource="{Binding Items}" />
<StackPanel>
</Window>
Комментарии:
1. Спасибо вам за это! Я надеюсь, что на этот раз у меня все получится, это было моей мечтой на это лето =)). Можете ли вы объяснить, каким
OnPropertyChanged
должен быть метод? Также я добавил больше информации о том, как я загружаю данные в DataGrid в настоящее время. Должен ли я использовать ` itemCollectionViewSource. Источник = ожидает загрузки maintabledataasync();` теперь внутри ViewModel?2. Спасибо. Пожалуйста, прочитайте мой полный ответ еще раз. Я решил некоторые проблемы с вашим кодом. Я покажу вам решение, как правильно обрабатывать ошибки модели представления, которые требуют взаимодействия с пользователем. Также как правильно запускать
ProgressBar
из viewmodel. Как общее правило: никогда не выполняйте логику, связанную с представлением, в вашей модели представления. Скорее запустите его с помощью событий, привязки данных или путем создания исключения (если оно адекватно). Я также показал, как инициализировать исходную коллекциюDataGrid
и добавил недостающееOnPropertyChanged
. Представление также обновляется, чтобы показать запускProgressBar
3. Я получаю пару ошибок. Я думаю, это
public string HasProgress
должно бытьpublic bool HasProgress
? В противном случае возникает ошибка о преобразованиях из bools в strings наоборот4. Также
public ObservableCollection<Item> Items { get; }
должно бытьpublic ObservableCollection<Item> Items { get; set; }
, иначе я получаю сообщение об ошибке только для чтения5. Это то, что я пытаюсь решить сейчас
IEnumerable<Item> resultItems = await LoadMainTableDataAsync();
Не удается неявно преобразовать тип ‘System.Data.DataView’ в ‘System. Коллекции. Общий. IEnumerable<Liinos_inspector_FilterTest.Item>’. Существует явное преобразование (вам не хватает приведения?)