Toast equivalent for Xamarin Forms - c#

Is there any way using Xamarin Forms (not Android or iOS specific) to have a pop-up, like Android does with Toast, that needs no user interaction and goes away after a (short) period of time?
From searching around all I'm seeing are alerts that need user clicks to go away.

There is a simple solution for this. By using the DependencyService you can easily get the Toast-Like approach in both Android and iOS.
Create an interface in your common package.
public interface IMessage
{
void LongAlert(string message);
void ShortAlert(string message);
}
Android section
[assembly: Xamarin.Forms.Dependency(typeof(MessageAndroid))]
namespace Your.Namespace
{
public class MessageAndroid : IMessage
{
public void LongAlert(string message)
{
Toast.MakeText(Application.Context, message, ToastLength.Long).Show();
}
public void ShortAlert(string message)
{
Toast.MakeText(Application.Context, message, ToastLength.Short).Show();
}
}
}
iOS section
In iOs there is no native solution like Toast, so we need to implement our own approach.
[assembly: Xamarin.Forms.Dependency(typeof(MessageIOS))]
namespace Your.Namespace
{
public class MessageIOS : IMessage
{
const double LONG_DELAY = 3.5;
const double SHORT_DELAY = 2.0;
NSTimer alertDelay;
UIAlertController alert;
public void LongAlert(string message)
{
ShowAlert(message, LONG_DELAY);
}
public void ShortAlert(string message)
{
ShowAlert(message, SHORT_DELAY);
}
void ShowAlert(string message, double seconds)
{
alertDelay = NSTimer.CreateScheduledTimer(seconds, (obj) =>
{
dismissMessage();
});
alert = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null);
}
void dismissMessage()
{
if (alert != null)
{
alert.DismissViewController(true, null);
}
if (alertDelay != null)
{
alertDelay.Dispose();
}
}
}
}
Please note that in each platform, we have to register our classes with DependencyService.
Now you can access out Toast service in anywhere in our project.
DependencyService.Get<IMessage>().ShortAlert(string message);
DependencyService.Get<IMessage>().LongAlert(string message);

You can use Acr.UserDialogs Package from nuget and code like below,
Acr.UserDialogs.UserDialogs.Instance.Toast(Message, new TimeSpan(3));

Here's a version of Alex Chengalan's iOS code that avoids the UI sticking when multiple messages are shown...
public class MessageIOS : IMessage
{
const double LONG_DELAY = 3.5;
const double SHORT_DELAY = 0.75;
public void LongAlert(string message)
{
ShowAlert(message, LONG_DELAY);
}
public void ShortAlert(string message)
{
ShowAlert(message, SHORT_DELAY);
}
void ShowAlert(string message, double seconds)
{
var alert = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
var alertDelay = NSTimer.CreateScheduledTimer(seconds, obj =>
{
DismissMessage(alert, obj);
});
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null);
}
void DismissMessage(UIAlertController alert, NSTimer alertDelay)
{
if (alert != null)
{
alert.DismissViewController(true, null);
}
if (alertDelay != null)
{
alertDelay.Dispose();
}
}
}

You can use SnackBar from Xamarin Community toolkit package, which uses native implementation in platforms where natively supported, because Toast is deprecated in API level 30, a SnackBar without an Action is equivalent to a Toast.
This method was deprecated in API level 30.
Custom toast views are deprecated. Apps can create a standard text toast with the makeText(android.content.Context, java.lang.CharSequence, int) method, or use a Snackbar when in the foreground. Starting from Android Build.VERSION_CODES#R, apps targeting API level Build.VERSION_CODES#R or higher that are in the background will not have custom toast views displayed. (source).
Starting with Xamarin Community toolkit
Install the Package on all your projects
include the namespace using Xamarin.CommunityToolkit.Extensions;
In your page code-behind show a SnackBar upon an event
await this.DisplayToastAsync("This is a Toast Message");
await this.DisplayToastAsync("This is a Toast Message for 5 seconds", 5000);
You may specify a duration for the SnackBar to disappear (in milliseconds) or leave the default one which equals 3 seconds.
Resources
SnackBar Sample
Official Repo https://github.com/xamarin/XamarinCommunityToolkit
Official Docs https://learn.microsoft.com/en-us/xamarin/community-toolkit/
EDIT
Anchored Toast: You may anchor the toast above a view (like screenshot above) by simply calling the extension method DisplayToastAsync() from that view (anchor) object instead from the page instance (this):
<Button x:name="floatingButton" .../>
await floatingButton.DisplayToastAsync("This is a Toast Message for 5 seconds", 5000);
Padding and corner radius: (starting from xct version 1.3.0 preview-1)
You can set the corner radius and the padding for your Toast like the following example:
var messageOptions = new MessageOptions
{
Message = "Toast with Padding and round corner",
Foreground = Color.White,
Font = Font.SystemFontOfSize(16),
Padding = new Thickness(20)
};
var options = new ToastOptions
{
MessageOptions = messageOptions,
CornerRadius = new Thickness(40, 40, 0, 0),
BackgroundColor = Color.FromHex("#CC0000")
};
await this.DisplayToastAsync(options);
PS: The same properties could be applied for the SnackBar view.
EDIT2
If what xct SnackBar offers doesn't fulfil your requirements or you want to display not only text but some complexe view, you might have to use a popup instead.

