日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
詳談WPF開(kāi)發(fā)中的數(shù)據(jù)虛擬化

編輯推薦《專題:讓你的代碼“炫”起來(lái)——WPF開(kāi)發(fā)教程》

UI虛擬化

當(dāng)一個(gè)WPF的ItemControl被綁定到一個(gè)大型集合的數(shù)據(jù)源時(shí),如果可以UI虛擬化,該控件將只為那些在可以看到的項(xiàng)創(chuàng)見(jiàn)可視化的容器(加上面和下面的少許)。這是一個(gè)完整集合中有代表性的一小部分。用戶移動(dòng)滾動(dòng)條時(shí),將為那些滾動(dòng)到可視區(qū)域的項(xiàng)創(chuàng)建新的可視化容器,那些不再可見(jiàn)的項(xiàng)的容器將被銷毀。當(dāng)容器設(shè)置為循環(huán)使用時(shí),它將再使用可視化容器代替不斷的創(chuàng)建和銷毀可視化容器,避免對(duì)象的實(shí)例化和垃圾回收器的過(guò)度工作。

數(shù)據(jù)虛擬化

數(shù)據(jù)虛擬化是指綁定到ItemControl的真實(shí)的數(shù)據(jù)對(duì)象的歸檔虛擬化的時(shí)間段。數(shù)據(jù)虛擬化不是由WPF提供的。作為對(duì)比,基本數(shù)據(jù)對(duì)象的小集合對(duì)內(nèi)存的消耗不是很多;但是,大集合的內(nèi)存消耗是非常嚴(yán)重的。另外,真實(shí)的檢索數(shù)據(jù)(例如,從數(shù)據(jù)庫(kù))和實(shí)例化數(shù)據(jù)對(duì)象是很耗時(shí)的,尤其當(dāng)是網(wǎng)絡(luò)數(shù)據(jù)調(diào)用時(shí)。因此,我們希望使用數(shù)據(jù)虛擬化機(jī)制來(lái)限制檢索的數(shù)據(jù)的數(shù)量和在內(nèi)存中生成數(shù)據(jù)對(duì)象的數(shù)量。

解決方案

總覽

這個(gè)解決方案是只在ItemControl綁定到IList接口的實(shí)現(xiàn)時(shí)起作用,而不是IEumerable的實(shí)現(xiàn),它并不枚舉整個(gè)列表,而只是讀取需要顯示的項(xiàng)。它使用Count屬性判斷集合的大小,推測(cè)并設(shè)置滾動(dòng)的范圍。然后使用列表索引重新確定要在屏幕上顯示的項(xiàng)。因此,創(chuàng)建一個(gè)可以報(bào)告具有大量項(xiàng)的,并且可以只檢索需要的項(xiàng)的IList。

IItemsProvider 為了利用這個(gè)解決方案,下面的數(shù)據(jù)源必須能提供集合中項(xiàng)的數(shù)量,并且能夠提供完整集合的小塊(或頁(yè))。這需要在IItemsProvider接口封裝。

/// 
/// Represents a provider of collection details.
///
/// The type of items in the collection.
public interface IItemsProvider
{
///
/// Fetches the total number of items available.
///
///
int FetchCount();

///
/// Fetches a range of items.
///
/// The start index.
/// The number of items to fetch.
///
IList FetchRange(int startIndex, int count);
}

如果下面的查詢是一個(gè)數(shù)據(jù)庫(kù)查詢,它是一個(gè)利用大多數(shù)據(jù)庫(kù)供應(yīng)商都提供的COUNT()聚集函數(shù)和OFFSET與LIMIT表達(dá)式的一個(gè)IItemProvider接口的一個(gè)簡(jiǎn)單實(shí)現(xiàn)。

VirtualizingCollection 這是一個(gè)執(zhí)行數(shù)據(jù)虛擬化的IList的實(shí)現(xiàn)。VirtualizingCollection(T)把整個(gè)集合分裝到一定數(shù)量的頁(yè)中。根據(jù)需要把頁(yè)加載到內(nèi)存中,在不需要時(shí)從釋放。

下面討論我們有興趣的部分。詳細(xì)信息請(qǐng)參考附件中的源代碼項(xiàng)目。

IList實(shí)現(xiàn)的***個(gè)方面是實(shí)現(xiàn)Count屬性。它通常被ItemsControl用來(lái)確定集合的大小,并呈現(xiàn)適當(dāng)?shù)臐L動(dòng)條。

