I am learning a new way to show validation errors and I am having very weird issue with forms, when I click on Add button the controls that are pushed below the screen disappears or becomes transparent.
here is my form https://i.stack.imgur.com/LIrD6.jpg
when I click on add button this happens https://i.stack.imgur.com/PWZLZ.jpg (Note: this is an extended screenshot and my screen ends on phone number control. when I scroll remaining blank space is for Add button)
when I click/focus on any control the screen is fixes by itself https://i.stack.imgur.com/Exw4z.jpg
I am using shell app and have no idea whats going wrong here. Any help is appreciated.
here is my code
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="PROOF.Views.Patient.NewPatientPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
xmlns:vm="clr-namespace:PROOF.ViewModels"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
Title="{Binding PageTitle}"
ios:Page.UseSafeArea="true"
x:DataType="vm:NewPatientViewModel"
Shell.TabBarIsVisible="False"
Visual="Material">
<ContentPage.Resources>
<ResourceDictionary>
<xct:InvertedBoolConverter x:Key="InvertedBoolConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<ScrollView>
<Grid Margin="10">
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Label Style="{DynamicResource HeadingLabel}" Text="Patient Info" />
<Entry
Placeholder="First Name"
Style="{DynamicResource DefaultEntry}"
Text="{Binding PatientFirstName}">
<Entry.Behaviors>
<xct:TextValidationBehavior
Flags="ValidateOnValueChanging"
IsValid="{Binding PatientFirstNameValid}"
RegexPattern="^(?!\s*$). " />
</Entry.Behaviors>
<Entry.Triggers>
<DataTrigger
Binding="{Binding PatientFirstNameValid}"
TargetType="Entry"
Value="False">
<Setter Property="PlaceholderColor" Value="{StaticResource Error}" />
</DataTrigger>
</Entry.Triggers>
</Entry>
<Label
IsVisible="{Binding PatientFirstNameValid, Converter={StaticResource InvertedBoolConverter}}"
Style="{DynamicResource ValidationLabel}"
Text="First Name is required." />
<Entry
Placeholder="Last Name"
Style="{DynamicResource DefaultEntry}"
Text="{Binding PatientLastName}">
<Entry.Behaviors>
<xct:TextValidationBehavior
Flags="ValidateOnValueChanging"
IsValid="{Binding PatientLastNameValid}"
RegexPattern="^(?!\s*$). " />
</Entry.Behaviors>
<Entry.Triggers>
<DataTrigger
Binding="{Binding PatientLastNameValid}"
TargetType="Entry"
Value="False">
<Setter Property="PlaceholderColor" Value="{StaticResource Error}" />
</DataTrigger>
</Entry.Triggers>
</Entry>
<Label
IsVisible="{Binding PatientLastNameValid, Converter={StaticResource InvertedBoolConverter}}"
Style="{DynamicResource ValidationLabel}"
Text="Last Name is required." />
<Entry
Keyboard="Email"
Placeholder="Email"
Style="{DynamicResource DefaultEntry}"
Text="{Binding PatientEmail}">
<Entry.Behaviors>
<xct:EmailValidationBehavior
DecorationFlags="Trim"
Flags="ValidateOnValueChanging"
IsValid="{Binding PatientEmailValid}" />
</Entry.Behaviors>
<Entry.Triggers>
<DataTrigger
Binding="{Binding PatientEmailValid}"
TargetType="Entry"
Value="False">
<Setter Property="PlaceholderColor" Value="{StaticResource Error}" />
</DataTrigger>
</Entry.Triggers>
</Entry>
<Label
IsVisible="{Binding PatientEmailValid, Converter={StaticResource InvertedBoolConverter}}"
Style="{DynamicResource ValidationLabel}"
Text="Email is required." />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0">
<Picker
Title="Year"
ItemsSource="{Binding DobYears}"
SelectedItem="{Binding SelectedDobYear}"
Style="{DynamicResource DefaultPicker}">
<Picker.Behaviors>
<xct:TextValidationBehavior
Flags="ValidateOnValueChanging"
IsValid="{Binding SelectedDobYearValid}"
RegexPattern="^(?!\s*$). " />
</Picker.Behaviors>
<Picker.Triggers>
<DataTrigger
Binding="{Binding SelectedDobYearValid}"
TargetType="Picker"
Value="False">
<Setter Property="TitleColor" Value="{StaticResource Error}" />
</DataTrigger>
</Picker.Triggers>
</Picker>
<Label
IsVisible="{Binding SelectedDobYearValid, Converter={StaticResource InvertedBoolConverter}}"
Style="{DynamicResource ValidationLabel}"
Text="Year is required." />
</StackLayout>
<StackLayout Grid.Column="1">
<Picker
Title="Month"
ItemsSource="{Binding DobMonths}"
SelectedItem="{Binding SelectedDobMonth}"
Style="{DynamicResource DefaultPicker}">
<Picker.Behaviors>
<xct:TextValidationBehavior
Flags="ValidateOnValueChanging"
IsValid="{Binding SelectedDobMonthValid}"
RegexPattern="^(?!\s*$). " />
</Picker.Behaviors>
<Picker.Triggers>
<DataTrigger
Binding="{Binding SelectedDobMonthValid}"
TargetType="Picker"
Value="False">
<Setter Property="TitleColor" Value="{StaticResource Error}" />
</DataTrigger>
</Picker.Triggers>
</Picker>
<Label
IsVisible="{Binding SelectedDobMonthValid, Converter={StaticResource InvertedBoolConverter}}"
Style="{DynamicResource ValidationLabel}"
Text="Year is required." />
</StackLayout>
<StackLayout Grid.Column="2">
<Picker
Title="Day"
ItemsSource="{Binding DobDays}"
SelectedItem="{Binding SelectedDobDay}"
Style="{DynamicResource DefaultPicker}">
<Picker.Behaviors>
<xct:TextValidationBehavior
Flags="ValidateOnValueChanging"
IsValid="{Binding SelectedDobDayValid}"
RegexPattern="^(?!\s*$). " />
</Picker.Behaviors>
<Picker.Triggers>
<DataTrigger
Binding="{Binding SelectedDobDayValid}"
TargetType="Picker"
Value="False">
<Setter Property="TitleColor" Value="{StaticResource Error}" />
</DataTrigger>
</Picker.Triggers>
</Picker>
<Label
IsVisible="{Binding SelectedDobDayValid, Converter={StaticResource InvertedBoolConverter}}"
Style="{DynamicResource ValidationLabel}"
Text="Year is required." />
</StackLayout>
</Grid>
<Label Style="{DynamicResource HeadingLabel}" Text="Guardian Info" />
<Entry
Placeholder="First Name"
Style="{DynamicResource DefaultEntry}"
Text="{Binding GuardianFirstName}">
<Entry.Behaviors>
<xct:TextValidationBehavior
Flags="ValidateOnValueChanging"
IsValid="{Binding GuardianFirstNameValid}"
RegexPattern="^(?!\s*$). " />
</Entry.Behaviors>
<Entry.Triggers>
<DataTrigger
Binding="{Binding GuardianFirstNameValid}"
TargetType="Entry"
Value="False">
<Setter Property="PlaceholderColor" Value="{StaticResource Error}" />
</DataTrigger>
</Entry.Triggers>
</Entry>
<Label
IsVisible="{Binding GuardianFirstNameValid, Converter={StaticResource InvertedBoolConverter}}"
Style="{DynamicResource ValidationLabel}"
Text="First Name is required." />
<Entry
Placeholder="Last Name"
Style="{DynamicResource DefaultEntry}"
Text="{Binding GuardianLastName}">
<Entry.Behaviors>
<xct:TextValidationBehavior
Flags="ValidateOnValueChanging"
IsValid="{Binding GuardianLastNameValid}"
RegexPattern="^(?!\s*$). " />
</Entry.Behaviors>
<Entry.Triggers>
<DataTrigger
Binding="{Binding GuardianLastNameValid}"
TargetType="Entry"
Value="False">
<Setter Property="PlaceholderColor" Value="{StaticResource Error}" />
</DataTrigger>
</Entry.Triggers>
</Entry>
<Label
IsVisible="{Binding GuardianLastNameValid, Converter={StaticResource InvertedBoolConverter}}"
Style="{DynamicResource ValidationLabel}"
Text="Last Name is required." />
<Entry
Keyboard="Email"
Placeholder="Email"
Style="{DynamicResource DefaultEntry}"
Text="{Binding GuardianEmail}">
<Entry.Behaviors>
<xct:EmailValidationBehavior
DecorationFlags="Trim"
Flags="ValidateOnValueChanging"
IsValid="{Binding GuardianEmailValid}" />
</Entry.Behaviors>
<Entry.Triggers>
<DataTrigger
Binding="{Binding GuardianEmailValid}"
TargetType="Entry"
Value="False">
<Setter Property="PlaceholderColor" Value="{StaticResource Error}" />
</DataTrigger>
</Entry.Triggers>
</Entry>
<Label
IsVisible="{Binding GuardianEmailValid, Converter={StaticResource InvertedBoolConverter}}"
Style="{DynamicResource ValidationLabel}"
Text="Email is required." />
<Entry
Keyboard="Numeric"
Placeholder="Phone Number"
Style="{DynamicResource DefaultEntry}"
Text="{Binding PhoneNumber}">
<Entry.Behaviors>
<xct:CharactersValidationBehavior
Flags="ValidateOnValueChanging"
IsValid="{Binding PhoneNumberValid}"
MinimumCharacterCount="10" />
</Entry.Behaviors>
<Entry.Triggers>
<DataTrigger
Binding="{Binding PhoneNumberValid}"
TargetType="Entry"
Value="False">
<Setter Property="PlaceholderColor" Value="{StaticResource Error}" />
</DataTrigger>
</Entry.Triggers>
</Entry>
<Label
IsVisible="{Binding PhoneNumberValid, Converter={StaticResource InvertedBoolConverter}}"
Style="{DynamicResource ValidationLabel}"
Text="Phone Number is required." />
<StackLayout IsVisible="{Binding IsAddViewVisible}">
<Button
Padding="5"
Command="{Binding AddCommand}"
Style="{x:StaticResource DefaultButton}"
Text="Add"
TextColor="White" />
</StackLayout>
<StackLayout Padding="0,10">
<StackLayout IsVisible="{Binding IsEditViewVisible}" Orientation="Horizontal">
<Button
Padding="5"
BackgroundColor="{x:StaticResource Error}"
Command="{Binding DeleteCommand}"
HorizontalOptions="FillAndExpand"
Text="Delete" />
<Button
Padding="5"
Command="{Binding EditCommand}"
HorizontalOptions="FillAndExpand"
Style="{x:StaticResource DefaultButton}"
Text="Edit"
TextColor="White" />
</StackLayout>
</StackLayout>
</StackLayout>
</Grid>
</ScrollView>
</ContentPage.Content>
</ContentPage>
View Model
namespace PROOF.ViewModels
{
[QueryProperty(nameof(SelectedPatientId), "SelectedPatientId")]
public class NewPatientViewModel : BaseViewModel
{
Page CurrentContext => Application.Current.MainPage;
public NewPatientViewModel()
{
AddCommand = new AsyncCommand(() => AddAsync(), allowsMultipleExecutions: false);
EditCommand = new AsyncCommand(() => EditAsync(), allowsMultipleExecutions: false);
DeleteCommand = new AsyncCommand(() => DeleteAsync(), allowsMultipleExecutions: false);
SetupDaysPicker();
SetupYearsPicker();
}
async Task AddAsync()
{
if (ValidateForm())
{
// api call
}
}
async Task EditAsync()
{
if (ValidateForm())
{
// api call
}
}
private bool ValidateForm()
{
PatientFirstNameValid = !string.IsNullOrWhiteSpace(PatientFirstName);
PatientLastNameValid = !string.IsNullOrWhiteSpace(PatientLastName);
SelectedDobMonthValid = SelectedDobMonth > 0;
SelectedDobDayValid = SelectedDobDay > 0;
SelectedDobYearValid = SelectedDobYear > 0;
GuardianFirstNameValid = !string.IsNullOrWhiteSpace(GuardianFirstName);
GuardianLastNameValid = !string.IsNullOrWhiteSpace(GuardianLastName);
//GuardianEmailValid = !string.IsNullOrWhiteSpace(GuardianEmail);
var isValid =
PatientFirstNameValid && PatientLastNameValid && PatientEmailValid &&
SelectedDobYearValid && SelectedDobMonthValid && SelectedDobDayValid &&
GuardianFirstNameValid && GuardianLastNameValid && GuardianEmailValid && PhoneNumberValid;
if (string.IsNullOrWhiteSpace(PatientEmail) && string.IsNullOrWhiteSpace(GuardianEmail) && string.IsNullOrWhiteSpace(PhoneNumber))
{
PatientEmailValid = false;
GuardianEmailValid = false;
PhoneNumberValid = false;
isValid = false;
}
return isValid;
}
async Task DeleteAsync()
{
// api call
}
private void UpdateSignUpButton()
{
ValidateForm();
}
async void LoadPatient(string id)
{
SelectedPatient = // api call to get patient
PatientFirstName = SelectedPatient.FirstName;
PatientLastName = SelectedPatient.LastName;
PageTitle = "Edit Patient";
IsEditViewVisible = true;
}
private string _pageTitle;
private string _patientFirstName;
private string _guardianFirstName;
private string _patientLastName;
private string _guardianLastName;
private string _title;
private string _patientEmail;
private string _guardianEmail;
private string _guardianPhoneNumber;
private bool _patientFirstNameValid;
private bool _guardianFirstNameValid;
private bool _patientLastNameValid;
private bool _guardianLastNameValid;
private bool _titleValid;
private bool _patientEmailValid;
private bool _guardianEmailValid;
private bool _phoneNumberValid;
private bool _isAddEnabled;
private bool _isEditEnabled;
private bool _isEditViewVisible;
private bool _isAddViewVisible;
private Patient _selectedPatient;
public AsyncCommand AddCommand { get; }
public AsyncCommand EditCommand { get; }
public AsyncCommand DeleteCommand { get; }
public string PatientId { get; set; }
public string PageTitle
{
get => _pageTitle;
set => SetProperty(ref _pageTitle, value);
}
public string GuardianFirstName
{
get => _guardianFirstName;
set
{
SetProperty(ref _guardianFirstName, value);
}
}
public string GuardianLastName
{
get => _guardianLastName;
set
{
SetProperty(ref _guardianLastName, value);
}
}
public string GuardianEmail
{
get => _guardianEmail;
set => SetProperty(ref _guardianEmail, value);
}
public string PatientFirstName
{
get => _patientFirstName;
set
{
SetProperty(ref _patientFirstName, value);
}
}
public string PatientLastName
{
get => _patientLastName;
set
{
SetProperty(ref _patientLastName, value);
}
}
public string PatientEmail
{
get => _patientEmail;
set => SetProperty(ref _patientEmail, value);
}
public string PhoneNumber
{
get => _guardianPhoneNumber;
set
{
SetProperty(ref _guardianPhoneNumber, value);
}
}
public bool IsAddViewVisible
{
get => _isAddViewVisible;
set => SetProperty(ref _isAddViewVisible, value);
}
public bool IsAddEnabled
{
get => _isAddEnabled;
set => SetProperty(ref _isAddEnabled, value);
}
public bool IsEditEnabled
{
get => _isEditEnabled;
set => SetProperty(ref _isEditEnabled, value);
}
public bool IsEditViewVisible
{
get => _isEditViewVisible;
set => SetProperty(ref _isEditViewVisible, value);
}
public bool GuardianFirstNameValid
{
get => _guardianFirstNameValid;
set
{
SetProperty(ref _guardianFirstNameValid, value);
}
}
public bool GuardianLastNameValid
{
get => _guardianLastNameValid;
set
{
SetProperty(ref _guardianLastNameValid, value);
}
}
public bool GuardianEmailValid
{
get => _guardianEmailValid;
set
{
SetProperty(ref _guardianEmailValid, value);
}
}
public bool PatientFirstNameValid
{
get => _patientFirstNameValid;
set
{
SetProperty(ref _patientFirstNameValid, value);
}
}
public bool PatientLastNameValid
{
get => _patientLastNameValid;
set
{
SetProperty(ref _patientLastNameValid, value);
}
}
public bool PatientEmailValid
{
get => _patientEmailValid;
set
{
SetProperty(ref _patientEmailValid, value);
}
}
public bool PhoneNumberValid
{
get => _phoneNumberValid;
set
{
SetProperty(ref _phoneNumberValid, value);
}
}
public Patient SelectedPatient
{
get => _selectedPatient;
set => SetProperty(ref _selectedPatient, value);
}
public string SelectedPatientId
{
set
{
if (string.IsNullOrWhiteSpace(value))
{
PageTitle = "Add Patient";
IsAddViewVisible = true;
}
else
{
LoadPatient(value);
}
}
}
private List<int> _dobYears;
private List<Month> _dobMonths = new List<Month>(Enum.GetValues(typeof(Month)).OfType<Month>().ToList());
private List<int> _dobDays;
private int _selectedDobYear;
private bool _selectedDobYearValid;
private Month _selectedDobMonth;
private bool _selectedDobMonthValid;
private int _selectedDobDay;
private bool _selectedDobDayValid;
private bool _isDobValid;
public List<int> DobDays
{
get => _dobDays;
set => SetProperty(ref _dobDays, value);
}
public List<Month> DobMonths
{
get => _dobMonths;
}
public List<int> DobYears
{
get => _dobYears;
set => SetProperty(ref _dobYears, value);
}
public int SelectedDobYear
{
get => _selectedDobYear;
set => SetProperty(ref _selectedDobYear, value);
}
public bool SelectedDobYearValid
{
get => _selectedDobYearValid;
set => SetProperty(ref _selectedDobYearValid, value);
}
public Month SelectedDobMonth
{
get => _selectedDobMonth;
set => SetProperty(ref _selectedDobMonth, value);
}
public bool SelectedDobMonthValid
{
get => _selectedDobMonthValid;
set => SetProperty(ref _selectedDobMonthValid, value);
}
public int SelectedDobDay
{
get => _selectedDobDay;
set => SetProperty(ref _selectedDobDay, value);
}
public bool SelectedDobDayValid
{
get => _selectedDobDayValid;
set => SetProperty(ref _selectedDobDayValid, value);
}
public bool IsDobValid
{
get => _isDobValid;
set => SetProperty(ref _isDobValid, value);
}
private void SetupDaysPicker()
{
DobDays = new List<int>();
List<int> days = new List<int>();
for (int i = 1; i <= 31; i )
{
string day = i.ToString("D2");
days.Add(int.Parse(day));
}
DobDays = days;
}
private void SetupYearsPicker()
{
DobYears = new List<int>();
List<int> years = new List<int>();
for (int i = DateTime.Now.Year; i >= 1900; i--)
{
string year = i.ToString("D4");
years.Add(int.Parse(year));
}
DobYears = years;
}
private DateTime FormatDateOfBirth()
{
return new DateTime(SelectedDobYear, (int)SelectedDobMonth, SelectedDobDay);
}
}
}
CodePudding user response:
You can add a StackLayout out of the ScrollView.
Please refer to the following code:
<!--add a StackLayout out of the ScrollView-->
<StackLayout>
<ScrollView VerticalOptions="EndAndExpand">
<!--other code-->
</ScrollView>
</StackLayout>