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);
  }