private int _count = -1;
public virtual int Count
{
    get
    {
if (_count == -1)
        {
            LoadCount();
        }
        return _count;
    }
    protected set
    {
        _count = value;
    }
}
protected virtual void LoadCount()
{
    Count = FetchCount();
}

protected int FetchCount()
{
    return ItemsProvider.FetchCount();
}

Count屬性使用延遲和懶惰加載(lazy loading)模式。它使用特殊值-1作為未加載的標(biāo)識(shí)。當(dāng)***次讀取它時(shí),它從ItemsProvider加載其實(shí)際的數(shù)量。

 IList接口的實(shí)現(xiàn)的另一個(gè)重要方面是索引的實(shí)現(xiàn)。

private int _count = -1;

public virtual int Count
{
get
{
if (_count == -1)
{
LoadCount();
}
return _count;
}
protected set
{
_count = value;
}
}

protected virtual void LoadCount()
{
Count = FetchCount();
}

protected int FetchCount()
{
return ItemsProvider.FetchCount();
}

這個(gè)索引是這個(gè)解決方案的一個(gè)聰明的操作。首先,它必須確定請(qǐng)求的項(xiàng)在哪個(gè)頁(yè)(pageIndex)中,在頁(yè)中的位置(pageOffset),然后調(diào)用RequestPage()方法請(qǐng)求該頁(yè)。

附加的步驟是然后根據(jù)pageOffset加載后一頁(yè)或前一頁(yè)。這基于一個(gè)假設(shè),如果用戶正在瀏覽第0頁(yè),那么他們有很高的機(jī)率接下來(lái)要滾動(dòng)瀏覽第1頁(yè)。提前把數(shù)據(jù)取來(lái),就可以無(wú)延遲的顯示。

然后調(diào)用CleanUpPages()清除(或卸載)所有不再使用的頁(yè)。

***,放置頁(yè)不可用的一個(gè)防御性的檢查, 當(dāng)RequestPage沒(méi)有同步操作時(shí)是必要的,例如在子類AsyncVirtualizingCollection 中。

// ...

private readonly Dictionary > _pages =
new Dictionary >();
private readonly Dictionary _pageTouchTimes =
new Dictionary ();

protected virtual void RequestPage(int pageIndex)
{
if (!_pages.ContainsKey(pageIndex))
{
_pages.Add(pageIndex, null);
_pageTouchTimes.Add(pageIndex, DateTime.Now);
LoadPage(pageIndex);
}
else
{
_pageTouchTimes[pageIndex] = DateTime.Now;
}
}

protected virtual void PopulatePage(int pageIndex, IList page)
{
if (_pages.ContainsKey(pageIndex))
_pages[pageIndex] = page;
}

public void CleanUpPages()
{
List keys = new List (_pageTouchTimes.Keys);
foreach (int key in keys)
{
// page 0 is a special case, since the WPF ItemsControl
// accesses the first item frequently
if ( key != 0 && (DateTime.Now -
_pageTouchTimes[key]).TotalMilliseconds > PageTimeout )
{
_pages.Remove(key);
_pageTouchTimes.Remove(key);
}
}
}

頁(yè)存儲(chǔ)在以頁(yè)索引為鍵的字典(Dictionary)中。一個(gè)附加的字典(Dictionary)記錄著每個(gè)頁(yè)的***存取時(shí)間,它用于在CleanUpPages()方法中移除較長(zhǎng)時(shí)間沒(méi)有存取的頁(yè)。

protected virtual void LoadPage(int pageIndex)
{
PopulatePage(pageIndex, FetchPage(pageIndex));
}

