Entry FindByName getting always null in xamarin forms mvvm - xamarin

I have a button
and i want to focus an entry on button click
so my button command is:
public Command FocusPassword
{
get
{
return new Command(() =>
{
Entry myEntry= Application.Current.MainPage.FindByName<Entry>("pEntry");
passwordEntry.Focus();
});
}
}
myEntry always returning null,so how to solve this?

I simply solve this:
Page currentPage = Navigation.NavigationStack.LastOrDefault();
Entry passwordEntry = currentPage.FindByName<Entry>("PassEntry");
passwordEntry.Focus();

Is it myEntry that is null, or passwordEntry? You are assigning the returned object to myEntry but then trying to set the focus on passwordEntry:
Entry myEntry= Application.Current.MainPage.FindByName<Entry>("pEntry");
passwordEntry.Focus();
And yes, make sure that the Entry does have the x:Name set to pEntry, e.g.:
<Entry x:Name="pEntry" ... />

You could try to use my demo as well.
MainPage.xaml (Do not forget to BindingContext)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:EntryDemo"
x:Class="EntryDemo.MainPage">
<StackLayout>
<Grid Margin="10">
<Grid.BindingContext>
<local:BindingViewModel/>
</Grid.BindingContext>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Entry
Grid.Row="0"
HorizontalOptions="CenterAndExpand"
Text="account"
x:Name="accountEntry"
/>
<Entry
Text="password"
Grid.Row="1"
HorizontalOptions="CenterAndExpand"
x:Name="pEntry"
/>
<Button
Grid.Row="2"
HorizontalOptions="CenterAndExpand"
x:Name="MyButton"
Text="FocusEntry "
Command="{Binding FocusCommand}"
/>
</Grid>
</StackLayout>
</ContentPage>
You should run FindByName in execute method
BindingViewModel.cs
public class BindingViewModel: BindableObject
{
public BindingViewModel()
{
FocusCommand = new Command(
execute: () =>
{
Entry accountEntry = Application.Current.MainPage.FindByName<Entry>("accountEntry");
Entry myEntry = Application.Current.MainPage.FindByName<Entry>("pEntry");
accountEntry.Focus();
}
);
}
public ICommand FocusCommand { private set; get; }
}

Related

FindByName control inside another control on ContentPage in Xamarin

