天天看點

第二十章:異步和檔案I/O.(二十二)

第一個SizeChanged處理程式位于頁面本身。代碼隐藏檔案使用此處理程式,使用您在先前示例中看到的技術,将mainGrid及其子項用于縱向或橫向模式。

第二個SizeChanged處理程式位于Image元素上。代碼隐藏檔案使用它來調整顯示十字準線和放大框的AbsoluteLayout的大小。在假設圖像将顯示方形位圖的情況下,必須使此AbsoluteLayout與Image顯示的位圖大小相同。

第三個SizeChanged處理程式位于AbsoluteLayout上,是以可以重新繪制十字準線和放大框以進行大小更改。

MandelbrotXF程式還執行一些小技巧,以確定位圖包含最佳像素數,這在位圖的像素與顯示器的像素之間存在一對一映射時發生。 XAML檔案包含名為testImage的第二個Image元素。此圖像不可見,因為不透明度設定為零,并且它是水準和垂直居中的,這意味着它将以一對一像素映射顯示。代碼隐藏檔案建立一個120像素的方形位圖,該位圖設定為此Image。 Image的結果大小讓程式知道與裝置無關的單元有多少像素,并且可以使用它來計算Mandelbrot位圖的最佳像素大小。 (不幸的是,它不适用于Windows運作時平台。)

這裡大緻是MandelbrotXFPage的代碼隐藏檔案的前半部分,主要顯示了MandelbrotViewModel類的執行個體化以及這些SizeChanged處理程式的互動:

namespace MandelbrotXF
{
    public partial class MandelbrotXFPage : ContentPage
    {
        MandelbrotViewModel mandelbrotViewModel;
        double pixelsPerUnit = 1;
        public MandelbrotXFPage()
        {
            InitializeComponent();
            // Instantiate ViewModel and get saved values.
            mandelbrotViewModel = new MandelbrotViewModel(2.5, 2.5)
            {
                PixelWidth = 1000,
                PixelHeight = 1000,
                CurrentCenter = new Complex(GetProperty("CenterReal", -0.75),
                                            GetProperty("CenterImaginary", 0.0)),
                CurrentMagnification = GetProperty("Magnification", 1.0),
                TargetMagnification = GetProperty("Magnification", 1.0),
                Iterations = GetProperty("Iterations", 8),
                RealOffset = 0.5,
                ImaginaryOffset = 0.5
            };
             // Set BindingContext on page.
            BindingContext = mandelbrotViewModel;
            // Set PropertyChanged handler on ViewModel for "manual" processing.
            mandelbrotViewModel.PropertyChanged += OnMandelbrotViewModelPropertyChanged;
            // Create test image to obtain pixels per device-independent unit.
            BmpMaker bmpMaker = new BmpMaker(120, 120);
            testImage.SizeChanged += (sender, args) =>
                {
                    pixelsPerUnit = bmpMaker.Width / testImage.Width;
                    SetPixelWidthAndHeight();
                 };
            testImage.Source = bmpMaker.Generate();
            // Gradually reduce opacity of crosshairs.
            Device.StartTimer(TimeSpan.FromMilliseconds(100), () =>
                {
                    realCrossHair.Opacity -= 0.01;
                    imagCrossHair.Opacity -= 0.01;
                    return true;
                });
        }
        // Method for accessing Properties dictionary if key is not yet present.
        T GetProperty<T>(string key, T defaultValue)
        {
            IDictionary<string, object> properties = Application.Current.Properties;
            if (properties.ContainsKey(key))
            {
                return (T)properties[key];
            }
            return defaultValue;
        }
        // Switch between portrait and landscape mode.
        void OnPageSizeChanged(object sender, EventArgs args)
        {
            if (Width == -1 || Height == -1)
                return;
            // Portrait mode.
            if (Width < Height)
            {
                mainGrid.RowDefinitions[1].Height = GridLength.Auto;
                mainGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Absolute);
                Grid.SetRow(controlPanelStack, 1);
                Grid.SetColumn(controlPanelStack, 0);
            }
            // Landscape mode.
            else
            {
                mainGrid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Absolute);
                mainGrid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
                Grid.SetRow(controlPanelStack, 0);
                Grid.SetColumn(controlPanelStack, 1);
            }
        }
        void OnImageSizeChanged(object sender, EventArgs args)
        {
            // Assure that crosshair layout is same size as Image.
            double size = Math.Min(image.Width, image.Height);
            crossHairLayout.WidthRequest = size;
            crossHairLayout.HeightRequest = size;
            // Calculate the pixel size of the Image element.
            SetPixelWidthAndHeight();
        }
        // Sets the Mandelbrot bitmap to optimum pixel width and height.
        void SetPixelWidthAndHeight()
        {
            int pixels = (int)(pixelsPerUnit * Math.Min(image.Width, image.Height));
            mandelbrotViewModel.PixelWidth = pixels;
            mandelbrotViewModel.PixelHeight = pixels;
        }
        // Redraw crosshairs if the crosshair layout changes size.
        void OnCrossHairLayoutSizeChanged(object sender, EventArgs args)
        {
            SetCrossHairs();
        }
    __
 
    }
}            