Adding to Alex's answer, here's the UWP variant:
public class Message : IMessage {
private const double LONG_DELAY = 3.5;
private const double SHORT_DELAY = 2.0;
public void LongAlert(string message) =>
ShowMessage(message, LONG_DELAY);
public void ShortAlert(string message) =>
ShowMessage(message, SHORT_DELAY);
private void ShowMessage(string message, double duration) {
var label = new TextBlock {
Text = message,
Foreground = new SolidColorBrush(Windows.UI.Colors.White),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
var style = new Style { TargetType = typeof(FlyoutPresenter) };
style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Windows.UI.Colors.Black)));
style.Setters.Add(new Setter(FrameworkElement.MaxHeightProperty, 1));
var flyout = new Flyout {
Content = label,
Placement = FlyoutPlacementMode.Full,
FlyoutPresenterStyle = style,
};
flyout.ShowAt(Window.Current.Content as FrameworkElement);
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(duration) };
timer.Tick += (sender, e) => {
timer.Stop();
flyout.Hide();
};
timer.Start();
}
}
Coloring and styling is up to you, the MaxHeightis actually required to keep the height at the minimum.

We'd normally use Egors Toasts plugin, but as it requires permissions on iOS for a current project we've gone a different route using Rg.Plugins.Popup nuget (https://github.com/rotorgames/Rg.Plugins.Popup).
I wrote a basic xaml/cs page of type PopupPage,
<?xml version="1.0" encoding="utf-8" ?>
<popup:PopupPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:popup="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
x:Class="YourApp.Controls.ToastPage">
...
and had it created by a service, whose interface you registered at app start or use Xamarin.Forms.DependencyService to fetch the service would be viable too.
The service news up the PopupPage derived page, and does
await PopupNavigation.PushAsync(newToastPage);
await Task.Delay(2000);
await PopupNavigation.PopAllAsync();
The Popup page can be dismissed by the user by tapping outside the page display (assuming it hasn't filled the screen).
This seems to work fine on iOS/Droid, but I'm open to correction if anyone knows what this is a risky way of doing it.

You can use IUserDialog NuGet and simply use it's toastAlert
var toastConfig = new ToastConfig("Toasting...");
toastConfig.SetDuration(3000);
toastConfig.SetBackgroundColor(System.Drawing.Color.FromArgb(12, 131, 193));
UserDialogs.Instance.Toast(toastConfig);

Here is a code snippet that I am using to show the toast in Xamarin.iOS
public void ShowToast(String message, UIView view)
{
UIView residualView = view.ViewWithTag(1989);
if (residualView != null)
residualView.RemoveFromSuperview();
var viewBack = new UIView(new CoreGraphics.CGRect(83, 0, 300, 100));
viewBack.BackgroundColor = UIColor.Black;
viewBack.Tag = 1989;
UILabel lblMsg = new UILabel(new CoreGraphics.CGRect(0, 20, 300, 60));
lblMsg.Lines = 2;
lblMsg.Text = message;
lblMsg.TextColor = UIColor.White;
lblMsg.TextAlignment = UITextAlignment.Center;
viewBack.Center = view.Center;
viewBack.AddSubview(lblMsg);
view.AddSubview(viewBack);
roundtheCorner(viewBack);
UIView.BeginAnimations("Toast");
UIView.SetAnimationDuration(3.0f);
viewBack.Alpha = 0.0f;
UIView.CommitAnimations();
}

I would recommend Plugin.Toast library from nuget. It works well.
CrossToastPopUp.Current.ShowToastMessage("my toast message");
or from ACR.UserDialogs Nuget libriary
UserDialogs.Instance.ShowLoading("Loading");

#MengTim, to fix the multiple toast issue in #alex-chengalan's solution, I simply wrapped everything within ShowAlert() with a check to see if alert and alertDelay are null, then within DismissMessage, nulled out alert and alertDelay.
void ShowAlert(string message, double seconds)
{
if(alert == null && alertDelay == null) {
alertDelay = NSTimer.CreateScheduledTimer(seconds, (obj) =>
{
DismissMessage();
});
alert = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null);
}
}
void DismissMessage()
{
if (alert != null)
{
alert.DismissViewController(true, null);
alert = null;
}
if (alertDelay != null)
{
alertDelay.Dispose();
alertDelay = null;
}
}
That seemed to at least clear up the UI hang, if you are looking for a quick fix. I was trying to display the toast on navigation to a new page, and believe that the PresentViewController being set was essentially cancelling out my navigation. Sorry I did not comment within the thread, my reputation is too low :(

This is my improved ShowAlert version of Ian Warburton's version to ensure that the toast is displayed even on popup page.
Furthermore, the toast is dissmissed if the user click outside the toast.
I used UIAlertControllerStyle.ActionSheet that look likes toast but it also work with UIAlertControllerStyle.Alert
void ShowAlert(string message, double seconds)
{
var alert = UIAlertController.Create(null, message, UIAlertControllerStyle.ActionSheet);
var alertDelay = NSTimer.CreateScheduledTimer(seconds, obj =>
{
DismissMessage(alert, obj);
});
var viewController = UIApplication.SharedApplication.KeyWindow.RootViewController;
while (viewController.PresentedViewController != null)
{
viewController = viewController.PresentedViewController;
}
viewController.PresentViewController(alert, true, () =>
{
UITapGestureRecognizer tapGesture = new UITapGestureRecognizer(_ => DismissMessage(alert, null));
alert.View.Superview?.Subviews[0].AddGestureRecognizer(tapGesture);
});
}
I hope this will help someone !

There is no built-in mechanism in Forms, but this nuget package supplies something similar
https://github.com/EgorBo/Toasts.Forms.Plugin
Note: These are not Android style toasts as requested in the question but UWP style toasts which are system wide notifications.

I customised a custom popup with Rg.Plugins.Popup NuGet
this is an example:
<pages:PopupPage.Animation>
<animations:ScaleAnimation
PositionIn="Center"
PositionOut="Center"
ScaleIn="1.2"
ScaleOut="0.8"
DurationIn="600"
DurationOut="600"
EasingIn="Linear"
EasingOut="Linear"/>
</pages:PopupPage.Animation>
<Frame CornerRadius="10"
HeightRequest="30"
VerticalOptions="End"
HorizontalOptions="Fill"
HasShadow="False"
Padding="0" Margin="40,50"
OutlineColor="LightGray">
<StackLayout
Opacity="0.4"
BackgroundColor="White">
<Label
x:Name="lbl"
LineBreakMode="WordWrap"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center" TextColor="Black" FontSize="12">
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String">
<On Platform="iOS" Value="NewJuneMedium" />
</OnPlatform>
</Label.FontFamily>
</Label>
</StackLayout>
</Frame>
then in your basecontentpage you can add the following code, to show and hide the "toast" after a while:
public async void showpopup(string msg)
{
await Navigation.PushPopupAsync(new Toast(msg));
await Task.Delay(3000);
await Navigation.PopPopupAsync(true);
}

I used
https://github.com/ishrakland/Toast/
In
https://www.nuget.org/packages/Plugin.Toast/
Example:
CrossToastPopUp.Current.ShowToastMessage ("Loading", Plugin.Toast.Abstractions.ToastLength.Short);
Give it a try.

The iOS answers above worked for me but for one little problem -- a warning: Attempt to present UIAlertController ... whose view is not in the window hierarchy!
After some search, I came across this unrelated answer which helped. The poster commented "This looks stupid but works", which is right on both counts.
So, I modified the ShowAlert() function above with these lines, which seem to work:
var rootVC = UIApplication.SharedApplication.KeyWindow.RootViewController;
while ( rootVC.PresentedViewController != null) {
rootVC = rootVC.PresentedViewController;
}
rootVC.PresentViewController( alert, true, null);

For UWP
public void ShowMessageFast(string message)
{
ToastNotifier ToastNotifier = ToastNotificationManager.CreateToastNotifier();
Windows.Data.Xml.Dom.XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
Windows.Data.Xml.Dom.XmlNodeList toastNodeList = toastXml.GetElementsByTagName("text");
toastNodeList.Item(0).AppendChild(toastXml.CreateTextNode("Test"));
toastNodeList.Item(1).AppendChild(toastXml.CreateTextNode(message));
Windows.Data.Xml.Dom.IXmlNode toastNode = toastXml.SelectSingleNode("/toast");
Windows.Data.Xml.Dom.XmlElement audio = toastXml.CreateElement("audio");
audio.SetAttribute("src", "ms-winsoundevent:Notification.SMS");
ToastNotification toast = new ToastNotification(toastXml);
toast.ExpirationTime = DateTime.Now.AddSeconds(4);
ToastNotifier.Show(toast);
}

Currently use xamarin essential in android:
//access mainthread
MainThread.BeginInvokeOnMainThread(() =>
{
Toast.MakeText(Application.Context, message, ToastLength.Short).Show();
});

Adding Alex's code, for UWP variant, I found a great implementation here
https://www.c-sharpcorner.com/article/xamarin/
Just come and leave a clap for him :)
[assembly:Xamarin.Forms.Dependency(typeof(Toast_UWP))]
namespace ToastMessage.UWP
{
class Toast_UWP : Toast
{
public void Show(string message)
{
ToastTemplateType toastTemplate = ToastTemplateType.ToastImageAndText01;
XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);
XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
toastTextElements[0].AppendChild(toastXml.CreateTextNode(message));
XmlNodeList toastImageAttributes = toastXml.GetElementsByTagName("image");
((XmlElement)toastImageAttributes[0]).SetAttribute("src", "ms-appx:///Assets/Logo.scale-240.png");
((XmlElement)toastImageAttributes[0]).SetAttribute("alt", "logo");
IXmlNode toastNode = toastXml.SelectSingleNode("/toast");
((XmlElement)toastNode).SetAttribute("duration", "short");
var toastNavigationUriString = "#/MainPage.xaml?param1=12345";
var toastElement = ((XmlElement)toastXml.SelectSingleNode("/toast"));
toastElement.SetAttribute("launch", toastNavigationUriString);
ToastNotification toast = new ToastNotification(toastXml);
ToastNotificationManager.CreateToastNotifier().Show(toast);
}
}
}
By default, your messages will be queued and notified one after one, delayed based on the message duration. If you want to replace the existing message by the new one immediately just add more code like below
ToastNotificationManager.History.Remove("YOUR_TAG");
// Code to create Toast message, like the above method
toast.Tag = "YOUR_TAG";
If you want to add audio to your toast message, add this to your code
var audio = toastXml.CreateElement("audio");
audio.SetAttribute("src", "ms-winsoundevent:Notification.Default");

