xamarin.forms - pass viewModel data to android activity - c#

I am making an OnKeyDown and Up for volume buttons event. If app detect both of them clicked then it should move to another Page. Thing is I want to pass BindingContext to new page.
Overall it is in MainActivity.cs:
public override bool OnKeyDown(Keycode keyCode, KeyEvent e)
{
if (keyCode == Keycode.VolumeUp)
{
_pressedKeys.Add(Keycode.VolumeUp);
if (_pressedKeys.Contains(Keycode.VolumeDown))
{
ChangePage(); //go to view
}
return true;
}
if (keyCode == Keycode.VolumeDown)
{
_pressedKeys.Add(Keycode.VolumeUp);
if (_pressedKeys.Contains(Keycode.VolumeUp))
{
ChangePage(); ////go to view
}
return true;
}
return base.OnKeyDown(keyCode, e);
}
Overall function is working and its nice. Problem is because I dont know how pass BindingContext. I tried to do something like this:
var mainViewModel = Application.Current.MainPage.BindingContext as MainViewModel;
But Application not contain a definition for Current...
I am still learning xamarin.forms and struggling with this from few hours... Could you help me guys?

Maybe you should use:
var mainViewModel = App.Current.MainPage.BindingContext as MainViewModel;
Check your App class name(in PCL library). This should work.

Related

Unable to hide Android Keyboard on Android 9

