Keyboard quickly hides and reappears on Android using Xamarin Forms - c#

We're trying to build a Chat App using Xamarin Forms, but we keep getting this annoying bug with the Android keyboard. Whenever the "Send" button is tapped, the focus on the Entry (text box for chatting) is lost and the keyboard disappears. That isn't what we wanted so we added this line to the TapGestureRecognizer:
messageEntry.Focus();
But for some reason, this doesn't happen fast enough, and often the keyboard goes down and immediately up again. This prevents users from quickly posting multiple message sequentially. Does anybody know how this can be fixed?

Thanks to the answer of #AdamKemp in this post, here is my solution. If the touch is within my EntryStackLayout (don't forget to create the empty custom renderer), then I don't dismiss the keyboard (which is what DispatchTouchEvent will do if CurrentFocus is EditText).
public class EditorAndButtonReproPage : ContentPage
{
public EditorAndButtonReproPage()
{
BackgroundColor = Color.Gray;
Padding = 50;
var editor = new Editor {HorizontalOptions = LayoutOptions.FillAndExpand};
var editorButton = new Button {Text = "OK", HorizontalOptions = LayoutOptions.End};
var editorLayout = new EntryStackLayout { Orientation = StackOrientation.Horizontal, Children = { editor, editorButton}, VerticalOptions = LayoutOptions.Start};
var entry = new ExtendedEntry { Placeholder = "Entry", HorizontalOptions = LayoutOptions.FillAndExpand };
var entryButton = new Button { Text = "OK", HorizontalOptions = LayoutOptions.End };
var entryLayout = new EntryStackLayout { Orientation = StackOrientation.Horizontal, Children = { entry, entryButton }, VerticalOptions = LayoutOptions.Start };
Content = new StackLayout {Children = {editorLayout, entryLayout}};
}
}
and in the MainActivity:
private bool _ignoreNewFocus;
public override bool DispatchTouchEvent(MotionEvent e)
{
var currentView = CurrentFocus;
var parent = currentView?.Parent?.Parent;
var entryStackLayout = parent as EntryStackLayout;
if (entryStackLayout != null)
{
var entryLayoutLocation = new int[2];
entryStackLayout.GetLocationOnScreen(entryLayoutLocation);
var x = e.RawX + entryStackLayout.Left - entryLayoutLocation[0];
var y = e.RawY + entryStackLayout.Top - entryLayoutLocation[1];
var entryStackLayoutRect = new Rectangle(entryStackLayout.Left, entryStackLayout.Top, entryStackLayout.Width, entryStackLayout.Height);
_ignoreNewFocus = entryStackLayoutRect.Contains(x, y);
}
var result = base.DispatchTouchEvent(e);
_ignoreNewFocus = false;
return result;
}
public override Android.Views.View CurrentFocus => _ignoreNewFocus ? null : base.CurrentFocus;

It'd be a bit of a hack, but you could spin off an async task that waits 50ms and then invokes the messageEntry.Focus() line on the main UI thread...

Related

Dynamically (programmatically) created tiles changing its isEnabled property via a timer

