(四)Market模块之TrendLineView分析
这一章我们添加Market模块到RI中。本章代码下载:MyRI_1.zip
需求分析:
要实现一个View,能根据PositionSummaryView的选取,而相应发生变化,我们将其命名为TrendLineView.xaml。这里使用到了一个现成的控件LineChart,它来自于StockTraderRI.ChartControls,我们只需要把它的ItemsSource属性与MarketHistory集合绑定在一起就可以了。另一个需要绑带的就是左上角的文字,根据PositionSummaryView中选取项的不同,而相应的改变文字,比如说STOCK2。
此外,为了模拟真实世界中股票的时刻变化,在Prism提供的源码中,创建了一个Timer来随机产生数字,从而影响PositionSummaryView中GridView中数据的变化——这无疑增大了理解Prism框架的难度。于是,在我的程序中,暂时抛弃了这一部分,在这个系列的最后一章,会将它加上。
准备工作
1. 创建Data文件夹,在其中创建两个数据文件Market.xml和MarketHistory.xml。然后打开项目的资源文件Resources.resx,选择File选项,把这两个数据文件复制过去。
2. 创建Service文件夹,创建两个接口IMarketFeedService和IMarketHistoryService,从而为以后实现依赖注入做准备。
public interface IMarketFeedService { decimal GetPrice(string tickerSymbol); long GetVolume(string tickerSymbol); bool SymbolExists(string tickerSymbol); }public interface IMarketHistoryService { MarketHistoryCollection GetPriceHistory(string tickerSymbol); }
可以看到,接口所提供的方法,都是和数据源进行交互。
3. 在Service文件夹中,创建两个Service,即MarketFeedService和MarketHistoryService,分别派生自上面两个接口,分别用来读取上面两个数据文件。
下面是MarketFeedService在调试时的例子。
下面开始设计TrendLineView。
1.这次我们使用的是VM模式,即只有View和Model,而把Presenter融入到Model中。同时还实现了V中控件和M的数据绑定(通过DataContext)。
为此,要创建ITrendLineView和ITrendLinePresentationModel接口,并创建TrendLineView和TrendLinePresentationModel,分别实现上面的接口,其中,TrendLineView还是一个UserControl。
2.此外,我们采取Presenter-first(而不是View-first)的方式。首先要修改MarketModule,添加依赖注入,注册接口和实现类的mapping关系。
注意到,在注册Region和View之间Mapping关系的时候,原先是
regionViewRegistry.RegisterViewWithRegion("MarketRegion", typeof(Views.MarketView));
而现在我们要修改为
this.regionManager.RegisterViewWithRegion(RegionNames.ResearchRegion, () => this.container.Resolve<ITrendLinePresentationModel>().View);
就是说,把IRegionViewRegistry替换为IRegionManager,我翻阅过Prism实现源码,发现二者是没有区别的,随便使用哪个都可以,这里我们采用IRegionManager,以和原来的版本保持一致。
P-first和V-first的区别仅在于RegisterViewWithRegion方法的第2个参数,对于typeof(Views.MarketView),就是说先进入View,然后在View中实例化Presenter;而对于
this.container.Resolve<ITrendLinePresentationModel>().View
,就是说,先实例化Presenter(这里是Model),然后在Presenter中实例化View并返回。这就产生了二者的构造函数在执行上的先后顺序。
3.设计ITrendLineView和ITrendLinePresentationModel接口
public interface ITrendLineView { ITrendLinePresentationModel Model { get; set; } }public interface ITrendLinePresentationModel { ITrendLineView View { get; } string TickerSymbol { get; } MarketHistoryCollection HistoryCollection { get; } }
我们要遵循这样的设计原则。首先,V和M要互相引用,也就是这两个接口中的Model和View属性。其次,要把Model中用于绑定的属性放到接口中,这里是TickerSymbol和HistoryCollection。
4.设计TrendLineView
View一般都很简单,在xaml中绑定到空间,在后台cs文件中,实现Model属性,将它绑定到这个UserControl的DataContext就可以了。
5.设计TrendLinePresentationModel
具体的逻辑都在这里。首先,如果有联动属性,那么Model就要实现INotifyPropertyChanged接口,这里TickerSymbol和HistoryCollection都是联动属性。其次,就是构造函数了:
public TrendLinePresentationModel(ITrendLineView view, IMarketHistoryService marketHistoryService, IEventAggregator eventAggregator) { this.View = view; this.View.Model = this; this._marketHistoryService = marketHistoryService; eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Subscribe(this.TickerSymbolChanged); }
我们要遵循这样的设计原则:既然是P-first,那么就要在Model的构造函数中创建View:
this.View = view;
,并指定这个View的Model属性是自己:
this.View.Model = this;
嗯,好费解的2句话,是不是?——但是缺一不可。
构造函数的最后是订阅TickerSymbolSelectedEvent事件。当我们在PositionSummaryView的GridView中选中不同的行时,会触发这个事件,于是,饼图会因为数据源的改变而发生变化。考虑到这个事件在PositionSummaryView项目中也会使用到,因此,我们把它放到了类库StockTraderRI.Infrastructure中。
至此,Market创建完毕,但是运行程序后不会看到任何数据,因为Market模块只是订阅者,也就是说,只有当源PositionSummaryView实现了Publish事件,才会看到TrendLineView中的数据。