幾乎是一個電腦
現在是時候使用具有Execute和CanExecute方法的ICommand對象制作更複雜的ViewModel。 下一個程式幾乎就像一個電腦,隻是它隻添加了一系列數字。 ViewModel名為AdderViewModel,該程式名為AddingMachine。
我們先來看一下截圖:

在頁面頂部,您可以看到已輸入和添加的一系列數字的曆史記錄。這是ScrollView中的Label,是以它可以變得相當長。
這些數字的總和顯示在鍵盤上方的Entry視圖中。通常,該條目視圖包含您鍵入的數字,但是在您點選鍵盤右側的大加号後,條目将顯示累計金額,加号按鈕将被禁用。您需要開始輸入nother數字,以便累積的總和消失,并且需要啟用帶加号的按鈕。同樣,隻要您開始輸入,就會啟用倒退按鈕。
這些不是唯一可以禁用的鍵。當您鍵入的數字已經有小數點時,小數點被禁用,當數字包含16個字元時,所有數字鍵都被禁用。這是為了避免條目中的數字變得太長而無法顯示。
禁用這些按鈕是在ICommand接口中實作CanExecute方法的結果。
AdderViewModel類位于Xamarin.FormsBook.Toolkit庫中,并派生自ViewModelBase。以下是具有所有公共屬性及其支援字段的類的一部分:
public class AdderViewModel : ViewModelBase
{
string currentEntry = "0";
string historyString = "";
__
public string CurrentEntry
{
private set { SetProperty(ref currentEntry, value); }
get { return currentEntry; }
}
public string HistoryString
{
private set { SetProperty(ref historyString, value); }
get { return historyString; }
}
public ICommand ClearCommand { private set; get; }
public ICommand ClearEntryCommand { private set; get; }
public ICommand BackspaceCommand { private set; get; }
public ICommand NumericCommand { private set; get; }
public ICommand DecimalPointCommand { private set; get; }
public ICommand AddCommand { private set; get; }
__
}
所有屬性都有私有set通路器。 類型字元串的兩個屬性僅在内部基于鍵抽頭設定,并且類型ICommand的屬性在AdderViewModel構造函數中設定(稍後您将看到)。
這八個公共屬性是AddderViewModel中AddingMachine項目中XAML檔案需要了解的唯一部分。 這是XAML檔案。 它包含一個用于在縱向和橫向模式之間切換的兩行和兩列主網格,以及一個Label,Entry和15 Button元素,所有這些元素都綁定到AdderViewModel的八個公共屬性之一。 請注意,所有10位數按鈕的Command屬性都綁定到NumericCommand屬性,并且按鈕由CommandParameter屬性區分。 此CommandParameter屬性的設定作為參數傳遞給Execute和CanExecute方法:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AddingMachine.AddingMachinePage"
SizeChanged="OnPageSizeChanged">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="10, 20, 10, 10"
Android="10"
WinPhone="10" />
</ContentPage.Padding>
<Grid x:Name="mainGrid">
<!-- Initialized for Portrait mode. -->
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0" />
</Grid.ColumnDefinitions>
<!-- History display. -->
<ScrollView Grid.Row="0" Grid.Column="0"
Padding="5, 0">
<Label Text="{Binding HistoryString}" />
</ScrollView>
<!-- Keypad. -->
<Grid x:Name="keypadGrid"
Grid.Row="1" Grid.Column="0"
RowSpacing="2"
ColumnSpacing="2"
WidthRequest="240"
HeightRequest="360"
VerticalOptions="Center"
HorizontalOptions="Center">
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="Button">
<Setter Property="FontSize" Value="Large" />
<Setter Property="BorderWidth" Value="1" />
</Style>
</ResourceDictionary>
</Grid.Resources>
<Label Text="{Binding CurrentEntry}"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4"
FontSize="Large"
LineBreakMode="HeadTruncation"
VerticalOptions="Center"
HorizontalTextAlignment="End" />
<Button Text="C"
Grid.Row="1" Grid.Column="0"
Command="{Binding ClearCommand}" />
<Button Text="CE"
Grid.Row="1" Grid.Column="1"
Command="{Binding ClearEntryCommand}" />
<Button Text="⇦"
Grid.Row="1" Grid.Column="2"
Command="{Binding BackspaceCommand}" />
<Button Text="+"
Grid.Row="1" Grid.Column="3" Grid.RowSpan="5"
Command="{Binding AddCommand}" />
<Button Text="7"
Grid.Row="2" Grid.Column="0"
Command="{Binding NumericCommand}"
CommandParameter="7" />
<Button Text="8"
Grid.Row="2" Grid.Column="1"
Command="{Binding NumericCommand}"
CommandParameter="8" />
<Button Text="9"
Grid.Row="2" Grid.Column="2"
Command="{Binding NumericCommand}"
CommandParameter="9" />
<Button Text="4"
Grid.Row="3" Grid.Column="0"
Command="{Binding NumericCommand}"
CommandParameter="4" />
<Button Text="5"
Grid.Row="3" Grid.Column="1"
Command="{Binding NumericCommand}"
CommandParameter="5" />
<Button Text="6"
Grid.Row="3" Grid.Column="2"
Command="{Binding NumericCommand}"
CommandParameter="6" />
<Button Text="1"
Grid.Row="4" Grid.Column="0"
Command="{Binding NumericCommand}"
CommandParameter="1" />
<Button Text="2"
Grid.Row="4" Grid.Column="1"
Command="{Binding NumericCommand}"
CommandParameter="2" />
<Button Text="3"
Grid.Row="4" Grid.Column="2"
Command="{Binding NumericCommand}"
CommandParameter="3" />
<Button Text="0"
Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"
Command="{Binding NumericCommand}"
CommandParameter="0" />
<Button Text="·"
Grid.Row="5" Grid.Column="2"
Command="{Binding DecimalPointCommand}" />
</Grid>
</Grid>
</ContentPage>
您在XAML檔案中找不到的是對AdderViewModel的引用。由于您很快就會看到的原因,AdderViewModel在代碼中執行個體化。
添加機器邏輯的核心在六個ICommand屬性的Execute和CanExecute方法中。這些屬性都在下面顯示的AdderViewModel構造函數中初始化,而Execute和CanExecute方法都是lambda函數。
當Command構造函數中隻出現一個lambda函數時,這就是Execute方法(如參數名稱所示),并且始終啟用Button。這是ClearCommand和ClearEntryCommand的情況。
所有其他Command構造函數都有兩個lambda函數。第一個是Execute方法,第二個是CanExecute方法。如果要啟用Buttons,則CanExecute方法傳回true,否則傳回false。
除了NumericCommand之外,所有ICommand屬性都使用Command類的非泛型形式設定,NumericCommand需要一個Execute和CanExecute方法的參數來識别已敲擊的鍵:
public class AdderViewModel : ViewModelBase
{
__
bool isSumDisplayed = false;
double accumulatedSum = 0;
public AdderViewModel()
{
ClearCommand = new Command(
execute: () =>
{
HistoryString = "";
accumulatedSum = 0;
CurrentEntry = "0";
isSumDisplayed = false;
RefreshCanExecutes();
});
ClearEntryCommand = new Command(
execute: () =>
{
CurrentEntry = "0";
isSumDisplayed = false;
RefreshCanExecutes();
});
BackspaceCommand = new Command(
execute: () =>
{
CurrentEntry = CurrentEntry.Substring(0, CurrentEntry.Length - 1);
if (CurrentEntry.Length == 0)
{
CurrentEntry = "0";
}
RefreshCanExecutes();
},
canExecute: () =>
{
return !isSumDisplayed && (CurrentEntry.Length > 1 || CurrentEntry[0] != '0');
});
NumericCommand = new Command<string>(
execute: (string parameter) =>
{
if (isSumDisplayed || CurrentEntry == "0")
CurrentEntry = parameter;
else
CurrentEntry += parameter;
isSumDisplayed = false;
RefreshCanExecutes();
},
canExecute: (string parameter) =>
{
return isSumDisplayed || CurrentEntry.Length < 16;
});
DecimalPointCommand = new Command(
execute: () =>
{
if (isSumDisplayed)
CurrentEntry = "0.";
else
CurrentEntry += ".";
isSumDisplayed = false;
RefreshCanExecutes();
},
canExecute: () =>
{
return isSumDisplayed || !CurrentEntry.Contains(".");
});
AddCommand = new Command(
execute: () =>
{
double value = Double.Parse(CurrentEntry);
HistoryString += value.ToString() + " + ";
accumulatedSum += value;
CurrentEntry = accumulatedSum.ToString();
isSumDisplayed = true;
RefreshCanExecutes();
},
canExecute: () =>
{
return !isSumDisplayed;
});
}
void RefreshCanExecutes()
{
((Command)BackspaceCommand).ChangeCanExecute();
((Command)NumericCommand).ChangeCanExecute();
((Command)DecimalPointCommand).ChangeCanExecute();
((Command)AddCommand).ChangeCanExecute();
}
__
}
所有的Execute方法都是通過在構造函數之後調用名為RefreshCanExecute的方法來結束的。此方法調用實作CanExecute方法的四個Command對象中的每一個的ChangeCanExecute方法。該方法調用導緻Command對象觸發ChangeCanExecute事件。每個Button通過再次調用CanExecute方法來響應該事件,以确定是否應該啟用Button。
每個Execute方法都不需要調用所有四個ChangeCanExecute方法。例如,當NumericCommand的Execute方法執行時,不需要調用DecimalPointCommand的ChangeCanExecute方法。然而,事實證明,在邏輯和代碼整合方面更容易 - 隻需在每次按鍵後調用它們。
您可能更習慣将這些Execute和CanExecute方法實作為正常方法而不是lambda函數。或者你可能更舒服隻有一個Command對象來處理所有的鍵。每個鍵都可以有一個辨別CommandParameter字元串,您可以使用switch和case語句區分它們。
有很多方法可以實作指令邏輯,但應該清楚的是,指令的使用傾向于以靈活和理想的方式構造代碼。
一旦添加邏輯到位,為什麼不為減法,乘法和除法添加幾個按鈕?
好吧,增強邏輯來接受多個操作而不僅僅是一個操作并不是那麼容易。如果程式支援多個操作,則當使用者鍵入其中一個操作鍵時,需要儲存該操作以等待下一個數字。隻有在下一個數字完成後(通過按下另一個操作鍵或等号鍵發出信号)才會應用儲存的操作。
更簡單的方法是編寫反向波蘭表示法(RPN)電腦,其中操作在第二個數字的輸入之後。 RPN邏輯的簡單性是RPN電腦如此吸引程式員的一個重要原因!