Install nuget Acr.UserDialogs.
It contains Toasts exactly what you are looking for.
ToastEvent toastEvent = new ToastEvent();
var toastConfig = new ToastConfig(toastEvent,"Toasting...","");
toastConfig.SetDuration(2000);
UserDialogs.Instance.Toast(toastConfig);

You could use the Xamarin.Toolkit and follow this information provided by MS.
https://learn.microsoft.com/en-us/xamarin/community-toolkit/views/popup
In my application I could manage it by putting every code-thing between a viewmodel, the view and a method called DismissThisPopup(). So it is possible to controll it from outside of the current Mainpage.
To acomplish your request.
You could call await Task.Delay(5000); //as example 5sec
after you called your popup.Open();
so it might look like:
...
var vm = new MyPopUpViewModel()
vm.DisplayText = "this could be your text";
vm.DelayTimeForDismiss = 5000;
vm.IsLightDismissAllowed = false; //used that you can not close the popup by clicking around
await vm.OpenPopupAsync();
then in your vm OpenPopUpAsync()
...other properties and stuff
internal PopUpLoadingView popup = new PopUpLoadingView();//this is the view created with the informations from MS
public async Task OpenPopUp()
{
popup.BindingContext = this;
Application.Current.MainPage.Navigation.ShowPopup(popup: popup);
await Task.Delay(DelayTimeForDismiss);
popup.DismissThisPopup();
}
and in your PopUpLoadingView, insert this method:
...other stuff
public void DismissThisPopup()
{
Dismiss(this);
}