Creating an app that on tap of an webview input field, has to do an action. Catching and starting the selected action works fine, but due to it being started by clicking an input field, the keyboard is requested. On Android < Version 9, my currently code works just fine to hide the keyboard, but on Android Version 9, it doesn't.
I have tried all manor or combination of what was deemed the top answer on this post, but none have worked for my app on Android 9
Below is a bit of my code from my MainActivity, where the instance of my keyboard service implementation is created. the MainActivity code is then followed by my Keyboard service implementation made for android.
[Activity(Label = "Dental.App", Icon = "#mipmap/icon", Theme = "#style/MainTheme", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, WindowSoftInputMode = SoftInput.StateAlwaysHidden) ]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
...
DependencyService.Get<IServiceCollection>().SetKeyboardService(new KeyboardService(this, GetInputMethodManager()));
...
}
public InputMethodManager GetInputMethodManager()
{
return (InputMethodManager)GetSystemService(Context.InputMethodService);
}
}
public class KeyboardService : IKeyboardService
{
private InputMethodManager inputMethodManager;
private readonly object mainActivity;
public KeyboardService(object activity, InputMethodManager methodManager)
{
mainActivity = activity;
inputMethodManager = methodManager;
}
public bool IsKeyboardShown => inputMethodManager.IsAcceptingText;
public void HideKeyboard()
{
if (inputMethodManager == null || !(mainActivity is Activity activity)) return;
Logging.Log(LogType.Information, $"Attempting to Hide Keyboard via 1st method...");
//var view = activity.CurrentFocus;
var view = activity.FindViewById(Android.Resource.Id.Content).RootView;
if (view == null) Logging.Log(LogType.Warning, $"Failed to get View from Activity...");
var token = view?.WindowToken;
if (token == null) Logging.Log(LogType.Warning, $"Failed to get Token from View...");
var success = inputMethodManager.HideSoftInputFromWindow(token, HideSoftInputFlags.None);
Logging.Log(LogType.Information,
$"{nameof(inputMethodManager.HideSoftInputFromWindow)} returned => {success}");
if(success) view?.ClearFocus();
if (!IsKeyboardShown)
{
view?.ClearFocus();
return;
}
Logging.Log(LogType.Warning,
$"Failed to Hide Keyboard via {nameof(inputMethodManager.HideSoftInputFromWindow)}...");
HideKeyboardAttemptTwo(activity);
}
private void HideKeyboardAttemptTwo(Activity activity)
{
Logging.Log(LogType.Information, $"Attempting to Hide Keyboard via 2nd method...");
//var view = activity.CurrentFocus;
var view = activity.FindViewById(Android.Resource.Id.Content).RootView;
if (view == null) Logging.Log(LogType.Warning, $"Failed to get View from Activity...");
var token = view?.WindowToken;
if (token == null) Logging.Log(LogType.Warning, $"Failed to get Token from View...");
inputMethodManager.ToggleSoftInputFromWindow(token, ShowSoftInputFlags.None, HideSoftInputFlags.None);
if (!IsKeyboardShown)
{
view?.ClearFocus();
return;
}
Logging.Log(LogType.Warning, $"Failed to Hide Keyboard via {nameof(inputMethodManager.ToggleSoftInputFromWindow)}...");
}
public void ReInitializeInputMethod()
{
inputMethodManager = InputMethodManager.FromContext((Context) mainActivity);
}
None of the null check are coming back true, i.e nothing is null. The variable called success in the method HideKeyboard is returning false in 99% of all cases where it is called on a android version 9. In the 1% of the cases where it is true, the keyboard is still open. If the keyboard is still shown at the end of HideKeyboard, then the code attempts to close the keyboard via toggling it in the method HideKeyboardAttemptTwo. Doing it either of theses ways on Android 9 does not work, however running the exact same code on an Android 7.1 works just fine.
I'm not entirely sure that i have implemented the use of ToggleSoftInputFromWindow correctly, it is intended to only be able to run when the keyboard is open, i.e always used to hide the keyboard.
To reiterate my question: How do it successfully hide the keyboard on an Android 9.
If any additional information is needed, just ask, and i will attempt to find and supply it.
I uses this for my app, give it a try
Interface in main project
namespace *.Services.Interfaces
{
public interface IForceKeyboardDismissalService
{
void DismissKeyboard();
}
}
Phone specific code
using Plugin.CurrentActivity; //Nugget used to get activity
[assembly: Xamarin.Forms.Dependency(typeof(AndroidForceKeyboardDismissalService))]
namespace *.Droid.PhoneSpecific
{
public class AndroidForceKeyboardDismissalService : IForceKeyboardDismissalService
{
public void DismissKeyboard()
{
var imm = InputMethodManager.FromContext(CrossCurrentActivity.Current.Activity.ApplicationContext);
imm?.HideSoftInputFromWindow(CrossCurrentActivity.Current.Activity.Window.DecorView.WindowToken, HideSoftInputFlags.NotAlways);
var currentFocus = CrossCurrentActivity.Current.Activity.CurrentFocus;
if (currentFocus != null && currentFocus is EditText)
currentFocus.ClearFocus();
}
}
}
Usage
DependencyService.Get<IForceKeyboardDismissalService>().DismissKeyboard();
Let me know if its working for you.
To fix my problem i injected some JavaScript into the Webview, wherein i unfocused the input field, that was clicked.
On my Webview class i created a method that, given the string id of an element, would toggle whether or not that element is focused. As a second input, a boolean can be supplied, but defaulted to True, to indicate whether or not, you only want to unfocus the element.
public class AdvancedWebView : HybridWebView
{
...
public void ToggleElementFocus(string elementId, bool onlyUnFocus = true)
{
var js = GetJsInvertFocus(elementId, onlyUnFocus);
InjectJavaScript(js);
// Logging.Logging.Log(LogType.Information, $"Injected Javascript => {js}");
}
...
private string GetJsInvertFocus(string elementId, bool onlyUnFocus)
{
var builder = new StringBuilder();
builder.Append($"if (document.getElementById('{elementId}'))");
builder.Append("{");
builder.Append($"var element = document.getElementById('{elementId}');");
builder.Append($"if (element === document.activeElement)");
builder.Append("{");
builder.Append($"element.blur();");
builder.Append("}");
builder.Append($"else if({onlyUnFocus} == False)");
builder.Append("{");
builder.Append($"element.focus();");
builder.Append("}");
builder.Append("}");
return builder.ToString();
}
...
}
I'm extending the HybridWebview from XLabs, as it already has the functionality to inject JavaScript into the Webview. So that is where i get the InjectJavaScript method from.
On my page in my app, with the Webview, i then have a method that runs, when the element is clicked. To get a click event when clicking the Webview look at this link. During the method i figure out what the element id is from the event arguments, and then use this id to inject the JavaScript shown above, to unfocus the element, causing the keyboard to not appear at all. Below my OnClicked method can be seen.
public partial class DentalWebPage : AdvancedTabbedPage
{
...
private void DentalWebView_OnClicked(object sender, ClickEvent e)
{
try
{
if (LogUserPosition(sender, e)) return;
SwapToScanningTap();
}
catch (Exception ex)
{
Logging.Log(LogType.Exception,
ex.GetType().Namespace == typeof(BaseException).Namespace
? $"{ex.GetType()} => {ex}"
: $"{ex.GetType()} => {ex.Message}; Stacktrace => {ex.StackTrace}");
}
}
private bool LogUserPosition(object sender, ClickEvent e)
{
if (Config.DebugMode) Logging.Log(LogType.Debug, $"WebView was clicked...");
if (Config.DebugMode) Logging.Log(LogType.Debug, $"Element that was clicked is the following one => {e.Element}");
var success = Enum.TryParse(e.Element.Split(' ')[1].Split('=')[1], out clickedInputId);
if (!success && !(clickedInputId == InputId.MainContent_TextBoxInputStr ||
clickedInputId == InputId.MainContent_TextBoxScanOrder ||
clickedInputId == InputId.MainContent_TextBoxSelectProd ||
clickedInputId == InputId.MainContent_TextBoxStockReturn))
return true;
if (Config.DebugMode && webPageEnding == WebsiteControllers.Stock.ToString().ToLowerInvariant())
Logging.Log(LogType.Debug, $"WebView was clicked while on the stock page...");
return false;
}
private void SwapToScanningTap()
{
PerformOnMainThread(() =>
{
CurrentPage = Children[1];
ScanningToggle.IsToggled = true;
try
{
var isKeyboardShown = services.KeyboardService.IsKeyboardShown;
if (Config.DebugMode) Logging.Log(LogType.Debug, $"IsKeyboardShown returns => {isKeyboardShown}");
DentalWebView.ToggleElementFocus(clickedInputId.ToString());
}
catch (ObjectDisposedException)
{
if (DisposedReattempt) throw;
if (Config.DebugMode)
Logging.Log(LogType.Debug,
$"Input Method has been Disposed; Attempting to reinitialize it and rerun the {nameof(SwapToScanningTap)} method ones again");
DisposedReattempt = true;
services.KeyboardService.ReInitializeInputMethod();
SwapToScanningTap();
}
});
}
...
private void PerformOnMainThread(Action action)
{
try
{
Device.BeginInvokeOnMainThread(action);
}
catch (Exception ex)
{
Logging.Log(LogType.Exception,
ex.GetType().Namespace == typeof(BaseException).Namespace
? $"{ex.GetType()} => {ex}"
: $"{ex.GetType()} => {ex.Message}; Stacktrace => {ex.StackTrace}");
}
}
}
If you wish to get a understanding of the format of the string contained in e.Element, then go and look at the link supplied earlier.
Fell free to ask further questions, in case i missed something.

Multiple binding to RelayCommand in WPF MVVM Light

I have started working with WPF MVVM Light and now I'am trying to navigate between pages.
In the MainWindow I have added a "BackButton"
<Button Command='{Binding Main.GoBack, Mode=OneWay}' />
which is binding to MainViewModel method "RelayCommand GoBack".
private RelayCommand _goBack;
public RelayCommand GoBack
{
get
{
return _goBack
?? (_goBack = new RelayCommand(
() =>
_navigationService.GoBack();
}));
}
}
Why is this button changing view only once? If I want to click it secound time
it doesn't work (nothing happend). If I change page for another by another button its starting work again and againg only for once.
Part of implementation of FrameNavigationService:
public FrameNavigationService()
{
_pagesByKey = new Dictionary<string, Uri>();
_historic = new List<string>();
}
public void GoBack()
{
if (_historic.Count > 1)
{
_historic.RemoveAt(_historic.Count - 1);
NavigateTo(_historic.Last(), null);
}
}
public void NavigateTo(string pageKey)
{
NavigateTo(pageKey, null);
}
public virtual void NavigateTo(string pageKey, object parameter)
{
lock (_pagesByKey)
{
if (!_pagesByKey.ContainsKey(pageKey))
{
throw new ArgumentException(string.Format("No such page: {0} ", pageKey), "pageKey");
}
var frame = GetDescendantFromName(Application.Current.MainWindow, "MainFrame") as Frame;
if (frame != null)
{
frame.Source = _pagesByKey[pageKey];
}
Parameter = parameter;
_historic.Add(pageKey);
CurrentPageKey = pageKey;
}
}
What can I do to handle this? May be I should do it tottaly differently?
You should possibly not be doing goback at all.
Unless you really want to use the journal, using a frame and pages is a bad idea. It's a rare requirement to go back to the last view in desktop apps. What with them not being a web browser.
Maybe you have that requirement though.
If you have a frame then you have it's journal and you can just call goback on the frame's navigationservice.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.navigation.navigationservice.goback?view=netframework-4.8
You set keepalive on pages.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.page.keepalive?view=netframework-4.8
You wrote that code and it seems to be largely reproducing navigationservice functionality. From what you've shown us.
As it is.
Use type rather than a magic string as the key. A type is checked at compile time, a magic string is not and you can make mistakes.
Have you explored this issue at all? I think maybe this is one of those times that telling someone what they did wrong isn't really helping as much as telling them how they ought to diagnose.
Debugging is a key skill for any developer.
You have the code running in front of you.
Put break points in, step through and examine what is happening.
When you navigate, what ends up in _historic?
When you goback, what happens exactly?
When you click the goback that second time what path does it go down and what state is causing that.
Make sure you are using RelayCommand in GalaSoft.MvvmLight.CommandWpf,not at GalaSoft.MvvmLight.Command.RelayCommand

