實現 INotifyPropertyChanged 接口能夠在屬性更改後通知數據的使用者,這個相信大夥兒都知道。因而,有朋友會問:對於要實時顯示進度的狀況,好比更新進度條,能用這個實現嗎?異步
固然是能夠的,也很簡單,定義一個類,實現 INotifyPropertyChanged 接口,而後公開表示處理進度的屬性,而且在屬性更改後引起通知事件。async
而後把該類的實例與進度條進行綁定便可,和通常的綁定差很少。不過,有一點須要強調:一般是把屬性更改通知發送給UI對象的,多數狀況下,咱們在處理一些耗時操做都會在另外一個線程上執行,這就使得在實現這個接口時,引起PropertyChanged 事件的時候容易發生錯誤。爲了不錯誤發生,在實現接口時,應當用Dispatcher來引起事件,確保能在UI線程上執行。測試
一個比較不錯的方法,是先弄個抽象類,讓它實現 INotifyPropertyChanged 接口,後面你所定義的各類 Model 類就能夠從這個抽象類派生,這樣會至關省事。this
好比這樣:spa
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected async void OnPropertyChanged([CallerMemberName] string propName = "") { await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.High, () => { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); }); } }
由於在引起屬性更改通知時須要指定屬性名稱,我們使用一個技巧,在 OnPropertyChanged 方法的參數上加一個 CallerMemberNameAttribute,這麼一來,只要在被更改的屬性的set方法中調用這個方法,就會自動把屬性的名字傳給參數了,也不用咱們手動輸,既免去繁雜工做,也不容易弄錯。注意參數在附加CallerMemberNameAttribute後必定要給它一個默認值,說白了就是讓這個參數變成可選參數,以方便運行時庫在運行階段修改它。線程
調用 Window.Current.Dispatcher.RunAsync 方法確保事件引起的代碼是在UI線程上執行的,就不會出現因交叉線程更改用戶界面而致使的異常。code
好,上面說了一車的廢話,下面咱們來定義一個表示進度數據的類,主要包括進度的最大值、最小值,以及當前進度,這個類從剛纔定義的 NotifyPropertyChangedBase 類派生。對象
public sealed class ProgressData : NotifyPropertyChangedBase { private int _max, _min, _currvalue; public int Max { get { return _max; } set { if (value != _max) { _max = value; OnPropertyChanged(); } } } public int Min { get { return _min; } set { if (value != _min) { _min = value; OnPropertyChanged(); } } } public int CurrentValue { get { return _currvalue; } set { if (value != _currvalue) { _currvalue = value; OnPropertyChanged(); } } } }
在用戶界面上聲明 ProgressBar 控件,而後綁定到上面類型的屬性。blog
<ProgressBar Name="pb" Height="25" Margin="3,30,5,2" Maximum="{Binding Max}" Minimum="{Binding Min}" Value="{Binding CurrentValue}" SmallChange="1.0"/>
如何讓 ProgressData 實例與 ProgressBar 控件關聯呢,這好辦,由於有一個 DataContext 屬性,它能夠賦任何類型的值,而後控件中的綁定會從該屬性的值中尋找數據。接口
下面咱們來關聯一下。
m_progressdata = new ProgressData(); m_progressdata.Max = 100; m_progressdata.Min = 0; m_progressdata.CurrentValue = 0; this.pb.DataContext = m_progressdata;
注意看最後一行,不解釋。
而後定義一個基於 Task 的異步方法,來模擬在新線程上處理數據。
async Task TestSomethingAsync() { while (m_progressdata.CurrentValue < m_progressdata.Max) { m_progressdata.CurrentValue++; await Task.Delay(20); } }
隨後能夠進行測試了。
private async void OnClick(object sender, RoutedEventArgs e) { m_progressdata.CurrentValue = m_progressdata.Min; Button btn = sender as Button; btn.IsEnabled = false; await TestSomethingAsync(); btn.IsEnabled = true; }
至此,就完成進度的綁定了,當你在線程中處理進度時,你能夠不用管UI上用什麼來顯示進度了,哪怕是用文本顯示,或用進度條來顯示,都無所謂,數據與界面分離。
看看效果,仍是挺不錯的。
這種方法除了在UWP中可用,一樣適用於WPF項目,由於都是一脈相承的,因此老週一直堅持:只要WPF學好了,別的都好辦。