Silverlight Image binding Issue - data-binding

I am trying to bind web address of an image (BigImageURL) with a image control. It works fine mostly but for some images i am getting http 403 error (found out using fiddler) and obviously the image does not get displayed. I want to display a static image in case the http url is not resolved.
<Image x:Name="HoverImage" Source="{Binding BigImageURL}" />
I tried to write a converter
public class UriToImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType,object parameter, CultureInfo culture)
{
BitmapImage image = null;
try
{
image = new BitmapImage(new Uri(value.ToString()));
}
catch (Exception ex)
{
image= new BitmapImage(new Uri("..<mydefaultimageUrl>.."));
}
return image;
}
...
}
<Image x:Name="HoverImage" Source="{Binding BigImageURL,Converter={StaticResource myUriToImageSourceConverter}" />
didn't work !!
Even though the image url was not accessible, the converter didn't throw any exception. I don't think it tries to resolve the address or read image stream while creating the BitmapImage
Tried setting up a FallbackValue but it didn't work either.
<Image x:Name="HoverImage" Source="{Binding BigImageURL,FallbackValue=DefaultUrl}"/>
Any pointers ??
Thanks in advance

Actually you have to do something like this
<Image x:Name="HoverImage" Source="{Binding BigImageURL}"
ImageFailed="HoverImage_ImageFailed" />
and add event handler
private void HoverImage_ImageFailed(object sender, ExceptionRoutedEventArgs e)
{
var expection = e.ErrorException; // Here we could know what happend
HoverImage.Source = someDefaultUrl; // And here we add default Url...
}
In silverligth you have to handle not loader image and image exceptions with help of events... do not use databinding for that case..

Related

Load Image from URL with a Referrer Header

I need to download static images from my CDN and display them in my Xamarin.Forms view. I'm new to Xamarin specifically and I'm having a hard time figuring out how to load an image from a byte array pulled from a HTTP request.
I have the following XAML in my View:
<ListView ItemsSource="{Binding Images}">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell ImageSource="{Binding .}">
</ImageCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In my code behind I'm fetching the image and trying to bind to a Collection of ImageSource instances.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
public ObservableCollection<ImageSource> Images { get; set; } = new ObservableCollection<ImageSource>();
protected override async void OnAppearing()
{
base.OnAppearing();
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Referer", "https://foo.bar/");
var response = await client.GetAsync("https://foo.bar/library/5.jpg");
byte[] image = await response.Content.ReadAsByteArrayAsync();
var imageSource = ImageSource.FromStream(() => new MemoryStream(image));
this.Images.Add(imageSource);
}
}
When I do this the Image within the StackLayout is empty. I know I can instead just bind to a URL and the ImageSource can download it for me but I'm required to pass a Referrer Header. I can't see anyway for me to force the XAML Image view to allow custom headers, nor can I see a way to do it if I instantiate an instance of ImageSource in my code behind.
When I read the documentation on ImageSource it sounds like it only supports URI, File, Stream and App Resources to load images from. Since I had an array of bytes I wrapped it in a MemoryStream and called ImageSource.FromStream. Nothing loads when the app runs though - the ImageCell is empty.
I know the image was downloaded. I converted the byte[] to a Base64 string then dropped it into Base64 to Image Decoder. The image loads no problem so I know it's being downloaded properly. What I don't understand is why I can't get the image to render on a ImageSource.
I've also tried to just use a normal Image control and have the same problem.
<ScrollView>
<ContentView>
<StackLayout BindableLayout.ItemsSource="{Binding Images}" Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"></Image>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ContentView>
</ScrollView>
Am I doing something completely wrong here or is dynamic downloading of images from the internet with special Headers something I can't do in Xamarin.Forms?
Try to add this.BindingContext = this; in page constructor.
public MainPage()
{
InitializeComponent();
this.BindingContext = this;
}

ImageButton not firing Command binding in Xamarin Forms

