Providing an initial image placeholder for the WPF Image class - wpf

When i run the project a runtime error ocure:
Error: Property 'UriSource' or property 'StreamSource' must be set.
because this.ImageUri is null , i don't know why this.ImageUri be null ! help me
I have been working with the WPF ListBox using images as my list box items. The sourced image path points to a server hosting those images. While on fast network, the images appeared without any noticeable delay. However it became apparent over a slow link that the user experience degraded and I really wanted to show a placeholder image while the image was downloaded and decoded.
Surprisingly, I didn't find a solution in the blogosphere for this issue so I coded up a derived class to address this.
The sample XAML below is from my item container style. I replaced Image with my local class implementation local:ImageLoader.
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyData}">
...
<StackPanel Grid.Column="0" Margin="5">
<Border BorderThickness="0">
<MyControl:ImageLoader Width="50" Height="50" ImageUri="{Binding Path=profile_image_url_https, FallbackValue=profile_image_url_https}" InitialImage="/MyProject;component/Images/nopic.png" HorizontalAlignment="Left"></imgz:ImageLoader>
</Border>
</StackPanel>
...
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source = {StaticResource MyData}}" />
</Grid>
The heart of the handling for the initial image is in the OnLoaded() method, where I use a BitmapImage as the source and set the UriSource to the derived class' ImageUri dependency property, which allows for data binding. The initial image is updated to the actual image when the download completes or when a failure event is received. The class also optionally allows you to specify a "LoadFailedImage".
public class ImageLoader : Image
{
public static readonly DependencyProperty ImageUriProperty = DependencyProperty.Register(
"ImageUri", typeof(Uri), typeof(ImageLoader), new PropertyMetadata(null, null));
private BitmapImage loadedImage;
public ImageLoader()
{
this.Loaded += this.OnLoaded;
}
public string LoadFailedImage
{
get;
set;
}
public Uri ImageUri
{
get {return this.GetValue(ImageUriProperty) as Uri;}
set {this.SetValue(ImageUriProperty, value);}
}
public string InitialImage
{
get;
set;
}
private new ImageSource Source
{
get {return base.Source;}
set {base.Source = value;}
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// Loading the specified image
this.loadedImage = new BitmapImage();
this.loadedImage.BeginInit();
this.loadedImage.CacheOption = BitmapCacheOption.OnDemand;
this.loadedImage.DownloadCompleted += this.OnDownloadCompleted;
this.loadedImage.DownloadFailed += this.OnDownloadFailed;
this.loadedImage.UriSource = this.ImageUri;
this.loadedImage.EndInit();
// The image may be cached, in which case we will not use the initial image
if (!this.loadedImage.IsDownloading)
{
this.Source = this.loadedImage;
}
else
{
// Create InitialImage source if path is specified
if (!string.IsNullOrWhiteSpace(this.InitialImage))
{
BitmapImage initialImage = new BitmapImage();
// Load the initial bitmap from the local resource
initialImage.BeginInit();
initialImage.UriSource = new Uri(this.InitialImage, UriKind.Relative);
initialImage.DecodePixelWidth = (int)this.Width;
initialImage.EndInit();
// Set the initial image as the image source
this.Source = initialImage;
}
}
e.Handled = true;
}
private void OnDownloadFailed(object sender, ExceptionEventArgs e)
{
if (!string.IsNullOrWhiteSpace(this.LoadFailedImage))
{
BitmapImage failedImage = new BitmapImage();
// Load the initial bitmap from the local resource
failedImage.BeginInit();
failedImage.UriSource = new Uri(this.LoadFailedImage, UriKind.Relative);
failedImage.DecodePixelWidth = (int)this.Width;
failedImage.EndInit();
this.Source = failedImage;
}
}
private void OnDownloadCompleted(object sender, EventArgs e)
{
this.Source = this.loadedImage;
}
}
When i run the project a runtime error ocured:
Error: Property 'UriSource' or property 'StreamSource' must be set.
because this.ImageUri is null , i don't know why this.ImageUri be null ! help me

