Saving UserControl as a JPG - result is “squished”

2019-09-15 10:54发布

问题:

I'm having a strange issue with saving a UserControl as a JPG. Basically what I want to do is create a Live Tile that I can update using a Background Agent (as well as from within the app itself.)

I've followed the steps in this blog post which work great for when I create the tile; I have a custom UserControl with some TextBlocks on it which gets saved as a JPG into IsolatedStorage, exactly as the post says.

Creating the tile is no problem - the UserControl is rendered nicely as it should be. However, when I try to update the tile (using the exact same method - saving a new control as a JPG, then using that JPG as the BackgroundImage), things break down.

The resulting image that gets placed on the tile (and saved in IsolatedStorage) looks like this: squished tile (pulled from IsolatedStorage using the Isolated Storage Explorer Tool)

The background is black, and all the text runs down the side of the image (and overlaps each other) - the expected result is the background being the phone's accent colour, and the text appearing horizontal near the top.

The code used to generate and save the image is exactly the same in both instances - I've abstracted it out into a static method that returns StandardTileData. The only difference is where it is called from: in the working case where the tile is created, it's called from a page within the main app; in the non-working case (where the tile is updated), it's called from a page that can only be accessed by deep-linking from the tile itself.

Any thoughts? I'm guessing something's going wrong with the rendering of the Control to a JPG, since the actual image comes out this way.

The snippet of code that generates the image is here:

StandardTileData tileData = new StandardTileData();

// Create the Control that we'll render into an image.
TileImage image = new TileImage(textA, textB);
image.Measure(new Size(173, 173));
image.Arrange(new Rect(0, 0, 173, 173));

// Render and save it as a JPG.
WriteableBitmap bitmap = new WriteableBitmap(173, 173);
bitmap.Render(image, null);
bitmap.Invalidate();

IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
String imageFileName = "/Shared/ShellContent/tile" + locName + ".jpg";

using (IsolatedStorageFileStream stream = storage.CreateFile(imageFileName))
{
    bitmap.SaveJpeg(stream, 173, 173, 0, 100);
}

tileData.BackgroundImage = new Uri("isostore:" + imageFileName, UriKind.Absolute);

return tileData;

The XAML for the control I'm trying to convert is here:

<UserControl x:Class="Fourcast.TileImage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="173" d:DesignWidth="173" FontStretch="Normal" Height="173" Width="173">

    <Border Background="{StaticResource PhoneAccentBrush}">
        <StackPanel>
            <TextBlock  HorizontalAlignment="Stretch"
              TextWrapping="Wrap" VerticalAlignment="Stretch" Style="{StaticResource PhoneTextLargeStyle}" x:Name="Temperature"></TextBlock>
            <TextBlock x:Name="Condition" HorizontalAlignment="Stretch"
              TextWrapping="Wrap" VerticalAlignment="Stretch" Style="{StaticResource PhoneTextNormalStyle}">
            </TextBlock>
        </StackPanel>
    </Border>
</UserControl>

Update: after some investigation with the debugger, it appears the calls to Measure and Arrange don't seem to do anything when the method is called from the class that's updating the tile. When the tile is being created, however, these calls function as expected (the ActualWidth and ActualHeight of the control get changed to 173).

回答1:

Not sure what exactly is going on here, but here's what I ended up doing to workaround the issue.

I switched from trying to render a Control to defining a StackPanel in code, adding elements to it, then rendering that to an image. Oddly enough, though, the Arrange and Measure methods don't do anything unless another element has had them applied to it, and UpdateLayout called.

The full resulting code (minus the contents of the TextBlocks and writing to an image) is below.

StandardTileData tileData = new 

// None of this actually does anything, but for some reason the StackPanel
// won't render properly without it.
Border image = new Border();
image.Measure(new Size(173, 173));
image.Arrange(new Rect(0, 0, 173, 173));
image.UpdateLayout();
// End of function-less code.

StackPanel stpContent = new StackPanel();      

TextBlock txbTemperature = new TextBlock();
txbTemperature.Text = temperature;
txbTemperature.Style = (Style)Application.Current.Resources["PhoneTextLargeStyle"];
stpContent.Children.Add(txbTemperature);

TextBlock txbCondition = new TextBlock();
txbCondition.Text = condition;
txbCondition.Style = (Style)Application.Current.Resources["PhoneTextNormalStyle"];
stpContent.Children.Add(txbCondition);

stpContent.Measure(new Size(173, 173));
stpContent.Arrange(new Rect(0, 0, 173, 173));


回答2:

The way to get the background of the image to use the phone's accent color is to make it transparent. This will require that you save a PNG rather than a JPG.

The ouput image looks like everything is wrapping more thna it needs to. You probably need to adjust the widths of the elements you're including in your control. Without the XAML it's hard to be more specific.



回答3:

I also get this strange behaviour - squished tile (using Ree7 Tile toolkit),

Try call update tile into Dispatcher:

Deployment.Current.Dispatcher.BeginInvoke(() =>
{
    CustomTile tile = GetTile(card);
    StandardTileData tileData = tile.GetShellTileData();
    shellTile.Update(tileData);
});

Working for me