第二十章:异步和文件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));
目录
相关文章
|
Web App开发 Android开发
第二十章:异步和文件I/O.(二十三)
回到网上在本章之前,本书中唯一的异步代码涉及使用可移植类库WebRequest中唯一可用于此目的的合理类进行Web访问。 WebRequest类使用称为异步编程模型或APM的旧异步协议。 APM涉及两种方法,在WebRequest的情况下,这些方法称为BeginGetResponse和EndGetResponse。
716 0
|
JavaScript Android开发 iOS开发
第二十章:异步和文件I/O.(十二)
虽然每个方法都被定义为返回Task或Task 对象,但是方法的主体没有任何对Task或Task 的引用。相反,返回Task对象的方法只是执行一些工作,然后使用隐式return语句结束该方法。 ExistsAsync方法定义为返回Task 但返回true或false。
785 0