Insert Html file content in xamarin forms - xamarin

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

Related

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 - Adding constructor to MasterDetailPage.Master

I have a Xamarin.Forms app with a master-detail page and it works well.
But I've recently needed to add a parameter to the constructor of the master page (AttendPageMaster), but now I need to pass this constructor.
How do I add a parameter to the xaml?
<MasterDetailPage.Master>
<pages:AttendPageMaster x:Name="MasterPage" />
</MasterDetailPage.Master>
The code behind page with constructor:
public AttendPageMaster(AttendanceViewModel viewModel)
{
}
Let me know if you need any more info.
You do not have to pass ViewModel to Page via constructor, you can set the Page's BindingContext:
<MasterDetailPage.Master>
<pages:AttendPageMaster x:Name="MasterPage">
<pages:AttendPageMaster.BindingContext>
<myViewModels:AttendanceViewModel />
</pages:AttendPageMaster.BindingContext>
</pages:AttendPageMaster>
</MasterDetailPage.Master>
This solution will work if your ViewModel does not expect any parameters in constructor. Otherwise you may consider using ViewModelLocator and DI to inject the constructor parameters.
Please note that myViewModels should be defined in the header of your XAML page as xmlns:myViewModels.
P.S.: Previously you mentioned that you got an exception while trying to use code behind approach. You could easily solve it by setting the Title property of the AttendPageMaster. Example:
new AttendPageMaster(new AttendanceViewModel()){ Title = " " };
I managed to do this from the code behind by creating the menu page in the constructor of the masterdetail and assigning it to the "Master" property:
AttendMasterPageMaster MasterPage;
public AttendMasterPage(AttendanceViewModel viewModel)
{
MasterPage = new AttendMasterPageMaster(viewModel);
Detail = new NavigationPage((Page)Activator.CreateInstance(typeof(StartPage), viewModel));
Master = MasterPage;

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

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.

Xamarin Forms Force view to bind values

I have a view. I have a bindable property there.
public partial class OrderCard : ContentView
{
public static readonly BindableProperty OrderProperty = BindableProperty.Create(nameof(Order), typeof(Order), typeof(OrderCard), null);
public Order Order
{
get { return (Order)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
public OrderCard()
{
InitializeComponent();
}
}
In the xaml of this view I'm binding to Order property like this:
Text="{Binding Order.Name, Source={x:Reference Root}}"
Root is a name in the xaml of a view OrderCard
When I use this view in the page everything works ok.
But I want to measure it's size even before adding it to the page.
var orderCard = new OrderCard { Order = order};
SizeRequest sizeRequest = orderCard.Measure(OrdersContainer.Width/5, OrdersContainer.Height);
But it gives me wrong numbers because bindings isn't applied.
How to force to apply bindings when view isn't attached to the page?
Bindings do not require being attached to a Page or anything else to be applied.
You might think they're not applied if your method for figuring out is to set a breakpoint on get_Order because that is never used, the Xaml loader uses GetValue directly. The usual way of figuring out if a Binding is correctly applied, is to add a PassthroughConverter (don't look for it, you have to write it yourself) to the Binding and put the breakpoint in the Convert method.
That being said, you can't Measure anything unless it's added to a page that is rendered on screen. If you try to Measure before that, you indeed get dummy values.
I was able to solve this problem by not doing a property Order but passing an order as BindingContext. Then I can measure the size of a view without attaching it to a page like this:
var orderCard = new OrderCard { BindingContext = order};
SizeRequest sizeRequest = orderCard.Measure(widthToTryToFitInTheView,heightToTryToFitInTheView);

In Prism (Composite Application Guidelines), how can I get views dynamically loaded into TabControl?

In a Prism v2 application, I define two regions, each a tabitem in a tabcontrol:
<UniformGrid Margin="10">
<TabControl>
<TabItem Header="First" Name="MainRegion" cal:RegionManager.RegionName="MainRegion"/>
<TabItem Header="Second" Name="SecondRegion" cal:RegionManager.RegionName="SecondRegion"/>
</TabControl>
</UniformGrid>
In the bootstrapper two modules are loaded and each injects a view into each of the tabitems:
protected override IModuleCatalog GetModuleCatalog()
{
ModuleCatalog catalog = new ModuleCatalog();
catalog.AddModule(typeof(SecondModule.SecondModule));
catalog.AddModule(typeof(HelloWorldModule.HelloWorldModule));
return catalog;
}
Now, of course, I want to perform the decoupling magic that I keep reading about and uncomment one of the modules and see its tab item not appear at all. Instead, on the contrary, there are still two TabItems and one is empty. This tells me that my application is still tightly coupling data and UI as in the bad old WinForm days.
So what do I need to do here to make this dynamic, so that the UI changes dynamically based on what modules are loaded, i.e. so that I could load 10 modules/views in my bootstrapper and there would automatically be 10 TabItems in the TabControl?
INTERMEDIATE ANSWER:
If I just make one region in a TabControl:
<TabControl Name="MainRegion" cal:RegionManager.RegionName="MainRegion"/>
and then load both controls into the MainRegion:
public void Initialize()
{
regionManager.RegisterViewWithRegion("MainRegion", typeof(Views.SecondView));
}
...
public void Initialize()
{
regionManager.RegisterViewWithRegion("MainRegion", typeof(Views.HelloWorldView));
}
then I get a TabControl with two tabs, each with a view in it, which is what I want.
But the TabItem headers are not defined. How do I dynamically define the header (e.g. not in the XAML but dynamically back in the View classes)?
This works too:
public class View : UserControl
{
public string ViewName { get; set; }
}
and then in the shell:
<Window.Resources>
<Style TargetType="{x:Type TabItem}" x:Key="TabItemRegionStyle">
<Setter Property="Header" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.ViewName}" />
</Style>
</Window.Resources>
...
<TabControl cal:RegionManager.RegionName="RightRegion" Width="Auto" Height="Auto" HorizontalAlignment="Stretch" Grid.Column="2"
x:Name="RightRegion" ItemContainerStyle="{StaticResource TabItemRegionStyle}" />
Nice.
You can remove the ViwewName property on the view and change the binding on the TabItem value to be Value="{Binding DataContext.HeaderInfo}" ... where HeaderInfo is a property of your DataContext object - IE the business object which the Tab Item represents. This is a little more elegant.
You are on the right track with your modification.
The way I usually achieve the header is by adding an object to the region instead of a control, and datatemplating it with the control.
This object defines a property (let's say MyHeaderProperty) which I then use to bind to using an ItemContainerStyle on the TabControl.
I do not know if there is a way to achieve that without resorting to that kind of trick (an intermediate object and a DataTemplate).

Resources