Xamarin Forms PCL with Caliburn, only the original thread that created a view hierarchy can touch its views

In my Xamarin Forms PCL, I'm using Caliburn.Micro.Xamarin.Forms to implement the MVVM data binding. The structure of the project has a viewModel and a view and the data binding is done like this:
In the viewModel
private BindableCollection<TempListItem> _temperatures;
public BindableCollection<TempListItem> Temperatures
{
get { return _temperatures; }
set
{
if (value != _temperatures)
{
_temperatures = value;
NotifyOfPropertyChange(() => Temperatures);
}
}
}
In the view
var t2Name = new Label
{
FontSize = 17,
TextColor = (Color)Application.Current.Resources["ForegroundColor"]
};
t2Name.SetBinding(Label.TextProperty, new Binding("Temperatures[1].Name"));
I have this problem only the original thread that created a view hierarchy can touch its views that arise when I navigate to another page and I suppose is related to the data bindings.
_navigationService.For<MainViewModel>().Navigate();
I've solved this issue by calling the invocation in the main thread that "drawed" the UI.
For example, in the click button event handler:
private void btnSarasa_Clicked(object sender, EventArgs e)
{
Device.BeginInvokeOnMainThread(
() => { lblStatus.Text = "Sarasa"; }
);
}
Or in another event handler:
private void Ws_OnOpened()
{
Device.BeginInvokeOnMainThread(
() =>
{
ws.Send(#"{""protocol"":""json"",""version"":1}" + recordSeparator);
lblStatus.Text = "WS abierto";
ws.Send("{\"arguments\":[\"" + "444" + "-1\"],\"invocationId\":\"" + "0" + "\",\"target\":\"JoinGroup\",\"type\":1}" + recordSeparator);
}
);
}
There is another possible solution for your issue.
You could call the Device.InvokeOnMainThreadAsync in case you need the refresh to be that way, I mean, Async.
It also admits "typing" the task results.
In that case, use this:
Device.InvokeOnMainThreadAsync<string>

Frame.Navigate in LoadState

I'm having trouble trying to navigate automatically between pages in my Windows 8.1 app based on a little check. It just doesn't want to navigate to another page when doing this in LoadState, as if something isn't loaded yet, but it doesn't give an error either. When I insert a delay using (for example) await Task.Delay(2000) before doing Frame.Navigate, then my app will redirect without any problem.
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
MyData oData = await getData();
if (oData != null)
{
this.Frame.Navigate(typeof(newPage), oData);
}
else
{
// do something else
}
}
Do I have to put this code in another load- or navigated-event? Or how can I make this work?
In LoadState and SaveState you should only save and restore the page state (called when suspending and reactivating the app). Do nothing else (like navigating).
Put your logic into the OnNavigatedTo method instead...
If you want to navigate from method that called when page is loads, you should place your navigation code to OnNavigatedTo(...). But do not forget to wrap your code in Dispatcher.RunAsync(...) - Frame navigation in xaml return false
I tried calling Frame.Navigate(...) from the OnNavigatedTo method but still the navigation didn't occur.
There are other answers which say use Dispatcher.RunAsync, but that feels like it's making assumptions about the threading model of Windows Phone.
Here's what I do: attach a handler to the Loaded event of the page instead, and put my "redirect" logic in there. Loaded fires after OnNavigateTo and after NavigationHelper_LoadState, but before the page has become visible.
public LaunchPadPage() {
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
this.navigationHelper.SaveState += this.NavigationHelper_SaveState;
this.Loaded += LaunchPadPage_Loaded;
this.app = (App)App.Current;
}
private void NavigationHelper_LoadState(object sender, LoadStateEventArgs e) {
// Let's show the root zone items
// NB: In case we don't have this data yet, do nothing
if (app.Hierarchy != null)
{
DefaultViewModel["Items"] = app.Hierarchy.RootItems;
}
}
private void LaunchPadPage_Loaded(object sender, RoutedEventArgs e) {
// No data? Go to the downloads page instead.
if (app.Hierarchy == null)
{
Frame.Navigate(typeof(DownloadingPage));
}
}