I want to set focus on SearchBar when he appears. The problem is that SearchBar is placed inside of popup view and I need to access him from ViewModel.
In standard way I would use
Xamarin.Forms.SearchBar tmp_SearchBar = this.Page.FindByName("fro_SearchBar_NewItem") as Xamarin.Forms.SearchBar;
but that's not working anymore.
Here is XAML
<sfPopup:SfPopupLayout x:Name="fro_Popup_NewItem" Opened="fro_Popup_NewItem_Opened" Grid.Row="1" HorizontalOptions="Center" VerticalOptions="Center" BackgroundColor="Black">
<sfPopup:SfPopupLayout.PopupView>
<sfPopup:PopupView BackgroundColor="Black" WidthRequest ="400" HeightRequest ="100" ShowFooter="False" ShowHeader="False">
<sfPopup:PopupView.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!--Search bar-->
<Grid Grid.Row="0" HorizontalOptions="Center" VerticalOptions="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<SearchBar x:Name="fro_SearchBar_NewItem"
Grid.Column="0"
Text="{Binding SearchText_Popup, Mode=TwoWay}"
SearchCommand="{Binding SearchCommand}"
Placeholder="Find"
CancelButtonColor="White"
TextColor="White"
PlaceholderColor="Gray"/>
</Grid>
</Grid>
</DataTemplate>
</sfPopup:PopupView.ContentTemplate>
</sfPopup:PopupView>
</sfPopup:SfPopupLayout.PopupView>
</sfPopup:SfPopupLayout>
Nested FindByName doesn't work either.
Syncfusion.XForms.PopupLayout.SfPopupLayout tmp_Popup = _Page.FindByName("fro_Popup_NewItem") as Syncfusion.XForms.PopupLayout.SfPopupLayout;
Xamarin.Forms.SearchBar tmp_SearchBar = tmp_Popup.FindByName("fro_SearchBar_NewItem") as Xamarin.Forms.SearchBar;
Thanks
You can use Task to open a new thread to complete this operation in code-behind.
Xaml:
<SearchBar x:Name="fro_SearchBar_NewItem" Placeholder="...." BackgroundColor="White"/>
Code behind:
public partial class PagePop : Popup
{
public PagePop()
{
InitializeComponent();
Task.Run(() => myTask());//Create and start the thread
}
private void myTask()
{
Thread.Sleep(300);
fro_SearchBar_NewItem.Focus();
}
}
It seems that this wasn't so far from right direction, neither of the comments actually.
Syncfusion.XForms.PopupLayout.SfPopupLayout tmp_Popup = _Page.FindByName("fro_Popup_NewItem") as Syncfusion.XForms.PopupLayout.SfPopupLayout;
Xamarin.Forms.SearchBar tmp_SearchBar = tmp_Popup.FindByName("fro_SearchBar_NewItem") as Xamarin.Forms.SearchBar;
but I found the one which works and I don't have to create separate XAML for Popup.
private void fro_Popup_NewItem_Opened(object sender, EventArgs e)
{
try
{
var nativeObject = (object)fro_Popup_NewItem.GetType().GetRuntimeProperties().FirstOrDefault(x => x.Name.Equals("NativeObject")).GetValue(fro_Popup_NewItem);
var formsPopupviewContentTemplate = nativeObject.GetType().GetRuntimeFields().FirstOrDefault(x => x.Name.Equals("formsPopupViewContentTemplate")).GetValue(nativeObject);
var SearchBar = (formsPopupviewContentTemplate as Grid).FindByName<SearchBar>("fro_SearchBar_NewItem");
SearchBar?.Focus();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Thanks for help, everyone.

Xamarin Forms TemplateBinding

I understand XAML doesn't really have a facility to extend (inherit) one page from another, but I have seen others use ControlTemplates to achieve this effect.
I am having trouble getting a Binding to work in this scenario. In my example, I have a base ContentPage extended by MainPage. Also, I am using FreshMVVM, so that is why the view model is minimal as FreshMVVM handles all the plumbing of property change notifications.
When the app runs, the label should get the value "Xamarin Forms Header" initialized in the MainPageModel.
I have the fully runnable source on github here, but here's the code. Can someone see what the issue is?
MainPage.xaml
<local:PageBase xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SampleApp"
x:Class="SampleApp.MainPage">
<StackLayout>
</StackLayout>
</local:PageBase>
MainPageModel.xaml
public class MainPageModel : FreshBasePageModel
{
public MainPageModel()
{
LabelText = "Xamarin Forms Header";
}
public string LabelText { get; set; }
}
PageBase.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SampleApp.PageBase">
<ContentPage.ControlTemplate>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Text="{Binding LabelText}" />
<ContentPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</ContentPage.ControlTemplate>
</ContentPage>
you could try to change like this:
MainPage.xaml :
<?xml version="1.0" encoding="utf-8" ?>
<local:PageBase xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SampleApp"
x:Class="SampleApp.MainPage"
LabelText ="{Binding LabelText} "
>
<StackLayout>
</StackLayout>
</local:PageBase>
PageBase.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="SampleApp.PageBase">
<ContentPage.ControlTemplate>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Text="{TemplateBinding LabelText}" />
<ContentPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</ContentPage.ControlTemplate>
</ContentPage>
in your PageBase.xaml.cs:
public partial class PageBase : ContentPage
{
public static readonly BindableProperty LabelTextProperty = BindableProperty.Create("LabelText", typeof(string), typeof(PageBase), "Control Template Demo App");
public string LabelText
{
get { return (string)GetValue(LabelTextProperty); }
}
public PageBase ()
{
InitializeComponent ();
}
}
you could refer to TemplateBinding
TemplateBinding doesn't bind to the View Model so whole code is fundamentally wrong, please read about the topic https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/control-templates/template-binding
Also of note, this would likely work if you used Binding instead of TemplateBinding, but I don't think it makes sense to do that as said something is fundamentally wrong.

How to pass a List to a model and later on Binding to Grid

How to get the complete list of players to display on Home Page, below the line of code displays a single record
var details = (from x in conn.Table<PlayerDetails>() select x).ToList();
Is this problem with my code or Home.xaml ? I have tried
playermodel = details[0];
playermodel = details[1];
playermodel = details[2];
but still gives the only a single record.
My Home.xaml given below;
<?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="soccerapp.Home" BackgroundColor="White" Title="Home">
<ContentPage.Content>
<Grid x:Name="gridDisplay" Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Use Grid.Column to specify column -->
<Label Grid.Row="0" Grid.Column="0" Text="{Binding FullName}" BackgroundColor="#92f459"/>
<Label x:Name="HomeLabel" Text="Home Page is here" TextColor="White" Grid.Row="0" Grid.Column="1"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" FontSize="Small"></Label>
</Grid>
</ContentPage.Content>
</ContentPage>
Below is my Home.xaml.cs
namespace soccerapp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Home : ContentPage
{
public SQLiteConnection conn = null;
public Home(string parameter1)
{
InitializeComponent();
conn = DependencyService.Get<Isqlite>().GetConnection();
conn.CreateTable<PlayerDetails>();
DisplayDetails();
}
public void DisplayDetails()
{
var details = (from x in conn.Table<PlayerDetails>() select x).ToList();
PlayerDetails playermodel = details[0];
gridDisplay.BindingContext = playermodel;
}
}
}
I changed your xaml code and use collectionView to achieve your requirement.
In your Home.xaml,I use a collectionView to show data of players, you can Add other labels to show other data in DataTemplate. If you want to change the number of items per line, just change the value of Span:
<ContentPage.Resources>
<DataTemplate x:Key="playerTemplate">
<ContentView>
<StackLayout Margin="10,10" BackgroundColor="LightGreen">
<Label Text="{Binding FullName}" />
<Label Text="{Binding Mobile}" />
<!--Add other labels here to show want you want to show-->
</StackLayout>
</ContentView>
</DataTemplate>
</ContentPage.Resources>
<StackLayout Margin="20">
<CollectionView x:Name="collectionview"
ItemTemplate="{StaticResource playerTemplate}" >
<!--span here decides the number of items shows in one line. Now is 3 items one line-->
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="3" />
</CollectionView.ItemsLayout>
</CollectionView>
</StackLayout>
In your code behind, every time you get data, set it to the itemSource and it will change in the collectionView:
public partial class Home : ContentPage
{
public SQLiteConnection conn = null;
public PlayerDetails playermodel;
ObservableCollection<PlayerDetails> players;
public Home(string parameter1)
{
InitializeComponent();
conn = DependencyService.Get<Isqlite>().GetConnection();
conn.CreateTable<PlayerDetails>();
players = new ObservableCollection<PlayerDetails>();
collectionview.ItemsSource = players;
DisplayDetails();
}
public void DisplayDetails()
{
List<PlayerDetails> details = (from x in conn.Table<PlayerDetails>() select x).ToList();
for (int i = 0; i < details.Count; i++)
{
players.Add(details[i]);
}
}
}
Refer: collectionview
Here is the result:
Update:
CollectionView is available in the Xamarin.Forms 4.0 pre-releases.
However, it is currently experimental and can only be used by adding
the following line of code to your AppDelegate class on iOS, or to
your MainActivity class on Android, before calling Forms.Init:
Forms.SetFlags("CollectionView_Experimental");
You can use for this the Flex-Layout
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/flex-layout
https://devblogs.microsoft.com/xamarin/adaptive-mobile-designs-with-flexlayout/

Get the click command from template to the view model in xamarin

In the template there is a button and created as below called x:Name="cartbutton"
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
x:Class="FormsApplication.Templates.ListItemTemplate"
xmlns:controls="clr-namespace:FormsControls.Base;assembly=FormsControls.Base"
ColumnSpacing="0" RowSpacing="0" HeightRequest="350" Padding="8,0,8,8" x:Name="ListItemTempPage">
<Grid.RowDefinitions>
<RowDefinition Height="300" />
<RowDefinition Height="30" />
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<controls:RoundedBoxView Grid.RowSpan="2" BackgroundColor="White" BorderColor="Silver" CornerRadius="8" BorderThickness="2"/>
<ffimageloading:CachedImage Grid.RowSpan="2" Source="{Binding ImageSource}" Aspect="AspectFill" DownsampleToViewSize="False"/>
<Button x:Name="cartbutton" Grid.Row="0" HorizontalOptions="End" VerticalOptions="Start" Image="lowim.png" BackgroundColor="Transparent" Margin="0,5,5,0" Command="{Binding ParentContext.Itemtapped, Source={x:Reference ListItemTempPage}}" CommandParameter="{Binding .}"/>
</Grid>
and the parent page called by the reference the template as below
<ListView x:Name="listView" Grid.Row="1" Margin="0,8,0,0"
HasUnevenRows="true" ItemsSource="{Binding FoodItems}"
SeparatorColor="Transparent" SeparatorVisibility="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<templates:ListItemTemplate ParentContext="{Binding BindingContext, Source={x:Reference ListItemPage}}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<controls:EventToCommandBehavior EventName="ItemTapped" Command="{Binding ListItemTappedCommand}" CommandParameter="{x:Reference listView}"/>
</ListView.Behaviors>
</ListView>
finally the viewmodel goes with the icommand as below
public SelectItemViewModel(IAppContext context, IRestService restService) : base(context)
{
_restService = restService;
Title = "Category Products";
ListItemTappedCommand = new Command<ListView>(OnListItemTappedCommand);
PageAppearingCommand = new Command(OnPageAppearingCommand);
Itemtapped = new Command(OnItemtapped);
}
public ICommand Itemtapped { get; }
private void OnItemtapped()
{
Navigator.PushAsync<SelectItemViewModel>();
}
so the probelm is when compiling there is an error saying that
No property, bindable property, or event found for 'ParentContext', or mismatching type between value and property.
so basically saying that i am unable to give the button command ( in the template page) to trigger in the viewmodel properly, help will be appreciated to figure this out, thanks.
I'd introduce a AddToCartCommand in your child control, which is invoked if the button is clicked.
// in your ListItemTemplate.xaml.cs
public static BindableProperty AddToCartCommandProperty = BindableProperty.Create(/* ... */);
public ICommand AddToCartCommand
{
get => (ICommand)GetValue(AddToCartCommandProperty);
set => SetValue(AddToCartCommandProperty, value);
}
public void Button_OnClick(object sender, EventArgs e)
{
this.AddToCartCommand?.Execute(null);
}
To bind to this from your parent view, you'll need a reference to that parent view with x:Name="Page" (or however you'd like to name your page) and then use a {Binding Source={x:Reference Page}, Path=BindingContext.Itemtapped}
<ContentPage ... x:Name="Page"> <!-- I have elided attributes that are not relevant to my answer -->
<!-- Elided -->
<ListView ...>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<templates:ListItemTemplate AddToCartCommand="{Binding Source={x:Reference Page}, Path=BindingContext.Itemtapped}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<!-- Elided -->
</ListView>
<!-- Elided -->
</ContentPage>