If it isn't the semicolon typo in InitialImage="/MyProject;component/Images/nopic.png",
maybe it's better to set your InitialImage as Default in ImageUri
public static readonly DependencyProperty ImageUriProperty = DependencyProperty.Register(
"ImageUri", typeof(Uri), typeof(ImageLoader), new PropertyMetadata(new Uri("/MyProject/component/Images/nopic.png"), null));
UPDATE:
You have to bind to Image.Source and you could use PriorityBinding to show a placeholder.
<Image.Source>
<PriorityBinding>
<!--highest priority sources are first in the list-->
<Binding Path="YourImageUri"
IsAsync="True" />
<Binding Path="InitialImageUri"
IsAsync="True" />
</PriorityBinding>
</Image.Source>
For a "LoadFailedImage" a would subsribe to Image.ImageFailed Event.
Hope this helps.

Related

Binding a Color to a DependencyProperty in UserControl

I have a UserControl that has a Grid with a Background property that is bound. All of my other bindings work as expected, but for some reason, the only color I get in my UserControl is the default value I set for the DependencyProperty.
Referencing the UserControl in MainWindow.xaml:
<controls:MyUserControl Title="{Binding Path=MyObjects[0].Title" MyControlColor="{Binding Path=MyObjects[0].Color}" />
Title shows up as expected but the color is unchanged.
MyUserControl code (I use MyControlColorBrush for the color source, which just converts MyControlColor to a SolidColorBrush. Code on down.):
<Grid Background="{Binding Path=MyControlColorBrush, RelativeSource={RelativeSource AncestorType=UserControl}}">
<TextBlock Text="{Binding Path=Title, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</Grid>
MyUserControl.xaml.cs code:
public Color MyControlColor
{
get { return (Color)GetValue(MyControlColorProperty); }
set { SetValue(MyControlColorProperty, value); }
}
public static readonly DependencyProperty MyControlColorProperty = DependencyProperty.Register("MyControlColor", typeof(Color), typeof(MyUserControl), new PropertyMetadata(Colors.Black));
And then a property that just converts the color to a SolidColorBrush:
public SolidColorBrush MyControlColorBrush
{
get { return new SolidColorBrush(MyControlColor); }
}
Any ideas on what I could be missing? If I check the value of MyControlColor, it's showing the right color, but the background of the Grid just isn't changing from Black.
The binding to MyControlColorBrush only happens once when your page is first loaded. Your binding to MyObjects[0].Color is causing your dependency property to update but there's nothing indicating to the rest of your app that MyControlColorBrush needs to be updated as well.
There are a few ways to achieve this, the easiest is probably to just create a read-only dependency property for your brush that you update whenever you detect a change in your color property (this is similar to how the Width/ActualWidth properties work). Your control will need a DP for the color:
public Color MyControlColor
{
get { return (Color)GetValue(MyControlColorProperty); }
set { SetValue(MyControlColorProperty, value); }
}
public static readonly DependencyProperty MyControlColorProperty =
DependencyProperty.Register("MyControlColor", typeof(Color), typeof(MyUserControl),
new PropertyMetadata(Colors.Black, OnColorChanged));
And then a read-only DP for the brush:
public Brush MyControlColorBrush
{
get { return (Brush)GetValue(MyControlColorBrushProperty); }
protected set { SetValue(MyControlColorBrushPropertyKey, value); }
}
private static readonly DependencyPropertyKey MyControlColorBrushPropertyKey
= DependencyProperty.RegisterReadOnly("MyControlColorBrush", typeof(Brush), typeof(MyUserControl),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.None));
public static readonly DependencyProperty MyControlColorBrushProperty = MyControlColorBrushPropertyKey.DependencyProperty;
And you'll update the brush whenever your color DP changes:
private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MyUserControl).MyControlColorBrush = new SolidColorBrush((Color)e.NewValue);
}
GUI elements in your custom control then bind to the read-only DP, e.g.:
<Grid Background="{Binding Path=MyControlColorBrush, RelativeSource={RelativeSource AncestorType=local:MyUserControl}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

WPF OnMouseEnter change image using events