Remove / hide the Bing map offline message when offline

I'm developing a Windows Phone app that uses the older WP7 Microsoft.Phone.Controls.Maps.Map / Bing Map control.
The map tiles are being served up from a local source so the app doesn't not need a network connection to work. Unfortunately the map control insists on showing an "Unable to contact Server. Please try again later." message over the map when offline.
Does anyone know of a method to remove / hide this message?
Just in case you're curious - I'm developing a WP8 app but using the depreciated WP7 Bing map control as the new WP8 map control provides no method for replacing the Bing base map.
i think this may suits you better:
void YourPage_Loaded(object sender, RoutedEventArgs e)
{
m_Map.ZoomLevel = 11;
m_Map.LayoutUpdated += m_Map_LayoutUpdated;
}
void m_Map_LayoutUpdated(object sender, EventArgs e)
{
if (!isRemoved)
{
RemoveOverlayTextBlock();
}
}
void RemoveOverlayTextBlock()
{
var textBlock = m_Map.DescendantsAndSelf.OfType<TextBlock>()
.SingleOrDefault(d => d.Text.Contains("Invalid Credentials") ||
d.Text.Contains("Unable to contact Server"));
if (textBlock != null)
{
var parentBorder = textBlock.Parent as Border;
if (parentBorder != null)
{
parentBorder.Visibility = Visibility.Collapsed;
}
isRemoved = true;
}
}
You have to include a class LinqToVisualTree witch can be downloaded from here.
And here is the original post
You can either handle the LoadingError event per instance or extend the Map control yourself as described in this post. You can then remove the layer than contains the error message so that it's not shown to the user.
public partial class CachedMap : Map
{
public CachedMap() : base()
{
base.LoadingError += (s, e) =>
{
base.RootLayer.Children.RemoveAt(5);
};
}
}
I know it's a very old thread, but anyways...
You can listen for LoadingError event as suggested #keyboardP, search for LoadingErrorMessage control in visual tree and simply hide it.
Map.LoadingError += MapOnLoadingError;
private void MapOnLoadingError(object sender, LoadingErrorEventArgs e)
{
var errorMessage = Map.FindChildOfType<LoadingErrorMessage>();
errorMessage.Visibility = Visibility.Collapsed;
}

Categories