Xamarin: add image to my button from PCL (not from Resources) - xamarin

I'm working on a Xamarin.Forms project utilizing PCL (not the shared project).
I have a few images in my Resources folders in both Android and iOS project.
This works and the icons show in buttons as they're supposed to:
<Button Image="speaker.png" Grid.Row="0" Grid.Column="0" />
I also have a folder in my PCL project with some images: images/icons/speaker.png
I've tried this:
<Button Image="{local:EmbeddedImage TestThree.images.icons.speaker.png}" />
...but that didn't work...
I would like those buttons to show images from my images folder in my PCL project.
So my question would be...
<Button WHAT GOES HERE? Grid.Row="0" Grid.Column="0" />

When Button.Image wants FileImageStream, I give it to him. But as images in the project I still use embedded resources PNG files in PCL (or .NET standard 2.0) library (project).
For example the PCL project name is "MyProject" and I have an image placed in its subfolder "Images\Icons\ok.png". Then the resource name (e.g. for ImageSource.FromResource) is "MyProject.Images.Icons.ok.png".
This method copies embedded resource file into the file in application local storage (only first time).
public static async Task<string> CopyIcon(string fileName)
{
if (String.IsNullOrEmpty(fileName)) return "";
try
{
// Create (or open if already exists) subfolder "icons" in application local storage
var fld = await FileSystem.Current.LocalStorage.CreateFolderAsync("icons", CreationCollisionOption.OpenIfExists);
if (fld == null) return ""; // Failed to create folder
// Check if the file has not been copied earlier
if (await fld.CheckExistsAsync(fileName) == ExistenceCheckResult.FileExists)
return (await fld.GetFileAsync(fileName))?.Path; // Image copy already exists
// Source assembly and embedded resource path
string imageSrcPath = $"MyProject.Images.Icons.{fileName}"; // Full resource name
var assembly = typeof(FileUtils).GetTypeInfo().Assembly;
// Copy image from resources to the file in application local storage
var file = await fld.CreateFileAsync(fileName, CreationCollisionOption.OpenIfExists);
using (var target = await file.OpenAsync(PCLStorage.FileAccess.ReadAndWrite))
using (Stream source = assembly.GetManifestResourceStream(imageSrcPath))
await source.CopyToAsync(target); // Copy file stream
return file.Path; // Result is the path to the new file
}
catch
{
return ""; // No image displayed when error
}
}
When I have a regular file, I can use it for the FileImageStream (Button.Image).
The first option is use it from the code.
public partial class MainPage : ContentPage
{
protected async override void OnAppearing()
{
base.OnAppearing();
btnOk.Image = await FileUtils.CopyIcon("ok.png");
}
}
Also I can use it in the XAML, but I must create an implementation of IMarkupExtension interface.
[ContentProperty("Source")]
public class ImageFileEx : IMarkupExtension
{
public string Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
return Task.Run(async () => await FileUtils.CopyIcon(Source)).Result;
}
}
Now I can assign the image direct in the XAML.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyProject"
x:Class="MyProject.MainPage"
xmlns:lcl="clr-namespace:MyProject;assembly=MyProject">
<Grid VerticalOptions="Fill" HorizontalOptions="Fill">
<Button Image="{lcl:ImageFileEx Source='ok.png'}" Text="OK" />
</Grid>
</ContentPage>
For this solution the NuGet PCLStorage is needed.