Is it possible to access a specific resource in the xaml using events?
I'm trying to have the image change when mouse enters a grid, but the problem is I don't have access to change the image from the event.
I want to do something like:
OnMouseEnter(object sender, MouseEventArgs e) {
((Image)GetResource("logo")).Source = "pathToImage.jpg";
}
Where 'logo' is the name of the image resource.
It seems you're just trying to set the Source property of an Image element declared somewhere in your XAML. All you need to do is to set the x:Name attribute, which will generate a member in your Window class, like
<Image x:Name="image"/>
Now you directly access the element in code behind, and assign a value to its Source property like this:
image.Source = new BitmapImage(new Uri("pathToImage.jpg"));
If the element you are attaching the OnMouseEnter on is a FrameworkElement then you can try do something like the following:
OnMouseEnter(object sender, MouseEventArgs e) {
var element = sender as FrameworkElement;
var image = element.FindResource("logo") as Image;
image.Source = new BitmapImage(new Uri("pathToImage.jpg"));
}
Otherwise you may have to change your "resource" from:
<Image x:Key="logo" Source="initialImage.jpg" />
to something like:
<Image x:Key="logo" Source="{Binding Source={x:Static LogoImage.SingletonInstance}, Path=Logo}" />
And have a class something along the lines of:
public class LogoImage : INotifyPropertyChanged {
public static LogoImage SingletonInstance { get; } = new LogoImage();
public ImageSource Logo { get; private set; }
public void SetLogo(ImageSource image)
{
Logo = image;
RaiseNotifyPropertyChanged(nameof(Logo));
}
// Implement INotifyPropertyChanged
}

Setting XAML property value into user control from a consumer view

I have a custom UserControl that contains a grid ...I wish to set the ItemsSource property of that grid by xaml code of of a data template in a resource dictionary...
then I have used dependency property... this is my implementation...
public partial class MyControlGrid : UserControl
{
// Dependency Property
public static readonly DependencyProperty MyItemSourceProperty =
DependencyProperty.Register("MyItemSource", typeof(ICollectionView),
typeof(MyControlGrid), new FrameworkPropertyMetadata(null, OnMyItemSourcePropertyChanged));
IDictionary<string, string> _columns = new Dictionary<string, string>();
private static void OnMyItemSourcePropertyChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
// When the color changes, set the icon color PlayButton
MyControlGrid muc = (MyControlGrid)obj;
ICollectionView value = (ICollectionView)args.NewValue;
if (value != null)
{
muc.MyGridControl.ItemsSource = value;
}
}
public ICollectionView MyItemSource
{
get
{
return (ICollectionView)GetValue(MyItemSourceProperty);
}
set
{
SetValue(MyItemSourceProperty, value);
//OnTargetPowerChanged(this, new DependencyPropertyChangedEventArgs(TargetPowerProperty, value, value));
// Old value irrelevant.
}
}
public MyControlGrid()
{
InitializeComponent();
}
}
this is the user control xaml code
<UserControl x:Class="GUI.Design.Templates.MyControlGrid"
Name="MyListControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfTkit="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:Templates="clr-namespace:Emule.GUI.Design.Templates">
<StackPanel>
<WpfTkit:DataGrid ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Templates:MyControlGrid}}, Path=MyItemSource}"
x:Name="MyGridControl"
<StackPanel>
this is the binding path expression I use
<basic:MyControlGrid MyItemSource="{Binding MyDataContextVisibleCollection}"/>
this dont work and wpf output window dont show me any errors
note that, naturally, if I bind this directly in the user controls work fine
<WpfTkit:DataGrid ItemsSource="{Binding MyDataContextVisibleCollection}"
Waths I wrong?
thanks
p.s. sorry for my english
this
answer show me the way
use of PropertyChangedCallback work fine with my code:
public static readonly DependencyProperty MyItemSourceProperty =
DependencyProperty.Register("MyItemSource", typeof(IEnumerable),
typeof(MyControlGrid), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MyControlGrid.OnItemsSourceChanged)));
alternatively I have to remove comment on OnTargetPowerChanged and fire the property changed event
set
{
SetValue(MyItemSourceProperty, value);
//OnTargetPowerChanged(this, new DependencyPropertyChangedEventArgs(TargetPowerProperty, value, value));
// Old value irrelevant.
}
correct with
public ICollectionView MyItemSource
{
get
{
return (ICollectionView)GetValue(MyItemSourceProperty);
}
set
{
SetValue(MyItemSourceProperty, value);
OnItemsSourceChanged(this, new DependencyPropertyChangedEventArgs(MyItemSourceProperty, value, value));
}
}

