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