protected IList FetchPage(int pageIndex)
{
return

為完成該解決方案,F(xiàn)etchPage()執(zhí)行從ItemProvider中抓取數(shù)據(jù),LoadPage()方法完成調(diào)用PopulatePage方法獲取頁(yè)并把該頁(yè)存儲(chǔ)到字典(Dictionary)中的工作。

看起來(lái)好象有一些太多的不全邏輯的方法(a few too many inconsequential methods),但這樣設(shè)計(jì)是有原因的:每一個(gè)方法做且只做一件事,有助于提高代碼的可讀性,并使在子類中進(jìn)行功能擴(kuò)展和維護(hù)變得容易,下面可以看到。

類VirtualizingCollection 實(shí)現(xiàn)了數(shù)據(jù)虛擬化的基本目標(biāo)。不幸的是,在使用中,它有一個(gè)嚴(yán)重不足:數(shù)據(jù)抓取方法是全部同步執(zhí)行的。這就是說(shuō)它們要在UI線程中執(zhí)行,造成一個(gè)緩慢的程序

AsyncVirtualizingCollection 類AsyncVirtualizingCollection 繼承自VirtualizingCollection ,重載了Load方法,以實(shí)現(xiàn)數(shù)據(jù)的異步加載。

WPF中異步數(shù)據(jù)源的關(guān)鍵是在數(shù)據(jù)抓取完成后必須通知UI的數(shù)據(jù)綁定。在規(guī)則的對(duì)象中,是通過(guò)實(shí)現(xiàn)INotifyPropertyChanged接口實(shí)現(xiàn)的。對(duì)一個(gè)集合的實(shí)現(xiàn),需要緊密的關(guān)系,INotifyCollectionChanged。那是ObservableCollection 要使用的接口。

public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler h = CollectionChanged;
if (h != null)
h(this, e);
}

private void FireCollectionReset()
{
NotifyCollectionChangedEventArgs e =
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(e);
}

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler h = PropertyChanged;
if (h != null)
h(this, e);
}

private void FirePropertyChanged(string propertyName)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
OnPropertyChanged(e);
}

實(shí)現(xiàn)了INotifyCollectionChanged接口和INotifyPropertyChanged接口。提供數(shù)據(jù)綁定彈性最大化。這個(gè)實(shí)現(xiàn)沒(méi)有任何要注意的。

protected override void LoadCount()
{
Count = 0;
IsLoading = true;
ThreadPool.QueueUserWorkItem(LoadCountWork);
}

private void LoadCountWork(object args)
{
int count = FetchCount();
SynchronizationContext.Send(LoadCountCompleted, count);
}

private void LoadCountCompleted(object args)
{
Count = (int)args;
IsLoading = false;
FireCollectionReset();
}

在重載的LoadCount()方法中,抓取是由ThreadPool(線程池)異步調(diào)用的。一旦完成,就會(huì)重置Count,UI的更新是由INotifyCollectionChanged接口調(diào)用FireCollectionReset方法實(shí)現(xiàn)的。注意LoadCountCompleted方法會(huì)在UI線程通過(guò)SynchronizationContext再一次被調(diào)用。假定集合的實(shí)例在UI線程中被創(chuàng)建,SynchronationContext屬性就會(huì)被設(shè)置。

protected override void LoadPage(int index){IsLoading = true;
ThreadPool.QueueUserWorkItem(LoadPageWork, index);}
private void LoadPageWork(object args){    
int pageIndex = (int)args;    IList
     
       page = FetchPage(pageIndex);
     
SynchronizationContext.Send(LoadPageCompleted, new object[]{pageIndex, page});}
private void LoadPageCompleted(object args){int pageIndex=(int)((object[]) args)[0];
IList
     
       page = (IList
      
       )((object[])args)[1];    PopulatePage(pageIndex, page);
      
     
IsLoading = false;    FireCollectionReset();}

頁(yè)數(shù)據(jù)的加載遵循相同的慣例,再一次調(diào)用FireCollectionReset方法更新用戶UI。

也要注意IsLoading屬性是一個(gè)簡(jiǎn)單的標(biāo)識(shí),可以用來(lái)告知UI集合正在加載。當(dāng)IsLoading改變后,由INotifyPropertyChanged機(jī)制調(diào)用FirePropertyChanged方法更新UI。

public bool IsLoading{ get{ return _isLoading; }   
set {if ( value != _isLoading ){  _isLoading = value;
FirePropertyChanged("IsLoading");}    }}

演示項(xiàng)目

為了演示這個(gè)解決方案,我創(chuàng)建了一個(gè)簡(jiǎn)單的示例項(xiàng)目(包括附加的源代碼項(xiàng)目)。

首先,創(chuàng)建一個(gè)IItemsProvider的一個(gè)實(shí)現(xiàn),它通過(guò)使用線程休眠來(lái)模擬網(wǎng)絡(luò)或磁盤(pán)行為的延遲提供虛擬數(shù)據(jù)。

