How to display an image from a json url on the internet - c#

new xamarin programmer here. I need to display a comic image from the internet using a URL but somehow, the system keeps telling me that the link to the url does not work but I don't know why when I instantiate a new UriImageSource.
My first method was to try displaying the image using BitMapImage but it is only available for WindowsForms or WPF so I need an alternative for that as well if UriImageSource does not work for me. I'm on a Mac btw.
This is the xaml:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Weather_App.MainPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackLayout Grid.Row="1" Orientation="Horizontal" HorizontalOptions="Center">
<Image x:Name="backgroundImage" Margin="20"/>
</StackLayout>
</Grid>
</ContentPage>
This is the MainPage.cs:
using Xamarin.Forms;
namespace Weather_App
{
// Learn more about making custom code visible in the Xamarin.Forms previewer
// by visiting https://aka.ms/xamarinforms-previewer
[DesignTimeVisible(false)]
public partial class MainPage : ContentPage
{
private int maxNumber = 0;
private int currentNumber = 0;
public MainPage()
{
InitializeComponent();
ViewModel.ApiHelper.InitializeClient();
string url = Convert.ToString(ComicProcessor.LoadComic());
backgroundImage.Source = new UriImageSource
{
Uri = new Uri(url),
CachingEnabled = false,
CacheValidity = TimeSpan.FromHours(1)
};
}
}
}
Finally, this is the viewmodel/LoadComic method. I tried to return the comic at first instead of the url but since the BitMapImage didn't exist for Mac, I returned the url instead because I thought I could have used it for the UriImageSource instance. The comic properties include an integer Num and a string Img.
namespace Weather_App
{
public class ComicProcessor
{
public static int MaxComicNumber { get; set; }
public async static Task<string> LoadComic(int comicNumber = 0)
{
string url = "";
if (comicNumber > 0)
{
url = $"https://xkcd.com/{comicNumber}/info.0.json";
}
else
{
url = $"https://xkcd.com/info.0.json";
}
using (HttpResponseMessage response = await ViewModel.ApiHelper.ApiClient.GetAsync(url))
{
if (response.IsSuccessStatusCode)//If response successful do something then
{
// Takes data in as json and converted it to the type you have given and match anything that it finds
ComicModel comic = await response.Content.ReadAsAsync<ComicModel>();
if (comicNumber == 0)
{
MaxComicNumber = comic.Num;
}
return url;
}
else
{
// Outputs reason why it wasn't successful
throw new Exception(response.ReasonPhrase);
}
}
}
}
}

I would suggest you use FFImageLoading's CachedImage for this.
It is a library that is vastly accepted by the community and is great with caching and has memory handling options as well.
You can check their Git wiki to understand the library in depth.
Download it form Nuget
Call CachedImageRenderer.Init() on each platform. Let’s put it on MainActivity.cs of our Android project and AppDelegate.cs of iOS.
Then add its namespace and use it like this:
<ffimageloading:CachedImage
HorizontalOptions="Center" VerticalOptions="Center"
DownsampleToViewSize="true"
Source = "{Binding ImageUrl}">
</ffimageloading:CachedImage>
Xamarin.Forms.Image is an option but I personally feel it doesn't work well with URL images.
Also just give CachedImage the URL and it will do the downloading for you!
Goodluck,
Feel free to get back if you have questions.

Related

MapsUI 1.4.0 Xamarin differemce versions Newtonsoft.Json