而不是将一堆事件處理程式附加到XAML檔案中的使用者界面元素,而是代碼隐藏檔案的構造函數将PropertyChanged處理程式附加到MandelbrotViewModel執行個體。 對多個屬性的更改需要重新繪制十字準線和大小調整框,并且對任何屬性的任何更改都會使十字準線重新進入視圖:

{
    {
        __
        async void OnMandelbrotViewModelPropertyChanged(object sender, 
                                                        PropertyChangedEventArgs args)
        {
            // Set opacity back to 1.
            realCrossHair.Opacity = 1;
            imagCrossHair.Opacity = 1;
            switch (args.PropertyName)
            {
                case "RealOffset":
                case "ImaginaryOffset":
                case "CurrentMagnification":
                case "TargetMagnification":
                    // Redraw crosshairs if these properties change
                    SetCrossHairs();
                    break;
                case "BitmapInfo":
                    // Create bitmap based on the iteration counts.
                    DisplayNewBitmap(mandelbrotViewModel.BitmapInfo);
                    // Save properties for the next time program is run.
                    IDictionary<string, object> properties = Application.Current.Properties;
                    properties["CenterReal"] = mandelbrotViewModel.TargetCenter.Real;
                    properties["CenterImaginary"] = mandelbrotViewModel.TargetCenter.Imaginary;
                    properties["Magnification"] = mandelbrotViewModel.TargetMagnification;
                    properties["Iterations"] = mandelbrotViewModel.Iterations;
                    await Application.Current.SavePropertiesAsync();
                    break;
            }
        }
        void SetCrossHairs()
        {
            // Size of the layout for the crosshairs and zoom box.
            Size layoutSize = new Size(crossHairLayout.Width, crossHairLayout.Height);
            // Fractional position of center of crosshair.
            double xCenter = mandelbrotViewModel.RealOffset; 
            double yCenter = 1 - mandelbrotViewModel.ImaginaryOffset; 
            // Calculate dimension of zoom box.
            double boxSize = mandelbrotViewModel.CurrentMagnification / 
                             mandelbrotViewModel.TargetMagnification;
            // Fractional positions of zoom box corners.
            double xLeft = xCenter - boxSize / 2;
            double xRight = xCenter + boxSize / 2;
            double yTop = yCenter - boxSize / 2;
            double yBottom = yCenter + boxSize / 2;
            // Set all the layout bounds.
            SetLayoutBounds(realCrossHair, 
                            new Rectangle(xCenter, yTop, 0, boxSize), 
                            layoutSize);
            SetLayoutBounds(imagCrossHair,
                            new Rectangle(xLeft, yCenter, boxSize, 0), 
                            layoutSize);
            SetLayoutBounds(topBox, new Rectangle(xLeft, yTop, boxSize, 0), layoutSize);
            SetLayoutBounds(bottomBox, new Rectangle(xLeft, yBottom, boxSize, 0), layoutSize);
            SetLayoutBounds(leftBox, new Rectangle(xLeft, yTop, 0, boxSize), layoutSize);
            SetLayoutBounds(rightBox, new Rectangle(xRight, yTop, 0, boxSize), layoutSize);
        }
        void SetLayoutBounds(View view, Rectangle fractionalRect, Size layoutSize)
        {
            if (layoutSize.Width == -1 || layoutSize.Height == -1)
            {
                AbsoluteLayout.SetLayoutBounds(view, new Rectangle());
                return;
            }
            const double thickness = 1;
            Rectangle absoluteRect = new Rectangle();
            // Horizontal lines.
            if (fractionalRect.Height == 0 && fractionalRect.Y > 0 && fractionalRect.Y < 1)
            {
                double xLeft = Math.Max(0, fractionalRect.Left);
                double xRight = Math.Min(1, fractionalRect.Right);
                absoluteRect = new Rectangle(layoutSize.Width * xLeft,
                                             layoutSize.Height * fractionalRect.Y,
                                             layoutSize.Width * (xRight - xLeft),
                                             thickness);
            }
            // Vertical lines.
            else if (fractionalRect.Width == 0 && fractionalRect.X > 0 && fractionalRect.X < 1)
            {
                double yTop = Math.Max(0, fractionalRect.Top);
                double yBottom = Math.Min(1, fractionalRect.Bottom);
                absoluteRect = new Rectangle(layoutSize.Width * fractionalRect.X,
                                             layoutSize.Height * yTop,
                                             thickness,
                                             layoutSize.Height * (yBottom - yTop));
            }
            AbsoluteLayout.SetLayoutBounds(view, absoluteRect);
        }
        __
    }
}           

