例说MVVM和ThreadPoolCompleteVersion.docx
《例说MVVM和ThreadPoolCompleteVersion.docx》由会员分享,可在线阅读,更多相关《例说MVVM和ThreadPoolCompleteVersion.docx(16页珍藏版)》请在冰豆网上搜索。
![例说MVVM和ThreadPoolCompleteVersion.docx](https://file1.bdocx.com/fileroot1/2023-4/19/b270d5b6-eab5-4327-bc95-9b459994e33b/b270d5b6-eab5-4327-bc95-9b459994e33b1.gif)
例说MVVM和ThreadPoolCompleteVersion
前言
随着微软WindowsPresentationFoundation(简称WPF)的绚丽外衣诱惑以及快捷灵活的开发方式的深入人心,WPF将取代.net的WindowsForms以及传统的MFC成为新一代应用开发的主力军,成为必然。
本文将通过一个股票小软件的例子,来说明WPF中的新的架构模式“MVVM”以及.net的强大ThreadPool的使用。
股票小软件?
前一段时间,一个证券公司的朋友推荐我试用了一下某证券公司的股票软件,里面的一个资金统计功能非常喜欢,但是由于这个软件是试用版,所以想长期使用的话,还是需要自己想办法自力更生。
于是,逆向这个软件程序,拿到反汇编的程序,调试,抓数据包,最后分析出这个资金统计功能的机理。
哈哈,其实实现起来不难。
这就开工自己写个类似的功能给自己用吧。
什么是MVVM
MVVM是Model-View-ViewModel的简写,是从MVC演化而来的,特定于WPF的一个架构模式,其实与MVC类似,只是其中的Controller转换成了ViewModel。
但是就是这一个转换,给WPF的程序架构和设计带来的异常的灵活性。
不论MVVM还是MVC,起核心宗旨都是关注分离,让专人负责专工。
但是传统的MVC在经过这么多年的实践当中,出现的问题是,并不能真正的做到Model,View与Controller的分离。
微软推出MVVM后,使ViewModel成为Model与View的联系纽带。
MVVM帮助我们把业务与UI展示分离的更彻底。
这样的好处就有一大堆啦:
简化测试,降低维护成本,可以更容易的跨设备、跨平台(可以在WP7,Silverlight上使用),提高代码重用率,让开发人员与UI设计人员更容易的协作,等等。
那么,在MVVM中,Model,View,ViewModel的职责具体是什么呢?
⏹View,用来封装UI以及UI逻辑;
⏹Model,封装应用程序的业务逻辑和数据;(注:
在实际应用中,这个Model有所谓的贫血与充血之分。
我在例子中采用的是贫血模型)
⏹ViewModel,封装展示逻辑与状态。
下面上图,来直观的看看MVVM:
(本图来自Prism4)
View部分
理想情况下,View部分的所有代码都应该位于XAML中,在codebehind中只包含一个构造方法,调用InitializeCoomponent方法。
但是在某些情况下,codebehind也会包含一些与XAML有关系的代码,例如一些复杂的动画。
但是不应该把任何需要做单元测试的代码放在codebehind中。
ViewModel部分
Viewmodel封装的是view部分的展示逻辑也数据。
它并没有直接引用view,也不依赖与具体的view实现。
Viewmodel中提供view所需的属性以及command,使用变更通知事件将这些属性和command绑定到view上。
Model部分
Model中包含业务逻辑,用来读取和管理应用数据。
通常,model代表的是应用程序的客户端的领域模型。
基于应用程序的数据模型,可以在model中定义相应的数据结构,实现相应的业务和校验逻辑。
还可以把数据访问与缓存逻辑也放在model中。
为了通知view,model的数据有变化,WPF引入了“变更通知事件”。
WPF定义了接口INotifyPropertyChanged,INotifyCollectionChanged。
可以根据具体情况,让model实现INotifyPropertyChanged和/或INotifyCollectionChanged接口,使view能够感知到model的数据变化,并刷新view。
股软的资金统计功能的分析
股软的资金统计功能,就是定时的到某个服务器上读取最新的数据,并展示给用户。
为了能够定时启动程序读取数据,在程序里需要使用一个timer来计时。
由于股票数量很多,而每个请求只能读取一个股票的数据,所以这里需要使用多线程的方式来在每次timer启动时,读取多个股票的数据。
于是,就有了下图中的设计:
针对这个图,下面我们分析下其中的Model,View和ViewModel:
Model部分
Capital是model。
它继承自ObservableObject,而ObservableObject实现接口INotifyPropertyChanged。
这也就意味着,Captial对象的数据有变化时,view可以感知到这个变化,并刷新view。
ObservableObjectclass
publicabstractclassObservableObject:
INotifyPropertyChanged
{
#regionINotifyPropertyChangedMembers
publiceventPropertyChangedEventHandlerPropertyChanged;
#endregion
#regionProtectedMethods
///
///RaisesthePropertyChangedevent.
///
///Thenameofthepropertythatwaschanged.
protectedvoidRaisePropertyChangedEvent(stringpropertyName)
{
if(PropertyChanged!
=null)
{
vare=newPropertyChangedEventArgs(propertyName);
PropertyChanged(this,e);
}
}
#endregion
}
Capitalclass
publicclassCapital:
ObservableObject
{
privateintstockId;
privatestringstockCode;
privatestringstockType;
privatedoublemainInitiativeBuyVol;
privatedoublemainInitiativeSellVol;
privatedoubleretailInitiativeBuyVol;
privatedoubleretailInitiativeSellVol;
privatedoubleinstitutionBuyVol;
privatedoubleinstitutionSellVol;
privateDateTimecapitalDateTime;
publicCapital(stringstockType,stringstockCode,intstockId)
{
this.stockType=stockType;
this.stockCode=stockCode;
this.stockId=stockId;
}
publicintStockId
{
get{returnthis.stockId;}
set{this.stockId=value;}
}
publicstringStockType
{
get{returnthis.stockType;}
set{this.stockType=value;}
}
publicstringStockCode
{
get{returnthis.stockCode;}
set{this.stockCode=value;}
}
publicdoubleMainInitiativeBuyVol
{
get{returnthis.mainInitiativeBuyVol;}
set
{
this.mainInitiativeBuyVol=value;
base.RaisePropertyChangedEvent("MainInitiativeBuyVol");
}
}
publicdoubleMainInitiativeSellVol
{
get{returnthis.mainInitiativeSellVol;}
set
{
this.mainInitiativeSellVol=value;
base.RaisePropertyChangedEvent("MainInitiativeSellVol");
}
}
publicdoubleRetailInitiativeBuyVol
{
get{returnthis.retailInitiativeBuyVol;}
set
{
this.retailInitiativeBuyVol=value;
base.RaisePropertyChangedEvent("RetailInitiativeBuyVol");
}
}
publicdoubleRetailInitiativeSellVol
{
get{returnthis.retailInitiativeSellVol;}
set
{
this.retailInitiativeSellVol=value;
base.RaisePropertyChangedEvent("RetailInitiativeSellVol");
}
}
publicdoubleInstitutionBuyVol
{
get{returnthis.institutionBuyVol;}
set
{
this.institutionBuyVol=value;
base.RaisePropertyChangedEvent("InstitutionBuyVol");
}
}
publicdoubleInstitutionSellVol
{
get{returnthis.institutionSellVol;}
set
{
this.institutionSellVol=value;
base.RaisePropertyChangedEvent("InstitutionSellVol");
}
}
publicDateTimeCapitalDateTime
{
get{returnthis.capitalDateTime;}
set
{
this.capitalDateTime=value;
base.RaisePropertyChangedEvent("CapitalDateTime");
}
}
}
在每个属性值有变化时,都需要使用RaisePropertyChangedEvent(stringpropName)方法来通知view。
ViewModel部分
CapitalListViewModel是一个viewmodel。
它使用CapitalListService定时读取股票数据,并更新Capital对象。
CapitalListViewModelclass
publicclassCapitalListViewModel
{
privateCapitalListService_capitalListService;
publicCapitalListViewModel()
{
_capitalListService=newCapitalListService();
_capitalListService.Start();
Debug.WriteLine("CreateCapitalListViewModel.");
}
publicListCapitalList
{
get{return_capitalListService.CapitalList;}
}
#regionoverridemethods;
protectedoverridevoidOnDispose()
{
base.OnDispose();
_capitalListService.Stop();
}
#endregion
}
CapitalListViewMode暴露的属性CapitalList,用来绑定到view上。
CapitalListServiceclass
publicclassCapitalListService
{
privateListcapitalList;
privateboolisRunning=false;
privateintnumPerRequest=5;
privateTimertimer;
publicCapitalListService()
{
capitalList=ReadFile("sh");
capitalList.AddRange(ReadFile("sz"));
capitalList.AsReadOnly();
}
publicListCapitalList
{
get{returnthis.capitalList;}
}
publicvoidStart()
{
Debug.WriteLine("StartingCapitalListService...");
if(isRunning)
{
Stop();
}
isRunning=true;
timer=newTimer(newTimerCallback(Process),null,0,1000);
Debug.WriteLine("CapitalListServiceStarted.");
}
intcurrIndex=0;
privatevoidProcess(objectstateInfo)
{
timer.Change(Timeout.Infinite,Timeout.Infinite);
intnumOfRequest=(currIndex+numPerRequest>capitalList.Count)?
(capitalList.Count-currIndex):
numPerRequest;
Debug.WriteLine("CapitalListServiceisREFRESHING"+numOfRequest+"datas...");
if(numOfRequest==0)
{
currIndex=0;
}
else
{
ListsubList=capitalList.GetRange(currIndex,numOfRequest);
currIndex+=numOfRequest;
CapitalDataEngine.GetData(subList);
Thread.Sleep(2000);
}
if(isRunning)
timer.Change(0,1000);
else
timer=null;
}
publicvoidStop()
{
if(isRunning)
{
isRunning=false;
while(timer!
=null)
{
Thread.Sleep(5);
}
}
}
#regionInitializecapitaldata
privateListReadFile(stringstockType)
{
returnnull;//TODO:
LoadCapitallistfromdatabase.
}
#endregion
}
在CapitalListService中,创建了一个timer,并设置timer每1秒中启动一次,调用CaptialDataEngine读取数据,更新Capital对象列表。
CapitalDataEngineclass
internalclassThreadStateObject{
publicThreadStateObject(Capitalcapital,ManualResetEventresetEvent){
this.CapitalData=capital;
this.ResetEventObject=resetEvent;
}
publicCapitalCapitalData{get;set;}
publicManualResetEventResetEventObject{get;set;}
}
publicstaticclassCapitalDataEngine
{
publicstaticvoidGetData(ListcapitalList){
ManualResetEvent[]mResetEvents=newManualResetEvent[capitalList.Count];
for(inti=0;iCapitalcapital=capitalList[i];
mResetEvents[i]=newManualResetEvent(false);
ThreadStateObjectstate=newThreadStateObject(capital,mResetEvents[i]);
ThreadPool.QueueUserWorkItem(newWaitCallback(RequestCapital),state);
}
WaitHandle.WaitAll(manualEvents,newTimeSpan(0,0,5),false);
}
privatestaticvoidRequestCapital(objectstate){
ThreadStateObjectthreadStateObject=(ThreadStateObject)state;
Capitalcaptial=threadStateObject.CapitalData;
stringreturnString=string.Empty;//TODO:
sendrequesttoloadstockdata.
ParseCapitalData(returnString,captial);
threadStateObject.ResetEventObject.Set();
}
///
///Parsedatafromreturnedstringandupdatecapitalobject.
///
///
///
privatestaticvoidParseCapitalData(stringcapitalData,Capitalcapital){
//TODO:
implementsthelogictoparsedataandupdatecapitalobject.
}
}
在CapitalDataEngine中,最值得关注的部分就是ThreadPool的使用。
.netframework内部提供了一个ThreadPool,因此这里就直接使用这个ThreadPool,来实现多线程读取数据,更新Captial对象列表的任务。
ThreadPool的使用,包括三部分:
1,定义State对象,把要在线程里使用的数据,以及一个线程任务执行结束的开关对象封装在里面。
内部类ThreadStateObject就是这里所用到的state对象。
2,将workitem放入线程池队列。
在上面代码中,ThreadPool.QueueUserWorkItem(newWaitCallback(RequestCapital),state);把workitem放入了线程队列中。
3,监控处理任务的线程的结束状态。
这里使用的是WaitHandle.WaitAll(manualEvents,newTimeSpan(0,0,5),false);来等待所有的ManualResetEvent都set了以后,才退出这个方法调用
View部分
CapitalListView是一个view。
CapitalListViewModel关联到这个view,并使用binding机制,将Captial对象列表绑定到view上。
CapitalListView.xaml
Class="MyStock.View.CapitalListView"
xmlns="
xmlns:
x="
xmlns:
my="
Title="CapitalListView"Height="300"Width="765">
DataGridAutoGenerateColumns="False"Name="MainGrid"ItemsSource="{BindingCapitalList}">
DataGrid.Columns>
DataGridTextColumnHeader="StockCode"IsReadOnly="True"Width="75"Binding="{BindingPath=StockCode}"/>
DataGridTextColumnHeader="主力买量"IsReadOnly="True"Width="90"Binding="{BindingPath=MainInitiativeBuyV