在WPF应用程序中显示和过滤XML数据

时间:2014-09-24 22:07:15

标签: c# xml wpf datagrid filter

我正在创建一个非常简单的WPF应用程序,它从XML文件加载数据并将其显示为表格。但是,我需要将表格自由文本过滤(通过任何值,任何列)。问题是XML数据将具有未知数量的属性,因此我无法将数据转换为已知/具体对象(请注意,对于任何给定的XML文件,属性的数量将为总是一样的)。

我最初尝试过 this post 中的代码,但发现因为我无法将我的XML数据转换为已知对象,所以我无法使用Filter谓词方法DataGridCollectionView(当我传入DataSet时,CanFilter方法设置为false)。因此,我发现我必须使用接受CustomFilter的BindingListCollectionView,但它是一个“klunky”SQL类型的过滤器。

这是一个随机的XML文件,可用于此目的。

<products>
  <product Name="Widget" Amount="123.45" Size="10 in" SomeOtherValue="foo" />
  <product Name="Screw" Amount="8.52" Size="5.1 cm" SomeOtherValue="bar" />
  <product Name="Bolt" Amount="2.66" Size="4 in" SomeOtherValue="con" />
  <product Name="Hinge" Amount="14.00" Size="22 cm" SomeOtherValue="toso" />
</products>

以下代码(有点),但看起来很脏。此外,它的行为很奇怪,如果我提供无效的过滤器值“xxx”,则不返回任何结果(正常),但是对于“xxxx”,返回所有结果,并且再次返回“xxxxx”,不返回任何结果。这是我需要帮助的 FilterString 方法。如何清理此代码并消除我的异常过滤行为?

public partial class MainWindow : INotifyPropertyChanged
{

    private BindingListCollectionView _bindingListCollection;
    private string _filterString;

    public MainWindow()
    {
        InitializeComponent();
        var dataSet = new DataSet();
        dataSet.ReadXml(@"D:\index.xml");
        BindingListCollection = (BindingListCollectionView)CollectionViewSource.GetDefaultView(dataSet.Tables[0]);
        BindingListCollection.CustomFilter = null;
    }

    public BindingListCollectionView BindingListCollection
    {
        get { return _bindingListCollection; }
        set { _bindingListCollection = value; NotifyPropertyChanged("BindingListCollection"); }
    }

    public string FilterString
    {
        get { return _filterString; }
        set
        {
            _filterString = value;
            if (_bindingListCollection == null) return;
            var filterValue = string.Empty;
            foreach (DataRowView dataRowView in _bindingListCollection.SourceCollection)
            {
                // Loop through each column in the underlying data and manually create a SQL filter
                foreach (var columnName in dataRowView.DataView.DataViewManager.DataSet.Tables[0].Columns)
                {
                    filterValue += string.Format("{0} LIKE '%" + _filterString + "%' OR ", columnName);
                }
                // I don't want to iterate through the rows, I just need to get to the raw table columns, so I break here
                break; 
            }

            if (filterValue == string.Empty)
            {
                BindingListCollection.CustomFilter = null;
            }
            else
            {
                // add something so the last "OR" doesn't throw things off
                BindingListCollection.CustomFilter = filterValue + " 0 = 1";
            }

            NotifyPropertyChanged("FilterString");
            _bindingListCollection.Refresh();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(string property)
    {
        if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); }
    }

}

如果您希望它帮助排除故障,这里也是演示代码

<Window x:Class="MyViewer.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="368" Width="1002" Name="UI" >
    <StackPanel DataContext="{Binding ElementName=UI}">
        <TextBox Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}" />
        <DataGrid ItemsSource="{Binding BindingListCollection}" />
    </StackPanel>
</Window>

2 个答案:

答案 0 :(得分:1)

这确实是一个有点杀手的代码,让我给你一个替代方案:

var dataSet = new DataSet();
dataSet.ReadXml(@"D:\temp\my.xml");

var defaultView = new DataView(dataSet.Tables[0]);

/*
    Operator LIKE is used to include only values that match a pattern with wildcards. 
    Wildcard character is * or %, it can be at the beginning of a pattern '*value', at the end 'value*', or at both '*value*'. 
    Wildcard in the middle of a patern 'va*lue' is not allowed.
*/
Func<string, string> escapeLike = valueWithoutWildcards => {
  StringBuilder sb = new StringBuilder();
  for (int i = 0; i < valueWithoutWildcards.Length; i++)
  {
    char c = valueWithoutWildcards[i];
    if (c == '*' || c == '%' || c == '[' || c == ']')
      sb.Append("[").Append(c).Append("]");
    else if (c == '\'')
      sb.Append("''");
    else
      sb.Append(c);
  }

  return sb.ToString();
};


var w = new Window();
w.Loaded += (o,e) => {

    var stackPanel = new StackPanel();
    var myButton = new Button();
    myButton.Content = "Click me!";
    myButton.Click += delegate { 

        var userQueryString = "screw";

        // todo; you might want to escape dataColumn.ColumnName, but not sure
        var finalQueryList = new List<string>();
        foreach(DataColumn dataColumn in defaultView.Table.Columns)
            finalQueryList.Add(string.Format("{0} LIKE '%{1}%'", 
                 dataColumn.ColumnName, escapeLike(userQueryString)));

        defaultView.RowFilter = string.Join(" OR ", finalQueryList); 
    };

    stackPanel.Children.Add(new DataGrid() { ItemsSource = defaultView });
    stackPanel.Children.Add(myButton);

    w.Content = stackPanel;
};


w.Show();

new Application().Run(w);

ps该代码只是为了展示一些东西:

  • 我不明白为什么你甚至需要摆弄BindingListCollection,这是一个非常糟糕的事情..
  • 您需要知道用户可以键入不同的字符,这将破坏“类似SQL”的查询。要了解您需要照顾的内容:http://www.csharp-examples.net/dataview-rowfilter/我已经实现了一些,以进行演示。
  • 无需刷新defaultView,它始终保持“最新”
  • “OR”魔术并不好( 0 = 1 )。
  • 如果您打算使用string.Format,请坚持使用它。没有交替。

答案 1 :(得分:1)

我认为您可以使用ObservableCollection<DataRow>,并使用DataSet过滤DataRow.ItemArray。像:

我将使用LINQ查询原始数据,生成IEnumerable<DataRow>的结果,然后将其转换为DataTable,以便视图与使用DataTableExtensions.CopyToDataTable方法绑定。

//Warning not tested.....

public partial class MainWindow : INotifyPropertyChanged
{
private DataSet _dataSet;
private string _filterString;

public MainWindow()
{
    InitializeComponent();
    _dataSet = new DataSet();
    _dataSet.ReadXml(@"D:\index.xml");
    FilterString=null;

}

public DataTable BindingListCollection
{
    get {
         return FilteredList.CopyToDataTable();
        }
}

public IEnumerable<DataRow> FilteredList
{
    get {
         //may need to check _dataSet is not null 
         return  string.IsNullOrEmpty(FilterString)?                        
                    from DataRow dr in _dataSet.Tables[0].Rows select dr
                   :from DataRow dr in _dataSet.Tables[0].Rows
                         where dr.ItemArray.Count(c => c.ToString().IndexOf(FilterString,StringComparison.InvariantCultureIgnoreCase)>=0) > 0
                         select dr;
        }
}

public string FilterString
{
    get { return _filterString; }
    set
    {
        _filterString = value;

        NotifyPropertyChanged("FilterString");
        NotifyPropertyChanged("BindingListCollection");
    }
}

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
    if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); }
}

}