Windows Phone Binding Image Location - silverlight

I am trying to place a collection of images at certain places on a canvas through Binding.
For some reason, the images are displaying but are NOT at the locations which I have specified.
C#
_roomView.Room = new Room
{
Items = new List<Item> {
new Item {ImageUri = "/Escape;component/Images/Items/a.jpg", XPosition = 190, YPosition = 50},
new Item {ImageUri = "/Escape;component/Images/Items/b.png", XPosition = 390, YPosition = 100},
new Item {ImageUri = "/Escape;component/Images/Items/b.png", XPosition = 490, YPosition = 600}}
};
listBoxItems.ItemsSource = _roomView.Room.Items;
XML
<Canvas>
<Image Source="{Binding ImageUri}" Stretch="None" Canvas.Left="{Binding Room.Items.XPosition}" Canvas.Top="{Binding Room.Items.YPosition}"/>
</Canvas>
XPosition and YPosition are of type int. I have tried changing them to double but the images still aren't being displayed where I want them to be. They only get displayed at the top left of the screen - on top of each other.
Can anyone see the problem?

You're using the pack URI format which is only valid if your images have a Build Action of "Embedded Resource". Even then, I'm not sure this URI format is actually supported for Image.Source and if the URI has to be Relative or Absolute. Check the images Build Action (Right click --> Properties on those files) and try changing the UriKind.
Either way, unless you have a very good reason it's best to keep media assets as:
Build Action = Content and use the normal path URIs. Adding images to DLLs as Embedded Resources is bad for startup performance since it takes longer to load your bigger DLLs into the AppDomain.
It's also bad from a memory usage perspective since the DLLs loaded into memory are now bigger.
Bottom line:
<Image Source="myPicture.png" /> format is much better for performance than
<Image Source="myAssemblyName;component/myPicture.png" /> syntax.

Your C# code has collection of images.
Your XAML only shows a single image. If the XAML is in the listbox data template, you'll get several canvases, with 1 image each.
To display a collection of images on the single Canvas, you could use e.g. <ItemsControl>, set ItemsPanel to a Canvas, and in the data template you specify a single image with source, canvas.left and canvas.top.
If you're trying to do what I've described with a ListBox instead of ItemsControl, it wont work because it has one more layer of UI elements between ItemsPanel and item, namely the item container.
P.S. Fastest way to troubleshoot such GUI problems - XAML Spy, I've bought myself a personal license. Evaluation is 21 days long, fully functional.

Related

Image object rendering is extremly slow in WPF DataTemplate

I'm creating a simple file browser with thumbnails. I'm using a ListBox with a custom DataTemplate to display objects in an ObservableCollection.
<DataTemplate>
<StackPanel Margin="5">
<Image Source="{Binding Path=ThumbnailPath}"/>
<Label Background="White" Content="{Binding Path=FileName}"/>
</StackPanel>
</DataTemplate>
The objects (of my custom class File) have just these two string properties: ThumbnailPath and FileName. When user selects a folder, a BackgroundWorker gets a list of files and creates instances of the File class. These instances are dispatched to the UI thread (in groups of 10) using BW's ReportProgress. In the event handler they are added to the ObservableCollection bound to my ListBox.
The problem is when at least 20-30 Files are to be added to my collection; the UI freezes for almost three seconds before the ListBox gets updated. Don't even ask what happens when a folder contains hundreds of files. Everything is propely prepared in the background, so I guess the problem arises when WPF starts to initialize and render empty Image elements. When I comment out the Image from the DataTemplate, it takes a blink of an eye to update the collection and its view.
Is there anything that can be done about this? I know could create the whole View object in the background thread (a new StackPanel, add children new Label and new Image, set values), but the whole point of DataBinding and templating should be to avoid the need to do this... So how do I fill a ListBox with an Image in its DataTemplate without losing responsiveness?
PS: The actual thumbnails are generated by FFmpeg and saved to a file, but this process only starts after all items (with blank image object) are displayed, so they are of no concern in the context of this question.
Try this:
Change ThumbnailPath to the type of BitmapImage;
when setting property use
BitmapImage bi1 = new BitmapImage();
// BitmapImage.UriSource must be in a BeginInit/EndInit block
bi1.BeginInit();
bi1.UriSource = new Uri(#"C:\filepath.jpg");
To save significant application memory, set the DecodePixelWidth or
// DecodePixelHeight of the BitmapImage value of the image source to the desired
// height or width of the rendered image. If you don't do this, the application will
// cache the image as though it were rendered as its normal size rather then just
// the size that is displayed.
// Note: In order to preserve aspect ratio, set DecodePixelWidth
// or DecodePixelHeight but not both.
bi1.DecodePixelWidth = 200;
bi1.EndInit();
bi1.Freeze();
//if you do not Freeze, your app will leak memory.

Reduce Image resolution in WPF image control

I have a image control in WPF. I need to reduce the image size control width and height. But when i Do that , the image is not looking good. The data loss is more.
So i thought to reduce the image resolution instead of just changing the image control width and height.
can any one help me how to change the image resolution of the binded image in WPF image control
[I mean image is already binded to image control now I have to the change the resolution only]
In .NET 4 they changed the default image scaling to a low quality one...so you can use BitmapScalingMode to switch back to the higher quality one:
<Image RenderOptions.BitmapScalingMode="HighQuality"
Source="myimage.png"
Width="100"
Height="100" />
http://10rem.net/blog/2010/05/01/crappy-image-resizing-in-wpf-try-renderoptionsbitmapscalingmode
You can also combine the above with other options like the Decode options if your source image is a huge image (this just reduces memory usage in your application).
http://www.shujaat.net/2010/08/wpf-saving-application-memory-by.html
other options to prevent "blurryness" is to put UseLayoutRounding="True" on your root element (i.e. Window)....it's recommended to use this in .NET 4 rather than SnapToDevicePixels:
When should I use SnapsToDevicePixels in WPF 4.0?
http://blogs.msdn.com/b/text/archive/2009/08/27/layout-rounding.aspx
You can use the DecodePixelWidth property like this:
<Image Stretch="Fill">
<Image.Source>
<BitmapImage CacheOption="OnLoad" DecodePixelWidth="2500" UriSource="Images/image.jpg"/>
</Image.Source>
</Image>
1) Have a try with a ViewBox : put your image within a ViewBox.
2) Sometimes the rendering engine looses quality because of pixel alignement issues, especially on low-resolution device. Have a look to SnapsToDevicePixels property, and try to set it to true in the containing control AND / OR in the ViewBox.
3) Obviously you could write a Control that perform a resolution change but it is quite some work.
Try this.
Image img = new Image();
var buffer = System.IO.File.ReadAllBytes(_filePath);
MemoryStream ms = new MemoryStream(buffer);
BitmapImage src = new BitmapImage();
src.BeginInit();
src.StreamSource = ms;
src.DecodePixelHeight = 200;//Your wanted image height
src.DecodePixelWidth = 300; //Your wanted image width
src.EndInit();
img.Source = src;