The reason that does not work is because the properties are bound to different types.
Button's Image property takes a "FileImageSource" - Github
Image's Source property takes a "ImageSource" - Github
From the local:EmbeddedImage im guessing you were using the extension from Xamarin forms image docs
That would not work because it loads a "StreamImageSource" instead of "FileImageSource".
In practice you should not do this as it would not load from different dimension images(#2x, xhdpi etc) and would give bad quality images and not support multiple resolutions.
You could use a view container(Stack layout etc) with a TapGestureRecognizer and an image centered inside it or create a custom renderer which really is more effort than its worth. None of these still would still obviously not handle multiple images though.
The correct solution would be to generate the correct size images from the base(Which I would assume would be MDPI Android or 1X for iOS) and put them in the correct folders and reference them in your working Button example.

Related

Changing/Updating Image in XAML

I'm having an issue updating my Images in my Xamarin UWP app. First, I'd like to say I have seen multiple other threads on this issue, but none have been able to solve my predicament and a lot are very outdated.
Here is my scenario: I have three images I am using, named green.png, red.png, and gray.png. I am displaying one Image in my Xamarin app, and depending on a specific float called in my associated .cs file, I want to change the Image to another color. So, for example, if the float goes below 15, I want the Image to be the Red one.
Here is how I am currently displaying my Images without code for changing them, i.e. this code works fine and my Images appear in the app:
<Image Source="{pages:ImageResource BLE.Client.Pages.red.png}" Opacity="0.6"/>
They are currently stored in the same directory as the XAML files themselves. I know that on Android there is a Resources folder, but I don't see any equivalent for UWP, so I am loading my Images this way.
One way I attempted to do this based on another post I saw here is this:
<Image Source="{Binding HeadColor, StringFormat='pages:ImageResource BLE.Client.Pages.\{0\}.png', Mode=TwoWay}" Opacity="0.6"/>
The way this is supposed to function is depending on the value of my float, I used the string HeadColor in my .cs file and do an OnPropertyChanged on it. It always contains either the string "green", "red", or "gray", and with this method it should slot itself into the Image location string. However, this does not work.
For reference, here is how I update my HeadColor string in my .cs file:
private string _HeadColor;
public string HeadColor {get => _HeadColor; set {_HeadColor = value; OnPropertyChanged("HeadColor");}}
...
if (SensorAvgValue > 15) {_HeadColor = "green";}
else {_HeadColor = "red";}
RaisePropertyChanged(() => HeadColor);
I have also tried using an IValueConverter, but that does not work either.
So, in summary, my question is how to best go about dynamically changing which Image I'd like to use. They are all the same dimensions and in the same directory, the only difference being their names "green.png", "red.png", and "gray.png". Is there a better way to call/load the Images?
Thanks!
this works for me on iOS - I don't have a UWP env to test with, but it should work the same. I have two images "reddot.png" and "greendot.png" in my iOS Resources
<StackLayout Padding="20,50,20,50">
<Image Source="{Binding HeadColor, StringFormat='\{0\}dot.png'}" Opacity="0.6"/>
<Button Clicked="ChangeColor" Text="Click!!" />
</StackLayout>
code-behind
public partial class MainPage : ContentPage, INotifyPropertyChanged
{
private string headColor = "red";
public string HeadColor
{
get
{
return headColor;
}
set
{
headColor = value;
OnPropertyChanged();
}
}
public MainPage()
{
InitializeComponent();
this.BindingContext = this;
}
protected void ChangeColor(object sender, EventArgs args)
{
if (HeadColor == "red")
{
HeadColor = "green";
}
else
{
HeadColor = "red";
}
}
}

is there any way to set Source for Image From .standard Class Library in UWP

I have create Xamarin Native App only create UI Part platform specfic and shared code(Model,ViewModel) using .net stadard class Library.
In UWP Project I set Source Path Like this
Source="pack://application:,,,/MyClassLibraryName;Component/Assets/AppLogo.png"
It does not work for me!
I have create Xamarin Native App only create UI Part platform specfic and shared code(Model,ViewModel) using .net stadard class Library
For this scenario, you could refer this document that make ImageResourceExtension to load the Embedded image from .net stadard class Library.
We need add the following code to .net stadard class Library.
[Preserve(AllMembers = true)]
[ContentProperty(nameof(Source))]
public class ImageExtension : IMarkupExtension
{
public string Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Source == null)
return null;
// Do your translation lookup here, using whatever method you require
var imageSource = ImageSource.FromResource(Source, typeof(ImageExtension).GetTypeInfo().Assembly);
return imageSource;
}
}
Then add image file to .net standard class library and edit the Build Action to Embedded
Usage
ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mySource="clr-namespace:ImageLib;assembly=ImageLib"
x:Class="WorkingWithImages.EmbeddedImagesXaml">
<StackLayout Margin="20,35,20,20">
<Image Source="{mySource:Image ImageLib.logo.jpg}" />
</StackLayout>
/ContentPage>
Please note: Image path is classlibname.imagename.jpg
Update
You could use uwp uri scheme to add the image path to imgage control, for example
<ImageBrush ImageSource="ms-appx:///Image/AppLogoicon.png" Stretch="UniformToFill" x:Name="Image" />