You can use DisplayAlert("", "", "", "" );

Related

Windows Lock Screen, add Text programmatically

I want to display custom text or control on the Windows 10 Lockscreen, when I click on a button. I tried it with an UWP Application.
My goal is something like this:
And the Code I tried:
ToastContent content = new ToastContent()
{
//Duration = ToastDuration.Long,
Scenario = ToastScenario.Reminder,
Visual = new ToastVisual()
{
BindingGeneric = new ToastBindingGeneric()
{
Attribution = new ToastGenericAttributionText()
{
Text = "Hello World"
}
}
},
Actions = new ToastActionsCustom()
{
Buttons = {
new ToastButton ("mycontent", "myargs")
}
}
};
var notification = new ToastNotification(content.GetXml());
ToastNotificationManager.CreateToastNotifier().Show(notification);
Also I saw this post and tried it of course, but it wasnt helpfull: Windows Lock Screen display text programmatically C#
Maybe you could help me to achive my goald
I thank you in advance
The screenshot you post above is smtc that used to show current playing music, for enable it you need to enable the app Background Media Playback, but it only use to show the media info, it can't use to share custom info like you mentioned scenario.
For your scenario, the better way is register your app LockScreen capability like the following.
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="xxxxx.UWP.App">
.......
<uap:LockScreen BadgeLogo="Assets\BadgeLogo.png" Notification="badgeAndTileText"/>
</uap:VisualElements>
</Application>
</Applications>
And set the app as main toast in the lock screen Setting page -> Personalization -> lock screen -> Choose one app to show detailed status on the lock screen If you have resisted the app, it will show in the apps list.
Code Sample
private void Button_Click(object sender, RoutedEventArgs e)
{
TileContent content = new TileContent()
{
Visual = new TileVisual()
{
LockDetailedStatus1 = "Hello world",
TileWide = new TileBinding() { }
}
};
var notification = new TileNotification(content.GetXml());
TileUpdateManager.CreateTileUpdaterForApplication().Update(notification);
}