Hey all I am not sure why I am having such a hard time with this but I am needing to be able to change a tiles isEnabled property depending on if my program detects an internet connection or not.
The WPF code that I am using and needing to change is the following:
<dxlc:TileLayoutControl x:Name="tileLayoutControl1" Margin="150,63,153,57" Padding="5"
AllowAddFlowBreaksDuringItemMoving="False" AllowItemMoving="False" Orientation="Horizontal" ScrollBars="None"
TileClick="tileLayoutControl1_TileClick">
</dxlc:TileLayoutControl>
This gets populated by the code behind on startup:
private void createMenu()
{
List<String> menuIcons = new List<string>();
menuIcons.Add("gamesIcon.png");
menuIcons.Add("movieIcon.png");
menuIcons.Add("musicIcon.png");
menuIcons.Add("televisionIcon.png");
menuIcons.Add("youTubeIcon.png");
menuIcons.Add("androidIcon.png");
foreach (String item in menuIcons)
{
Image finalImage = new Image();
BitmapImage image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri("/img/" + item.ToString(), UriKind.Relative);
image.EndInit();
finalImage.Source = image;
image = null;
tileLayoutControl1.Children.Add(new Tile()
{
Content = finalImage,
Name = item.ToString().Replace(".png", ""),
Tag = item.ToString().Replace(".png", "").Replace(".jpg", ""),
Width = 255,
Height = 288,
IsEnabled = false,
Margin = new Thickness(0, 0, 50, 20),
Background = Brushes.Transparent,
BorderThickness = new Thickness(0, 0, 0, 0)
}
}
}
and finally the code that I have checking the internet connection within a timer:
private bool checkInternet()
{
try
{
Ping myPing = new Ping();
PingReply reply = myPing.Send("google.com", 1000, new byte[32], new PingOptions());
return (reply.Status == IPStatus.Success);
}
catch (Exception)
{
return false;
}
}
public firstWindow()
{
InitializeComponent();
var startTimeSpan = TimeSpan.Zero;
var periodTimeSpan = TimeSpan.FromMinutes(5);
var timer = new System.Threading.Timer((e) =>
{
bool hasInternet = checkInternet();
TileLayoutControl test = (TileLayoutControl)this.tileLayoutControl1.FindName("youTubeIcon");
test.IsEnabled = true;
tileLayoutControl1.RegisterName("youTubeIcon", this.isEnabled = true);
}, null, startTimeSpan, periodTimeSpan);
}
I am currently not getting any errors for the lines:
TileLayoutControl test = (TileLayoutControl)this.tileLayoutControl1.FindName("youTubeIcon");
test.IsEnabled = true;
tileLayoutControl1.RegisterName("youTubeIcon", this.isEnabled = true);
But test is null and RegisterName doesn't seem to change the isEnabled property when I try to click on the tile.
I've also read that using Command="{Binding ClickCommand}" may be my answer but that seems to only pertain to MVVM type of patterns which I am not using for this particle program.
I also know that checking the tileLayoutCibtril1 has a Children property that does contain my dynamically created icons but tileLayoutCibtril1.Children does not have a .FindName proeprty.
So what am I missing?
Try to call ApplyTemplate on TileLayoutControl after foreach statement. The template for TileLayoutControl has not yet been applied and therefore FindName returns null. ApplyTemplate builds the current template’s visual tree
foreach (String item in menuIcons)
{
...
}
tileLayoutControl1.ApplyTemplate();

Мy content disappears in order for a Google ad to appear on startup using Xamarin.iOS

I have the following problem. When I start the application, my content does not appear, and only the banner of Google Ad Mob appears.
Only the GPS permission message appears but not the content itself.
The code I use :
public MainPage()
{
InitializeComponent();
_restServiceForecast = new RestServiceForecast();
_ = DisplayHourlyForecastStartUp();
_ = ButtonClickedGPS();
co2lbl.Text = "CO2";
nolbl.Text = "NO";
no2lbl.Text = "NO2";
o3lbl.Text = "O3";
so2lbl.Text = "SO2";
pm2_5lbl.Text = "PM2_5";
pm10lbl.Text = "PM10";
nh3lbl.Text = "NH3";
frameFinePowders.IsVisible = false;
AdmobControl admobControl = new AdmobControl()
{
AdUnitId = AppConstants.BannerId
};
Label adLabel = new Label() { Text = "Ads will be displayed here!" };
Content = new StackLayout()
{
Children = { adLabel, admobControl }
};
this.Title = "Admob Page";
}
How do I make the banner appear under a certain frame that I want?

Shadow over CalendarDatePicker in Dialog

There is a shadow over the CalendarDatePicker flyout. This "effect" is probably connected to the Dialog which host the control. I attached the Dialog's function. Do you know how to remove it?
Image: https://ibb.co/ZJjqfWs
private async void CreateNewDocumentDialog() {
// Creating dialoge window
var textBlock1 = new TextBlock
{
Text = "Kérem adja meg az alábbi adatokat!",
FontSize = 16
};
var docName = new TextBox
{
Name = "docName",
Margin = new Thickness(0, 10, 0, 10),
PlaceholderText = "Documentum neve"
};
var docValidUntil = new CalendarDatePicker
{
Header = "Dokumentum érvényessége:",
Date = DateTime.Now.AddYears(4),
};
var contentPanel = new StackPanel();
contentPanel.Children.Add(textBlock1);
contentPanel.Children.Add(docName);
contentPanel.Children.Add(docValidUntil);
ContentDialog CreateNewDocumentDialog = new ContentDialog
{
Content = contentPanel,
PrimaryButtonText = "Új bejegyzés létrehozása",
CloseButtonText = "Mégse",
// Default button responds to Enter
DefaultButton = ContentDialogButton.Primary
};
// Waiting for user input
ContentDialogResult result = await CreateNewDocumentDialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
// Process user input if Create New button pressed/activated
if (docName.Text != "")
{
var tempDate = docValidUntil.Date;
DocumentList.Add(new Document(docName.Text, tempDate.Value.DateTime));
}
}
else
{
// Exit dialog if Cancel button pressed
}
}

