C# 使用 MVVM 从 WPF 中的 TextBox 进行正确的 DataGrid 搜索

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15467553/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 16:56:04  来源:igfitidea点击:

Proper DataGrid search from TextBox in WPF using MVVM

c#wpfsearchmvvmdatagrid

提问by Daryl Behrens

I am new to the MVVM pattern, and a little confused on when to use Code Behind. I have a very simple form right now, that includes one TextBox, and one DataGrid. What I would like is to be able to have the DataGrid change its selected item based on the TextBox.

我是 MVVM 模式的新手,对何时使用代码隐藏有点困惑。我现在有一个非常简单的表单,其中包括一个 TextBox 和一个 DataGrid。我想要的是能够让 DataGrid 基于 TextBox 更改其选定的项目。

I have done this in Code Behind and it works fine using the following code:

我在代码隐藏中完成了此操作,并且使用以下代码可以正常工作:

private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
    for (int i = 0; i < dataGrid1.Items.Count; i++)
    {
        string cellContent = dtReferral.Rows[i][0].ToString();
        try
        {
            if (cellContent != null && cellContent.Substring(0, textBox1.Text.Length).Equals(textBox1.Text))
            {
                object item = dataGrid1.Items[i];
                dataGrid1.SelectedItem = item;
                dataGrid1.ScrollIntoView(item);
                //row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
                break;
            }
        }
        catch { }
    }
}

Now, I just want to highlight the Item in the Datagrid that starts with text in textbox, and allow the user to press a button to edit selected item.

现在,我只想突出显示 Datagrid 中以文本框中的文本开头的项目,并允许用户按下按钮来编辑所选项目。

Is it okay to have this logic in the Code Behind file? Or would I need to do this through some sort of binding? If I should do this through the View Model with Binding, any direction would be appreciated. Thank you.

在代码隐藏文件中可以有这个逻辑吗?或者我需要通过某种绑定来做到这一点吗?如果我应该通过带有绑定的视图模型来做到这一点,任何方向都将不胜感激。谢谢你。

采纳答案by sa_ddam213

If you only want to highlight the cells with the text from the TextBoxyou could make an AttatchedPropertyfor the DataGridto accept your search value from the TextBoxand create another AttatchedPropertyfor the Cellto indicate a match that you can usee to set properties in the Cellstyle. Then we create a IMultiValueConverterto check the Cellvalue for a match to the search Text.

如果你只是想突出与从该文本的单元格TextBox,你可以做一个AttatchedPropertyDataGrid接受来自您的搜索值TextBox,并创建另一个AttatchedProperty用于Cell指示匹配,你可以USEE在设置属性Cell的风格。然后我们创建一个IMultiValueConverter来检查Cell与搜索匹配的值Text

This way its reusable on other projects as you only need the AttachedPropertiesand Converter

这样它就可以在其他项目上重用,因为你只需要AttachedPropertiesConverter

Bind the AttachedPropertySearchValueto your TextBoxTextproperty.

将 绑定AttachedPropertySearchValue到您的TextBoxText财产。

 <DataGrid local:DataGridTextSearch.SearchValue="{Binding ElementName=SearchBox, Path=Text, UpdateSourceTrigger=PropertyChanged}" 

Then create a Stylefor DataGridCelland create a Setter for the AttachedPropertyIsTextMatchusing the IMultiValueConverterto return if the cells text matches the SearchValue

然后创建一个StyleforDataGridCell并创建一个 Setter 以AttachedPropertyIsTextMatch使用IMultiValueConverter如果单元格文本匹配SearchValue

<Setter Property="local:DataGridTextSearch.IsTextMatch">
    <Setter.Value>
        <MultiBinding Converter="{StaticResource SearchValueConverter}">
            <Binding RelativeSource="{RelativeSource Self}" Path="Content.Text" />
            <Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
        </MultiBinding>
    </Setter.Value>
</Setter>

Then we can use the Cellsattached IsTextMatchproperty to set a highlight using a Trigger

然后我们可以使用Cells附加IsTextMatch属性使用Trigger

<Style.Triggers>
    <Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
        <Setter Property="Background" Value="Orange" />
    </Trigger>
</Style.Triggers>

Here is a working example showing my rambilings :)

这是一个工作示例,显示了我的散漫:)

Code:

代码:

namespace WpfApplication17
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            for (int i = 0; i < 20; i++)
            {
                TestData.Add(new TestClass { MyProperty = GetRandomText(), MyProperty2 = GetRandomText(), MyProperty3 = GetRandomText() });
            }
        }

        private string GetRandomText()
        {
            return System.IO.Path.GetFileNameWithoutExtension(System.IO.Path.GetRandomFileName());
        }

        private ObservableCollection<TestClass> _testData = new ObservableCollection<TestClass>();
        public ObservableCollection<TestClass> TestData
        {
            get { return _testData; }
            set { _testData = value; }
        }
    }

    public class TestClass
    {
        public string MyProperty { get; set; }
        public string MyProperty2 { get; set; }
        public string MyProperty3 { get; set; }
    }

    public static class DataGridTextSearch
    {
        // Using a DependencyProperty as the backing store for SearchValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SearchValueProperty =
            DependencyProperty.RegisterAttached("SearchValue", typeof(string), typeof(DataGridTextSearch),
                new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.Inherits));

        public static string GetSearchValue(DependencyObject obj)
        {
            return (string)obj.GetValue(SearchValueProperty);
        }

        public static void SetSearchValue(DependencyObject obj, string value)
        {
            obj.SetValue(SearchValueProperty, value);
        }

        // Using a DependencyProperty as the backing store for IsTextMatch.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsTextMatchProperty =
            DependencyProperty.RegisterAttached("IsTextMatch", typeof(bool), typeof(DataGridTextSearch), new UIPropertyMetadata(false));

        public static bool GetIsTextMatch(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsTextMatchProperty);
        }

        public static void SetIsTextMatch(DependencyObject obj, bool value)
        {
            obj.SetValue(IsTextMatchProperty, value);
        }
    }

    public class SearchValueConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string cellText = values[0] == null ? string.Empty : values[0].ToString();
            string searchText = values[1] as string;

            if (!string.IsNullOrEmpty(searchText) && !string.IsNullOrEmpty(cellText))
            {
                return cellText.ToLower().StartsWith(searchText.ToLower());
            }
            return false;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }
    }
}