How to flip Camera used by ZXing in Xamaring Forms

As part of the app i'm developing i want the ability to change between using the front or back camera, but from my searches and attempts i haven't been able to get it to work using the front camera.
The scanner view doing the scanning is the one from ZXing.Net.Mobile.Forms called ZXingScannerView, defined in my xaml like so, together with the button that should do the flipping of the camera.
<elements:AdvancedTabbedPage
...
xmlns:elements="clr-namespace:Wolf.Utility.Main.Xamarin.Elements;assembly=Wolf.Utility.Main"
xmlns:forms="clr-namespace:ZXing.Net.Mobile.Forms;assembly=ZXing.Net.Mobile.Forms">
...
<ContentPage>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{x:Static resources:AppResources.CameraFlipText}" x:Name="CameraFlipButton" Clicked="CameraFlipButton_OnClicked"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
...
<forms:ZXingScannerView x:Name="ScannerView" HeightRequest="200" IsAnalyzing="False" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" IsVisible="False" IsScanning="True"/>
...
</ContentPage.Content>
</ContentPage>
The Button can be seen in the top right of the following image, while the Scanner View is only visible while scanning is On, which it is not on the image.
Image of the Page where Scannning is happening
Clicking the button should toggle between using the front and back camera, with the front one as the default. Clicking the button however, doesn't seem to do anything, other then write to my Log. Code for the Clicked event of the button can be seen below.
...
private void CameraFlipButton_OnClicked(object sender, EventArgs e)
{
Logging.Log(LogType.Information, "Flipping Camera...");
Config.DefaultOptions.UseFrontCameraIfAvailable = !Config.DefaultOptions.UseFrontCameraIfAvailable;
Config.CustomOptions.UseFrontCameraIfAvailable = !Config.CustomOptions.UseFrontCameraIfAvailable;
if (!ScanningToggle.IsToggled) return;
Logging.Log(LogType.Information, "Restarting Scanning...");
ScanningToggle.IsToggled = false;
ScanningToggle.IsToggled = true;
}
The options mentioned in the above code is defined as so, in my Config class. Additional values in the one called CustomOptions are set in my Init method of my Config class, but those are irrelevant to this question.
public class Config
{
...
public static MobileBarcodeScanningOptions CustomOptions = new MobileBarcodeScanningOptions() { UseFrontCameraIfAvailable = true };
public static MobileBarcodeScanningOptions DefaultOptions = new MobileBarcodeScanningOptions() { UseFrontCameraIfAvailable = true };
...
}
The options that my scanner will use, is always picked between these two, depending on a few user inputs in the settings.
Attempting to get it to work i have also tried to...
Invert the value UseFrontCameraIfAvailable, while scanning is running
Invert the value UseFrontCameraIfAvailable on the options used to start the scan with and then restarting the scan - The code shown above.
Change IsScanning of the ZXingScannerView from true to false, while restarting the scanning with changed options, but this just resulted in the camera freezing.
Found this one as i was about to submit the question. I'm going to attempt to follow that one tomorrow, but would still very much like input on mine.
Fell free to ask questions, or ask for additional code if i have left something out, that you think could help.
I managed to figure out how to successfully flip the camera.
To do so i first remove the ZXingScannerView from my stack that contains it.
Then i create a new instance of the ZXingScannerView, copying over all the settings from the old one (layout positioning, ZXingScannerView specific values and so on).
Then i re-add the ZXingScannerView to the stack, and from there any changes to the UseFrontCameraIfAvailable property took effect.
The code it made to succeed, is as follows. First the generic method that copies over properties, then the method that recreates the ZXingScannerView, and lastly my method that enables the scanning.
public class GenericFactory
{
// Assistance with Setter Accessibility: https://stackoverflow.com/questions/3762456/how-to-check-if-property-setter-is-public
public static T CopyProperties<T>(T newObject, T oldObject, bool ignoreDefaults = true,
bool skipSelectedProperties = true, params string[] skippedProperties) where T : class
{
var type = typeof(T);
var properties = type.GetProperties();
foreach (var property in properties)
{
if (ignoreDefaults && property.GetValue(oldObject) == default)
continue;
if (skipSelectedProperties && skippedProperties.Contains(property.Name))
continue;
if (!property.CanWrite)
continue;
property.SetValue(newObject, property.GetValue(oldObject));
}
return newObject;
}
}
private void RecreateScannerView()
{
if (Config.DebugMode) Logging.Log(LogType.Debug, $"{nam1eof(RecreateScannerView)} method called");
ScannerStack.Children.Remove(ScannerView);
if (Config.DebugMode)
Logging.Log(LogType.Debug,
$"Coping properties from existing {nameof(ZXingScannerView)} into a new {nameof(ZXingScannerView)}");
ScannerView = GenericFactory.CopyProperties(new ZXingScannerView() {IsScanning = false}, ScannerView,
skippedProperties: new List<string>() {nameof(ScannerView.IsScanning)}.ToArray());
ScannerView.OnScanResult += ScannerView_OnScanResult;
ScannerStack.Children.Add(ScannerView);
}
private void EnableScan(MobileBarcodeScanningOptions imputedOptions = null)
{
if (Config.DebugMode) Logging.Log(LogType.Debug, $"{nameof(EnableScan)} Method is run in Thread named => {Thread.CurrentThread.Name}");
var chosenOptions = imputedOptions ?? (Config.UseCustomOptions ? Config.CustomOptions : Config.DefaultOptions);
if (Config.DebugMode)
Logging.Log(LogType.Information,
$"Chose this option for Scanning => {(imputedOptions != null ? nameof(imputedOptions) : (Config.UseCustomOptions ? nameof(Config.CustomOptions) : nameof(Config.DefaultOptions)))}");
ScannerView.Options = chosenOptions;
RecreateScannerView();
Logging.Log(LogType.Information, $"Starting the Scanning...");
ScannerView.IsScanning = true;
ScannerView.IsAnalyzing = true;
ScannerView.IsVisible = true;
if (Config.DebugMode)
Logging.Log(LogType.Debug,
$"{nameof(EnableScan)} Called and Finished; ScannerView.IsAnalyzing => {ScannerView.IsAnalyzing}; ScannerView.IsVisible => {ScannerView.IsVisible}");
}
My method to flip the value of UseFrontCameraIfAvailable is the one shown in the question above.
Hope to this ends up helping others who might stumble upon a likewise issue.
I don't think it can switch the front and back cameras when it has started scanning with Zxing,so the option has to be chosen and set beforehand
var options = new MobileBarcodeScanningOptions
{
AutoRotate = true,
UseNativeScanning = true,
TryHarder = true,
TryInverted = true,
UseFrontCameraIfAvailable = true
};
var scannedCode = await _scanner.Scan(options);