Xamarin Custom Map Pin Renderers

Firstly I would like to disclose that I am a new developer and just starting out coding with Xamarin and C sharp.
Ok. So I am creating a custom renderer for the map pin in my application. I have been using the following sample code found here: https://github.com/xamarin/xamarin-forms-samples/tree/master/CustomRenderers/Map/Pin
As it currently stands I am trying to set the popup on the pins so that when I click on them they will either go to a page in the app or possibly have a popup come up with more details on that specific pin location. Currently when I click on the pin popup it takes me to a website.
Here is a screen shot of the app:
enter image description here
I have been looking at this code and Im trying to figure out if it would be possible to change the URL to a local page?
InitializeComponent();
var position1 = new Position(52.649005, -7.250712);
var position2 = new Position(52.648061, -7.252879);
var position3 = new Position(52.650519, -7.249260);
var position4 = new Position(52.652680, -7.244724);
var position5 = new Position(52.648061, -7.252879);
var position6 = new Position(52.648061, -7.252879);
var pin1 = new CustomPin
{
Type = PinType.Place,
Position = position1,
Label = "Butler House",
Address = "394 Pacific Ave, San Francisco CA",
Id = "test",
Url = "http://xamarin.com/about/"
};
var pin2 = new CustomPin
{
Type = PinType.Place,
Position = position2,
Label = "Talbots Tower",
Address = "394 Pacific Ave, San Francisco CA",
Id = "",
Url = "http://xamarin.com/about/"
};
In the CustomMapRender.cs file here is where I believe I need to be able to alter the CustomPin.URL to allow me to click and get local pages in the app itself.
protected override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
{
MKAnnotationView annotationView = null;
if (annotation is MKUserLocation)
return null;
var customPin = GetCustomPin(annotation as MKPointAnnotation);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
annotationView = mapView.DequeueReusableAnnotation(customPin.Id.ToString());
if (annotationView == null)
{
annotationView = new CustomMKAnnotationView(annotation, customPin.Id.ToString());
annotationView.Image = UIImage.FromFile("pin-red-10.png"); //pin is set by image not changable by colour attributes
annotationView.CalloutOffset = new CGPoint(0, 0);
annotationView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile("Ireland-icon.png"));
annotationView.RightCalloutAccessoryView = UIButton.FromType(UIButtonType.DetailDisclosure);
((CustomMKAnnotationView)annotationView).Id = customPin.Id.ToString();
((CustomMKAnnotationView)annotationView).Url = customPin.Url;
}
annotationView.CanShowCallout = true;
return annotationView;
Any help or insight on this would be much appreciated as Im going around in circles trying so many different things at present.
Thanks
Michael
GetViewForAnnotation method is for getting the MKAnnotationView (i.e., the callout that's displaying the details of the Pin).
What you need is the tap event of the MKMapView callout. Add an EventHandler to CalloutAccessoryControlTapped and handle the logic for navigation.
In the sample provided it would be this part
void OnCalloutAccessoryControlTapped(object sender, MKMapViewAccessoryTappedEventArgs e)
{
// handle navigation here instead of opening the webpage
var customView = e.View as CustomMKAnnotationView;
if (!string.IsNullOrWhiteSpace(customView.Url))
{
UIApplication.SharedApplication.OpenUrl(new Foundation.NSUrl(customView.Url));
}
}

Navigate to another ContentPage from async method before the async method returns a value

I need to start Another ContentPage before this async method returns value:
public class GettingCountry : ContentPage
{
public static List<string> CountriesList = new List<string>();
MainPage mainPage = new MainPage();
public async Task<List<RootObject>> FetchAsync(string url)
{
string jsonString;
using (var httpClient = new System.Net.Http.HttpClient())
{
var stream = await httpClient.GetStreamAsync(url);
StreamReader reader = new StreamReader(stream);
jsonString = reader.ReadToEnd();
}
var listOfCountries = new List<RootObject>();
var responseCountries = JArray.Parse(JObject.Parse(jsonString)["response"]["items"].ToString());
foreach (var countryInResponse in responseCountries)
{
var rootObject = new RootObject((int)countryInResponse["id"], (string)countryInResponse["title"]);
CountriesList.Add(rootObject.Title);
}
//I NEED TO NAVIGATE TO FillingPage() FROM HERE:
await Navigation.PushAsync(new FillingPage());
//await Navigation.PushModalAsync(new NavigationPage(new FillingPage()));
return listOfCountries;
}
The page that is need to be started is:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class FillingPage : ContentPage
{
public FillingPage ()
{
GettingCountry gettingCountry = new GettingCountry();
Label header = new Label
{
Text = "Заполните бланк",
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand,
TextColor = Color.Blue
};
Entry nameEntry = new Entry()
{
Placeholder = "Имя",
};
Entry surnameEntry = new Entry()
{
Placeholder = "Фамилия"
};
Picker countryPicker = new Picker()
{
Title = "Страна",
VerticalOptions = LayoutOptions.CenterAndExpand
};
foreach (string country in GettingCountry.CountriesList)
{
countryPicker.Items.Add(country);
}
SearchBar townSearchBar = new SearchBar()
{
Placeholder = "Город",
SearchCommand = new Command(() =>
{
})
};
SearchBar universitySearchBar = new SearchBar()
{
Placeholder = "Университет",
SearchCommand = new Command(() =>
{
})
};
Button myButton = new Button()
{
TextColor = Color.Green,
Text = "Выполнить",
FontSize = 22
};
// Accomodate iPhone status bar.
this.Padding = new Thickness(10, Device.OnPlatform(20, 0, 0), 10, 5);
// Build the page.
this.Content = new StackLayout
{
Children =
{
header,
nameEntry,
surnameEntry,
countryPicker,
townSearchBar,
universitySearchBar,
myButton
}
};
}
}
}
But this code await Navigation.PushAsync(new FillingPage()); works well only when I press a button. When I press a button the needed page starts well. But the same code inside a method does not work. I have debagged it. It goes to a FillingPage() but doesn`t launches it when I try to launch it from inside the async method.
This is likely a result of the operation not being performed on the main thread. Try wrapping your code like this:
Device.BeginInvokeOnMainThread(async () =>
{
await Navigation.PushAsync(new FillingPage());
}
Edit: After a private message, I learned that there was not enough information in the question to know the real issue. The application is calling FetchAsync in Application.OnStart and it's not part of the view hiearchy at all so navigation methods would not work. The following was provided:
protected override void OnStart ()
{
getCountry();
}
private async void getCountry()
{
var url = "...";
GettingCountry gettingCountry = new GettingCountry();
await gettingCountry.FetchAsync(url);
}
GettingCountry is a ContentPage being used like some kind of data access class and it's not currently part of the UI as MainPage is set to something else. A quick hack would be something more like:
private async void getCountry()
{
var url = "...";
GettingCountry gettingCountry = new GettingCountry();
var data = await gettingCountry.FetchAsync(url);
await MainPage.Navigation.PushAsync(new FillingPage(data));
}
I would suggest two further areas to look at improving.
Consider refactoring GettingCountry as it does not need to be a ContentPage.
Investigate an alternative calling so that async void is not used.

Categories

Resources