Image source not updating dynamically

I have an Image that I have bound the 'Source' property of to the page's corresponding 'ViewModel' property named 'Capture' as follows:
View Code:
<Image Height="150" HorizontalAlignment="Left" Margin="50,583,0,0" x:Name="img_FlickrPic" Stretch="Fill" VerticalAlignment="Top" Width="200" Grid.Row="1" Source="{Binding Capture}"/>
Corresponding ViewModel code
public class SubmitViewModel : ViewModelBase
{
private CameraCaptureTask cameraCapture;
public ImageSource Capture { get; set; }
public RelayCommand CaptureCommand { get; set; }
/// <summary>
/// Initializes a new instance of the SubmitViewModel class.
/// </summary>
public SubmitViewModel()
{
Capture = new BitmapImage();
CaptureCommand = new RelayCommand(() => CapturePhoto());
}
private object CapturePhoto()
{
cameraCapture = new CameraCaptureTask();
cameraCapture.Completed += cameraCapture_Completed;
cameraCapture.Show();
return null;
}
void cameraCapture_Completed(object sender, PhotoResult e)
{
if (e == null || e.TaskResult != TaskResult.OK)
{
return;
}
BitmapImage bitmap = new BitmapImage();
bitmap.SetSource(e.ChosenPhoto);
Capture = bitmap;
}
}
As you notice, I have bound a capture button to the view model as well using 'behavior's instead of a click event in order to keep the code behind clean. When I hit the button, the camera gets invoked and once I hit capture and then press the 'Accept' button the 'cameraCapture_Completed' event fires as expected and the code in there executes. However the last step where the 'Capture' property (which my Image's Source property on the view is bound to) is set, I expect the Image to dynamically update with the captured photo. This does not happen. The viewmodel inherits from 'ViewModelBase' which in turn implements INotifyPropertyChanged, so that shouldn't be a problem. Why aren't any modifications to the 'Capture' property being reflected by the Image in the UI? Am I messing up somewhere here?
Thanks!
You have to manually raise the ChangedPropertyEvent with property name "Capture". Something like this.
ImageSource mCapture;
public ImageSource Capture {
get {return mCapture;}
set { mCapture = value;
RaiseChangedProperty("Capture");
}
}

Databinding TextBlock Runs in Silverlight / WP7

I'm using Silverlight on Windows Phone 7.
I want to display the first part of some text in a TextBlock in bold, and the rest in normal font. The complete text must wrap. I want the bolded part to contain text from one property in my ViewModel, and the plain text to contain text from a different property.
The TextBlock is defined in a DataTemplate associated with a LongListSelector.
My initial attempt was:
<TextBlock TextWrapping="Wrap">
<TextBlock.Inlines>
<Run Text="{Binding Property1}" FontWeight="Bold"/>
<Run Text="{Binding Property2}"/>
</TextBlock.Inlines>
</TextBlock>
This fails at runtime with the spectacularly unhelpful "AG_E_RUNTIME_MANAGED_UNKNOWN_ERROR". This is a known issue because the Run element is not a FrameworkElement and cannot be bound.
My next attempt was to put placeholders in place, and then update them in code:
<TextBlock Loaded="TextBlockLoaded" TextWrapping="Wrap">
<TextBlock.Inlines>
<Run FontWeight="Bold">Placeholder1</Run>
<Run>Placeholder2</Run>
</TextBlock.Inlines>
</TextBlock>
In the code-behind (yes I am desparate!):
private void TextBlockLoaded(object sender, RoutedEventArgs e)
{
var textBlock = (TextBlock)sender;
var viewModel = (ViewModel)textBlock.DataContext;
var prop1Run = (Run)textBlock.Inlines[0];
var prop2Run = (Run)textBlock.Inlines[1];
prop1Run.Text = viewModel.Property1;
prop2Run.Text = viewModel.Property2;
}
This seemed to work, but because I am using the LongListSelector, although items get recycled, the Loaded codebehind event handler doesn't re-initialize the Runs, so very quickly the wrong text is displayed...
I've looked at using the LongListSelector's Linked event (which I already use to free up images that I display in the list), but I can't see how I can use that to re-initialize the Runs' text properties.
Any help appreciated!
I finally found a solution that works for me.
As I mention in the comment, Paul Stovell's approach would not work.
Instead I used a similar approach to add an attached property to the TextBlock, bound to the TextBlock's DataContext, and attached properties on the runs, indicating which ViewModel properties they should be bound to:
<TextBlock TextWrapping="Wrap"
Views:BindableRuns.Target="{Binding}">
<TextBlock.Inlines>
<Run FontWeight="Bold" Views:BindableRuns.Target="Property1"/>
<Run Views:BindableRuns.Target="Property2"/>
</TextBlock.Inlines>
</TextBlock>
Then in my attached TextBox Target (datacontext) property's changed event, I update the Runs, and subscribe to be notified of changes to the TextBox Target properties. When a TextBox Target property changes, I updated any associated Run's text accordingly.
public static class BindableRuns
{
private static readonly Dictionary<INotifyPropertyChanged, PropertyChangedHandler>
Handlers = new Dictionary<INotifyPropertyChanged, PropertyChangedHandler>();
private static void TargetPropertyPropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
if(!(dependencyObject is TextBlock)) return;
var textBlock = (TextBlock)dependencyObject;
AddHandler(e.NewValue as INotifyPropertyChanged, textBlock);
RemoveHandler(e.OldValue as INotifyPropertyChanged);
InitializeRuns(textBlock, e.NewValue);
}
private static void AddHandler(INotifyPropertyChanged dataContext,
TextBlock textBlock)
{
if (dataContext == null) return;
var propertyChangedHandler = new PropertyChangedHandler(textBlock);
dataContext.PropertyChanged += propertyChangedHandler.PropertyChanged;
Handlers[dataContext] = propertyChangedHandler;
}
private static void RemoveHandler(INotifyPropertyChanged dataContext)
{
if (dataContext == null || !Handlers.ContainsKey(dataContext)) return;
dataContext.PropertyChanged -= Handlers[dataContext].PropertyChanged;
Handlers.Remove(dataContext);
}
private static void InitializeRuns(TextBlock textBlock, object dataContext)
{
if (dataContext == null) return;
var runs = from run in textBlock.Inlines.OfType<Run>()
let propertyName = (string)run.GetValue(TargetProperty)
where propertyName != null
select new { Run = run, PropertyName = propertyName };
foreach (var run in runs)
{
var property = dataContext.GetType().GetProperty(run.PropertyName);
run.Run.Text = (string)property.GetValue(dataContext, null);
}
}
private class PropertyChangedHandler
{
private readonly TextBlock _textBlock;
public PropertyChangedHandler(TextBlock textBlock)
{
_textBlock = textBlock;
}
public void PropertyChanged(object sender,
PropertyChangedEventArgs propertyChangedArgs)
{
var propertyName = propertyChangedArgs.PropertyName;
var run = _textBlock.Inlines.OfType<Run>()
.Where(r => (string) r.GetValue(TargetProperty) == propertyName)
.SingleOrDefault();
if(run == null) return;
var property = sender.GetType().GetProperty(propertyName);
run.Text = (string)property.GetValue(sender, null);
}
}
public static object GetTarget(DependencyObject obj)
{
return obj.GetValue(TargetProperty);
}
public static void SetTarget(DependencyObject obj,
object value)
{
obj.SetValue(TargetProperty, value);
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.RegisterAttached("Target",
typeof(object),
typeof(BindableRuns),
new PropertyMetadata(null,
TargetPropertyPropertyChanged));
}
I suggest you give the BindableRun a try. I've only used it in WPF, but I don't see why it wouldn't work in Silverlight.

Resources