Opening platform specific screens after login screen in cross platform shared code Xamarin?

I want to open an Android activity for Android devices and iOS screen for iOS devices from login screen. The code of the login screen, I have written is in shared portable code Area. I have used interface and #if ANDROID #endif, but it is also not working.
Android screen will only contain a textview and an iOS screen will contain a Image. This is my Login Page in portable folder
namespace DemoApp
{
public class HomePage : ContentPage
{
public HomePage()
{
var title = new Label
{
Text = "Welcome to CloudCakes",
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
HorizontalOptions = LayoutOptions.CenterAndExpand,
};
var email = new Entry
{
Placeholder = "E-Mail",
};
var password = new Entry
{
Placeholder = "Password",
IsPassword = true
};
var login = new Button
{
Text = "Login"
};
// With the `PushModalAsync` method we navigate the user
// the the orders page and do not give them an option to
// navigate back to the Homepage by clicking the back button
login.Clicked += (sender, e) =>
{
App.UserPreferences.Open();
// await Navigation.PushModalAsync(new MainPage());
};
Content = new StackLayout
{
Padding = 30,
Spacing = 10,
Children = { title, email, password, login}
};
}
}
}
And, this is the page in Droid folder where I want to navigate on clicking login button
name space
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Page1 : IUserPreferences
{
public Page1()
{
InitializeComponent();
}
public void Open()
{
var title = new Label
{
Text = "Welcome to Xamarin Forms IOS Screen!!",
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
HorizontalOptions = LayoutOptions.CenterAndExpand,
};
Content = new StackLayout
{
Padding = 30,
Spacing = 10,
Children = { title }
};
}
}
}
Open is a method of interface which is present in the portable folder.
I assume that you're using Xamarin.Forms, so what you need to do is actually quite easy to achieve.
Instead of having a separate page for each platform, create one new XAML page with the following code:
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="YourApp.Views.MainPage">
<ContentView>
<OnPlatform x:TypeArguments="View">
<OnPlatform.iOS>
<Image Source="something.png" />
</OnPlatform.iOS>
<OnPlatform.Android>
<Label Text="Android" />
</OnPlatform.Android>
</OnPlatform>
</ContentView>
</ContentPage>
This way you can control what to have on the page for each platform.
To navigate to MainPage from your LoginPage, you need to implement the Clicked event (or preferrably use Commands) in the code behind (MainPage.xaml.cs) and do the navigation there:
private void SomeButton_Clicked(object sender, System.EventArgs e)
{
await this.Navigation.PushAsync(new MainPage());
}
In the long run, you should look into doing all this outside the code behind with ViewModel's and Commands.
Edit: Since the first solution didn't work for you, here's what to do:
1) Subclass ContentPage in your core project.
public class MainPage : ContentPage { }
2) Implement a PageRenderer for each platform separately. You'll be able to use native layout definition for your pages as you mentioned.
There's a great article on the Xamarin blog on how to achieve this. You can also find the source code on GitHub.

