パフォーマンスを向上させるために DataGrid
フィルタリング方法を書き直しました。これが最も速い方法ですが、私のプログラムを実行する遅いマシンではまだ改善の余地があると思います。
ユーザーがフィルタに一致するテキストを searchBox
に入力すると、 DataGrid
は ObservableCollection
にバインドされます ListBox
フィルタに追加されます。 ListBox
は、パフォーマンスを向上させるため、最初の25の CompanyModels
に制限されています。これはユーザタイプとして呼ばれるメソッドです。
private void FilterCompanies()
{
FilteredCompanies = new ObservableCollection();
FilteredCompanies.Clear();
string searchText = CleanseText(searchBox.Text.ToLower());
foreach (CompanyModel company in AllCompanies)
{
if (!string.IsNullOrEmpty(searchText))
{
if (FilteredCompanies.Count <= 25)
{
if (CompanyMatchesFilters(company))
{
if (CleanseText(company.Name.ToLower()).Contains(searchText) ||
CleanseText(company.Town.ToLower()).Contains(searchText) ||
CleanseText(company.Postcode.ToLower()).Contains(searchText))
{
FilteredCompanies.Add(company);
}
}
}
}
}
if (FilteredCompanies.Count > 0)
{
companiesListBox.ItemsSource = FilteredCompanies;
companiesListBox.Visibility = Visibility.Visible;
}
else
{
companiesListBox.ItemsSource = null;
companiesListBox.Visibility = Visibility.Collapsed;
}
}
コードから、フィルタリングにはさらに2つの方法があることがわかります。
CompanyMatchesFilters
private bool CompanyMatchesFilters(CompanyModel company)
{
foreach (FilterItem item in firstListBoxItems)
{
if (item.ID == 1 && company.CurrentStatus != 1)
{
return false;
}
if (item.ID == 2 && company.Subcontractor != 1)
{
return false;
}
if (item.ID == 3 && company.Supplier != 1)
{
return false;
}
if (item.ID == 4 && company.Planthire != 1)
{
return false;
}
if (item.ID == 5 && company.Architect != 1)
{
return false;
}
if (item.ID == 6 && company.QS != 1)
{
return false;
}
if (item.ID == 7 && company.ProjectManager != 1)
{
return false;
}
if (item.ID == 8 && company.StructEng != 1)
{
return false;
}
if (item.ID == 9 && company.ServiceEng != 1)
{
return false;
}
}
return true;
}
CleanseText
private static Regex badChars = new Regex("[^A-Za-z0-9']");
private string CleanseText(string text)
{
return badChars.Replace(text, "");
}
何かアドバイスがあれば、とても感謝しています。
これをLINQクエリにリファクタリングして、(おそらく)読みやすさを向上させることができます。
AllCompanies.Where(c => CompanyMatchesFilters(c))
.Where(c => CleanseText(company.Name).Contains(searchText)
|| CleanseText(company.Town).Contains(searchText)
|| CleanseText(company.Postcode).Contains(searchText))
.Take(25);
パフォーマンスに関して - あなたが示したコードの中で、潜在的なパフォーマンスのボトルネックとして私を襲うものは何もありません。おそらくそれを特定するためにプロファイラーを実行する必要があるでしょう。あなたが特に見たいと思うかもしれない1つの事は何が検索を引き起こすのか、そしてそれがどのくらいの頻度で実行されるのかです。たとえば、検索ボックスに10文字を入力した場合、1回または10回検索しますか。フィルタリングロジックはかなり軽量に見えますが、キー入力ごとに25個のアイテムテンプレートをUIで再作成することによるパフォーマンスへの影響は深刻です( DataTemplate
の複雑さによって異なります)。 Rx
には、UIイベントの調整に役立つ可能性のあるいくつかの優れた拡張機能があります。
P.S CleanseText
内のすべての意味のある文字を削除することの意味は何ですか?その時あなたはどんなキャラクターに興味がありますか?これは非常に不明瞭です、あなたはあなたのコードのその部分を文書化するべきです。
私が提案するいくつかの変更点:
private void FilterCompanies()
{
FilteredCompanies = new ObservableCollection();
//FilteredCompanies.Clear(); <- Pointless, you already cleared it when setting a new instance
string searchText = CleanseText(searchBox.Text.ToLower());
if (!string.IsNullOrEmpty(searchText)) //put outside foreach to avoid pointless foreach iteration
{
foreach (CompanyModel company in AllCompanies)
{
if (CompanyMatchesFilters(company))
{
//.Contains() is not case sensitive, no point on .ToLower()
if (CleanseText(company.Name).Contains(searchText) || CleanseText(company).Contains(searchText) || CleanseText(company.Postcode).Contains(searchText))
{
FilteredCompanies.Add(company);
if (FilteredCompanies.Count <= 25)
break;
}
}
}
}
companiesListBox.ItemsSource = FilteredCompanies.Any() ? FilteredCompanies : null;
companiesListBox.Visibility = FilteredCompanies.Any() ? Visibility.Visible : Visibility.Collapsed;
}
CompanyMatchesFilters
can be refactored as well. Just put all the company properties into an array. This will work because they seem to be of the same type.
private bool CompanyMatchesFilters(CompanyModel company)
{
var values = new[]{
company.CurrentStatus,
company.Subcontractor,
company.Supplier,
company.Planthire,
company.Architect,
company.QS,
company.ProjectManager,
company.StructEng,
company.ServiceEng
}
return !firstListBoxItems.Any(item => item.ID - 1 < values.Length
&& values[item.ID - 1] != 1);
}
すべての最適化とフィルタリングロジックを脇に置いています。
データコレクションをグループ化、並べ替え、フィルタ処理、および移動するためのビューを表します。
例えば ListBox
のフィルタや並べ替えを適用します。
ListBox
があなたのview-modelのプロパティにバインドされているとしましょう:
ここで、companyは次のように初期化された ICollectionView
です。
public ICollectionView Companies { get; }
public CompaniesViewModel()
{
var companies = GetCompanies();
_companiesView = CollectionViewSource.GetDefaultView(companies);
}
次のステップは、署名付きの Filter
プロパティを介してフィルタを追加することです。
public virtual Predicate
これはそれを意味します
項目がビューに含めるのに適しているかどうかを判断するために使用されるメソッドを取得または設定します。
それでは、一度に1つの会社が受け取ることができるフィルター方式を定義しましょう
private bool CompanyFilter(object item)
{
var company = item as Company;
//the optimized and pretty filtering logic ;-]
return true/false...
}
そしてフィルタを設定します。
Companies.Filter = CompanyFilter;
これまでのすべては、フィルタリングを準備するために一度だけ行います。繰り返す必要があるのは、フィルタ設定を変更し、適切な場所とタイミングで Refresh
メソッドを呼び出してそれを参照することです。
Companies.Refresh();
There is more to this which you can read on wpftutorial - How to Navigate, Group, Sort and Filter Data in WPF
先読みとしてのフィルタリング方法の設定が単純なシナリオに適用されることを付け加えたいと思います。あなたの場合は、フィルタロジックとプロパティ全体を CompanyFilter
クラスにカプセル化して、そのインスタンスをビューモデルで使用するほうが良いでしょう。この方法で、あなたはより良いフィルタをテストして、他のオブジェクトを巻き込むことなしにそれがするべきであるようにそれが機能することを確かめることができます。
class CompanyFilter
{
public bool FilterCompany(object item)
{
var company = item as company;
//filtering...
}
//properties...
//other filter helper methods.... but private
}
CleanseText(company.Prop.ToLower())を何度も呼び出す方法
そして私はもっと多くの検索ロジックをCompanayに取り込むだろう
public class Company
{
private string name = string.Empty;
private string cleanName = string.Empty;
public string Name
{
get { return name; }
set
{
if (name == value)
return;
name = value;
NotifyPropertryChanged("Name");
cleanName = CleanseText(name.ToLower());
NotifyPropertryChanged("CleanName");
}
}
public string CleanName
{
get { return cleanName; }
}
public bool MatchSearch(string searchText)
{
searchText = CleanseText(searchText);
if (string.IsNullOrEmpty(searchText))
return false;
return (cleanName.Contains(searchText) ||
cleanTown.Contains(searchText) ||
cleanPostcode.Contains(searchText));
}
}
私はMatchesFiltersをCompanyに引っ張ってフィルタのコレクションを渡すことさえします。
では、CompaniesListBox.ItemsSourceをバインドしないでください。 FilterCompanies()
メソッド内でUI要素を参照しないようにする
FilterCompanies()でFilteredCompaniesを新しくしてはいけません
FilteredCompaniesを1回正しくバインドします - 消去して追加 - それが目的です ObservableCollectionはこれを行います