I'm just creating a simple example where I want to enable/disable a Switchcell (named switch2) by turning on/off another switchcell (named switch1).
I'm using the binding method. I've tried this code with an "Entry" element (trying to disable it) and it works perfectly, but with the property "IsEnabledProperty" of "switchcell" it seems not working. (I'm not using Xaml, i'm using PCL).
Xamarin Forms updated to the latest version (2.3.0.49).
Is this a Xamarin issue?
Here's the code:
BindingContext = new DetailsViewModel();
SwitchCell switch1 = new SwitchCell()
{
Text = "Switch",
};
switch1.SetBinding(SwitchCell.OnProperty, new Binding("Test", BindingMode.TwoWay));
SwitchCell switch2 = new SwitchCell()
{
Text = "Visibilita",
};
switch2.SetBinding(SwitchCell.IsEnabledProperty, "Test");
and here is the DetailsViewModel.cs:
public class DetailsViewModel : INotifyPropertyChanged
{
bool test;
public bool Test
{
get { return test; }
set
{
if (test != value)
{
test = value;
OnPropertyChanged("Test");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Actually this is bug from Xamarin, I think is not yet fixed first check without binding whether is working or not
Related
Here is the code for the custom renderer i used to assign a custom icon as my back button.
namespace MyProjectName.Droid.Renderers
{
public class MyNavigationRenderer: PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
var context = (Activity)Xamarin.Forms.Forms.Context;
var toolbar = context.FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Droid.Resource.Id.toolbar);
toolbar.NavigationIcon = AndroidX.Core.Content.ContextCompat.GetDrawable(context, Resource.Drawable.bbutton_nav);
}
}
}
This code successfully replaces the native back arrow icon with my custom bbutton_nav. When i navigate forward(Navigate.PushAsync()), the custom icon appears on all the upcoming screens. But when i click on the back icon to go back one page(Navigate.PopAsync()), the old native back arrow reappears instead of the new custom icon that was set by the renderer. When i tried debugging , i found out that the renderer class was not getting called when navigating back(Navigation.PopAsync()).
Any help on how to mitigate this issue is appreciated. Thanks
Create a custom renderer for NavigationPage instead of Page , and override the OnLayout method .
Android will change the detault icon back in UpdateToolbar method , and OnLayout method is triggered every time while current page is changed.
Android Solution
[assembly: ExportRenderer(typeof(NavigationPage), typeof(MyNavigationRenderer))]
namespace FormsApp.Droid
{
public class MyNavigationRenderer : NavigationPageRenderer
{
Context _context;
AndroidX.AppCompat.Widget.Toolbar _toolbar;
public MyNavigationRenderer(Context context) : base(context)
{
_context = context;
}
public override void OnViewAdded(Android.Views.View child)
{
base.OnViewAdded(child);
if (child.GetType() == typeof(AndroidX.AppCompat.Widget.Toolbar))
{
_toolbar = (AndroidX.AppCompat.Widget.Toolbar)child;
_toolbar.SetNavigationIcon(Resource.Drawable.bbutton_nav);
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if (_toolbar != null)
{
if (_toolbar.NavigationIcon != null)
{
_toolbar.NavigationIcon = AndroidX.Core.Content.ContextCompat.GetDrawable(_context, Resource.Drawable.bbutton_nav);
}
}
}
}
}
Refer to https://forums.xamarin.com/discussion/183344/how-to-change-navigation-back-button-icon .
iOS Solution
[assembly: ExportRenderer(typeof(NavigationPage), typeof(MyRenderer))]
namespace FormsApp.iOS
{
class MyRenderer : NavigationRenderer
{
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
if (this.NavigationBar.TopItem.BackBarButtonItem == null)
{
this.NavigationBar.BackIndicatorImage = UIImage.FromFile("dots.png");
this.NavigationBar.BackIndicatorTransitionMaskImage = UIImage.FromFile("dots.png");
this.NavigationBar.TopItem.BackBarButtonItem = new UIBarButtonItem("", UIBarButtonItemStyle.Plain, null);
}
}
}
}
To provide some context, I'm writing a Xamarin.Forms application and utilizing data binding with INotifyPropertyChanged. Currently I have an inventory counter displayed on a button. The text on this button displays the bounded "Count" variable (e.g Current Inventory: 35). When I press the button, I push a screen onto the navigation stack which allows me to edit this "Count" variable. I use the class implementation like this
public class UserInventory : INotifyPropertyChanged
{
private int count = 0;
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public int Count
{
get => Preferences.Get(nameof(Count),0);
set
{
if (count == value || value <1)
return;
Preferences.Set(nameof(Count), value);
//count = value;
//Application.Current.Properties["Count"] = count;
OnPropertyChanged(nameof(Count));
//OnPropertyChanged(nameof(displayName));
}
}
public UserInventory()
{
}
void OnPropertyChanged(string count)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(count));
}
}
I add this class in Xaml according to the tutorial on the Xamarin <ContentPage.BindingContext>
<local:UserInventory />
</ContentPage.BindingContext>
So the variables are bounded correctly and I have no issues seeing updates on the current page or when I push new pages. The issue is when I swipe back on iOS the previous screen with the button "Current Inventory: 35" does not update to reflect the new changes. If I push that screen the changes are reflected.
Is there anyway to ensure the bounded data is updated when you go back (PopAsync()) ?
Try overriding page's OnAppearing() method and call OnPropertyChanged from there.
Assuming 'UserInventory' the binded VM.....
public partial class Page1:ContentPage
{
public Page1()
{
InitializeComponent();
VM = (UserInventory)BindingContext;
}
public UserInventory VM { get; }
protected override void OnAppearing()
{
VM.Notify();
base.OnAppearing();
}
}
.
public class UserInventory: INotifyPropertyChanged
{
........
public void Notify()
{
OnPropertyChanged(nameof(Count));
}
}
I’m new to Xamarin.Forms and tried using WebView on my Windows 10 x64 v1803 machine with UWP but I can’t see how to get it to work with WebGL.
Sites which use WebGL either display a message that “Your video card does not support WebGL or just don’t display and graphical content at all.
Is this a limitation of UWP or WebView itself?
Is it a WebView configuration issue?
WebGL works in all other browsers on this machine.
UWP WebView control is support WebGL. There is similar issue case in msdn you could refer. Please try to use SeparateProcess mode WebView to replace the default one.
public MainPage()
{
this.InitializeComponent();
var MyWebView = new WebView(WebViewExecutionMode.SeparateProcess);
MyWebView.Source = new Uri("http://cycleblob.com/");
this.RootGrid.Children.Add(MyWebView);
}
I had the same problem, but with the newer Xamarin Forms it took a little more poking around to get this took work right. However, I do like that they moved the native WebView resolver back to the responsibility of the UWP/iOS/Android project (as a native XAML object) instead of using code branching with compiler directives in the Shared project.
Start by creating a HybridWebView class in the shared project to use as your WebForm view object:
public class HybridWebView : Xamarin.Forms.WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public void RegisterAction(Action<string> callback)
{
action = callback;
}
public void Cleanup()
{
action = null;
}
public void InvokeAction(string data)
{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
}
Then in the UWP project, create a custom renderer, which will construct the native WebView and relay the events back to the WebForms object in the Shared project:
Put this at the top of the namespace, to link the HybridWebView with the Custom Renderer:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(WebViewRenderer2))]
Then create the renderer class (for the IOS and android projects, if you leave this class out, it defaults to the standard native controls which seem to work fine for me):
public class WebViewRenderer2 : ViewRenderer<Xamarin.Forms.WebView, Windows.UI.Xaml.Controls.WebView>, IWebViewDelegate
{
Windows.UI.Xaml.Controls.WebView _control;
public void LoadHtml(string html, string baseUrl)
{
}
public void LoadUrl(string url)
{
}
protected override void Dispose(bool disposing)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
if (_control == null) {
_control = new Windows.UI.Xaml.Controls.WebView(WebViewExecutionMode.SeparateProcess);
SetNativeControl(_control);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var xamWebView = sender as HybridWebView;
switch(e.PropertyName.ToLower())
{
case "source":
var urlSource = xamWebView.Source as Xamarin.Forms.UrlWebViewSource;
_control.Source = new Uri(urlSource.Url);
break;
case "width":
_control.Width = xamWebView.Width;
break;
case "height":
_control.Height = xamWebView.Height;
break;
case "isfocused":
var focused = xamWebView.IsFocused;
if (focused)
_control.Focus(FocusState.Programmatic);
else
_control.Focus(FocusState.Unfocused);
break;
}
}
}
You can also use the Custom Renderer to inject scripts, and you can use it to communicate from the native webview back to the Xamarin App, as seen here: HybridWebView Communication
I'm trying to use a library that doesn't has a .Net SDK, but as I want to use it only to return a string, I thought I could use it's JS SDK by creating a custom WebView that returns strings (https://xamarinhelp.com/xamarin-forms-webview-executing-javascript/).
The first problem that I faced was that a CustomRenderer is not called in Xamarin.Forms until the View is added to a Page (or at least I couldn't make it be called). To fix this I added a call to Platform.CreateRenderer in each platform.
It did the trick and the CustomRenderer executed. But when I tried to call a JS function to retrieve a string, the app just hung and stayed that way.
I didn't try to insert the WebView in a Page because I want it to be independent of the page that the app is current on, and as I want a "code-only" html, I don't see the point of adding it somewhere.
My classes:
JSEvaluator
namespace MyNamespace.Views
{
public class JSEvaluator : WebView
{
public static BindableProperty EvaluateJavascriptProperty = BindableProperty.Create(nameof(EvaluateJavascript), typeof(Func<string, Task<string>>), typeof(JSEvaluator), null, BindingMode.OneWayToSource);
public Func<string, Task<string>> EvaluateJavascript
{
get { return (Func<string, Task<string>>)GetValue(EvaluateJavascriptProperty); }
set { SetValue(EvaluateJavascriptProperty, value); }
}
public JSEvaluator()
{
}
}
}
UWP Renderer
[assembly: ExportRenderer(typeof(JSEvaluator), typeof(JSEvaluatorRenderer))]
namespace MyNamespace.UWP.Renderers
{
public class JSEvaluatorRenderer : WebViewRenderer
{
public JSEvaluatorRenderer() { }
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
var webView = e.NewElement as JSEvaluator;
if (webView != null)
webView.EvaluateJavascript = async (js) =>
{
return await Control.InvokeScriptAsync("eval", new[] { js });
};
}
}
}
Creation and use
if (jsEvaluator == null)
{
jsEvaluator = new JSEvaluator { Source = new HtmlWebViewSource { Html = HTML.html } };
#if __ANDROID__
Xamarin.Forms.Platform.Android.Platform.CreateRenderer(jsEvaluator);
#elif __IOS__
Xamarin.Forms.Platform.iOS.Platform.CreateRenderer(jsEvaluator);
#elif WINDOWS_UWP
Xamarin.Forms.Platform.UWP.Platform.CreateRenderer(jsEvaluator);
#endif
}
Thanks for the help :)
I had to add the WebView to a page, as #SushiHangover said in the comment. With this done, it worked as expected.
In a SL4 application i need to restyle my TabItems (actually add a button in the header).
So i took the TabItem's control template from here and added the functionality i wanted.
This seems to work fine, (i could dynamically add tabitems) with one exception:
i think this posted control template is behaving somehow "arbitrary": every time the mouse hoovers over a non selected TabItem header, this gets selected WHITHOUT clicking!! (afaik this is not the default behavior: the user user has to click a header to make this tabitem the selected one).
I tried to find why it is behaving like this, with no luck!
Is there someone who can enlighten my darkness???
Thanks in advance!
Well it turns out the error was not in the control template but in the class, the style was applied to.
In detail: the class the style was applied to is the following (in it you will see my comment about the "wrong behavior"):
public class WorkspaceViewModel : TabItem
{
public WorkspaceViewModel()
{
DefaultStyleKey = typeof(WorkspaceViewModel);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button closeButtonSel = base.GetTemplateChild("PART_CloseTopSelected") as Button;
Button closeButtonUnsel = base.GetTemplateChild("PART_CloseTopUnSelected") as Button;
if (closeButtonSel != null)
closeButtonSel.Click += new RoutedEventHandler(closeButtonSel_Click);
if (closeButtonUnsel != null)
closeButtonUnsel.Click += new RoutedEventHandler(closeButtonSel_Click);
//this part is causing the effect i was complaining about!
//and has to be removed
this.MouseEnter += delegate(object sender, MouseEventArgs e)
{
IsSelected = true;
};
}
void closeButtonSel_Click(object sender, RoutedEventArgs e)
{
//this is the close request method used in the CloseTabItemCommand
OnRequestClose();
}
#region CloseTabItemCommand
private RelayCommand closeTabItemCommand;
public ICommand CloseTabItemCommand
{
get
{
if (this.closeTabItemCommand == null)
this.closeTabItemCommand = new RelayCommand(p => this.OnRequestClose(), p => this.CanCloseTabItem());
return this.closeTabItemCommand;
}
}
private bool CanCloseTabItem()
{
return true;
}
public event EventHandler RequestClose;
private void OnRequestClose()
{
if (RequestClose != null)
RequestClose(this, EventArgs.Empty);
}
#endregion
}