I want to use a custom IBehavior to be able to show/hide the StatusBar from XAML in a Universal app targeted at Windows Phone 8.1.
The code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xaml.Interactivity;
using Windows.ApplicationModel;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
namespace test.Behaviors
{
public class StatusBarBehavior : DependencyObject, IBehavior
{
public DependencyObject AssociatedObject { get; private set; }
public void Attach(DependencyObject associatedObject) { }
public void Detach() { }
public StatusBarBehavior()
{
}
public bool Show
{
get { return Convert.ToBoolean(GetValue(ShowProperty)); }
set { SetValue(ShowProperty, value); }
}
public static readonly DependencyProperty ShowProperty =
DependencyProperty.Register("Show",
typeof(object),
typeof(StatusBarBehavior),
new PropertyMetadata(null, OnIsVisibleChanged));
private static async void OnIsVisibleChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var statusBar = StatusBar.GetForCurrentView();
if (statusBar == null || e == null || e.NewValue == null || DesignMode.DesignModeEnabled)
return;
if (Convert.ToBoolean(e.NewValue))
await statusBar.ShowAsync();
else
await statusBar.HideAsync();
}
}
}
XAML:
<Page
x:Class="test.TestPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:test"
xmlns:behavior="using:test.Behaviors"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<i:Interaction.Behaviors>
<behavior:StatusBarBehavior Show="False"/>
</i:Interaction.Behaviors>
<!-- page content -->
</Page>
The Behaviors SDK (12.0) has been added as a reference to the project.
Unfortunatly in Visual Studio 2013 (Community edition, Update 4), the design window for the page in question shows an error:
COMException: Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))
StackTrace: Empty
InnerException: None
However, the StatusBarBehavior works perfectly fine when I deploy the app on a device, no errors are thrown.
Is there a way to fix this error? Having the design window is quite necessary to preview the layout of the pages...
Changing the OnVisibleChanged event to
private static async void OnIsVisibleChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (DesignMode.DesignModeEnabled) { return; }
var statusBar = StatusBar.GetForCurrentView();
if (statusBar == null || e == null || e.NewValue == null)
return;
if (Convert.ToBoolean(e.NewValue))
await statusBar.ShowAsync();
else
await statusBar.HideAsync();
}
fixes the issue. I assume StatusBar.GetForCurrentView() throws an error in the design view.
Related
I can download some files in a random page (a pdf from google, for example) but in the page I need to download them from, I get "Unsuccessful Download" notification on smartphone, with no exception thrown for me. Is there a way to know why this is happening?
Code from the renderer that I use to download below.
using Android.App;
using Android.Webkit;
using MPS.Libertas.Mobile.Droid.Renderers;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;
using Android.Content;
using Xamarin.Essentials;
using System.IO;
using System;
using Android.Widget;
using static Android.Provider.MediaStore;
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(MPS_MyWebViewRenderer))]
namespace MPS.Libertas.Mobile.Droid.Renderers
{
internal class MPS_MyWebViewRenderer : WebViewRenderer
{
public MPS_MyWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
Control.Download += OnDownloadStart;
}
private void OnDownloadStart(object sender, Android.Webkit.DownloadEventArgs e)
{
try
{
var url = e.Url;
string url_formatted = url.Replace("blob:", "");
DownloadManager.Request request = new DownloadManager.Request(Android.Net.Uri.Parse(url_formatted));
request.AllowScanningByMediaScanner();
request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
request.SetMimeType("application/pdf");
// if this path is not create, we can create it.
string thmblibrary = FileSystem.AppDataDirectory + "/download";
if (!Directory.Exists(thmblibrary))
Directory.CreateDirectory(thmblibrary);
request.SetDestinationInExternalFilesDir(Android.App.Application.Context, FileSystem.AppDataDirectory, "download");
DownloadManager dm = (DownloadManager)Android.App.Application.Context.GetSystemService(Android.App.Application.DownloadService);
dm.Enqueue(request);
}
catch (System.Exception ex)
{
var message = ex.Message;
throw;
}
}
}
}
Using a custom renderer one can disable the swiping gesture of an CarouselPage on iOS like so:
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(CarouselPage), typeof(CustomCarouselPageRenderer))]
namespace App.iOS
{
public class CustomCarouselPageRenderer : CarouselPageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
UIView view = this.NativeView;
UIScrollView scrollView = (UIKit.UIScrollView)view.Subviews[0];
scrollView.ScrollEnabled = false;
}
}
}
How to accomplish the same on Android?
using Android.Content;
using XamFormsApp.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CarouselPage), typeof(CustomCarouselPageRenderer))]
namespace StixApp.Droid.Renderers
{
public class CustomCarouselPageRenderer : VisualElementRenderer<CarouselPage>
{
public CustomCarouselPageRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<CarouselPage> e)
{
base.OnElementChanged(e);
var view = this.RootView;
X
X
}
}
}
There appears to be no way to access Subviews in the same way. One can access Children like so
Android.Views.View view = (Android.Views.View)GetChildAt(i);
How to know which Child is ScrollView if any?
Using a loop to check for this, like so,
for (int i = 0; i < ChildCount; ++i)
{
Android.Views.View view = (Android.Views.View)GetChildAt(i);
if (view is ScrollView)
{
}
}
Yields the following: "The given expression is never of the provided (ScrollView) type"
So! How to disable CarouselPage swipe/scrolling as is done in iOS quite elegantly?
UPDATE: Please see sample solution.
A couple of things.
For Android the view you are looking for is not a ScrollView but a ViewPager.
This can be found under the index 0 with the GetChildAt method.
Also, why are you using VisualElementRenderer<CarouselPage> as the parent class of your CustomCarouselPageRenderer. Instead use the CarouselPageRenderer as you did with iOS.
One last thing is that on Android the Scroll of a ViewPager cannot be disabled. To get this behavior you can listen to the Touch event. Setting the Handled property of TouchEventArgs to true will prevent the scrolling from happening.
Your whole class would look something like:
[assembly: ExportRenderer(typeof(CarouselPage), typeof(CustomCarouselPageRenderer))]
namespace StixApp.Droid.Renderers
{
public class CustomCarouselPageRenderer : CarouselPageRenderer
{
private bool _canScroll = false;
public CustomCarouselPageRenderer(Context context) : base(context)
{
}
public CustomCarouselPageRenderer()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<CarouselPage> e)
{
base.OnElementChanged(e);
if (this.ChildCount > 0 && this.GetChildAt(0) is ViewPager viewPager)
{
viewPager.Touch -= ViewPagerTouched;
viewPager.Touch += ViewPagerTouched;
}
}
private void ViewPagerTouched(object sender, TouchEventArgs e)
{
e.Handled = !_canScroll;
}
}
}
Just change the value of _canScroll to true to allow the scrolling.
Hope this helps.-
Overridden Methods in ViewPager class:
public class NonSwipeableViewPager : ViewPager
{
public override bool OnTouchEvent(MotionEvent e)
{
return false;
}
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
return false;
}
public override bool ExecuteKeyEvent(KeyEvent ev)
{
return false;
}
}
Changes to CarouselPageRenderer:
In class declaration:
public class MyCarouselPageRenderer : VisualElementRenderer<CarouselPage>
{
NonSwipeableViewPager _viewPager;
}
In OnElementChanged:
protected override void OnElementChanged(ElementChangedEventArgs<CarouselPage> e)
{
_viewPager = new NonSwipeableViewPager(Context);
}
Note: CarouselPageAdapter, CarouselPageRenderer, MeasureSpecFactory, ObjectJavaBox, and PageContainer all had to be copied from the Xamarin.Forms github to enable a custom CarouselPageRenderer implementation. All of this is in the github sample but hopefully this spells it out more clearly for future visitors.
Note2: I would like to stress that the true behavior we were trying to achieve is probably best done with a NavigationPage as this allows us to easily pop and push any and all pages at any time without having to address the swipe issue. That being said, hopefully this solution serves to aid anyone who need this behavior on a CarouselPage.
Using the Xamarin Forms WebView control, I'm overriding the OnBackButtonPressed() and finding that the CanGoBack always returns false in UWP.
I don't see this problem in Android.
Is this a XF bug or am I doing something wrong?
Note: I'm running XF v2.3.3.193
EDIT: I upgraded to XF 2.3.4.247 and the problem persists.
I have created a code sample and reproduce your issue when the WebView browse several website. And I have found reason in the Xamarin.Forms source code.
void UpdateCanGoBackForward()
{
((IWebViewController)Element).CanGoBack = Control.CanGoBack;
((IWebViewController)Element).CanGoForward = Control.CanGoForward;
}
The CanGoBack property will be changed when UpdateCanGoBackForward method invoked. And UpdateCanGoBackForward method was called only when the native NavigationCompleted event was invoked. So if some website could not be loaded quickly, the CanGoBack property would not be changed.
You can improve this design by custom WebView. And you could follow the code below.
CustomWebView.cs
Add the new property for CustomWebView.
public class CustomWebView : WebView
{
public bool CCanGoBack { get; set; }
public CustomWebView()
{
}
}
CustomWebViewRenderer.cs
And change the property when the ContentLoading event invoked.
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))]
namespace CustomWebViewTest.UWP
{
public class CustomWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.ContentLoading += Control_ContentLoading;
}
}
private void Control_ContentLoading(Windows.UI.Xaml.Controls.WebView sender, Windows.UI.Xaml.Controls.WebViewContentLoadingEventArgs args)
{
(Element as CustomWebView).CCanGoBack = Control.CanGoBack;
}
}
}
MainPage.cs
private void backClicked(object sender, EventArgs e)
{
if (Browser.CCanGoBack)
{
Browser.GoBack();
}
}
I'm trying to understand how Caliburn.Micro works with Windows Phone (and MVVM in general) so I created a basic Windows Phone Application, installed Caliburn.Micro NuGet package (v1.2.0 - the latest for now) and followed the few instructions here and there. So, I ended up with:
WMAppManifest.xml
<DefaultTask Name ="_default" NavigationPage="Views/HomeView.xaml"/>
Framework/AppBootstrapper.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using Caliburn.Micro;
using MyCaliburn.PhoneUI.ViewModels;
namespace MyCaliburn.PhoneUI.Framework
{
public class AppBootstrapper : PhoneBootstrapper
{
PhoneContainer container;
protected override void Configure()
{
container = new PhoneContainer(RootFrame);
container.RegisterPhoneServices();
container.Singleton<HomeViewModel>();
}
protected override void OnUnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
if (Debugger.IsAttached)
{
Debugger.Break();
e.Handled = true;
}
else
{
MessageBox.Show("An unexpected error occured, sorry about the troubles.", "Oops...", MessageBoxButton.OK);
e.Handled = true;
}
base.OnUnhandledException(sender, e);
}
protected override object GetInstance(Type service, string key)
{
return container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
container.BuildUp(instance);
}
}
}
ViewModels/HomeViewModel.cs
using Caliburn.Micro;
namespace MyCaliburn.PhoneUI.ViewModels
{
public class HomeViewModel : Screen
{
public HomeViewModel()
{
//DisplayName = "Home";
}
}
}
View/HomeView.xaml.cs (the XAML page is the default Window Phone Portrait Page)
using Microsoft.Phone.Controls;
namespace MyCaliburn.PhoneUI.Views
{
public partial class HomeView : PhoneApplicationPage
{
public HomeView()
{
InitializeComponent();
}
}
}
App.xaml
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyCaliburn.PhoneUI.App"
xmlns:Framework="clr-namespace:MyCaliburn.PhoneUI.Framework">
<!--Application Resources-->
<Application.Resources>
<Framework:AppBootstrapper x:Key="bootstrapper" />
</Application.Resources>
</Application>
App.xaml.cs
using System.Windows;
namespace MyCaliburn.PhoneUI
{
public partial class App : Application
{
/// <summary>
/// Constructor for the Application object.
/// </summary>
public App()
{
// Standard Silverlight initialization
InitializeComponent();
}
}
}
Now, when I hit F5, the application runs and exits without showing any page or exception and doesn't hit any breakpoints that I sit.
Can anyone tells me what's missing in my code which prevents the application from running?
Thanks in advance.
Many times when I end up with an app that does not start - it turns out that due to some refactoring the App class is not the startup object any more. Right-click on the project in solution explorer, go to properties/Application and make sure Startup object is set correctly.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
namespace WindowsPhoneApplication7
{
public partial class Listbox : UserControl
{
public Listbox()
{
InitializeComponent();
}
private void listbox(object sender, MouseEventArgs e)
{
this.NavigationService.Navigate(new Uri("/home.xaml", UriKind.Relative));
}
}
}
An error is occur .......
does not contain a definition for 'NavigationService' and no extension method 'NavigationService' accepting a first argument of type ' could be found (are you missing a using directive or an assembly reference?)
NavigationService is a property on the PhoneApplicationPage class. You are not deriving from that class, you are deriving from UserControl.
You need to get the parent phone page the user control is on and get the NavigationService reference from there.
Your compiler error is because it cannot locate a definition for NavigationService on the Listbox class you have made.
What Adam said is correct. But a easy solution is to define following static utility methods in App.xaml.cs
public static PhoneApplicationFrame CurrentRootVisual
{
get
{
return (App.Current.RootVisual as PhoneApplicationFrame);
}
}
public static bool Navigate(Uri source)
{
if (CurrentRootVisual != null)
return CurrentRootVisual.Navigate(source);
return false;
}
public static void GoBack()
{
if (CurrentRootVisual != null)
CurrentRootVisual.GoBack();
}
Then you can just do:
App.Navigate(yourNavigateUri)
or
App.GoBack()
From anywhere you like!
Dispatcher.BeginInvoke(() =>
NavigationService.Navigate(new Uri("/home.xaml", UriKind.Relative)));