Saving UserControl as a JPG - result is "squished"

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

Is it possible to load ListboxItems of type Image as user scrolls the content using a WrapPanel as the PanelTemplate?

I'm developing an app for Windows Phone 7 that populates a WrapPanel with a list of objects retrieved from an ObservableCollection<Photo>.
<ListBox ItemsSource="{Binding Photo}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding File}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel ItemHeight="150" ItemWidth="150" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
It's working how it's supposed to work (in the means that it's loading all the images), but I'm having some performance issues since WrapPanel : Panel. So it doesn't virtualize any data, loading all the Image objects of the ListBox, even the ones that the user can't see.
This approach is OK when ObservableCollection<Photo>.Count <= 30 but as the Collection gets bigger and bigger things start to get slow.
Since the user can have up to 1000 images, it's simply not going to work this way. Even though I'm binding Thumbnails to display the Image object.
I've tried to use David Anson's LowProfileImageLoader to create Images off the UI thread. And to bind the Images as the user scrolls the ListBox. But it doesn't work since it's expecting an UriSource and I'm actually binding a BitmapImage to the Image.Source, because the images are beeing loaded from IsolatedStorage.
public class Photo : INotifyPropertyChanged, INotifyPropertyChanging
{
...
...
public BitmapImage File
{
get
{
// Thumbnail
string filePath = Path.Combine("Images", FileName);
byte[] data;
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream isfs = isf.OpenFile(filePath, FileMode.Open, FileAccess.Read))
{
data = new byte[isfs.Length];
isfs.Read(data, 0, data.Length);
isfs.Close();
}
}
MemoryStream ms = new MemoryStream(data);
BitmapImage bi = new BitmapImage();
bi.SetSource(ms);
return bi;
}
}
}
Can anyone help me in this task of making the content (photos) load as the user sees it? Is there anything such as a VirtualizingWrapPanel or WrapPanel : VirtualizingPanel?
Thanks. If any code snippet is needed feel free to ask.
Loading the images to memory from the storage when the user scrolls will make your UI jittery especially when the user tries to scroll or see more elements very quickly.
But, if you have all the images in memory and then would like to bind the images to the listbox on scrolling, you could use the VirtualizingStackPanel. A wrap panel would need to know the size of the images to display correctly. A workaround is possible if you know the size of the images and if they're all the same.
You could customize the VirtualizingStackPanel and position the images in rows like the way they would in a wrappanel, or any other way you like.
Hope that helps.
Although,
I am not sure if this will solve the problem when you have 1000 heavy images. But it is worth a try, if you haven't done so.
EDIT: This article might help.
Try creating a wrap panel from a virtualized listbox.
Here is a link how to do it. it works pretty good, but it has 1 problem... it will have a tilt effect on a whole line instead of an item... I'm currently investigating that problem.

Cannot find the image by providing the relative path in Silverlight

I'm working on a Silverlight application and I have a hard time setting the relative path of an image in my application.
This is a brief picture of my project directory:
MyProject
L images
L mountain1.jpg
L SpriteObjects
L MountainFactory.cs
L GameScreen.xaml
Originally, I painted an Rectangle using an image brush from the GameScreen.xaml.
<Rectangle Name="rec_bg" HorizontalAlignment="Left" VerticalAlignment="Top" Width="800" Height="600">
<Rectangle.Fill>
<ImageBrush x:Name="ib_bg" ImageSource="./images/mountain1.jpg" ></ImageBrush>
</Rectangle.Fill>
</Rectangle>
This xaml code works, that is it can find the image in the images folder without any problem.
I want to change the image dynamically, and I created a class called MountainFactory.cs. In this class, I have a method with this code:
ImageBrush brush = new ImageBrush();
BitmapImage image = new BitmapImage(new Uri("./images/mountain" + level + ".jpg", UriKind.Relative));
brush.ImageSource = image;
This method will return an image brush which is applied to the rec_bg Rectangle object.
However, this code cannot find the specified image.
Does anyone who know how to fix this?
Thanks.
It depends on how you are embedded the image. I'm assuming you have it set as "Content" so it's not embedded in the DLL but does embed in the XAP. In that case, you simply access it relative to the project root, so you would be using:
"images/mountain..."
(Note, you don't use the ./ in front of it, you are automatically relative to the root of the application). If you have it set as an embedded resource, you'll need to extract it out of the DLL using a stream - I don't recommend that.
Short story boring: try removing the leading ./ and make sure the properties of the Image have it as Content.

Resources