Insert Html file content in xamarin forms

I have one Html file. That html file contains only some div. and i have applied some styles to that all div like margin, color and font-family. Now i want to make a page in my xamarin form app(both in ios and android) and want to add that html file's whole contain in my page.
I have tried by using web view control of xamarin forms. But my html file's content is too long, and because of that, xamarin code is going too long too as we have to applied html as a string to web view control. so i don't want to use that way.
So please give me brief explanation or solution to add html file in xamarin forms.
Hope for better solution.
Thanks in advance.
Not 100% sure what do you mean by "xamarin code is going to long too".
But you can make HtmlWebViewSource object in your ViewModel or in a code-behind, depending what approach you are using and later on you can set or bind WebViews's Source property to it.
In order to set it from code-behind you can do it like this:
var htmlSource = new HtmlWebViewSource();
htmlSource.Html = #"<html><body><h1>Xamarin is awesome</h1></body></html>";
yourWebView.Source = htmlSource;
On the other hand, if you are using MVVM and whole View-ViewModel concept you just need to have a property in your ViewModel of type HtmlWebViewSource and do the simple binding in your View.
Let's say you have the property of type HtmlWebViewSource named HtmlSource, you can set its value to your HTML content like we did in the previous example and than bind to it from WebView control, that should look something like this:
<WebView x:Name="yourWebView" Source="{Binding HtmlSource}" WidthRequest="1000" HeightRequest="1000" />
Hope this was helpful for you, wishing you lots of luck with coding!
If you want to load local html in your contentpage, I suggest you can use DependencyService to get html url from Android Assets file, I create simple in android that you can take a look.
Firstly, creating html in Android--Assets file, name as TestWebPage.html.
Then Creating Interface in Form, IBaseUrl.
public interface IBaseUrl
{
string GetUrl();
}
Then implementing this interface in Android platform:
public class LocalFiles : IBaseUrl
{
public string GetUrl()
{
return "file:///android_asset/";
}
}
ContentPage code:
<StackLayout>
<!-- Place new controls here -->
<WebView
x:Name="webviewjava"
HeightRequest="300"
WidthRequest="300" />
</StackLayout>
public MainPage()
{
InitializeComponent();
var urlSource = new UrlWebViewSource();
string baseUrl = DependencyService.Get<IBaseUrl>().GetUrl();
string filePathUrl = Path.Combine(baseUrl, "TestWebPage.html");
urlSource.Url = filePathUrl;
webviewjava.Source = urlSource;
}
Here is the sample at github, you can take a look:
https://github.com/CherryBu/htmlapp

Loading an html asset that's stored within Xamarin.Forms into a webview

