I have a WPF DataGrid
with 18 columns and each column has a TextBox
above it so I can filter the column.
Each TextBox
binds Width
to ActualWidth
of the column.
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBox Width="{Binding Path=ActualWidth, ElementName=Column1}"
Text="{Binding FilterFirstName}"/>
<TextBox Width="{Binding Path=ActualWidth, ElementName=Column2}"
Text="{Binding FilterLastName}"/>
<TextBox Width="{Binding Path=ActualWidth, ElementName=Column3}"
Text="{Binding FilterAge}"/>
<!-- 15 more -->
</StackPanel>
<DataGrid x:Name="dataGridUsers" Grid.Row="1"
ItemsSource="{Binding Users}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="Column1" Width="*"
Binding="{Binding FirstName}"/>
<DataGridTextColumn x:Name="Column2" Width="*"
Binding="{Binding LastName}"/>
<DataGridTextColumn x:Name="Column3" Width="*"
Binding="{Binding Age}"/>
<!-- 15 more -->
</DataGrid.Columns>
</DataGrid>
I know that I can bind TextBox
Text
to a List<string>
like this:
<TextBox Width="{Binding Path=ActualWidth, ElementName=Column1}"
Text="{Binding Filters[0]}"/>
<TextBox Width="{Binding Path=ActualWidth, ElementName=Column2}"
Text="{Binding Filters[1]}"/>
<TextBox Width="{Binding Path=ActualWidth, ElementName=Column3}"
Text="{Binding Filters[2]}"/>
I would like to bind Width
of the TextBox
to ActualWidth
of the column with something like this:
<TextBox Width="{Binding Path=ActualWidth, Source=dataGridUsers.Columns[0]}"
Text="{Binding Filters[0]}"/>
<TextBox Width="{Binding Path=ActualWidth, Source=dataGridUsers.Columns[1]}"
Text="{Binding Filters[1]}"/>
<TextBox Width="{Binding Path=ActualWidth, Source=dataGridUsers.Columns[2]}"
Text="{Binding Filters[2]}"/>
Because then I could use ItemsControl
instead of StackPanel
but it doesn't work this way.
Is there any other way I could achieve this?
How to use ItemsControl
with TextBox
to filter DataGrid columns.
It is possible to use ItemsControl
and bind to DataGrid.Columns
like this:
<ItemsControl Grid.Row="0" ItemsSource="{Binding Path=Columns,
ElementName=dataGrid}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Width="{Binding ActualWidth}">
<TextBox.Resources>
<local:ListIndexToValueConverter
x:Key="listIndexToValueConverter"/>
</TextBox.Resources>
<TextBox.Text>
<MultiBinding Converter="{StaticResource
listIndexToValueConverter}" UpdateSourceTrigger="PropertyChanged">
<Binding Path="DataContext.Filters"
ElementName="userControl"/>
<Binding Path="DisplayIndex"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<DataGrid x:Name="dataGrid" Grid.Row="1" ItemsSource="{Binding Users}">
<DataGrid.Columns>
<DataGridTextColumn DisplayIndex="0" Width="*" Binding="{Binding FirstName}"/>
<DataGridTextColumn DisplayIndex="1" Width="*" Binding="{Binding LastName}"/>
<DataGridTextColumn DisplayIndex="2" Width="*" Binding="{Binding Age}"/>
</DataGrid.Columns>
</DataGrid>
Because you set the ItemsControl.ItemsSource
to DataGrid.Columns
instead of DataContext.Filters
you have to set DataGridColumn.DisplayIndex
and use IMultiValueConverter
to be able to access DataContext.Filters
again:
public class ListIndexToValueConverter : IMultiValueConverter
{
private IList _list;
private int _index;
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
if (values.Length < 2)
return Binding.DoNothing;
if (values[0] is IList && values[1] is int)
{
_list = (IList)values[0];
_index = (int)values[1];
return _list[_index];
}
return Binding.DoNothing;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
_list[_index] = value;
return new object[] { Binding.DoNothing, Binding.DoNothing };
}
}
The ViewModel
:
public class UsersViewModel : BindableBase
{
public ObservableCollection<User> Users { get; set; }
private ICollectionView _usersView;
public ObservableCollection<string> Filters { get; set; }
public UsersViewModel()
{
_usersView = CollectionViewSource.GetDefaultView(Users);
_usersView.Filter = delegate (object item)
{
User user = item as User;
List<string> columns = new List<string>()
{ user.FirstName, user.LastName, user.Age };
bool include = true;
for (int i = 0; i < columns.Count; ++i)
{
if (!string.IsNullOrEmpty(Filters[i]) &&
columns[i].IndexOf(Filters[i],
StringComparison.OrdinalIgnoreCase) == -1)
{
include = false;
break;
}
}
return include;
};
Filters.CollectionChanged += (object sender,
NotifyCollectionChangedEventArgs e) => _usersView.Refresh();
}
}