I am very first to this work. I want to make a data table which is filtered on its column name. Here is what I want:
I want to click TYPE and there are only "A", "B", "C". So I can see only selected type contents.
umm like this one. but only in the column name. the highest column.
I am using C#, .NET 6.0, WPF.
Can you show me some simple or complex codes by C# and WPF? I dont know much about it but keep studying.
THANKS
CodePudding user response:
You always filter a ItemsControl
like DataGrid
by assigning a filter expression to its associated ICollectionView.Filter
property.
For this purpose, you can retrieve the ICollectionView
of an ItemsControl
by accessing the ItemsControl.Items
property (in case you have direct access to the ItemsControl
instance.
Otherwise, you can always use the static helper method CollectionViewSource.GetDefaultView(mySourceCollection)
, which of course requires a direct reference to the source collection (for example in a data binding scenario).
Assuming you have a ComboBox
to select the filter predicate e.g. a string
value or enum
value (whatever the data type of the Type
property/column is) for the corresponding column and a DataGrid
that shows data by binding to a collection of data models of type MyRowItem
which has the properties Name
, Content
, Type
and Exceptions
, a very simple solution could be as follows:
Handle the ComboBox.SelectedItemChanged
event in your code-behind
private void OnComboBoxSelectedItemChanged(object sender, EventArgs e)
{
var comboBox = sender as ComboBox;
var selectedColumnValue = comboBox.SelectedItem as string;
// ICollectionView.Filter is of type 'Predicate<object>',
// so we can assign a simple lambda expression as filter predicate.
// The predicate must return 'true' if the tested item must be displayed.
this.DataGridView.Items.Filter = item => (item as MyRowItem).Type == selectedColumnValue;
}
<Window>
<StackPanel>
<DataGrid x:Name="DataGridView" />
<ComboBox SelectedItemChanged="OnComboBoxSelectedItemChanged" />
</StackPanel>
</Window>
It's important to understand the concept of collection views. The WPF binding engine will reference the collection view of a collection instead the collection itself: Collection views.
Even if you are assigning the source collection locally without any data binding to the ItemsControl.ItemsSource
, the ItemsControl
will internally register the collection with the binding engine in order to track changes.
For this reason, to improve performance you should always assign a collection that implements INotifyCollectionChanged
(like the ObservableCollection
for example) to the ItemsControl.ItemsSource
property.
CodePudding user response:
Example:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Core2022.SO.nojob
{
public enum TypeEnum
{ A, B, C }
public class RowEntity
{
public string Name { get; set; } = string.Empty;
public TypeEnum Type { get; set; }
}
public class TableVM
{
public ObservableCollection<RowEntity> Rows { get; } = new();
public TypeEnum? SelectedType { get; set; }
public static IReadOnlyList<TypeEnum> Types { get; } = Array.AsReadOnly((TypeEnum[])Enum.GetValues(typeof(TypeEnum)));
}
}
<Window x:Class="Core2022.SO.nojob.ColumnFilterWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Core2022.SO.nojob"
mc:Ignorable="d"
Title="ColumnFilterWindow" Height="450" Width="800"
DataContext="{DynamicResource vm}">
<Window.Resources>
<local:TableVM x:Key="vm">
<local:TableVM.Rows>
<local:RowEntity Name="first" Type="B"/>
<local:RowEntity Name="second" Type="A"/>
<local:RowEntity Name="third" Type="C"/>
<local:RowEntity Name="fourth" Type="B"/>
<local:RowEntity Name="fifth" Type="A"/>
<local:RowEntity Name="sixth" Type="C"/>
<local:RowEntity Name="seventh" Type="B"/>
<local:RowEntity Name="eighth" Type="A"/>
<local:RowEntity Name="ninth" Type="C"/>
<local:RowEntity Name="tenth" Type="B"/>
<local:RowEntity Name="eleventh" Type="A"/>
<local:RowEntity Name="twelfth" Type="C"/>
</local:TableVM.Rows>
</local:TableVM>
</Window.Resources>
<Grid>
<DataGrid x:Name="dGrid"
ItemsSource="{Binding Rows}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Name"/>
<DataGridComboBoxColumn SelectedItemBinding="{Binding Type}"
ItemsSource="{x:Static local:TableVM.Types}">
<DataGridComboBoxColumn.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="TYPE"/>
<ComboBox x:Name="cBox" ItemsSource="{x:Static local:TableVM.Types}"
SelectionChanged="OnTypeSelectionChanged"
SelectedItem="{Binding SelectedType, Source={StaticResource vm}}"/>
<Button Content="❌" Foreground="Red" FontWeight="ExtraBold"
Click="OnClearClick"/>
</StackPanel>
</DataGridComboBoxColumn.Header>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace Core2022.SO.nojob
{
public partial class ColumnFilterWindow : Window
{
public ColumnFilterWindow()
{
InitializeComponent();
}
private void OnTypeSelectionChanged(object sender, SelectionChangedEventArgs e)
{
Selector selector = (Selector)sender;
if (selector.SelectedItem is TypeEnum type)
{
dGrid.Items.Filter = item =>
{
return item is RowEntity rowEntity &&
rowEntity.Type == type;
};
}
else
{
dGrid.Items.Filter = null;
}
}
private void OnClearClick(object sender, RoutedEventArgs e)
{
cBox.SelectedIndex = -1;
}
}
}