第二十章:非同步和檔案I/O.(二十一)

wangccsy發表於2019-01-01

因此,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處理程式。


相關文章