MVVM مقابل البيانات الافتراضية للبيانات
-
24-09-2019 - |
سؤال
لديّ TreeView مرتبط بمثيلات Tree of ViewModel. المشكلة هي أن بيانات النموذج تأتي من مستودع بطيء ، لذا أحتاج إلى محاكاة افتراضية للبيانات. يجب تحميل قائمة ViewModel الفرعية أسفل العقدة فقط عند توسيع عقدة عرض الشجرة الأصل ويجب تفريغها عند انهيارها.
كيف يمكن تنفيذ ذلك أثناء الالتزام بمبادئ MVVM؟ كيف يمكن أن يتم إخطار ViewModel بأنه يحتاج إلى تحميل أو تفريغ الفرعية؟ وذلك عندما تم توسيع عقدة أو انهيار دون معرفة أي شيء عن وجود Treeview؟
شيء ما يجعلني أشعر أن المحاكاة الافتراضية للبيانات لا تسير على ما يرام مع MVVM. نظرًا لأن المحاكاة الافتراضية للبيانات ، يحتاج ViewModel عمومًا إلى معرفة الكثير عن الحالة الحالية لواجهة المستخدم و Aslo للتحكم في الكثير من الجوانب في واجهة المستخدم. خذ مثالًا آخر:
قائمة عرض مع البيانات الافتراضية للبيانات. ستحتاج ViewModel إلى التحكم في طول ScrolLthumb الخاص بـ ListView لأنه يعتمد على عدد العناصر الموجودة في النموذج. أيضًا عندما يقوم المستخدم بالتمرير ، سيحتاج ViewModel إلى معرفة الموقف الذي تم تمريره إليه ومدى حجم ListView (عدد العناصر التي تناسب حاليًا) لتكون قادرة على تحميل الجزء المناسب من بيانات النموذج من المستودع.
المحلول
الطريقة السهلة لحل ذلك هي تطبيق "مجموعة الافتراضية" التي تحافظ على مراجع ضعيفة إلى عناصرها جنبًا إلى جنب مع خوارزمية لجلب / إنشاء عناصر. رمز هذه المجموعة معقدًا إلى حد ما ، مع جميع الواجهات المطلوبة وهياكل البيانات لتتبع نطاقات البيانات المحملة بكفاءة ولكن هنا واجهة برمجة تطبيقات جزئية لفئة تم تصنيفها على أساس الفهارس:
public class VirtualizingCollection<T>
: IList<T>, ICollection<T>, IEnumerable<T>,
IList, ICollection, IEnumerable,
INotifyPropertyChanged, INotifyCollectionChanged
{
protected abstract void FetchItems(int requestedIndex, int gapStartIndex, int gapEndIndex);
protected void RecordFetchedItems(int startIndex, int count, IEnumerable items) ...
protected void RecordInsertOrDelete(int startIndex, int countPlusOrMinus) ...
protected virtual void OnCollectionChanged(CollectionChangedEventArgs e) ...
protected virtual void Cleanup();
}
بنية البيانات الداخلية هنا هي شجرة متوازنة من نطاقات البيانات ، مع كل نطاق بيانات يحتوي على فهرس البدء ومجموعة من المراجع الضعيفة.
تم تصميم هذه الفئة ليتم فئة فرعية لتوفير المنطق لتحميل البيانات فعليًا. إليكم كيف يعمل:
- في مُنشئ الفئة الفرعية ،
RecordInsertOrDelete
يتم استدعاؤه لتعيين حجم المجموعة الأولي - عندما يتم الوصول إلى عنصر باستخدام
IList/ICollection/IEnumerable
, ، يتم استخدام الشجرة للعثور على عنصر البيانات. إذا وجدت في الشجرة وهناك مرجع ضعيف وما زال المرجع الضعيف يشير إلى كائن حياة ، يتم إرجاع هذا الكائن ، وإلا يتم تحميله وإعادته. - عندما يحتاج عنصر ما إلى تحميل نطاق ، يتم حساب نطاق الفهرس عن طريق البحث إلى الأمام والعودة على الرغم من أن بنية البيانات للعنصر التالي/السابق الذي تم تحميله بالفعل ، ثم الملخص
FetchItems
يسمى بحيث يمكن للفئة الفرعية تحميل العناصر. - في الفئة الفرعية
FetchItems
التنفيذ ، يتم جلب العناصر ثمRecordFetchedItems
يتم استدعاؤه لتحديث شجرة النطاقات مع العناصر الجديدة. مطلوب بعض التعقيد هنا لدمج العقد المجاورة لمنع الكثير من نمو الأشجار. - عندما تحصل الفئة الفرعية على إشعار بتغييرات البيانات الخارجية ، يمكنها الاتصال
RecordInsertOrDelete
لتحديث تتبع الفهرس. تبدأ هذه التحديثات فهارس. بالنسبة لإدراج ما ، قد يؤدي ذلك أيضًا إلى تقسيم نطاق ، وللحذف قد يتطلب ذلك إعادة إنشاء واحدة أو أكثر من النطاقات أصغر. يتم استخدام هذه الخوارزمية نفسها داخليًا عند إضافة العناصر / حذفها من خلالIList
وIList<T>
واجهات. - ال
Cleanup
تسمى الطريقة في الخلفية للبحث بشكل متزايد عن شجرة النطاقاتWeakReferences
والنطاقات الكاملة التي يمكن التخلص منها ، وكذلك للنطاقات المتفجرة للغاية (على سبيل المثال واحد فقطWeakReference
في نطاق مع 1000 فتحة)
لاحظ أن FetchItems
يتم تمرير مجموعة من العناصر التي تم تفريغها حتى يتمكن من استخدام مجريات الأمور لتحميل عناصر متعددة في وقت واحد. هناك مثل هذا الاستدلال البسيط هو تحميل 100 عنصر التالي أو حتى نهاية الفجوة الحالية ، أيهما يأتي أولاً.
مع VirtualizingCollection
, ، سيؤدي المحاكاة الافتراضية المدمجة في WPF إلى تحميل البيانات في الأوقات المناسبة ListBox
, ComboBox
, ، إلخ ، طالما أنك تستخدم EG. VirtualizingStackPanel
بدلاً من StackPanel
.
ل TreeView
, ، خطوة أخرى مطلوبة: في HierarchicalDataTemplate
تعيين أ MultiBinding
ل ItemsSource
هذا يرتبط بحقك ItemsSource
وكذلك ل IsExpanded
على الوالد الملموس. المحول ل MultiBinding
إرجاع قيمته الأولى ( ItemsSource
) إذا كانت القيمة الثانية ( IsExpanded
القيمة) صحيحة ، وإلا فإنه يعود فارغة. ما يفعل TreeView
يتم إسقاط جميع الإشارات إلى محتويات التجميع على الفور بحيث VirtualizingCollection
يمكن تنظيفها.
لاحظ أنه لا يلزم القيام بالمحاكاة الافتراضية على أساس الفهارس. في سيناريو شجرة ، يمكن أن يكون كل شيء أو لا شيء ، وفي سيناريو القائمة يمكن استخدام عدد تقديري وملء النطاقات حسب الضرورة باستخدام آلية "بدء" / "مفتاح النهاية". يكون هذا مفيدًا عندما تتغير البيانات الأساسية ويجب أن يتتبع العرض الظاهري موقعه الحالي بناءً على المفتاح الموجود في الجزء العلوي من الشاشة.
نصائح أخرى
<TreeView
VirtualizingStackPanel.IsVirtualizing = "True"
VirtualizingStackPanel.VirtualizationMode = "Recycling"
VirtualizingStackPanel.CleanUpVirtualizedItem="TreeView_CleanUpVirtualizedItem">
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
</TreeView>