I have an ImageButton that is not firing Command binding command using VMMV architecture. First all other bindings are working properly in the view.
Here is button:
<ImageButton Command="{Binding SelectedItemCommand}" Source="{Binding Logo}" Grid.Row="0" Grid.Column="1" HeightRequest="150" WidthRequest="150" HorizontalOptions="CenterAndExpand" VerticalOptions="EndAndExpand"></ImageButton>
and in ViewModel:
public ICommand SelectedItemCommand => new Command(GetSelectedItem);
When I click the image nothing happens. I've even tried to bind to Pressed parameter but from everything I have read only the Command parameter should be used in a binding scenario. Putting a breakpoint on the function GetSelectedItem never gets reached.
What am I doing wrong here?
Sorry been away for a few days. So nothing was working on the suggestions even though they really should be clicking wouldn't fire command. Anyway I managed to get it to fire now using an eventhandler like so:
SelectedItemCommand = new Command<string>(param => OnItemSelected(param));
public void OnItemSelected(string img1_2)
{
PressedEventHandler?.Invoke(this, EventArgs.Empty);
}
The param captures the CommandParameter so I know which image to the question was clicked "img1" "img2" to do something specific. So my function now accepts a sender object and empty eventarg. I would like to instead pass img1_2 value but that doesn't appear to be possible as of now. What is cusrious is the sender object contains all the properties and values from the images (like an array of all my properties) but I cannot seem to get at them.
Attempted this:
string str = Item1Image.ToString(); // property in sender and viewmodel
But this returns a null value and not value listed in the sender object value?
Any additional thoughts?
TIA!
Rick...
public ICommand SelectedItemCommand {get; private set;}
...
public YourViewModel(){
...
SelectedItemCommand = new Command(GetSelectedItem);
...
}
...
Or
public ICommand SelectedItemCommand{
get
{
return new Command(() => {
//Do something
});
}
}

How To Stop App Data From Growing When Working With Photos

Posted this on the Xamarin forum and haven't gotten feedback yet.
Hoping someone here might be able to assist.
I'm writing an app (Xamarin.Forms) that allows the user to take photo's (using Xam.Plugin.Media) and then display them in a list view.
I noticed the app data growing with each photo taken. And I'm hoping someone could shed some light on what the issue is.
I do not wish to save the photo on the device.
I've only tested this on android at the moment.
I wrote a sample app to simulate the issue. Steps taken as well as the XAML and view model code to follow.
Install app.
Go to Android / Settings / Apps / {AppName} / Storage
The app data is now approximately 10Mb and cache less than 1Mb.
Start the app, take a photo but DO NOT press 'OK' to confirm the photo taken.
Note that the app data has now increased with approximately the size of the photo in memory. (Let's use 5Mb, which brings the app data to 15Mb)
Press 'OK' to confirm the photo taken which then adds the photo to the list view.
Note that the app data has again increased with approximately the size of the photo in memory. (Again 5Mb, which brings the app data to 20Mb)
So with each photo taken the app data increases with approximately double the size of the photo taken, and as a result the app will end up using most of the device storage after a month of use.
I'm not sure if I'm missing something simple, or if this is a result of poor coding.
Any advice would be greatly appreciated.
Thanks in advance.
XAML:
<ContentPage.Content>
<Grid RowSpacing="0" Margin="10,10,10,0">
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Text="Take Photo" Command="{Binding TakePhotoCommand}"/>
<Label Grid.Row="1" Text="Photo List" HorizontalOptions="Center"/>
<ListView Grid.Row="2" ItemsSource="{Binding PhotoList}" RowHeight="250">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Image Source="{Binding ImageSource}" Aspect="Fill" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage.Content>
View Model:
public class CameraVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<MyPhoto> photoList {get;set;}
public ObservableCollection<MyPhoto> PhotoList
{
get { return photoList; }
set
{
if(value != photoList)
{
photoList = value;
OnPropertyChanged("PhotoList");
}
}
}
public ICommand TakePhotoCommand { get; set; }
public async void TakePhoto()
{
try
{
await CrossMedia.Current.Initialize();
var mediaFile = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions() { });
if (mediaFile == null)
{
return;
}
PhotoList.Add(new MyPhoto()
{
ImageSource = ImageSource.FromStream(() =>
{
Stream stream = mediaFile.GetStream();
mediaFile.Dispose();
return stream;
})
});
}
catch (Exception ex)
{
throw ex;
}
}
public CameraVM()
{
PhotoList = new ObservableCollection<MyPhoto>();
TakePhotoCommand = new Command(() =>
{
TakePhoto();
});
}
}
After some further investigation I found that when setting the SaveMetaData and SaveToAlbum properties on the StoreCameraMediaOptions object to false, and then deleting the photo from the device after using it, the app data still increased, but not as much. When going through the device general memory usage, I noticed that the memory used for the default camera app was also quite large. After clearing the camera's memory, my app's data when back to a few kilobytes. I proceeded to take some more photos on my app, the app data grew (as expected), but after a couple of hours it was back to a few kilobytes. I suppose the device clears the memory for unused apps after a while.
Here is my working code. My app uploads the photo taken to a server and then displays this photo in a list view using the relevant URL. This is why i was able to delete the photo from the device afterwards. I suspect that if you try and display the photo taken using an ImageSource gathered from the photo taken it won't display after deleting the photo.
Only tested on android (Galaxy S8)
await CrossMedia.Current.Initialize();
StoreCameraMediaOptions storeCameraMediaOptions = new StoreCameraMediaOptions()
{
SaveMetaData = false,
SaveToAlbum = false
};
MediaFile mediaFile = await CrossMedia.Current.TakePhotoAsync(storeCameraMediaOptions);
if (mediaFile != null)
{
string base64String = Convert.ToBase64String(ULHelper.ToByteArray(mediaFile.GetStream()));
// Logic to upload photo to server using base64String
if (File.Exists(mediaFile.Path))
{
File.Delete(mediaFile.Path);
}
mediaFile.Dispose();
}