I am writing a app where you can show your location on the screen. Everything works fine but the map is just one pixel line high. Is there a solution for this?
<Grid x:Name="MapGrid"
HorizontalOptions="CenterAndExpand"/>
<Grid>
This is the main cs code of my project, i dont get breaking errors just warnings about packages. My app will start just fine just showing just one pixel line of the map.
using System;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Essentials;
using Mapsui.Utilities;
using Mapsui.Projection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
private MapsuiView mapControl;
public MainPage()
{
InitializeComponent();
mapControl = new MapsuiView();
mapControl.NativeMap.Layers.Add(OpenStreetMap.CreateTileLayer());
MapGrid.Children.Add(mapControl);
Device.StartTimer(TimeSpan.FromSeconds(2), () =>
{
GoToLocation();
return true;
});
async void GoToLocation()
{
Location location = await getLocation();
if (location != null)
{
var sphericalMercatorCoordinate = SphericalMercator.FromLonLat(location.Longitude, location.Latitude);
mapControl.NativeMap.NavigateTo(sphericalMercatorCoordinate);
mapControl.NativeMap.NavigateTo(mapControl.NativeMap.Resolutions[15]);
}
}
async Task<Location> getLocation()
{
Location location = null;
try
{
var request = new GeolocationRequest(GeolocationAccuracy.Best);
location = await Geolocation.GetLocationAsync(request);
if(location != null)
{
Console.WriteLine($"Latitude: {location.Latitude}, " +
$"Longitude: {location.Longitude}, " +
$"Altitude: {location.Altitude}, " +
$"Accuracy: {location.Accuracy}");
}
}
catch (FeatureNotSupportedException fnsEx)
{
Console.WriteLine(fnsEx.ToString());
}
catch (FeatureNotEnabledException fneEx)
{
Console.WriteLine(fneEx.ToString());
}
catch (PermissionException pEx)
{
Console.WriteLine(pEx.ToString());
}
catch (Exception ex)
{
Console.WriteLine("Overige fout: " + ex.ToString());
}
return location;
}
}
}
}
This is a class which i added because it wouldn't work otherwise.
using System;
using System.Collections.Generic;
using System.Text;
namespace mobile_83504_3
{
public class MapsuiView : Xamarin.Forms.View
{
public Mapsui.Map NativeMap { get; }
protected internal MapsuiView()
{
NativeMap = new Mapsui.Map();
}
}
}
I'm using MapsUI 1.4.0 and it's trying to connect to a Newtonsoft.Json 9.0.0 that coudn't be found. so it takes 9.0.1, i don't know if that is the error but that is one of the few warnings that i get. alongside with a .NET framework that might not be fully compatible.
Everything works fine but the map is just one pixel line high. Is there a solution for this?
To use the Mapsui, it's not necessary to create a custom mapsui view. Add the declare line code to the xaml file, the view will be available.
Check the code:
<ContentPage
...
xmlns:mapsui="clr-namespace:Mapsui.UI.Forms;assembly=Mapsui.UI.Forms">
<StackLayout>
<mapsui:MapView x:Name="mapView"
VerticalOptions="FillAndExpand"
HorizontalOptions="Fill"
BackgroundColor="Gray" />
</StackLayout>
</ContentPage>
How to use Mapsui 2.0.1 with Xamarin.Forms?

RequestNavigate to Hyperlink with page anchor

