因此,MandelbrotViewModel类具有许多属性,但如果您不考虑用户界面,则可能与您定义的属性不同。 CurrentCenter属性是程序当前显示的图像中心的复数,CurrentMagnification也适用于该图像。 但TargetMagnification绑定到Stepper的当前设置,它将应用于下一个计算的图像。 RealOffset和ImaginaryOffset属性绑定到两个Slider元素,范围从0到1.从CurrentCenter,CurrentMagnification,RealOffset和ImaginaryOffset属性,ViewModel可以计算TargetCenter属性。 这是下一个计算图像的中心。 如您所见,该TargetCenter属性用于显示两个滑块下方的复数:
namespace MandelbrotXF
{
class MandelbrotViewModel : ViewModelBase
{
// Set via constructor arguments.
readonly double baseWidth;
readonly double baseHeight;
// Backing fields for properties.
Complex currentCenter, targetCenter;
int pixelWidth, pixelHeight;
double currentMagnification, targetMagnification;
int iterations;
double realOffset, imaginaryOffset;
bool isBusy;
double progress;
BitmapInfo bitmapInfo;
public MandelbrotViewModel(double baseWidth, double baseHeight)
{
this.baseWidth = baseWidth;
this.baseHeight = baseHeight;
// Create MandelbrotModel object.
MandelbrotModel model = new MandelbrotModel();
// Progress reporter
Progress<double> progressReporter = new Progress<double>((double progress) =>
{
Progress = progress;
});
CancellationTokenSource cancelTokenSource = null;
// Define CalculateCommand and CancelCommand.
CalculateCommand = new Command(
execute: async () =>
{
// Disable this button and enable Cancel button.
IsBusy = true;
((Command)CalculateCommand).ChangeCanExecute();
((Command)CancelCommand).ChangeCanExecute();
// Create CancellationToken.
cancelTokenSource = new CancellationTokenSource();
CancellationToken cancelToken = cancelTokenSource.Token;
try
{
// Perform the calculation.
BitmapInfo = await model.CalculateAsync(TargetCenter,
baseWidth / TargetMagnification,
baseHeight / TargetMagnification,
PixelWidth, PixelHeight,
Iterations,
progressReporter,
cancelToken);
// Processing only for a successful completion.
CurrentCenter = TargetCenter;
CurrentMagnification = TargetMagnification;
RealOffset = 0.5;
ImaginaryOffset = 0.5;
}
catch (OperationCanceledException)
{
// Operation cancelled!
}
catch
{
// Another type of exception? This should not occur.
}
// Processing regardless of success or cancellation.
Progress = 0;
IsBusy = false;
// Disable Cancel button and enable this button.
((Command)CalculateCommand).ChangeCanExecute();
((Command)CancelCommand).ChangeCanExecute();
},
canExecute: () =>
{
return !IsBusy;
});
CancelCommand = new Command(
execute: () =>
{
cancelTokenSource.Cancel();
},
canExecute: () =>
{
return IsBusy;
});
}
public int PixelWidth
{
set { SetProperty(ref pixelWidth, value); }
get { return pixelWidth; }
}
public int PixelHeight
{
set { SetProperty(ref pixelHeight, value); }
get { return pixelHeight; }
}
public Complex CurrentCenter
{
set
{
if (SetProperty(ref currentCenter, value))
CalculateTargetCenter();
}
get { return currentCenter; }
}
public Complex TargetCenter
{
private set { SetProperty(ref targetCenter, value); }
get { return targetCenter; }
}
public double CurrentMagnification
{
set { SetProperty(ref currentMagnification, value); }
get { return currentMagnification; }
}
public double TargetMagnification
{
set { SetProperty(ref targetMagnification, value); }
get { return targetMagnification; }
}
public int Iterations
{
set { SetProperty(ref iterations, value); }
get { return iterations; }
}
// These two properties range from 0 to 1.
// They indicate a new center relative to the
// current width and height, which is the baseWidth
// and baseHeight divided by CurrentMagnification.
public double RealOffset
{
set
{
if (SetProperty(ref realOffset, value))
CalculateTargetCenter();
}
get { return realOffset; }
}
public double ImaginaryOffset
{
set
{
if (SetProperty(ref imaginaryOffset, value))
CalculateTargetCenter();
}
get { return imaginaryOffset; }
}
void CalculateTargetCenter()
{
double width = baseWidth / CurrentMagnification;
double height = baseHeight / CurrentMagnification;
TargetCenter = new Complex(CurrentCenter.Real + (RealOffset - 0.5) * width,
CurrentCenter.Imaginary + (ImaginaryOffset - 0.5) *
height);
}
public bool IsBusy
{
private set { SetProperty(ref isBusy, value); }
get { return isBusy; }
}
public double Progress
{
private set { SetProperty(ref progress, value); }
get { return progress; }
}
public BitmapInfo BitmapInfo
{
private set { SetProperty(ref bitmapInfo, value); }
get { return bitmapInfo; }
}
public ICommand CalculateCommand { private set; get; }
public ICommand CancelCommand { private set; get; }
}
}
MandelbrotViewModel还为Calculate和Cancel按钮,Progress属性和IsBusy属性定义了ICommand类型的两个属性。 正如您将看到的,IsBusy属性用于显示这两个按钮中的一个并隐藏另一个按钮,并在计算过程中禁用其余的用户界面。 这两个ICommand属性是在类的构造函数中使用lambda函数实现的。
XAML文件中的数据绑定到MandelbrotViewModel中的属性需要Xamarin.FormsBook.Toolkit库中的两个新绑定转换器。 第一个简单地否定了bool值:
namespace Xamarin.FormsBook.Toolkit
{
public class BooleanNegationConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return !(bool)value;
}
}
}
它与ViewModel的IsBusy属性一起使用。 当IsBusy为true时,需要将几个元素的IsEnabled属性和Go按钮的IsVisible属性设置为false。
两个Stepper元素实际上控制ViewModel中值的指数。 例如,Stepper值为8,对应于Iterations或TargetMagnification值256.该转换需要base-2对数转换器:
namespace Xamarin.FormsBook.Toolkit
{
public class BaseTwoLogConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value is int)
{
return Math.Log((int)value) / Math.Log(2);
}
return Math.Log((double)value) / Math.Log(2);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
double returnValue = Math.Pow(2, (double)value);
if (targetType == typeof(int))
{
return (int) returnValue;
}
return returnValue;
}
}
}
这是XAML文件,绑定到ViewModel的Progress,RealOffset,ImaginaryOffset,TargetCenter,TargetMagnification,Iterations,IsBusy,CalculateCommand和CancelCommand属性:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="MandelbrotXF.MandelbrotXFPage"
SizeChanged="OnPageSizeChanged">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<ContentPage.Resources>
<ResourceDictionary>
<toolkit:BooleanNegationConverter x:Key="negate" />
<toolkit:BaseTwoLogConverter x:Key="base2log" />
</ResourceDictionary>
</ContentPage.Resources>
<Grid x:Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0" />
</Grid.ColumnDefinitions>
<!-- Image for determining pixels per unit. -->
<Image x:Name="testImage"
Grid.Row="0" Grid.Column="0"
Opacity="0"
HorizontalOptions="Center"
VerticalOptions="Center" />
<!-- Image for Mandelbrot Set. -->
<Image x:Name="image"
Grid.Row="0" Grid.Column="0"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
SizeChanged="OnImageSizeChanged" />
<AbsoluteLayout x:Name="crossHairLayout"
Grid.Row="0" Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
SizeChanged="OnCrossHairLayoutSizeChanged">
<AbsoluteLayout.Resources>
<ResourceDictionary>
<Style TargetType="BoxView">
<Setter Property="Color" Value="White" />
<Setter Property="AbsoluteLayout.LayoutBounds" Value="0,0,0,0" />
</Style>
</ResourceDictionary>
</AbsoluteLayout.Resources>
<BoxView x:Name="realCrossHair" />
<BoxView x:Name="imagCrossHair" />
<BoxView x:Name="topBox" />
<BoxView x:Name="bottomBox" />
<BoxView x:Name="leftBox" />
<BoxView x:Name="rightBox" />
</AbsoluteLayout>
<StackLayout x:Name="controlPanelStack"
Grid.Row="1" Grid.Column="0"
Padding="10">
<ProgressBar Progress="{Binding Progress}"
VerticalOptions="CenterAndExpand" />
<StackLayout VerticalOptions="CenterAndExpand">
<Slider Value="{Binding RealOffset, Mode=TwoWay}"
IsEnabled="{Binding IsBusy, Converter={StaticResource negate}}" />
<Slider Value="{Binding ImaginaryOffset, Mode=TwoWay}"
IsEnabled="{Binding IsBusy, Converter={StaticResource negate}}" />
<Label Text="{Binding TargetCenter, StringFormat='{0}'}"
FontSize="Small"
HorizontalTextAlignment="Center" />
</StackLayout>
<Grid VerticalOptions="CenterAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Magnification factor stepper and display. -->
<Stepper x:Name="magnificationStepper"
Grid.Row="0" Grid.Column="0"
Value="{Binding TargetMagnification,
Converter={StaticResource base2log}}"
IsEnabled="{Binding IsBusy, Converter={StaticResource negate}}"
VerticalOptions="Center" />
<StackLayout Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
Spacing="0"
VerticalOptions="Start">
<Label Text="zoom 2"
FontSize="Medium" />
<Label Text="{Binding Source={x:Reference magnificationStepper},
Path=Value,
StringFormat='{0}'}"
FontSize="Micro" />
</StackLayout>
<!-- Iterations factor stepper and display. -->
<Stepper x:Name="iterationsStepper"
Grid.Row="1" Grid.Column="0"
Value="{Binding Iterations, Converter={StaticResource base2log}}"
IsEnabled="{Binding IsBusy, Converter={StaticResource negate}}"
VerticalOptions="Center" />
<StackLayout Grid.Row="1" Grid.Column="1"
Orientation="Horizontal"
Spacing="0"
VerticalOptions="End">
<Label Text="loop 2"
FontSize="Medium" />
<Label Text="{Binding Source={x:Reference iterationsStepper},
Path=Value,
StringFormat='{0}'}"
FontSize="Micro" />
</StackLayout>
<!-- Go / Cancel buttons. -->
<Grid Grid.Row="0" Grid.Column="1" Grid.RowSpan="2"
HorizontalOptions="End"
VerticalOptions="Center">
<Button Text="Go"
Command="{Binding CalculateCommand}"
IsVisible="{Binding IsBusy, Converter={StaticResource negate}}" />
<Button Text="Cancel"
Command="{Binding CancelCommand}"
IsVisible="{Binding IsBusy}" />
</Grid>
</Grid>
</StackLayout>
</Grid>
</ContentPage>
此XAML文件仅安装三个事件处理程序,它们都是SizeChanged处理程序。