public class DemoCustomerProvider : IItemsProvider
    
     
{
private readonly int _count;
private readonly int _fetchDelay;

public DemoCustomerProvider(int count, int fetchDelay)
{
_count = count;
_fetchDelay = fetchDelay;
}

public int FetchCount()
{
Thread.Sleep(_fetchDelay);
return _count;
}

public IList FetchRange(int startIndex, int count)
{
Thread.Sleep(_fetchDelay);

List list = new List ();
for( int i=startIndex; i {
Customer customer = new Customer {Id = i+1, Name = "Customer " + (i+1)};
list.Add(customer);
}
return list;
}
}

普遍存在的Customer(消費(fèi)者)對(duì)象作為集合中的項(xiàng)。

為了允許用戶試驗(yàn)不同的列表實(shí)現(xiàn),創(chuàng)建一個(gè)包含ListView的簡(jiǎn)單WPF窗體。

    
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Data Virtualization Demo - By Paul McClean" Height="600" Width="600">

















TextAlignment="Right" VerticalAlignment="Center"/>
Text="1000000" Width="60" VerticalAlignment="Center"/>
TextAlignment="Right" VerticalAlignment="Center"/>
Text="1000" Width="60" VerticalAlignment="Center"/>






TextAlignment="Right" VerticalAlignment="Center"/>
Margin="5" Content="List(T)" VerticalAlignment="Center"/>
Margin="5" Content="VirtualizingList(T)"
VerticalAlignment="Center"/>
Margin="5" Content="AsyncVirtualizingList(T)"
IsChecked="True" VerticalAlignment="Center"/>


TextAlignment="Right" VerticalAlignment="Center"/>
Text="100" Width="60" VerticalAlignment="Center"/>
TextAlignment="Right" VerticalAlignment="Center"/>
Text="30" Width="60" VerticalAlignment="Center"/>





VerticalAlignment="Center"/>
Width="80" VerticalAlignment="Center"/>

Margin="5" Width="100" VerticalAlignment="Center"/>

Fill="Blue" Margin="5" VerticalAlignment="Center">







Storyboard.TargetProperty=
"(TextBlock.RenderTransform).(RotateTransform.Angle)"
From="0" To="360" Duration="0:0:5"
RepeatBehavior="Forever" />






FontStyle="Italic" Text="Pause in animation indicates UI thread stalled."/>






                                        

沒(méi)有必要去探究XAML的細(xì)節(jié)。惟一要注意的是使用ListView的定制風(fēng)格改變其背景和響應(yīng)IsLoading屬性時(shí)的光標(biāo)。

public partial class DemoWindow
{
///
/// Initializes a new instance of the class.
///
public DemoWindow()
{
InitializeComponent();

// use a timer to periodically update the memory usage
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 1);
timer.Tick += timer_Tick;
timer.Start();
}

private void timer_Tick(object sender, EventArgs e)
{
tbMemory.Text = string.Format("{0:0.00} MB",
GC.GetTotalMemory(true)/1024.0/1024.0);
}

private void Button_Click(object sender, RoutedEventArgs e)
{
// create the demo items provider according to specified parameters
int numItems = int.Parse(tbNumItems.Text);
int fetchDelay = int.Parse(tbFetchDelay.Text);
DemoCustomerProvider customerProvider =
new DemoCustomerProvider(numItems, fetchDelay);

// create the collection according to specified parameters
int pageSize = int.Parse(tbPageSize.Text);
int pageTimeout = int.Parse(tbPageTimeout.Text);

if ( rbNormal.IsChecked.Value )
{
DataContext = new List (customerProvider.FetchRange(0,
customerProvider.FetchCount()));
}
else if ( rbVirtualizing.IsChecked.Value )
{
DataContext = new VirtualizingCollection (customerProvider, pageSize);
}
else if ( rbAsync.IsChecked.Value )
{
DataContext = new AsyncVirtualizingCollection (customerProvider,
pageSize, pageTimeout*1000);
}
}
}

窗體設(shè)計(jì)是相當(dāng)?shù)暮?jiǎn)單,只是為了演示這個(gè)解決方案。

用戶可以在DomoCustomerProvider中配置項(xiàng)的數(shù)量和模擬抓取的延遲。

責(zé)任編輯:彭凡
來(lái)源: 博客園 .NET WPF 數(shù)據(jù)虛擬化 UI


新聞名稱:詳談WPF開(kāi)發(fā)中的數(shù)據(jù)虛擬化
網(wǎng)站網(wǎng)址:http://www.5511xx.com/article/cdsphec.html