第一个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));