I need to open a local .HTM file and navigate to a specific anchor name.
In this case it is an alarm information file with over 1,000 alarms / anchors.
In my test example (full code below) the Uri Fragment doesn't make it into the browser.
I have tried other ways of creating the hyperlink but this is as close as I could get.
The Test App:
Result:
MainWindow.xaml
<Window x:Class="HyperlinkWithPageAnchor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="100" Width="200">
<Grid>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
<Hyperlink NavigateUri="{Binding HyperlinkNavUri}" RequestNavigate="Hyperlink_RequestNavigate">
<TextBlock Text="Link Text"/>
</Hyperlink>
</TextBlock>
</Grid>
</Window>
MainWindow.xaml.cs
namespace HyperlinkWithPageAnchor
{
using System;
using System.Windows;
using System.ComponentModel;
using System.Windows.Navigation;
public partial class MainWindow : Window, INotifyPropertyChanged
{
private Uri _hyperlinkNavUri;
public Uri HyperlinkNavUri
{
get { return _hyperlinkNavUri; }
set { _hyperlinkNavUri = value; OnPropertyChanged(nameof(HyperlinkNavUri)); }
}
public MainWindow()
{
InitializeComponent(); DataContext = this;
// Desired Address: file:///C:/OSP-P/P-MANUAL/MPA/ENG/ALARM-A.HTM#1101
UriBuilder uBuild = new UriBuilder(new Uri("file://"));
uBuild.Path = #"C:\OSP-P\P-MANUAL\MPA\ENG\ALARM-A.HTM";
uBuild.Fragment = "1101";
HyperlinkNavUri = uBuild.Uri;
}
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
try { string link = e.Uri.ToString(); MessageBox.Show(link); System.Diagnostics.Process.Start(link); }
catch (Exception ex) { MessageBox.Show(ex.Message); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
}
}
It seems that if you let the OS identify the default browser itself, it will remove the anchor from the URI.
You need to use the following overload of Process.Start which allows to specify the executable and the parameters:
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = #"C:\Program Files\Internet Explorer\iexplore.exe";
processStartInfo.Arguments = "file:///C:/OSP-P/P-MANUAL/MPA/ENG/ALARM-A.HTM#1101";
Process.Start(processStartInfo);
If you want to use browser defined by the user instead of an hard-coded one, you will have to query the Windows Registry in order to retrieve it.
For example on old version of windows (before Vista I think), you have to use the following registry key: HKEY_CLASSES_ROOT\http\shell\open\command. On later release, this key contains the default browser (if the browser does not made any change).
private string GetDefaultBrowser()
{
string regKey = #"HTTP\shell\open\command";
using (RegistryKey registrykey = Registry.ClassesRoot.OpenSubKey(regKey, false))
{
return ((string)registrykey.GetValue(null, null)).Split('"')[1];
}
}
On Windows 10, it is a bit more complex due to the application launcher that allows to select the default application. To retrieve the browser chosen by the user, you have to query the following registry key: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.html\UserChoice. It the key does not exist you have to fallback on the previously mentioned key: HKEY_CLASSES_ROOT\http\shell\open\command.
private string GetDefaultBrowserOnWin10()
{
string execPath;
try
{
string extension = ".htm"; // either .htm or .html
RegistryKey propertyBag = Registry.CurrentUser.OpenSubKey($#"Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\{extension}\UserChoice", false);
var browserProgId = propertyBag.GetValue("ProgId").ToString(); ;
using (RegistryKey execCommandKey = Registry.ClassesRoot.OpenSubKey(browserProgId + #"\shell\open\command", false))
{
execPath = execCommandKey.GetValue(null).ToString().ToLower().Replace("\"", "");
if (IsDefaultLauncherApp(execPath))
{
System.Diagnostics.Debug.WriteLine("No user-defined browser or IE selected; anchor will be lost.");
}
}
if (!execPath.EndsWith("exe"))
{
execPath = execPath.Substring(0, execPath.LastIndexOf(".exe") + 4);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
execPath = GetDefaultBrowser();
}
return execPath;
}
private bool IsDefaultLauncherApp(string appPath)
{
return appPath.Contains("launchwinapp.exe");
}
This will work for all browsers except Microsoft Edge, which do not allow that at the moment.
You can use in you program like this:
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
try {
string link = e.Uri.ToString();
System.Diagnostics.ProcessStartInfo processStartInfo = new System.Diagnostics.ProcessStartInfo();
processStartInfo.FileName = GetDefaultBrowserOnWin10();
processStartInfo.Arguments = link;
System.Diagnostics.Process.Start(processStartInfo);
} catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
Some additional answers:
How to find default web browser using C#?

Xamarin.Forms XAML Workbook Demonstration (Android)

How Can i do this for the Android Xamarin Workbook ?
I'm stack at
Xamarin.Forms.Forms.Init();
var a = new App();
KeyWindow.RootViewController = a.MainPage.CreateViewController();
How to this for Android :
Xamarin.Forms.Forms.Init(needs 2 pram)
KeyWindow.RootViewController
a.MainPage.CreateViewController()
Here Is the Code For iOS
https://developer.xamarin.com/workbooks/xamarin-forms/user-interface/xaml/LoadXaml.workbook
Xamarin.Forms XAML Workbook Demonstration (iOS)
Steps to use XAML
1. Start by importing the NuGets for Xamarin.Forms and the iOS Platform Renderers
```csharp
#r "Xamarin.Forms.Platform.iOS"
#r "Xamarin.Forms.Core"
#r "Xamarin.Forms.Xaml"
#r "Xamarin.Forms.Platform"
```
And for this hack to work, add the Dynamic Xamarin Forms (preview)
NuGet (which contains the magic to load XAML from a string):
```csharp
#r "Xamarin.Forms.Dynamic"
```
2. Add the using statements next:
```csharp
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
```
3. Write up a simple XAML ContentPageto render on iOS:
```csharp
static string xaml = #"<?xml version='1.0' encoding='UTF-8' ?>
<ContentPage xmlns='http://xamarin.com/schemas/2014/forms'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class='XamlPage'
Title='Xaml Text' Padding='40'>
<StackLayout Orientation='Vertical'>
<Label Text='Hello from XAML' x:Name='helloLabel'/>
<BoxView Color='Blue' WidthRequest='300' HeightRequest='2' />
</StackLayout>
</ContentPage>";
```
4. Bootstrap the Xamarin.Forms app object and and for the main page class, then use the Dynamic Xamarin Forms LoadFromXaml extension method to parse the xaml string:
```csharp
public class App : Application
{
public ContentPage XamlPage {get;set;}
public App ()
{
XamlPage = new ContentPage();
XamlPage.LoadFromXaml (xaml); // loads XAML
MainPage = XamlPage;
}
}
```
5. Finally, set the iOS root view controller directly (in a real Xamarin.Forms app, this would be taken care of by the FormsApplicationDelegate subclass):
```csharp
Xamarin.Forms.Forms.Init();
var a = new App();
KeyWindow.RootViewController = a.MainPage.CreateViewController();
```
One More Thing...
Loading XAML in this way does not allow strongly-typed access to the elements by their x:Name, instead they can only be referenced using FindByNameas shown here to update the label:
```csharp
var l = a.XamlPage.FindByName<Xamarin.Forms.Label>("helloLabel");
l.Text = "Updated by the Workbook!";
a.XamlPage.Content
```

Loading Local HTML with WebView Xamarin Forms

I am trying to load a local HTML page in a webview with Xamarin forms.
I am using the basic example in the dev docs although I can get a URL to load I can't get my own HTML pages to load. This only needs to be done through Android so there is no worries about about IOS and Windows.
The Xaml:
<WebView
x:Name="webviewjava"></WebView>
The code behind:
public partial class javscriptExample : ContentPage
{
public interface IBaseUrl { string Get(); }
public javscriptExample()
{
InitializeComponent();
var source = new HtmlWebViewSource();
source.BaseUrl = DependencyService.Get<IBaseUrl>().Get();
webviewjava.Source = source;
}
}
The platform specific file (LocalFile.cs):
Just to note this has been set as an Android asset.
[assembly: Dependency(typeof(LocalFiles))]
namespace maptesting.Droid
{
public class LocalFiles: IBaseUrl
{
public string Get()
{
return "file:///android_asset/";
}
}
}
and under the asset's folder there is a 'TestWebPage.html', also set as an Android asset.
Although I dont know what the problem is I have put it through debug and the base url is coming back blank. Just to be clear im not getting a file not found, the screen is simply blank.
Also, and Im not sure if this makes a difference. There is no syntax highlighting on 'IBaseUrl' in the LocalFiles.cs file. So I'm not sure if it can 'see' it.
Any ideas?
I am also suffering with the same issue,but I resolved in the following way
Use "UrlWebViewSource" instead of "HtmlWebViewSource"
var urlSource = new UrlWebViewSource();
string baseUrl = DependencyService.Get<IWebViewBaseUrl>().GetBaseUrl();
string filePathUrl = Path.Combine(baseUrl, "imprint.html");
urlSource.Url = filePathUrl;
WebBrowser.Source = urlSource;
You must check the file properties for Build Action = BundleResource
Try this code to load local html file
var source = new HtmlWebViewSource();
string url = DependencyService.Get<IBaseUrl>().GetBaseUrl();
string TempUrl = Path.Combine(url, "terms.html");
source.BaseUrl = url;
string html;
try
{
using (var sr = new StreamReader(TempUrl))
{
html = sr.ReadToEnd();
source.Html = html;
}
}
catch(Exception ex){
Console.WriteLine(ex.Message);
}
Implementations of the interface for each platform must then be provided
iOS
[assembly: Dependency(typeof(BaseUrl))]
namespace yournamespace
{
public class BaseUrl: IBaseUrl
{
public string GetBaseUrl()
{
return NSBundle.MainBundle.BundlePath;
}
}
}
Android
[assembly: Dependency (typeof(BaseUrl))]
namespace yournamespace {
public class BaseUrl_Android : IBaseUrl {
public string Get() {
return "file:///android_asset/";
}
}
}
WebView.BaseUrl only tells the WebView where to start looking for files. It's the root folder of the "web site". By default browsers will load the file index.html, so if you rename your file to index.html I believe it should load automatically.
I think this should be possible too:
webviewjava.BaseUrl = DependencyService.Get<IBaseUrl>().Get();
webviewjava.Source = "TestWebPage.html";
Here you're saying "use this location as the default place to look for files" and "look up this file and use it as the source for the HTML".
This is an old post but It may help someone looking to implement with Android, iOS and UWP with just one HTML file. With this approach you only use one HTML file for all platforms.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/files?tabs=vsmac#loading-files-embedded-as-resources
Im not sure if this counts but I found a work around. Instead of taking the above route I simply did this:
webviewjava.Source = "file:///android_asset/TestWebPage.html";
in the code behind, and just left out the IBaseUrl call altogether.
This works as its supposed to.

Creating Markup Extension with Converter

I'm trying to create a Markup Extension that will take a string of HTML, convert it to a FlowDocument, and return the FlowDocument. I'm fairly new to creating Markup Extensions and I'm hoping this will be obvious to someone with more experience. Here is my code:
[MarkupExtensionReturnType(typeof(FlowDocument))]
public class HtmlToXamlExtension : MarkupExtension
{
public HtmlToXamlExtension(String source)
{
this.Source = source;
}
[ConstructorArgument("source")]
public String Source { get; set; }
public Type LocalizationResourceType { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (this.Source == null)
{
throw new InvalidOperationException("Source must be set.");
}
FlowDocument flowDocument = new FlowDocument();
flowDocument.PagePadding = new Thickness(0, 0, 0, 0);
string xaml = HtmlToXamlConverter.ConvertHtmlToXaml(Source.ToString(), false);
using (MemoryStream stream = new MemoryStream((new ASCIIEncoding()).GetBytes(xaml)))
{
TextRange text = new TextRange(flowDocument.ContentStart, flowDocument.ContentEnd);
text.Load(stream, DataFormats.Xaml);
}
return flowDocument;
}
}
Update: Here is the XAML.
<RadioButton.ToolTip>
<FlowDocumentScrollViewer Document="{ext:HtmlToXaml Source={x:Static res:ExtrudeXaml.RadioButtonCreateBody_TooltipContent}}" ScrollViewer.VerticalScrollBarVisibility="Hidden" />
</RadioButton.ToolTip>
And my VS error list:
Error 3 Unknown property 'Source' for type 'MS.Internal.Markup.MarkupExtensionParser+UnknownMarkupExtension' encountered while parsing a Markup Extension. Line 89 Position 49.
Error 1 The type "HtmlToXamlExtension" does not include a constructor that has the specified number of arguments.
Error 2 No constructor for type 'HtmlToXamlExtension' has 0 parameters.
You implemented you MarkupExtension without default constructor:
So you have 2 options:
Delete your specific constructor (Anyway you set Source
directly)
Change invocation of you HtmlToXamlExtension if you remove Source= part, then Wpf will try to find constructor matching all unnamed fields right after ext:HtmlToXaml part:
<RadioButton.ToolTip>
<FlowDocumentScrollViewer
Document="{ext:HtmlToXaml {x:Static res:ExtrudeXaml.RadioButtonCreateBody_TooltipContent}}"
ScrollViewer.VerticalScrollBarVisibility="Hidden" />
</RadioButton.ToolTip>
UPD: Even though it works, but MSDN says, that you should have default constructor
Hope it helps.
You should create default constructor for your markup extension, and everything will be fine.
It helped to me to install the .NET 4.7 (Developer Pack), I've seen this bug at .NET 4.6 but after upgrading it's gone.

Categories