Reputation: 446
I have a Grid in a user control:
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneAccentBrush}">
<Image
x:Name="imgBack"
Opacity=".25" />
<Grid VerticalAlignment="Top" HorizontalAlignment="Left">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Some textblocks -->
</Grid>
The size of the grid & source of the image is set dynamically at runtime:
public void ChangeSizeAndImage(int width, int height, string backImagePath)
{
// Set the height and width
LayoutRoot.Width = width;
LayoutRoot.Height = height;
// Set the background image
imgBack.Source = new BitmapImage(new Uri(backImagePath, UriKind.RelativeOrAbsolute));
this.UpdateLayout();
}
I then call a method that generates a jpeg from the ui element & saves it to IsolatedStorage (later adding to a Windows Phone Live Tile)
public static Uri CreateImageFromUIElement(UIElement element, int width, int height)
{
// Instance to store the image in
IsolatedStorageFile userStore = IsolatedStorageFile.GetUserStoreForApplication();
// Random ID to use for the file name
string id = Guid.NewGuid().ToString("n");
// create directory to read/write from
const string shellContentDirectory = "Shared\\ShellContent";
userStore.CreateDirectory(shellContentDirectory);
// Render the UIElement into a writeable bitmap
WriteableBitmap bitmap = new WriteableBitmap(element, new CompositeTransform());
// Save the bitmap to a file. This stream must be closed in order to be read later
string imagePath = shellContentDirectory + "\\" + id + ".jpg";
using (IsolatedStorageFileStream stream = userStore.OpenFile(imagePath, System.IO.FileMode.Create))
{
bitmap.SaveJpeg(stream, width, height, 0, 100);
}
// Uri to get us to the image
Uri imageUri = new Uri("isostore:/" + imagePath.Replace('\\', '/'));
return imageUri;
}
I'm able to successfully use the image on the tile, except I only see the image with a black background, and I don't see any of the text. I'm thinking that this has something to do with jpeg's and transparent PNG's, however, I'm setting the background on the Grid above with a color, so I'm not sure why it would be considered to be transparent.
Edit 1
I am initializing this user control programmatically, setting some properties, and passing it to the create image method. I've found that if I add this user control to a page, then use call the create image method, it works just fine. There must be something about it rendering on a page that causes everything to line up properly. Unfortunately, this approach isn't possible in my situation. Is there a step I can do to get this to work? UpdateLayout();
on the control doesn't work.
Upvotes: 0
Views: 1358
Reputation: 11727
This works for me:
UserControl myControl = new UserControl();
myControl.Arrange(new Rect(0, 0, Width, Height));
WriteableBitmap writeable = new WriteableBitmap(Width, Height);
Writeable.Render(Control, null);
writeable.Invalidate();
IsolatedStorageFileStream fileStream = myIsolatedStorage.CreateFile("/Shared/ShellContent/" + Filename);
System.Windows.Media.Imaging.Extensions.SaveJpeg(writeable, fileStream, Width, Height, 0, 100);
fileStream.Close();
Hope this helps.
Upvotes: 0
Reputation: 831
You are hitting upon a huge challenge to rendering XAML in a background fashion. I ran around and around this same issue before finally realizing what it was. Worse still, every so often it would work just fine!
The problem is the way images are loaded. When you create the controls, the Image element is created and the image loading/decoding is kicked off to another thread. In the meantime, your thread continues to run. You render the control to a file and save it, and a moment later (and here's the part that you don't see) the image finishes loading, and the actual rendering occurs. So frustrating!
You can't just stick a delay in there since you don't know how long it will take (and I'm not sure it would even work that way. What you have to do is split up your work into multiple sections, and subscribe to the ImageOpened event on the Image. When the event fires, you can trust that all is fine and save the bitmap.
In a background agent, this is tricky though, since you need to hold off on calling NotifyComplete() until then, and of course the image could fail to load for a number of reasons. For completeness then, you also need to subscribe to the ImageFailed event and handle it too!
In a WP8 app, you should use the async/await pattern to make this whole flow easier. In WP7, you need to synchronize things yourself. I can post some code if you need it.
I wish I had an easier solution for you, but hopefully this will get you moving in the right direction!
Upvotes: 1