xamarin.forms Handle Clicked event on WebView

I want to handle the click/tap event on a WebView control
I've tried the GestureRecognizers but nothing happens, i think maybe the WebView has some sort of making the event handled "true".
<WebView>
<WebView.GestureRecognizers>
<TapGestureRecognizer
Tapped="OnWebViewClick"
NumberOfTapsRequired="1" />
</WebView.GestureRecognizers>
</WebView>
And I've tried it using c# code behind too but no luck :/
I am using this hack: put the WebView in a Grid and insert a BoxView in the same cell. Then put a handler on the BoxView.
You can use the HybridWebView from XLabs, and use javascript injection to invoke and handle clicks in your Xamarin control. The injected javascript code can add a click-event listener at capture stage. When a click is detected it uses Native callback to send information back to C# code.
For example - you can define a custom control like this:
public class ClickEventArgs : EventArgs
{
public string Element { get; set; }
}
public class ClickableWebView : XLabs.Forms.Controls.HybridWebView
{
public event EventHandler<ClickEventArgs> Clicked;
public static readonly BindableProperty ClickCommandProperty =
BindableProperty.Create("ClickCommand", typeof(ICommand), typeof(ClickableWebView), null);
public ICommand ClickCommand
{
get { return (ICommand)GetValue(ClickCommandProperty); }
set { SetValue(ClickCommandProperty, value); }
}
public ClickableWebView()
{
LoadFinished += (sender, e) =>
InjectJavaScript(#"
document.body.addEventListener('click', function(e) {
e = e || window.event;
var target = e.target || e.srcElement;
Native('invokeClick', 'tag='+target.tagName+' id='+target.id+' name='+target.name);
}, true /* to ensure we capture it first*/);
");
this.RegisterCallback("invokeClick", (string el) => {
var args = new ClickEventArgs { Element = el };
Clicked?.Invoke(this, args);
ClickCommand?.Execute(args);
});
}
}
Sample XAML usage
<local:ClickableWebView
Uri="https://google.com"
Clicked="Handle_Clicked"
/>
and sample code-behind
void Handle_Clicked(object sender, CustomWebView.ClickEventArgs e)
{
Xamarin.Forms.Application.Current.MainPage.DisplayAlert("WebView Clicked", e.Element, "Dismiss");
}
** Output **
Alternatively, you can also bind ClickCommand property to implement this using MVVM pattern.
Another option is to handle the click in html and do a navigation that doesn't go anywhere. You can put something like this in your html
<div onclick="window.location.href='#click#'">...</div>
So a click anywhere inside there would cause a navigation. If you only have a button, you could just use
<a href='#click'>...</a>
Then in your WebView control wire up the Navigating event, and check if the new url contains "#click". If so, do your click handling code and call e.Cancel=true in the event to prevent the browser from completing the navigation.
Note that onclick handlers don't work on body or document elements in Xamarin Webview. At least not on iOS.
I've found the simplest approach is to use a Grid with two controls, the WebView and a Button
<Grid>
<WebView
Grid.Column="0"
Grid.Row="0"
HeightRequest="100"
WidthRequest="1000" />
<Button
Grid.Column="0"
Grid.Row="0"
BackgroundColor="Transparent"
HorizontalOptions="Fill"
VerticalOptions="Fill"
Clicked="OnWebViewTapped"/>
</Grid>
The button covers the WebView and intercepts taps.
Gesture recognizer doesn't work with WebView. You can try using MR.Gestures
To get all the features you will have to purchase a license.
If you forget to configure the license key properly or the key does not match your app name, then all the events will still be raised, but the properties of the EventArgs will be empty. That may be enough for the tap and long press events, but not for the more complicated ones.
An easier workaround is to use the 'Focused' event on your webview. You can implement it as below:
var wb = new WebView
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
Source = "https://stackoverflow.com/questions/56320611/webview-gesturerecognition-not-working-in-xamarin-forms",
};
wb.Focused += (s, e) =>
{
//Handle your logic here!
wb.Unfocus();
};