How to use Windows.Devices.Enumeration.DevicePicker with Prism (MVVM)?

I am working on a new UWP application that interacts with some hardware via Bluetooth. Using the windows-universal-samples repo as a guide I was able to sucessfully get what I wanted working.
Now I am trying to refactor the code I wrote in a click event handler into a view model class using Prism. However I don't know how to approach this. In other scenarios where I need to pass data between a View and ViewModel I would create a property on the ViewModel and bind it to the control in the view's XAML.
The problem is that Windows.Devices.Enumaration.DevicePicker is used in a way that doesn't seem compatible with the MVVM pattern. In the click handler, the data and control are merged together and I don't see how I can make some kind of list property on the view model and then bind it to the view. Here is the simplest example of the code I am working with:
async void DiscoverButton_OnClick(object sender, RoutedEventArgs e)
{
var devicePicker = new DevicePicker();
devicePicker.Filter.SupportedDeviceSelectors.Add(BluetoothLEDevice.GetDeviceSelectorFromPairingState(true));
// Calculate the position to show the picker (right below the buttons)
var ge = DiscoverButton.TransformToVisual(null);
var point = ge.TransformPoint(new Point());
var rect = new Rect(point, new Point(100, 100));
var device = await devicePicker.PickSingleDeviceAsync(rect);
var bluetoothLEDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
}
See PickSingleDeviceAsync() creates a control directly.
Now I am trying to refactor the code I wrote in a click event handler into a view model class using Prism. However I don't know how to approach this.
You could bind command for your button and use CommandParameter to pass parameter to the command.
Please refer to the following code sample for details:
<Button x:Name="btn" Content="device" Command="{Binding ClickCommand}" CommandParameter="{Binding ElementName=btn}"></Button>
public class MianViewModel : BindableBase
{
public ICommand ClickCommand
{
get;
private set;
}
public MianViewModel()
{
ClickCommand = new DelegateCommand<object>(ClickedMethod);
}
private async void ClickedMethod(object obj)
{
var devicePicker = new DevicePicker();
devicePicker.Filter.SupportedDeviceSelectors.Add(BluetoothLEDevice.GetDeviceSelectorFromPairingState(true));
// Calculate the position to show the picker (right below the buttons)
Button DiscoverButton = obj as Button;
if (DiscoverButton != null)
{
var ge = DiscoverButton.TransformToVisual(null);
var point = ge.TransformPoint(new Point());
var rect = new Rect(point, new Point(100, 100));
var device = await devicePicker.PickSingleDeviceAsync(rect);
if (device != null)
{
var bluetoothLEDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
}
}
}
}
The solution I came up with was to abandon the built in UI provided by DevicePicker and instead create my own UI to use with DeviceWatcher. For example:
void StartWatcher()
{
ResultCollection.Clear();
string selector = BluetoothLEDevice.GetDeviceSelector();
DeviceWatcher = DeviceInformation.CreateWatcher(selector);
DeviceWatcher.Added += async (deviceWatcher, deviceInformation) =>
{
await OnUiThread(() =>
{
ResultCollection.Add(deviceInformation);
});
};
DeviceWatcher.Start();
}
Where ResultCollection would be bound from the view model to the view.

Updating TextBlock at start of code block in MainWindow asynchronously

