ViewModel 类与图形层的交互
如前所述,ViewModel 是应用程序的图形部分与软件逻辑实现之间的连接器。 它是程序图形表述,其实现应用程序逻辑调用,并针对应用程序逻辑部分的回调在图形上做出反应。 相应地,来自 ViewModel 部分的公开属性对应于应用程序图形部分中的每个可编辑字段。 这些属性可以是 getter(只读),在这种情况下不能在图形中更改;也可以是 setter,如此即可覆盖隐藏在此属性后面的对象。 在前面的部分里,我们已经详细研究过数据绑定技术。 故此,我在这里仅提供一些示例。
文本字段是通过可读写权限的属性进行连接。 举例,考虑一个字段,该字段指示正在执行优化的资产名称。 该字段的 XAML 标记极其简单。
<TextBox Width="100" IsEnabled="{Binding EnableMainTogles, UpdateSourceTrigger=PropertyChanged}" Text="{Binding AssetName}"/>
除了设置文本窗口的宽度外,它还含有字段 IsEnabled 和 Text。 第一个设置该字段是否可编辑。 如果将其设置为 true,则该字段可编辑。 如果为 false,则该字段被锁定。 “Text” 字段包含在此字段中输入的文本。 然后,每个结构都有一对花括号。 其内容设置对象与特定公共属性的连接,而属性来自在 “Binding” 参数之后指定的 ViewModel 类。
后还可以跟一定数量的参数。 例如,UpdateSourceTrigger 参数指示此应用程序的图形部分的更新方法。 在我们的示例中使用的值(PropertyChanged)表示,仅当触发 ViewModel 类中的 OnPropertyChanged 事件时,图形部分才会更新,并且在 “Binding” 参数之后指定传递的名称(在本例中为 “EnableMainTogles”) 。
如果 “Text” 参数并未与字符串绑定,而是绑定 double 型参数,则此字段中仅允许数字。 如果绑定到 int 类型,则只允许整数型。 换言之,此实现能够依据需求设置输入值的类型。
在 ViewModel 部分里,字段显示如下:
IsEnabled 参数:
/// <summary> /// If the switch = false, then the most important fields are not available /// </summary> public bool EnableMainTogles { get; private set; } = true;
以及 Text 参数:
/// <summary> /// Name of the asset selected for tests / optimization /// </summary> public string AssetName { get; set; }
如您所见,它们两个都即可写入也可读取数据。 仅有的区别在于 EnableMainTogles 属性仅提供来自 AutoOptimiserVM 类的写入访问权限(即,来自其自身),因此无法从外部对其进行编辑。
如果我们研究任何数据集合,譬如举例来说,前向验证优化结果列表,则它所对应的属性包含数值列表。 我们来研究一个含前向验证结果的表格:
<ListView ItemsSource="{Binding ForwardOptimisations}" SelectedIndex="{Binding SelectedForwardItem}" v:ListViewExtention.DoubleClickCommand="{Binding StartTestForward}"> <ListView.View> <GridView> <GridViewColumn Header="Date From" DisplayMemberBinding="{Binding From}"/> <GridViewColumn Header="Date Till" DisplayMemberBinding="{Binding Till}"/> <GridViewColumn Header="Payoff" DisplayMemberBinding="{Binding Payoff}"/> <GridViewColumn Header="Profit pactor" DisplayMemberBinding="{Binding ProfitFactor}"/> <GridViewColumn Header="Average Profit Factor" DisplayMemberBinding="{Binding AverageProfitFactor}"/> <GridViewColumn Header="Recovery factor" DisplayMemberBinding="{Binding RecoveryFactor}"/> <GridViewColumn Header="Average Recovery Factor" DisplayMemberBinding="{Binding AverageRecoveryFactor}"/> <GridViewColumn Header="PL" DisplayMemberBinding="{Binding PL}"/> <GridViewColumn Header="DD" DisplayMemberBinding="{Binding DD}"/> <GridViewColumn Header="Altman Z score" DisplayMemberBinding="{Binding AltmanZScore}"/> <GridViewColumn Header="Total trades" DisplayMemberBinding="{Binding TotalTrades}"/> <GridViewColumn Header="VaR 90" DisplayMemberBinding="{Binding VaR90}"/> <GridViewColumn Header="VaR 95" DisplayMemberBinding="{Binding VaR95}"/> <GridViewColumn Header="VaR 99" DisplayMemberBinding="{Binding VaR99}"/> <GridViewColumn Header="Mx" DisplayMemberBinding="{Binding Mx}"/> <GridViewColumn Header="Std" DisplayMemberBinding="{Binding Std}"/> </GridView> </ListView.View> </ListView>
从标记中可以看出,ListView 类型表是表格类本身的引用。 接下来是创建网格,即会在其中存储数据,和数据列。 提到所创建的类引用,我指的是 ListView 类。 这种看似简单的 XAML 标记代表了一种相当复杂,且经过深思熟虑的机制,该机制允许利用标记语言描述类,并操控类对象。 我们与 AutoOptimiserVM 类关联的所有字段都是这些类的属性。 在上面的表格示例中,我们处理了三个类:
- ListView — System.Windows.Controls.ListView.
- GridView — System.Windows.Controls.GridView,它是从 System.Windows.Controls.ViewBase 派生的,因此可用作 ListView 类 View 属性的初始化类。
- GridViewColumn — System.Windows.Controls.GridViewColumn.
ListView 类的 ItemsSource属性代表由表格构成的元素集合。 将此属性与 ViewModel 的集合连接后,我们为 Window 类提供了一种 DataContext,该 DataContext 要在表格中操作。 由于我们正在谈论一个表格,因此代表集合的表格必须由含有每个表格公开属性的类构成。 将 ItemsSource 属性与 ViewModel 的属性绑定在一起之后,该属性表示一个带有数据的表格,我们可以将每列与给定表格中的所需列值进行绑定。 此外,该表格还含有 SelectedIndex 属性和 ViewModel 中的 SelectedForwardItem 属性的连接。 ViewModel 需要知道用户在此表格中所选择的行。
在 ViewModel 部分中,与表格呈现绑定的属性实现如下:
/// <summary> /// Selected forward tests /// </summary> public ObservableCollection<ReportItem> ForwardOptimisations { get; } = new ObservableCollection<ReportItem>();
C# 标准库中的 ObservableCollection 类是一个对象,用于通知图形有关修改的信息。 这是因为该类已经含所提到的事件,并在每次更新其元素列表时都会调用它。 至于其余的,它是标准的数据集合。
SelectedForwardItem 属性执行若干角色:它在所选表格行上存储数据,并作为行选择回调。
/// <summary> /// Selected forward pass /// </summary> private int _selectedForwardItem; public int SelectedForwardItem { get => _selectedForwardItem; set { _selectedForwardItem = value; if (value > -1) { FillInBotParams(model.ForwardOptimisations[value]); FillInDailyPL(model.ForwardOptimisations[value]); FillInMaxPLDD(model.ForwardOptimisations[value]); } } }
由于该属性用作回调,因此(在我们的示例中)期望针对所设置的数值做出特别反应,故此 setter 必须包含此反应的实现,并作为函数。 由此,属性值存储在私密变量中。 若要从该变量接收数值,我们可从取值器(getter)直接访问它。 若要设置一个值,在赋值器(setter)中将数值存储在 "value" 变量里。'value' 变量没有题标,在 C# 语言里作为设置数值的特定别名。 如果 “value” 大于-1,则在“结果”选项卡中填充其他相关表格,这些表格会根据所选行进行更新。 这些表格包含交易机器人参数,平均利润,交易日的亏损,以及盈亏的最高/最低值。 需要在 “if” 条件下执行检查,如果因为所选表项索引为 -1,则意味着表格为空,因此不需要填充相关表格。 AutoOptimiserVM 类代码中提供了调用方法的实现。
此处是类的实现,优化结果也含有说明行。
/// <summary> /// Class - a wrapper for a report item (for a graphical interval) /// </summary> class ReportItem { /// <summary> /// Constructor /// </summary> /// <param name="item">Item</param> public ReportItem(OptimisationResult item) { result = item; } /// <summary> /// Report item /// </summary> private readonly OptimisationResult result; public DateTime From => result.report.DateBorders.From; public DateTime Till => result.report.DateBorders.Till; public double SortBy => result.SortBy; public double Payoff => result.report.OptimisationCoefficients.Payoff; public double ProfitFactor => result.report.OptimisationCoefficients.ProfitFactor; public double AverageProfitFactor => result.report.OptimisationCoefficients.AverageProfitFactor; public double RecoveryFactor => result.report.OptimisationCoefficients.RecoveryFactor; public double AverageRecoveryFactor => result.report.OptimisationCoefficients.AverageRecoveryFactor; public double PL => result.report.OptimisationCoefficients.PL; public double DD => result.report.OptimisationCoefficients.DD; public double AltmanZScore => result.report.OptimisationCoefficients.AltmanZScore; public int TotalTrades => result.report.OptimisationCoefficients.TotalTrades; public double VaR90 => result.report.OptimisationCoefficients.VaR.Q_90; public double VaR95 => result.report.OptimisationCoefficients.VaR.Q_95; public double VaR99 => result.report.OptimisationCoefficients.VaR.Q_99; public double Mx => result.report.OptimisationCoefficients.VaR.Mx; public double Std => result.report.OptimisationCoefficients.VaR.Std; }