How to bind to a WPF dependency property to window?

i create a dependency property to close a view from view model,
dependencyProperty:
public static class WindowBehaviors
{
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.RegisterAttached("IsOpen"
, typeof(bool),
typeof(WindowBehaviors),
new UIPropertyMetadata(false, IsOpenChanged));
private static void IsOpenChanged(DependencyObject obj,DependencyPropertyChangedEventArgs args)
{
Window window = Window.GetWindow(obj);
if (window != null && ((bool)args.NewValue))
window.Close();
}
public static bool GetIsOpen(Window target)
{
return (bool)target.GetValue(IsOpenProperty);
}
public static void SetIsOpen(Window target, bool value)
{
target.SetValue(IsOpenProperty, value);
}
}
and use it in my xaml like this:
<window
...
Command:WindowBehaviors.IsOpen="True">
it work's fine,but when i want to bind it to a property in viewModel,it dosen't work,and i guess,it dosen't work because i define the resource later in xaml.
in xaml:
<Window.Resources>
<VVM:myVieModel x:Key="myVieModel"/>
</Window.Resources>
and i don't know what should i do,where should i put this:
Command:WindowBehaviors.IsOpen="{binding Isopen}"
public MainWindow()
{
InitializeComponent();
// DO THIS
this.DataContext = Resources["myVieModel"];
}
You need to bind the data context for the scope where your binding is in. Usually this is fairly high up in your XAML, usually the first element in your form or control.
In your case, the data context beeing a static resource the folllowing should work:
<grid DataContext="{StaticResource myVieModel}">
<!-- the code with the binding goß into here -->
</grid>
Actually this is the same as ebattulga suggests, just the XAML way (no code behind).
Thanks for your helps,i fixed it and here is my solution,
i used to use MVVMToolkit but now i'm useing MVVMlight and as you know in MVVMLight,we just define Application Resources Once in App.xaml.so we can bind all the window's properties simply,hope this can help some people who has the same problem!!
app.xaml
<Application.Resources>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True" />
</Application.Resources>
and in the window(view)
DataContext="{Binding DefaultSpecItemVM, Source={StaticResource Locator}}"
and it works perfect.:D

Resources