Xaml:

Xml:

<Window x:Class="WpfApplication17.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication17"
        Title="MainWindow" Height="350" Width="525" Name="UI">

    <StackPanel DataContext="{Binding ElementName=UI}">
        <TextBox Name="SearchBox" />
        <DataGrid x:Name="grid" local:DataGridTextSearch.SearchValue="{Binding ElementName=SearchBox, Path=Text, UpdateSourceTrigger=PropertyChanged}" 
                  ItemsSource="{Binding TestData}" >
            <DataGrid.Resources>
                <local:SearchValueConverter x:Key="SearchValueConverter" />
                <Style TargetType="{x:Type DataGridCell}">
                    <Setter Property="local:DataGridTextSearch.IsTextMatch">
                        <Setter.Value>
                            <MultiBinding Converter="{StaticResource SearchValueConverter}">
                                <Binding RelativeSource="{RelativeSource Self}" Path="Content.Text" />
                                <Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                    <Style.Triggers>
                        <Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
                            <Setter Property="Background" Value="Orange" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.Resources>
        </DataGrid>
    </StackPanel>
</Window>

Result:

结果:

enter image description hereenter image description here

在此处输入图片说明在此处输入图片说明

Edit:

编辑:

If you just want to select the row based on a single Column you can modify quite easily :).

如果您只想根据单个列选择行,您可以很容易地修改:)。

Override the Style of DataGridRowinstead of DataGridCell.

覆盖样式DataGridRow而不是DataGridCell

  <Style TargetType="{x:Type DataGridRow}">

First pass in the property you want into the IMultiValueConverterthis should be your DataContext

首先传入您想要的属性,IMultiValueConverter这应该是您的DataContext

<MultiBinding Converter="{StaticResource SearchValueConverter}">
    <Binding RelativeSource="{RelativeSource Self}" Path="DataContext.MyProperty" />
    <Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
</MultiBinding>

Then change the Triggerto set IsSelectedon the Row

然后Trigger将设置更改IsSelectedRow

<Style.Triggers>
    <Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
        <Setter Property="IsSelected" Value="True" />
    </Trigger>
</Style.Triggers>

Should look like this:

应该是这样的:

 <DataGrid x:Name="grid" local:DataGridTextSearch.SearchValue="{Binding ElementName=SearchBox, Path=Text, UpdateSourceTrigger=PropertyChanged}" 
              ItemsSource="{Binding TestData}" >
        <DataGrid.Resources>
            <local:SearchValueConverter x:Key="SearchValueConverter" />
            <Style TargetType="{x:Type DataGridRow}">
                <Setter Property="local:DataGridTextSearch.IsTextMatch">
                    <Setter.Value>
                        <MultiBinding Converter="{StaticResource SearchValueConverter}">
                            <Binding RelativeSource="{RelativeSource Self}" Path="DataContext.MyProperty" />
                            <Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
                        </MultiBinding>
                    </Setter.Value>
                </Setter>
                <Style.Triggers>
                    <Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
                        <Setter Property="IsSelected" Value="True" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </DataGrid.Resources>
    </DataGrid>

Result:

结果:

enter image description here

在此处输入图片说明

回答by Heinrich

I have been using MVVM for quiet a while now, and I still prefer using at as a guideline rather than a strict practice, partly because it isn't always practical to do everything in MVVM pattern exactly, and even more so if you are not to familiar with it.
I would suggest just playing around with it until you manage to find a form of MVVM that suits you.
I don't believe it is taboo to have Code in the Code Behind of the MVVM if the code is UI related.
ScrollIntoView isn't a Bindable property so you if you want to bind to it you will have to create a dependency Property to indirectly handle the binding. As for setting the selected item you could do it through something like:

我已经安静地使用 MVVM 一段时间了,我仍然更喜欢使用 at 作为指导方针而不是严格的实践,部分原因是在 MVVM 模式中准确地做所有事情并不总是可行的,如果你不是,更是如此熟悉它。
我建议只是尝试一下,直到您设法找到适合您的 MVVM 形式。
如果代码与 UI 相关,我不认为在 MVVM 的代码隐藏中包含代码是禁忌。
ScrollIntoView 不是一个 Bindable 属性,所以如果你想绑定到它,你必须创建一个依赖属性来间接处理绑定。至于设置所选项目,您可以通过以下方式进行:

View:

看法:

<TextBox Height="23" Text={Binding Path=Selected, UpdateSourceTrigger=PropertyChanged} HorizontalAlignment="Left" Margin="90,147,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
<DataGrid AutoGenerateColumns="True" 
          ItemsSource="{Binding Path=ItemList}" 
          SelectedItem="{Binding Path=Selected}" >
</DataGrid>

ViewModel:

视图模型:

 private string _selected = "";
 public string Selected
 {
      get{ return _selected; }
      set
      {
            if(_selected == value) return;
            _selected = value;
            base.OnPropertyChanged("Selected");
      }
 }