I am using a custom renderer for showing a pdf file on my app.
Below are my renderer codes:
Main Project:
public class PdfWebView : WebView
{
public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(PdfWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
Android:
[assembly: ExportRenderer(typeof(PdfWebView), typeof(MyWebViewRenderer))]
namespace Projectname.Droid.Renderer
{
public class MyWebViewRenderer : WebViewRenderer
{
public MyWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var customWebView = Element as PdfWebView;
Control.Settings.AllowUniversalAccessFromFileURLs = true;
Control.Settings.JavaScriptEnabled = true;
Control.LoadUrl(string.Format("https://drive.google.com/viewerng/viewer?url={0}", customWebView.Uri.ToString()));
}
}
}
}
Xaml and Xaml.cs:
<local:PdfWebView
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
x:Name="pdf_Webview"/>
pdf_Webview.Uri = pdfurl;
But when I run the app on the android platform getting the below exception:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
No Renderers are using on the ios part and pdf is working fine on there. What is the actual issue on the android part?
Made below modifications on the renderer. Got this solution from my own thread.
public class MyWebViewRenderer : WebViewRenderer
{
PdfWebView customWebView;
public MyWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
customWebView = Element as PdfWebView;
Control.Settings.AllowUniversalAccessFromFileURLs = true;
Control.Settings.JavaScriptEnabled = true;
if (customWebView.Uri != null)
{
Control.LoadUrl(string.Format("https://drive.google.com/viewerng/viewer?url={0}", customWebView.Uri.ToString()));
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (customWebView.Uri != null && !Control.Url.Contains(customWebView.Uri))
{
Control.LoadUrl(string.Format("https://drive.google.com/viewerng/viewer?url={0}", customWebView.Uri.ToString()));
}
}
}
Related
im working with custon entry rendered, i need to hear from xaml in my custom render when i clicked my button
i have this code in my xaml
<local:MyEntry eventRefresh="true">
when i clicked my button this function is actived
private async void Execute(object sender)
{
var entry = ((MyEntry)view);
entry.eventRefresh = "false";
but my EntryRendered donot hear the change
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var element = Element as MyEntry;
You should define the property eventRefresh as Bindable Property .
in your custom Entry
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
namespace xxx
{
public class MyEntry:Entry,INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public static readonly BindableProperty eventRefreshProperty = BindableProperty.Create("eventRefresh", typeof(bool), typeof(MyEntry), true,propertyChanged:(obj,oldValue,newValue)=> {
//var entry = obj as MyEntry;
// entry.Text = newValue.ToString();
});
bool refresh;
public bool eventRefresh
{
get { return refresh; }
set {
if(refresh !=value)
{
refresh = value;
NotifyPropertyChanged("eventRefresh");
}
}
}
public MyEntry()
{
}
}
}
in xaml
<StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<local:MyEntry eventRefresh="{Binding Refresh}" BackgroundColor="{Binding BGcolor}" WidthRequest="200" HeightRequest="50" />
<Button Command="{Binding ClickCommand}" />
</StackLayout>
in View Model
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Color color;
public Color BGcolor
{
get { return color; }
set
{
if (color != value)
{
color = value;
NotifyPropertyChanged("BGcolor");
}
}
}
bool refresh;
public bool Refresh
{
get { return refresh; }
set
{
if (refresh != value)
{
refresh = value;
NotifyPropertyChanged("Refresh");
}
}
}
public ICommand ClickCommand { get; set; }
public MyViewModel()
{
BGcolor = Color.LightPink;
ClickCommand = new Command(()=> {
BGcolor = Color.Red;
});
}
}
in Custom Renderer
using System.ComponentModel;
using Android.Content;
using xxx;
using xxx.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly:ExportRenderer(typeof(MyEntry),typeof(NyEntryRenderer))]
namespace xxx.Droid
{
public class NyEntryRenderer : EntryRenderer
{
public NyEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
Element.TextChanged += Element_TextChanged;
}
}
private void Element_TextChanged(object sender, TextChangedEventArgs e)
{
// var content = Element.Text;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == MyEntry.BackgroundColorProperty.PropertyName)
{
// will been invoked when click button
}
}
}
}
Make your view model like this.
public class YourViewModel
{
public Command command
{
get
{
return new Command(() => {
//Change here button background colors
BackgroundColor = Color.Green;
});
}
}
private _backgroundColor = Color.White;
public Color BackgroundColor
{
get { return _backgroundColor;}
set
{
if (value == _backgroundColor)
return;
_backgroundColor = value;
NotifyOnPropertyChanged(nameof(BackgroundColor));
}
}
}
Your XAML
<local:MyEntry Text="{Binding Password}" Placeholder="Enter" />
<Button Text="send" Command="{Binding command}" BackgroundColor="{Binding BackgroundColor}"></Button>
like the title says I want to give through the user information to my viewmodel, but the problem is that the viewmodel is registered as a dependency and I am binding its content to the xaml page itself. How do I send the user information to the viewmodel itself?
Thank you!
Xaml.cs part:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Calendar : ContentPage
{
public Calendar(User user)
{
InitializeComponent();
FileImageSource image = new FileImageSource
{
File = "calendar.png"
};
Icon = image;// push user information to the ICalendarViewModel
BindingContext = AppContainer.Container.Resolve<ICalendarViewModel>();
}
}
Interface:
public interface ICalendarViewModel : INotifyPropertyChanged
{
}
Bootstrap part registering dependencies:
public class Bootstrap
{
public IContainer CreateContainer()
{
var containerBuilder = new ContainerBuilder();
RegisterDependencies(containerBuilder);
return containerBuilder.Build();
}
protected virtual void RegisterDependencies(ContainerBuilder builder)
{
builder.RegisterType<CalendarViewModel>()
.As<ICalendarViewModel>()
.SingleInstance();
}
}
CalendarViewModel: I do not know if this will help
public class CalendarViewModel : ViewModelBase, ICalendarViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
public string ErrorMessage { get; set; }
private CourseInformation _information;
private ICourseInformationRepository _repository;
public CalendarViewModel()
{
_repository = new CourseInformationRepository();
LoadData();
}
private ObservableCollection<CourseInformation> _courses;
public ObservableCollection<CourseInformation> Courses
{
get
{
return _courses;
}
set
{
_courses = value;
RaisePropertyChanged(nameof(Courses));
}
}
private void LoadData()
{
try
{
ObservableCollection<CourseInformation> CourseList = new ObservableCollection<CourseInformation>(_repository.GetAllCourseInformation());
Courses = new ObservableCollection<CourseInformation>();
DateTime date;
foreach (var course in CourseList)
{
string [] cour = course.Date.Split('/');
cour[2] = "20" + cour[2];
date = new DateTime(Convert.ToInt32(cour[2]), Convert.ToInt32(cour[1]), Convert.ToInt32(cour[0]));
if (date == DateTime.Now)//TESTING WITH TEST DATE, datetime.now
{
if (course.FromTime.Length < 4)
{
course.FromTime = "0" + course.FromTime;
}
if (course.UntilTime.Length < 4)
{
course.UntilTime = "0" + course.UntilTime;
}
course.FromTime = course.FromTime.Insert(2, ":");
course.UntilTime = course.UntilTime.Insert(2, ":");
Courses.Add(course);
}
}
}
catch (ServerUnavailableException e)
{
ErrorMessage = "Server is niet beschikbaar, ophalen van kalender is niet mogelijk.";
}
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Bootstrap binding in app.xaml.cs:
public partial class App : Application
{
public App()
{
InitializeComponent();
AppContainer.Container = new Bootstrap().CreateContainer();
MainPage = new LoginView();
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
I wanted to comment (not enough reputation) on #LeRoy, use a framework. I would recommend FreshMVVM and you can pass objects into the ViewModel and even pass in Services. It makes it all nice and clean, and it just works.
Should not your CalendarViewModel viewModel contain BindableBase ?
public class CalendarViewModel : BindableBase, ViewModelBase, ICalendarViewModel
what framework are you using? prism, freshmvvm.
Your View and Viewmodel is normally automatically handled by the framework, all you need to do is register your page.
Container.RegisterTypeForNavigation<Views.CalendarPage>();
I have a page called MapPage.xaml and a code behind called MapPage.xaml.cs. In my android project, I have another file called CustomMapRenderer.cs. In the CustomMapRenderer.cs file, I need to retrieve the item selected variable in a XAML picker found in my MapPage.xaml file, which changes when a user picks an option in my XAML picker.
How to I reference the picker from my CustomMapRenderer.cs?
In the CustomMapRenderer.cs file, I need to retrieve the item selected variable in a XAML picker found in my MapPage.xaml file, which changes when a user picks an option in my XAML picker.
If you followed the official doc Customizing a Map to create your CustomMapRenderer, then in PCL, there should be a class which inherits from Map, for example:
public class CustomMap : Map
{
}
Then, if your picker is another control in your MainPage, you can create a bindable property for your CustomMap, and override OnElementPropertyChanged in your renderer to get this property when it changed.
For example, in PCL:
public class MapWithMyZoomControl : Map
{
public ZoomState MyZoom
{
get { return (ZoomState)GetValue(MyZoomProperty); }
set { SetValue(MyZoomProperty, value); }
}
public static readonly BindableProperty MyZoomProperty =
BindableProperty.Create(
propertyName: "MyZoom",
returnType: typeof(ZoomState),
declaringType: typeof(MapWithMyZoomControl),
defaultValue: ZoomState.normal,
propertyChanged: OnZoomPropertyChanged);
public static void OnZoomPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
}
public enum ZoomState
{
normal,
zoomin,
zoomout
}
}
And in its renderer:
public class MapWithMyZoomControlRenderer : MapRenderer, IOnMapReadyCallback
{
private GoogleMap map;
public void OnMapReady(GoogleMap googleMap)
{
map = googleMap;
map.UiSettings.ZoomControlsEnabled = false;
}
protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
{
var formsMap = (MapWithMyZoomControl)e.NewElement;
((MapView)Control).GetMapAsync(this);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var element = Element as MapWithMyZoomControl;
if (e.PropertyName == "MyZoom" && map != null)
{
if (element.MyZoom == MapWithMyZoomControl.ZoomState.zoomin)
{
map.AnimateCamera(CameraUpdateFactory.ZoomIn());
}
else if (element.MyZoom == MapWithMyZoomControl.ZoomState.zoomout)
{
map.AnimateCamera(CameraUpdateFactory.ZoomOut());
}
element.MyZoom = MapWithMyZoomControl.ZoomState.normal;
}
}
}
Out of this map control, I use buttons to control to zoom the map:
map.MyZoom = MapWithMyZoomControl.ZoomState.zoomin;
It'a a demo, but you can modify it to make property connected to your picker.
I figured it out.
For anyone needing this. Please see the following.
After Watching Xamarin Evolve a million times I caught on.
class LoginButtonCustomRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
{
base.OnElementChanged(e);
Android.Widget.Button thisButton = Control as Android.Widget.Button;
thisButton.Touch += (object sender, TouchEventArgs e2) =>
{
if (e2.Event.Action == MotionEventActions.Down)
{
System.Diagnostics.Debug.WriteLine("TouchDownEvent");
// Had to use the e.NewElement
e.NewElement.Image = "pressed.png";
}
else if (e2.Event.Action == MotionEventActions.Up)
{
System.Diagnostics.Debug.WriteLine("TouchUpEvent");
}
};
}
}
you need call
Control.CallOnClick();
Here is a sample how to implement a two state ImageButton in Xamarin Forms:
PCL:
public class FancyButton : Button
{
public void SendClickedCommand()
{
ICommand command = this.Command;
if (command != null)
{
command.Execute(this.CommandParameter);
}
}
}
Android render:
public class FancyButtonAndroid : ButtonRenderer
{
Android.Widget.Button thisButton;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
{
base.OnElementChanged(e);
thisButton = Control as Android.Widget.Button;
thisButton.SetBackgroundResource(Resource.Drawable.btn_unpress);
thisButton.Touch += ThisButton_Touch;
thisButton.Click += HandleButtonClicked;
}
private void ThisButton_Touch(object sender, TouchEventArgs e)
{
e.Handled = false;
if (e.Event.Action == MotionEventActions.Down)
{
System.Diagnostics.Debug.WriteLine("TouchDownEvent");
thisButton.SetBackgroundResource(Resource.Drawable.btn_press);
}
else if (e.Event.Action == MotionEventActions.Up)
{
System.Diagnostics.Debug.WriteLine("TouchUpEvent");
thisButton.SetBackgroundResource(Resource.Drawable.btn_unpress);
}
}
private void HandleButtonClicked(object sender, EventArgs e)
{
if (Element != null && Element is FancyButton)
{
(Element as FancyButton).SendClickedCommand();
}
}
protected override void Dispose(bool disposing)
{
if (thisButton != null)
{
thisButton.Touch -= ThisButton_Touch;
thisButton.Click -= HandleButtonClicked;
}
base.Dispose(disposing);
}
}
Note: in the Touch event set: e.Handled = false; to cause the Click event to rise.
I have an MVC 5 project and using Unity Framework for Dependency Injection. Everything was working fine but suddenly I am getting below error
"The IControllerFactory 'App.Web.Models.UnityControllerFactory' did not return a controller for the name 'School'."
GLobal.asax.cs code
private static UnityContainer _container;
public static IUnityContainer Container
{
get { return _container; }
}
IUnityContainer IUnityContainerAccessor.Container
{
get { return Container; }
}
#endregion
protected void Application_Start()
{
if (_container == null)
{
_container = new UnityContainer();
ContainerConfig.RegisterTypes(_container);
}
WebApiConfig.Register(GlobalConfiguration.Configuration);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
ContainerConfig.cs
public static class ContainerConfig
{
public static void RegisterTypes(UnityContainer container)
{
container.RegisterInstance<IUnityContainer>(container);
container.RegisterType<Entities>(new HttpContextLifetimeManager<Entities>(), new InjectionConstructor());
container.RegisterType<IDatabaseContext, Entities>();
container.RegisterType<ICommonService, CommonService>();
container.RegisterType<ISearchService, SearchService>();
container.RegisterType<ILogger, Log4NetLogger>();
ControllerBuilder.Current.SetControllerFactory(typeof(UnityControllerFactory));
}
}
UnityControllerFactory.cs
public class UnityControllerFactory : DefaultControllerFactory
{
public override IController CreateController(RequestContext context, string controllerName)
{
try
{
var type = GetControllerType(context, controllerName);
if (type == null)
{
throw new InvalidOperationException(string.Format("Could not find a controller with the name {0}.", controllerName));
}
var container = GetContainer(context);
return (IController)container.Resolve(type);
}
catch
{
return null;
}
}
protected virtual IUnityContainer GetContainer(RequestContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
var unityContainerAccessor = context.HttpContext.ApplicationInstance as IUnityContainerAccessor;
if (unityContainerAccessor == null)
{
throw new InvalidOperationException("You must extend the HttpApplication in your web project and implement the IContainerAccessor to properly expose your container instance");
}
IUnityContainer container = unityContainerAccessor.Container;
if (container == null)
{
throw new InvalidOperationException("The container seems to be unavailable in your HttpApplication subclass");
}
return container;
}
}
Please help me to resolve this issue. Thanks in advance.
The problem got resolved. I have installed re-sharper tool. And in one of the business class I makes it as abstract. So it was throwing said error.