該程式的早期版本試圖使用AbsoluteLayout的比例大小和定位功能為六個BoxView元素,但它變得太難了。 小數值傳遞給SetLayoutBounds方法,但這些小數值用于根據AbsoluteLayout的大小計算坐标。

因為模型和ViewModel應該是獨立于平台的,是以MandelbrotModel和MandelbrotViewModel都不參與建立實際的位圖。 這些類将圖像表示為BitmapInfo值,它隻是一個像素寬度和高度,以及一個與疊代計數相對應的整數數組。 建立和顯示該位圖主要涉及使用BmpMaker并根據疊代計數應用顔色方案:

namespace MandelbrotXF
{
    {
        __
        void DisplayNewBitmap(BitmapInfo bitmapInfo)
        {
            // Create the bitmap.
            BmpMaker bmpMaker = new BmpMaker(bitmapInfo.PixelWidth, bitmapInfo.PixelHeight);
            // Set the colors.
            int index = 0;
            for (int row = 0; row < bitmapInfo.PixelHeight; row++)
            {
                for (int col = 0; col < bitmapInfo.PixelWidth; col++)
                {
                    int iterationCount = bitmapInfo.IterationCounts[index++];
                    // In the Mandelbrot set: Color black.
                    if (iterationCount == -1)
                    {
                        bmpMaker.SetPixel(row, col, 0, 0, 0);
                    }
                    // Not in the Mandelbrot set: Pick a color based on count.
                    else
                    {
                        double proportion = (iterationCount / 32.0) % 1;
                        if (proportion < 0.5)
                        {
                            bmpMaker.SetPixel(row, col, (int)(255 * (1 - 2 * proportion)),
                                                        0,
                                                        (int)(255 * 2 * proportion));
                        }        
                        else
                        {
                            proportion = 2 * (proportion - 0.5);
                            bmpMaker.SetPixel(row, col, 0,
                                                        (int)(255 * proportion),
                                                        (int)(255 * (1 - proportion)));
                        }
                    }
                }
            }
            image.Source = bmpMaker.Generate();
        }
    }
}           

随意嘗試配色方案。 一個簡單的替代方法是使用疊代計數來改變HSL顔色的色調:

double hue = (iterationCount / 64.0) % 1;
bmpMaker.SetPixel(row, col, Color.FromHsla(hue, 1, 0.5));           

繼續閱讀