我正在创建一个非常简单的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>
答案 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该代码只是为了展示一些东西:
答案 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)); }
}
}