I read in an existing question on how to use a webview in Xamarin.Android that suggests to write platform specific code on every supported platform.
Ideally, I want to store my html in a file within /assets/ in the Xamarin.Forms instead of storing it once for every OS.
I desire to have an easier way and simply store the asset within Xamarin.Forms. Is that possible?
I found a way to get it to work:
In RightClickOnProject/Properties/Resources there's a view that allows to add files that can be used as resources within Xamarin.Forms. I added there a HtmlFile.txt-file.
I add the WebView to the xaml via:
<ContentPage.Content>
<StackLayout>
<WebView x:Name="Web" VerticalOptions="FillAndExpand" />
</StackLayout>
</ContentPage.Content>
Then in the code-behind I do:
public partial class OurPage: ContentPage
{
public OurPage()
{
InitializeComponent();
Web.Source = new HtmlWebViewSource{Html = Properties.Resources.HtmlFile};
}
}
Well if I am correct you can sort of do it using an Embedded file in the asset folder and then reading it using System.IO something like this (Assuming you are using a NET Standard assembly):
Add the file that you want to read into the desired folder in your portable class and also ensure that the
Build Action: EmbeddedResource.
Then use GetManifestResourceStream to access the embedded file using its Resource ID, By default, the resource ID is the filename prefixed with the default namespace for the project it is embedded in - in this case, the assembly is WorkingWithFiles and the filename is PCLTextResource.txt, so the resource ID is WorkingWithFiles.PCLTextResource.txt. Now to get the desired file data all you need to so is something like this:
var assembly = IntrospectionExtensions.GetTypeInfo(typeof(LoadResourceText)).Assembly;
Stream stream = assembly.GetManifestResourceStream("WorkingWithFiles.PCLTextResource.txt");
string text = "";
using (var reader = new System.IO.StreamReader (stream)) {
text = reader.ReadToEnd ();
}
Where LoadResourceText is the cs file in which I am using this code to get the current assembly. Note that this should be the same assembly where the Embedded resource that you have just added is present and WorkingWithFiles.PCLTextResource.txt is the Assembly path for your Embedded resource for eg: If your assembly name is Foo and your file is in a folder called Asset then your path for this would be something like Foo.Asset.Your_File.txt
Once you are done with all this then the easy part begins, in your WebView page define a HtmlWebViewSource and assign it as the source to your WebView and Vola! you have loaded the data from the file your file.
var browser = new WebView();
var htmlSource = new HtmlWebViewSource();
htmlSource.Html = ;//your html string here
browser.Source = htmlSource;
Check How to work with files for more information.

Xamarin Forms Display image from Embedded resource using XAML

I'm trying to display and embedded image in a shared project resource (as explained here on microsoft documentation).
I created the ImageResourceExtension, the project compiles, but I get the following error when I start the project :
Could not load type 'Xamarin.Forms.Xaml.IReferenceProvider' from
assembly 'Xamarin.Forms.Core, Version=2.0.0.0
Here's my code :
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App1"
x:Class="App1.MainPage">
<StackLayout>
<Image x:Name="imgTest" Source="{local:ImageResource img.jpg}" />
</StackLayout>
</ContentPage>
EmbeddedImage.cs
namespace App1
{
[ContentProperty(nameof(Source))]
public class ImageResourceExtension : IMarkupExtension
{
public string Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Source == null)
return null;
// Do your translation lookup here, using whatever method you require
var imageSource = ImageSource.FromResource(Source, typeof(ImageResourceExtension).GetTypeInfo().Assembly);
return imageSource;
}
}
}
You can try this, it works for me
xyz.xaml.cs
imgName.Source = ImageSource.FromResource("ProjectName.Images.backicon.png");
xyz.xaml
<Image x:Name="imgName" />
Hope this code helps you!!
Not knowing if the answer is still valid for you, but I usually have an Assets folder outside my solution which contains Android specific folders (drawables), iOS specific folders (Resource with #2x and #3x) and a Common folder for shared images.
In my solution I add these files as a link in the folders they should be associated with. On Android in the Resources/Drawable and iOS in the Resource. In this case Common files can be shared across both platforms without physically copying them.
As a bonus, updates to these Assets can be overwritten in the folder outside my solution and will be used within my solution without any additional action to be taken.
Replace:
<Image x:Name="imgTest" Source="{local:ImageResource img.jpg}" />
With:
<Image x:Name="imgTest" Source="{local:ImageResource 'Project.Folder.img.jpg'}" />
And Replace:
var imageSource = ImageSource.FromResource(Source, typeof(...));
With:
var imageSource = ImageSource.FromResource(Source);
If you add the Image to your Android drawable folder, or to the iOS Resource folder, you don't need any extension. You can simply do:
<Image x:Name="imgTest" Source="img.jpg" />

Resources