Prism and Control Templates

I'm trying to rewrite the sample "SimpleThemeWithTemplateBinding" that uses ControlTemplates, with Prism.
I also added a simple
HeaderText = "changed";
in the button to change the HeaderText in the ControlTemplate, and it works, in the original sample.
So I copied the template in my app.xaml:
<ControlTemplate x:Key="TealTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.1*" />
<RowDefinition Height="0.8*" />
<RowDefinition Height="0.1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.05*" />
<ColumnDefinition Width="0.95*" />
</Grid.ColumnDefinitions>
<BoxView Grid.ColumnSpan="2" Color="Teal" />
<Label Grid.Column="1" Text="{TemplateBinding BindingContext.HeaderText}" TextColor="White" VerticalOptions="Center" />
<ContentPresenter Grid.Row="1" Grid.ColumnSpan="2" />
<BoxView Grid.Row="2" Grid.ColumnSpan="2" Color="Teal" />
<Label Grid.Row="2" Grid.Column="1" Text="(c) Xamarin 2016" TextColor="White" VerticalOptions="Center" />
</Grid>
</ControlTemplate>
and just changed
{TemplateBinding HeaderText}
to
{TemplateBinding BindingContext.HeaderText}
VIEW:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="TestAppNSPrism7.Views.PrismContentPage9">
<ContentPage.Content>
<ContentView x:Name="contentView" Padding="0,0,0,0" ControlTemplate="{StaticResource TealTemplate}" >
<StackLayout>
<Label Text="Welcome to Xamarin.Forms! page1"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
<Label Text="Buazz"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
<Button Command="{Binding ButtonChangeValueCommand}" Text="Change value" ></Button>
</StackLayout>
</ContentView>
</ContentPage.Content>
</ContentPage>
VIEWMODEL:
public class PrismContentPage9ViewModel : ViewModelBase
{
ControlTemplate tealTemplate;
private string _headerText = "test";
public string HeaderText
{
get
{
return _headerText;
}
set { SetProperty(ref _headerText, value); }
}
public PrismContentPage9ViewModel(INavigationService navigationService) : base(navigationService)
{
tealTemplate = (ControlTemplate)Application.Current.Resources["TealTemplate"];
}
private DelegateCommand _buttonChangeValueCommand;
public DelegateCommand ButtonChangeValueCommand =>
_buttonChangeValueCommand ?? (_buttonChangeValueCommand = new DelegateCommand(ExecuteButtonChangeValueCommand));
void ExecuteButtonChangeValueCommand()
{
HeaderText = "changed";
}
}
The Page gets loaded correctly, with the ControlTemplate, and the HeaderText is "test".
So it seems the HeaderText binding with the ControlTemplate is working.
But when I set the HeaderText to "changed", the Label doesn't get updated.
I debugged and checked that once I press the button it goes through ExecuteButtonChangeValueCommand() and SetProperty(ref _headerText, value)
Any suggestion?
Thanks!
I changed the TemplateBinding from :
Text="{TemplateBinding BindingContext.HeaderText}"
to:
Text="{TemplateBinding Parent.BindingContext.HeaderText}"
and it updates now when I press your changed button.
I believe its due to the template not having a binding context automatically set but the template's parent (PrismContentPage9) has its BindingContext auto-wired from Prism's AutowireViewModel property, e.g.,
prism:ViewModelLocator.AutowireViewModel="True"
Let me know if that works for you.

Resources