I have a problem that's been bugging me for days, I've tried every option and I'm now resulting in posting my own question to find some specific help from you guys.
I need to update a TextBlock at the start of code block, which is run on a simple button click.
Here's my code:
private void NewProject(bool blnCopy = false, string strFileName = null)
{
if (App.ApplicationBusy == false)
{
App.ApplicationBusy = true;
try
{
Action action = delegate()
{
Cursor = Cursors.Wait;
lblStatus.Text = "Opening Project...";
};
Dispatcher.Invoke(DispatcherPriority.Send, action);
if (blnCopy == false) { Project = new GSProject((App.RecentProjectCount + 1)); }
if (Project != null)
{
Projects.Add(Project);
if (blnCopy == false)
{
if (strFileName == null)
{
Project.ProjectName = string.Format("GSProject{0}", Projects.Count.ToString());
Project.ProjectDescription = string.Format("{0} - HW GS Project", Project.ProjectName);
Project.LoadResource();
}
else
{
Project.Load(strFileName);
}
}
else
{
Project = Project.Copy();
}
p_objNewPane = objDocker.AddDocument(Project.ProjectDisplayName, Project);
if (p_objNewPane != null)
{
p_objNewPane.DataContext = Project;
BindingOperations.SetBinding(p_objNewPane, ContentPane.HeaderProperty, new Binding("ProjectDisplayName") { Source = Project });
p_objNewPane.Closing += new EventHandler<PaneClosingEventArgs>(ContentPane_Closing);
}
if (Project.CalculationExists == true)
{
InitializeCalculation(true);
}
}
tabStartPage.Visibility = Visibility.Collapsed;
objDocumentTabs.SelectedIndex = 0;
}
catch (Exception ex)
{
ModernDialog.ShowMessage(string.Format("An error has occurred:{0}{0}{1}", Environment.NewLine, ex.Message), "Error", MessageBoxButton.OK, Application.Current.MainWindow);
}
finally
{
App.ApplicationBusy = false;
Cursor = Cursors.Arrow;
AppStatus = "Ready";
p_objNewPane = null;
}
}
}
At the start of the try block, I need to update the TextBlock (lblStatus) to say what's going on. The void itself, NewProject, is on the MainWindow, and is called by a button click.
Can someone please give me an idea of where I'm going wrong? I've tried countless potential solutions, so please don't be offended if I get back to you saying I've tried it.
Regards, Tom.
After a few painful days I managed to get this working. I was barking up the wrong tree completely by looking into Task Scheduling, etc. Instead all that was needed was a DependencyProperty.
XAML (Main Window):
<TextBlock x:Name="lblStatus"
Text="{Binding AppStatus, IsAsync=True}"
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontFamily="Segoe UI"
FontSize="12"
Foreground="White"
Margin="5, 0, 0, 0" />
C# (Main Window):
public string AppStatus
{
get { return (string)GetValue(AppStatusProperty); }
set { SetValue(AppStatusProperty, value); }
}
public static readonly DependencyProperty AppStatusProperty =
DependencyProperty.Register("AppStatus", typeof(string), typeof(MainWindow), new PropertyMetadata(null));
public void StatusBarUpdate(string strMainMessage)
{
Dispatcher.BeginInvoke((Action)(() => { AppStatus = strMainMessage; }));
}
I can then call the StatusBarUpdate method at any time and it will asynchronously update the UI.
You are using WPF, Therefore implement
INotifyPropertyChanged
and use proper data binding.
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public string StatusText
{
get {return this.m_statusText;}
set
{
if (value != this.m_statusText)
{
this.m_statusText= value;
NotifyPropertyChanged("StatusText");
}
}
}
And in XAML
<TextBox Text="{Binding Path=StatusText}"/>
You haven't mentioned what exactly is the problem, but looking at the code I can guess that the textbox shows "Opening Project..." only after completion of your code or shows "Ready" if AppStatus is doing the same thing.
If yes, the problem is that you are doing everything on the UI thread. Although you changed the text of the textbox, it will be rendered after your work is done, hence the problem. To fix this, you need to run the code from if (blnCopy == false) to objDocumentTabs.SelectedIndex = 0; on a worker thread. That will fix your problem.
You can use TPL for that and can also you .ContinueWith with TaskScheduler.FromCurrentSynchronizationContext and TaskContinuationOptions.OnlyOnRanToCompletion to execute the finally block.
EDIT:
As you are not using MVVM, you will need a lot of Invoke to make your code work. As I can see p_objNewPane is also an UI element just like Project is a view property, it will be difficult to translate all that to TPL. You can leave code from p_objNewPane = ... as it is (i.e. outside worker thread). That doesn't seem to be very cpu intensive except maybe InitializeCalculation which you can run in another worker thread.
A better approach is that you use await/async methods to wait for all heavy lifting methods.

Categories