【问题标题】:Set WPF Binding Source to DataGrid Columns将 WPF 绑定源设置为 DataGrid 列
【发布时间】:2019-06-05 13:14:54
【问题描述】:

我有一个包含 18 列的 WPF DataGrid,每列上方都有一个 TextBox,因此我可以过滤该列。

每个TextBoxWidth 绑定到列的ActualWidth

<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>

我知道我可以像这样将TextBox Text 绑定到List&lt;string&gt;

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

我想将TextBoxWidth 绑定到该列的ActualWidth,如下所示:

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

因为那时我可以使用ItemsControl 而不是StackPanel,但是这样不行

还有其他方法可以实现吗?

【问题讨论】:

    标签: c# wpf mvvm data-binding datagrid


    【解决方案1】:

    如何使用ItemsControlTextBox 过滤DataGrid 列。

    可以像这样使用ItemsControl 并绑定到DataGrid.Columns

    <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>
    

    因为您将ItemsControl.ItemsSource 设置为DataGrid.Columns 而不是DataContext.Filters,所以您必须设置DataGridColumn.DisplayIndex 并使用IMultiValueConverter 才能再次访问DataContext.Filters

    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 };
        }
    }
    

    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();
        }
    }
    

    【讨论】:

      【解决方案2】:

      您确实可以通过这样做绑定到DataGrid 的列

      <TextBox Width="{Binding Columns[0].ActualWidth, ElementName=dataGridUsers}" />
      

      但这不适用于您想要实现的目标。一旦您在运行时重新排序列,该顺序将不再与您的 TextBoxes 的顺序匹配。所以你也必须重新排序。
      注意:DataGridColumn.DisplayIndex 返回您的DataGrid 中的当前索引。


      更好的方法和推荐的方法是通过更改 DataGridColumn.HeaderTemplate 将“FilterTextBoxes”放在列标题中

      【讨论】:

      • 感谢DisplayIndex 的提示,我可以在MultiBinding 中使用它,这样它就可以正常工作了。
      【解决方案3】:

      您不能绑定到DataGrid Columns 属性,因为它们仅在逻辑树中 - 而不是可视树。 执行此类操作的唯一方法是更改​​ DataGridTextColumn.HeaderTemplate 并创建一个新的 DataTemplate,其中包含过滤器 TextBox

      【讨论】:

      • 是的,只需将 TextBox 放在列标题中即可。但请记住为绑定设置正确的DataContext
      • 我只是好奇:为什么在视觉树和逻辑树中绑定东西会有区别?
      猜你喜欢
      • 1970-01-01
      • 2011-09-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-09
      • 1970-01-01
      • 2018-12-28
      • 2015-04-27
      • 2014-12-15
      相关资源
      最近更新 更多