Windows 应用商店:将图像设置为 UI 元素的背景
Windows Store App: set an image as a background for an UI element
很抱歉问了一个非常基本的问题,但这可能是多年来我第一次感到非常困惑。
Windows 提供两组控件:Windows.UI.Xaml 命名空间(我认为这被称为 Metro),用于 Windows Store Apps,以及 System.Windows (WPF ) 桌面版。
由于我要开发 Windows 商店应用程序主要用于 Windows 8.1 和 Windows 10 phones,我将不得不坚持 Windows.UI.Xaml,这不仅有一组单独的 UI 元素,还有一组单独的位图、画笔等(Windows.UI.Xaml.Media vs System.Windows.Media)。
我发现 Windows.UI.Xaml 对图形的支持非常有限,比 WPF、Android 或(甚至!)iOS 平台提供的支持要少得多。首先,我遇到了一个简单的任务:平铺背景!
因为 Windows.UI.Xaml.Media.ImageBrush 不支持平铺,所以我想这样做 "manually"。一些网站建议制作无数个 children,每个都有一个方块。老实说,这对我来说看起来是一种相当尴尬的方法,所以我决定以一种看起来更自然的方式来做:创建一个 off-screen 平铺图像,将其分配给画笔并将画笔指定为背景面板。
平铺代码相当简单(它可能有错误,甚至可能不会在 phone 上 运行,因为使用了一些不可用的 类。
int panelWidth = (int) contentPanel.Width;
int panelHeight = (int) contentPanel.Height;
Bitmap bmpOffscreen = new Bitmap(panelWidth, panelHeight);
Graphics gOffscreen = Graphics.FromImage(bmpOffscreen);
string bmpPath = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets/just_a_tile.png");
System.Drawing.Image tile = System.Drawing.Image.FromFile(bmpPath, true);
int tileWidth = tile.Width;
int tileHeight = tile.Height;
for (int y = 0; y < panelHeight; y += tileHeight)
for (int x = 0; x < panelWidth; x += tileWidth)
gOffscreen.DrawImage(tile, x, y);
现在我大概在 bmpOffscreen 中有了平铺图像。但是如何将它分配给画笔呢?为此,我需要将 Bitmap 转换为 BitmapSource,但我找不到类似于 System.Windows.Imaging.CreateBitmapSourceFromHBitmap 的可用于 WPF 结构的东西!
嗯,首先 System.Drawing
命名空间在 Windows 通用平台中不可用,因此您将无法使用 Bitmap
class
但是,所有的希望都没有丢失 - 你可以使用
Windows.UI.Xaml.Media.Imaging.WriteableBitmap
如果您查看本页中包含的示例,您会发现图像数据一度被提取到一个字节数组 - 您需要做的就是根据需要复制它
如果您希望我包含完整的代码示例,请告诉我。
编辑:
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
Scenario4WriteableBitmap = new WriteableBitmap(2000, 2000);
// Ensure a file was selected
if (file != null)
{
using (IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
int columns = 4;
int rows = 4;
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
// Scale image to appropriate size
BitmapTransform transform = new BitmapTransform()
{
ScaledHeight = Convert.ToUInt32(Scenario4ImageContainer.Height),
ScaledWidth = Convert.ToUInt32(Scenario4ImageContainer.Width)
};
PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8, // WriteableBitmap uses BGRA format
BitmapAlphaMode.Straight,
transform,
ExifOrientationMode.IgnoreExifOrientation, // This sample ignores Exif orientation
ColorManagementMode.DoNotColorManage);
// An array containing the decoded image data, which could be modified before being displayed
byte[] sourcePixels = pixelData.DetachPixelData();
// Open a stream to copy the image contents to the WriteableBitmap's pixel buffer
using (Stream stream = Scenario4WriteableBitmap.PixelBuffer.AsStream())
{
for (int i = 0; i < columns * rows; i++)
{
await stream.WriteAsync(sourcePixels, 0, sourcePixels.Length);
}
}
}
// Redraw the WriteableBitmap
Scenario4WriteableBitmap.Invalidate();
Scenario4Image.Source = Scenario4WriteableBitmap;
Scenario4Image.Stretch = Stretch.None;
}
谢谢你,阿卡迪乌斯。由于澳大利亚时间略早于欧洲,
我有一个优势,在你发布之前看到了代码。我下载了
MSDN XAML images sample 这对我帮助很大。我给了你+1,但显然有人给了-1,所以它相互补偿。不要难过,我经常得到 -1,以至于我不再关注它 :)
所以我已经成功地使用 Windows 通用平台进行了平铺!在我的 Lumia 532 phone 上它可以工作 magnifique。感觉就像重新发明一个轮子,因为所有这些东西都必须由 SDK 来处理,而不是由第三方开发人员来处理。
public static async Task<bool> setupTiledBackground(Panel panel, string tilePath)
{
Brush backgroundBrush = await createTiledBackground((int)panel.Width, (int)panel.Height, TilePath);
if (backgroundBrush == null) return false;
panel.Background = backgroundBrush;
return true;
}
private static async Task<Brush> createTiledBackground(int width, int height, string tilePath)
{
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///" + tilePath));
byte[] sourcePixels;
int tileWidth, tileHeight;
using (IRandomAccessStream inputStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
if (inputStream == null) return null;
BitmapDecoder tileDecoder = await BitmapDecoder.CreateAsync(inputStream);
if (tileDecoder == null) return null;
tileWidth = (int)tileDecoder.PixelWidth;
tileHeight = (int) tileDecoder.PixelHeight;
PixelDataProvider pixelData = await tileDecoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8, // WriteableBitmap uses BGRA format
BitmapAlphaMode.Straight,
new BitmapTransform(),
ExifOrientationMode.IgnoreExifOrientation,
ColorManagementMode.DoNotColorManage);
sourcePixels = pixelData.DetachPixelData();
// fileStream.Dispose();
}
WriteableBitmap backgroundBitmap = new WriteableBitmap(width, height);
int tileBmpWidth = tileWidth << 2;
int screenBmpWidth = width << 2;
int tileSize = tileBmpWidth * tileHeight;
int sourceOffset = 0;
using (Stream outputStream = backgroundBitmap.PixelBuffer.AsStream())
{
for (int bmpY=0; bmpY < height; bmpY++) {
for (int bmpX = 0; bmpX < screenBmpWidth; bmpX += tileBmpWidth)
await outputStream.WriteAsync(sourcePixels, sourceOffset, Math.Min(screenBmpWidth - bmpX, tileBmpWidth));
if ((sourceOffset += tileBmpWidth) >= tileSize)
sourceOffset -= tileSize;
}
}
ImageBrush backgroundBrush = new ImageBrush();
backgroundBrush.ImageSource = backgroundBitmap; // It's very easy now!
return backgroundBrush; // Finita la comédia!
}
请注意:如果您在表单开始时执行此操作,则不应等待。
这不起作用:
public MainPage()
{
this.InitializeComponent();
bool result = setupTiledBackground(contextPanel, TilePath).Result;
}
这个有效:
private Task<bool> backgroundImageTask;
public MainPage()
{
this.InitializeComponent();
backgroundImageTask = setupTiledBackground(contextPanel, TilePath);
}
很抱歉问了一个非常基本的问题,但这可能是多年来我第一次感到非常困惑。
Windows 提供两组控件:Windows.UI.Xaml 命名空间(我认为这被称为 Metro),用于 Windows Store Apps,以及 System.Windows (WPF ) 桌面版。
由于我要开发 Windows 商店应用程序主要用于 Windows 8.1 和 Windows 10 phones,我将不得不坚持 Windows.UI.Xaml,这不仅有一组单独的 UI 元素,还有一组单独的位图、画笔等(Windows.UI.Xaml.Media vs System.Windows.Media)。
我发现 Windows.UI.Xaml 对图形的支持非常有限,比 WPF、Android 或(甚至!)iOS 平台提供的支持要少得多。首先,我遇到了一个简单的任务:平铺背景!
因为 Windows.UI.Xaml.Media.ImageBrush 不支持平铺,所以我想这样做 "manually"。一些网站建议制作无数个 children,每个都有一个方块。老实说,这对我来说看起来是一种相当尴尬的方法,所以我决定以一种看起来更自然的方式来做:创建一个 off-screen 平铺图像,将其分配给画笔并将画笔指定为背景面板。
平铺代码相当简单(它可能有错误,甚至可能不会在 phone 上 运行,因为使用了一些不可用的 类。
int panelWidth = (int) contentPanel.Width;
int panelHeight = (int) contentPanel.Height;
Bitmap bmpOffscreen = new Bitmap(panelWidth, panelHeight);
Graphics gOffscreen = Graphics.FromImage(bmpOffscreen);
string bmpPath = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets/just_a_tile.png");
System.Drawing.Image tile = System.Drawing.Image.FromFile(bmpPath, true);
int tileWidth = tile.Width;
int tileHeight = tile.Height;
for (int y = 0; y < panelHeight; y += tileHeight)
for (int x = 0; x < panelWidth; x += tileWidth)
gOffscreen.DrawImage(tile, x, y);
现在我大概在 bmpOffscreen 中有了平铺图像。但是如何将它分配给画笔呢?为此,我需要将 Bitmap 转换为 BitmapSource,但我找不到类似于 System.Windows.Imaging.CreateBitmapSourceFromHBitmap 的可用于 WPF 结构的东西!
嗯,首先 System.Drawing
命名空间在 Windows 通用平台中不可用,因此您将无法使用 Bitmap
class
但是,所有的希望都没有丢失 - 你可以使用 Windows.UI.Xaml.Media.Imaging.WriteableBitmap
如果您查看本页中包含的示例,您会发现图像数据一度被提取到一个字节数组 - 您需要做的就是根据需要复制它
如果您希望我包含完整的代码示例,请告诉我。
编辑:
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
Scenario4WriteableBitmap = new WriteableBitmap(2000, 2000);
// Ensure a file was selected
if (file != null)
{
using (IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
int columns = 4;
int rows = 4;
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
// Scale image to appropriate size
BitmapTransform transform = new BitmapTransform()
{
ScaledHeight = Convert.ToUInt32(Scenario4ImageContainer.Height),
ScaledWidth = Convert.ToUInt32(Scenario4ImageContainer.Width)
};
PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8, // WriteableBitmap uses BGRA format
BitmapAlphaMode.Straight,
transform,
ExifOrientationMode.IgnoreExifOrientation, // This sample ignores Exif orientation
ColorManagementMode.DoNotColorManage);
// An array containing the decoded image data, which could be modified before being displayed
byte[] sourcePixels = pixelData.DetachPixelData();
// Open a stream to copy the image contents to the WriteableBitmap's pixel buffer
using (Stream stream = Scenario4WriteableBitmap.PixelBuffer.AsStream())
{
for (int i = 0; i < columns * rows; i++)
{
await stream.WriteAsync(sourcePixels, 0, sourcePixels.Length);
}
}
}
// Redraw the WriteableBitmap
Scenario4WriteableBitmap.Invalidate();
Scenario4Image.Source = Scenario4WriteableBitmap;
Scenario4Image.Stretch = Stretch.None;
}
谢谢你,阿卡迪乌斯。由于澳大利亚时间略早于欧洲, 我有一个优势,在你发布之前看到了代码。我下载了 MSDN XAML images sample 这对我帮助很大。我给了你+1,但显然有人给了-1,所以它相互补偿。不要难过,我经常得到 -1,以至于我不再关注它 :)
所以我已经成功地使用 Windows 通用平台进行了平铺!在我的 Lumia 532 phone 上它可以工作 magnifique。感觉就像重新发明一个轮子,因为所有这些东西都必须由 SDK 来处理,而不是由第三方开发人员来处理。
public static async Task<bool> setupTiledBackground(Panel panel, string tilePath)
{
Brush backgroundBrush = await createTiledBackground((int)panel.Width, (int)panel.Height, TilePath);
if (backgroundBrush == null) return false;
panel.Background = backgroundBrush;
return true;
}
private static async Task<Brush> createTiledBackground(int width, int height, string tilePath)
{
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///" + tilePath));
byte[] sourcePixels;
int tileWidth, tileHeight;
using (IRandomAccessStream inputStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
if (inputStream == null) return null;
BitmapDecoder tileDecoder = await BitmapDecoder.CreateAsync(inputStream);
if (tileDecoder == null) return null;
tileWidth = (int)tileDecoder.PixelWidth;
tileHeight = (int) tileDecoder.PixelHeight;
PixelDataProvider pixelData = await tileDecoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8, // WriteableBitmap uses BGRA format
BitmapAlphaMode.Straight,
new BitmapTransform(),
ExifOrientationMode.IgnoreExifOrientation,
ColorManagementMode.DoNotColorManage);
sourcePixels = pixelData.DetachPixelData();
// fileStream.Dispose();
}
WriteableBitmap backgroundBitmap = new WriteableBitmap(width, height);
int tileBmpWidth = tileWidth << 2;
int screenBmpWidth = width << 2;
int tileSize = tileBmpWidth * tileHeight;
int sourceOffset = 0;
using (Stream outputStream = backgroundBitmap.PixelBuffer.AsStream())
{
for (int bmpY=0; bmpY < height; bmpY++) {
for (int bmpX = 0; bmpX < screenBmpWidth; bmpX += tileBmpWidth)
await outputStream.WriteAsync(sourcePixels, sourceOffset, Math.Min(screenBmpWidth - bmpX, tileBmpWidth));
if ((sourceOffset += tileBmpWidth) >= tileSize)
sourceOffset -= tileSize;
}
}
ImageBrush backgroundBrush = new ImageBrush();
backgroundBrush.ImageSource = backgroundBitmap; // It's very easy now!
return backgroundBrush; // Finita la comédia!
}
请注意:如果您在表单开始时执行此操作,则不应等待。 这不起作用:
public MainPage()
{
this.InitializeComponent();
bool result = setupTiledBackground(contextPanel, TilePath).Result;
}
这个有效:
private Task<bool> backgroundImageTask;
public MainPage()
{
this.InitializeComponent();
backgroundImageTask = setupTiledBackground(contextPanel, TilePath);
}