Xamarin Forms Binding not populated until reload - xamarin

I have a Xamarin Forms app that shows a picture that is bound to a class (RandomChallenge). The class is generated in the view model and assigned. The class has a Uri property which we want to bind to an image in the view.
When the app loads it doesn't show any picture, even though RandomChallenge is not null. If I make a change to the UI code whilst debugging and save it, when the debugger reloads the image is visible. Its as if the RandomChallenge class gets populated after the page renders? If I was to put a break point on the line RandomChallenge = await ChallengeDataStore.GetRandomChallenge(); RandomChallenge is populated.
ViewModel
public class MainMenuViewModel : BaseViewModel
{
public ICommand OpenWebCommand { get; }
public IdentificationChallenge RandomChallenge { get; set; }
public Command LoadChallengeCommand { get; set; }
public MainMenuViewModel()
{
Title = "Main Menu";
LoadChallengeCommand = new Command(async () => await ExecuteLoadChallengeCommand());
this.LoadChallengeCommand.Execute(null);
}
async Task ExecuteLoadChallengeCommand()
{
if (IsBusy)
return;
try
{
RandomChallenge = await ChallengeDataStore.GetRandomChallenge();
Debug.WriteLine("TEst");
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
}
UI
<?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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:microcharts="clr-namespace:Microcharts.Forms;assembly=Microcharts.Forms"
mc:Ignorable="d"
x:Class="HOG.MobileApp.Views.MainMenuPage"
xmlns:vm="clr-namespace:HOG.MobileApp.ViewModels"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
Title="{Binding Title}">
<ContentPage.Resources>
<ResourceDictionary>
<Color x:Key="Primary">#72a230</Color>
<Color x:Key="Accent">#72a230</Color>
<Color x:Key="LightTextColor">#72a230</Color>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Help" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<Image Source="{ Binding RandomChallenge.Picture }" HeightRequest="450" WidthRequest="450" />
</ContentPage.Content>

So the answer was to call OnPropertyChanged when RandomChallenge is assigned. The ViewModel did implement INotifyPropertyChanged on its parent class.
async Task ExecuteLoadChallengeCommand()
{
if (IsBusy)
return;
try
{
RandomChallenge = await ChallengeDataStore.GetRandomChallenge();
OnPropertyChanged("RandomChallenge");
Debug.WriteLine("TEst");
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}

Related

Getting StateLayout with CustomState to show image thumbnail

In my Xamarin Forms 5 app, I have a form users will fill out to create a post -- similar to Facebook posts.
The effect I'm trying to create is this:
There's an "Add Image" button that allows user to upload an image. Once the image is uploaded, I want to no longer display the button but display a thumbnail version of the uploaded image.
Here's what my XAML looks like:
<StackLayout
xct:StateLayout.CurrentState="{Binding MainState.None}"
xct:StateLayout.CurrentCustomStateKey="{Binding PostImageState}">
<xct:StateLayout.StateViews>
<xct:StateView StateKey="Custom" CustomStateKey="Image set">
<Image
Grid.Row="0"
Grid.Column="0"
Source="{Binding PostImageUrl}"
WidthRequest="30"
HeightRequest="30"/>
</xct:StateView>
</xct:StateLayout.StateViews>
<Button
Text="Add Image"
Command="{Binding AddImageCommand}"
BackgroundColor="{StaticResource SecondaryBackground}"
WidthRequest="100"
HeightRequest="35"
HorizontalOptions="Start"
Margin="10,0,0,0"/>
</StackLayout>
Here's an abbreviated version of my view model:
public class MyViewModel : BaseViewModel
{
public LayoutState _mainState;
string postImageUrl { get; set; }
string postImageState { get; set; } = "No image";
public MyViewModel()
{
Title = string.Empty;
IsBusy = true;
MainState = LayoutState.None;
AddImageCommand = new AsyncCommand(Add_Image_Tapped);
}
public LayoutState MainState
{
get => _mainState;
set => SetProperty(ref _mainState, value);
}
public string PostImageUrl
{
get => postImageUrl;
set
{
if (postImageUrl == value)
return;
postImageUrl = value;
OnPropertyChanged();
}
}
public string PostImageState
{
get => postImageState;
set
{
if (postImageState == value)
return;
postImageState = value;
OnPropertyChanged();
}
}
async Task Add_Image_Tapped()
{
// Upload image
// Once upload is done
PostImageUrl = uploadedFileUrl;
PostImageState = "Image set";
}
}
I haven't been able to get this to work. Currently, it's not even showing the "Add Image" button. Where am I making a mistake?
There are several problems with your code.
1.Since you use Binding for xct:StateLayout.CurrentState, we should bind it to a variable in ViewModel, here we should use MainState not MainState.None:
xct:StateLayout.CurrentState="{Binding MainState}"
2.Based on your requirement, we can use the value from LayoutState enumeration(for example StateKey="Success"),, we don't need add custom states.
3.If we want to hidden the button once uploading the image, we can bind MainState to property IsVisible of button , but need use Converter StateToBooleanConverter to convert State to bool.
Based on your code ,I created a simple demo, and it works properly on my side.
You can refer to the following code:
MyPage.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"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:viewmodel="clr-namespace:FormApp0314.ViewModel"
x:Class="FormApp0314.TestPage1">
<ContentPage.BindingContext>
<viewmodel:MyViewModel></viewmodel:MyViewModel>
</ContentPage.BindingContext>
<ContentPage.Resources>
<xct:StateToBooleanConverter x:Key="StateToBooleanConverter" />
</ContentPage.Resources>
<StackLayout
xct:StateLayout.CurrentState="{Binding MainState}">
<xct:StateLayout.StateViews>
<xct:StateView StateKey="Success" CustomStateKey="Image set">
<Image
Grid.Row="0"
Grid.Column="0"
Source="{ Binding PostImageUrl}"
WidthRequest="60"
HeightRequest="60"/>
</xct:StateView>
</xct:StateLayout.StateViews>
<Button
Text="Add Image"
Command="{Binding AddImageCommand}"
IsVisible="{Binding MainState, Converter={StaticResource StateToBooleanConverter}, ConverterParameter={x:Static xct:LayoutState.None}}"
WidthRequest="100"
HeightRequest="35"
HorizontalOptions="Start"
Margin="10,0,0,0" />
</StackLayout>
</ContentPage>
MyViewModel.cs
public class MyViewModel: BaseViewModel
{
public LayoutState _mainState;
string postImageUrl;
string postImageState = "No image";
public ICommand AddImageCommand { get; }
public MyViewModel()
{
MainState = LayoutState.None;
PostImageUrl = "bell.png";
AddImageCommand = CommandFactory.Create(Add_Image_Tapped);
}
async Task Add_Image_Tapped()
{
MainState = LayoutState.Success;
await Task.Delay(3000);
MainState = LayoutState.None;
}
public LayoutState MainState
{
get => _mainState;
set => SetProperty(ref _mainState, value);
}
public string PostImageUrl
{
get => postImageUrl;
set => SetProperty(ref postImageUrl, value);
}
public string PostImageState
{
get => postImageState;
set => SetProperty(ref postImageState, value);
}
}

Adding content on a custom content page

Ok, so I am trying to add a searchbar in the toolbar of my page.
The Search bar appears correctly in the toolbar and I can catch the on text changed event.
I created a new Xaml and cs page and changed content page to 'MySearchContentPage'
I Tried to add a grid and label on my new page created but nothing will show except for the searchbar. I added this just to see if I can get anything to display.
Am I adding it in the right place ? Or how do you add content to this page ?
I have done this by doing the following:
MySearchContentPage Class:
public class MySearchContentPage : ContentPage, ISearchPage
{
public MySearchContentPage()
{
SearchBarTextChanged += HandleSearchBarTextChanged;
}
public event EventHandler<string> SearchBarTextChanged;
public void OnSearchBarTextChanged(string text) => SearchBarTextChanged?.Invoke(this, text);
void HandleSearchBarTextChanged(object sender, string searchBarText)
{
//Logic to handle updated search bar text
}
}
ISearchPage:
public interface ISearchPage
{
void OnSearchBarTextChanged(string text);
event EventHandler<string> SearchBarTextChanged;
}
iOS renderer page:
public class MySearchContentPageRenderer : PageRenderer, IUISearchResultsUpdating
{
readonly UISearchController searchController;
bool _isFirstAppearing = true;
public override void WillMoveToParentViewController(UIViewController parent)
{
base.WillMoveToParentViewController(parent);
var searchController = new UISearchController(searchResultsController: null)
{
SearchResultsUpdater = this,
DimsBackgroundDuringPresentation = false,
HidesNavigationBarDuringPresentation = true,
HidesBottomBarWhenPushed = true
};
searchController.SearchBar.Placeholder = "Search Symptoms";
parent.NavigationItem.SearchController = searchController;
DefinesPresentationContext = true;
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
//Work-around to ensure the SearchController appears when the page first appears https://stackoverflow.com/a/46313164/5953643
if (_isFirstAppearing)
{
ParentViewController.NavigationItem.SearchController.Active = true;
ParentViewController.NavigationItem.SearchController.Active = false;
_isFirstAppearing = false;
}
}
public void UpdateSearchResultsForSearchController(UISearchController searchController)
{
if (Element is ISearchPage searchPage)
searchPage.OnSearchBarTextChanged(searchController.SearchBar.Text);
}
public MySearchContentPageRenderer()
{
var searchControllerr = new UISearchController(searchResultsController: null)
{
SearchResultsUpdater = this,
DimsBackgroundDuringPresentation = false,
HidesNavigationBarDuringPresentation = false,
HidesBottomBarWhenPushed = true
};
searchControllerr.SearchBar.Placeholder = string.Empty;
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
UINavigationBar.Appearance.TitleTextAttributes = new UIStringAttributes
{
ForegroundColor = UIColor.Red
};
}
public override void ViewDidLoad()
{
// base.ViewDidLoad();
// NavigationController.NavigationBar.PrefersLargeTitles = true;
// NavigationController.NavigationBar.BackgroundColor = UIColor.Red;
// var searchController = new UISearchController(searchResultsController: null);
// searchController.SearchBar.SearchBarStyle = UISearchBarStyle.Default;
// searchController.SearchBar.BackgroundColor = UIColor.Green;
// NavigationItem.SearchController = searchController;
// NavigationItem.HidesSearchBarWhenScrolling = false;
//searchController.SearchBar.SizeToFit();
//searchController.SearchBar.SearchBarStyle = UISearchBarStyle.Prominent;
////NavigationController.TabBarController
//this.sea
//NavigationController.TabBarController.NavigationItem.HidesSearchBarWhenScrolling = true;
//NavigationController.TabBarController.NavigationItem.SearchController = searchController;
//this.Title = "Search";
}
}
So far the outcome is this :
I can't seem to get anything else to add to this page. Can anyone explain why?
AddSymptomNew.xaml page:
<?xml version="1.0" encoding="UTF-8"?>
<visiblegyapp:MySearchContentPage
xmlns:visiblegyapp="clr-namespace:VisiblegyApp"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="VisiblegyApp.AddSymptomNew"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
ios:Page.LargeTitleDisplay="Always"
Title="Search Symptoms"
BackgroundColor="{DynamicResource BasePageColor}"
>
<ScrollView
x:Name="outerScrollView"
Padding="0"
>
<Grid
x:Name="layeringGrid"
RowSpacing="0"
VerticalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Text="test label" TextColor="Red" Grid.Row="1"/>
</Grid>
</ScrollView>
The cause is ContentPage is inheritable while XAML is not inheritable.
I would recommend you to use a custom contentview and add this contentView to MySearchContentPage .
For example, create a custom contentView here:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}
And in Xaml:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="App132.AddSymptomNewView">
<ContentView.Content>
<ScrollView
x:Name="outerScrollView"
Padding="0">
<Grid
x:Name="layeringGrid"
RowSpacing="0"
VerticalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Text="test label" TextColor="Red" Grid.Row="1"/>
</Grid>
</ScrollView>
</ContentView.Content>
</ContentView>
And use it in the MySearchContentPage :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:app132="clr-namespace:App132"
mc:Ignorable="d"
x:Class="App132.MainPage">
<app132:AddSymptomNewView/>
</ContentPage>

Binding CarouselPage content pages to view model

I am trying to use a CarouselPage in Xamarin Forms.
<?xml version="1.0" encoding="utf-8" ?>
<CarouselPage 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"
xmlns:views="clr-namespace:TestForms.Views;assembly=TestForms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="TestForms.Views.Photos" ItemsSource="{Binding Pages}">
<CarouselPage.ItemTemplate>
<DataTemplate>
<ContentPage >
<StackLayout VerticalOptions="StartAndExpand" Padding="50">
<Label Text="ContentPage"></Label>
<Label Text="{Binding Title}"></Label>
<Label Text="{Binding Description}"></Label>
</StackLayout>
</ContentPage>
</DataTemplate>
</CarouselPage.ItemTemplate>
</CarouselPage>
In the view model I have
List<ContentPage> ContentPages = new List<ContentPage>();
foreach (var photo in Photos)
{
var page = new ContentPage();
page.BindingContext = new PhotoDetailViewModel(photo);
ContentPages.Add(page);
}
Pages = new ObservableCollection<ContentPage>(ContentPages);
When I render this, I get a list of pages for all the photos. but I can't seem to bind the title or description in the individual page.
What am I missing here?
You have your CarouselPage wired up correctly
Just need to change your view model slightly.
I'm assuming your Title and Description Properties are in your PhotoDetailViewModel?
if so the binding you are creating in your CarouselPage is not working because it is binded to the List of ContentPage, which wouldn't have the properties "Title" and "Description"
in your CarouselPage your have already set up an ItemsSource binding which should automatically set the BindingContext of your child pages in your CarouselPage. So you dont need to do it manually.
So instead create an ObservableCollection of PhotoDetailViewModel in your ViewModel and bind the ItemsSource of your CarouselPage to that.
So Remove:
List<ContentPage> ContentPages = new List<ContentPage>();
foreach (var photo in Photos)
{
var page = new ContentPage();
page.BindingContext = new PhotoDetailViewModel(photo);
ContentPages.Add(page);
}
Pages = new ObservableCollection<ContentPage>(ContentPages);
And add:
Pages = new ObservableCollection<PhotoDetailViewModel>(Photos.Select(p => new PhotoDetailViewModel(p));
Make sure to change the Property Type of Pages to ObservableCollection<PhotoDetailViewModel>
And that should be all you need to change
As I said you should use ContentView instead of ContentPage. Below is working example
public partial class AnotherCarouselPage : ContentPage
{
public class Zoo
{
public string ImageUrl { get; set; }
public string Name { get; set; }
}
public ObservableCollection<Zoo> Zoos { get; set; }
public AnotherCarouselPage()
{
Zoos = new ObservableCollection<Zoo>
{
new Zoo
{
ImageUrl = "http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/23c1dd13-333a-459e-9e23-c3784e7cb434/2016-06-02_1049.png",
Name = "Woodland Park Zoo"
},
new Zoo
{
ImageUrl = "http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/6b60d27e-c1ec-4fe6-bebe-7386d545bb62/2016-06-02_1051.png",
Name = "Cleveland Zoo"
},
new Zoo
{
ImageUrl = "http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/e8179889-8189-4acb-bac5-812611199a03/2016-06-02_1053.png",
Name = "Phoenix Zoo"
}
};
InitializeComponent();
carousel.ItemsSource = Zoos;
carousel.PositionSelected += Carousel_PositionSelected;
carousel.ItemSelected += Carousel_ItemSelected;
}
private void Carousel_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
}
private void Carousel_PositionSelected(object sender, SelectedPositionChangedEventArgs e)
{
}
}
page xml
<?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:control="clr-namespace:Xamarin.Forms;assembly=Xamarin.Forms.CarouselView"
x:Class="ButtonRendererDemo.AnotherCarouselPage"
x:Name="devicePage"
BackgroundColor="Gray">
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<control:CarouselView x:Name="carousel" >
<control:CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}"/>
<Image Source="{Binding ImageUrl}"/>
</StackLayout>
</DataTemplate>
</control:CarouselView.ItemTemplate>
</control:CarouselView>
</StackLayout>
</ContentPage.Content>
</ContentPage>

System.NullReferenceException Xamarin Maps

I'm getting a NullException when will display the map, but can not detect;
I tried to change the name of the maps, locazation, etc., but without success.
You may be able to identify where is my mistake, please help me!
public partial class LocationPage : ContentPage
{
public Clinica _clinica;
public LocationPage(Clinica clinica)
{
InitializeComponent();
Clinica = clinica;
SetupMap();
}
public Clinica Clinica
{
get
{
return _clinica;
}
set
{
_clinica = value;
}
}
protected override void OnAppearing()
{
base.OnAppearing();
// Typically, is preferable to call into the viewmodel for OnAppearing() logic to be performed,
// but we're not doing that in this case because we need to interact with the Xamarin.Forms.Map property on this Page.
// In the future, the Map type and it's properties may get more binding support, so that the map setup can be omitted from code-behind.
SetupMap();
}
void SetupMap()
{
if (Device.OS != TargetPlatform.WinPhone && Device.OS != TargetPlatform.Windows)
{
var pin = new Pin()
{
Type = PinType.Place,
Position = new Position(Clinica.Latitude, Clinica.Longitude),
Label = Clinica.Nome
};
clinicaMap.Pins.Clear();
clinicaMap.Pins.Add(pin);
clinicaMap.MoveToRegion(MapSpan.FromCenterAndRadius(pin.Position, Distance.FromMiles(10)));
}
}
}
}
LocationPage.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"
xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
x:Class="CartaoDeTodos.View.LocationPage">
<StackLayout>
<maps:Map x:Name="clinicaMap"
VerticalOptions="FillAndExpand"/>
</StackLayout>
</ContentPage>

SelectedItem in TabbedPage.xaml does not work Xamarin.Form

Hey everyone Good Day I have tabbed created in xaml, I prefer xaml because I have login in xaml code. My Code
Tab.xaml
<TabbedPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:FormsSample.Views;assembly=FormsSample"
x:Class="FormsSample.Views.LoginPage"
x:Name="TbPage">
<TabbedPage.Children>
<ContentPage x:Name="TbLog" Title="Login">
</ContentPage>
<ContentPage x:Name="TbSch" Title="Schedule">
</ContentPage>
<ContentPage x:Name="TbLis" Title="Customers">
</ContentPage>
</TabbedPage.Children>
</TabbedPage>
Tab.xaml.cs
namespace FormsSample.Views
{
public partial class LoginPage : TabbedPage
{
private readonly TabbedPage _tbPage;
private readonly ContentPage _tbList;
private readonly ContentPage _tbLogn;
public LoginPage()
{
InitializeComponent ();
_tbPage = this.FindByName<TabbedPage>("TbPage");
_tbList = this.FindByName<ContentPage>("TbLis");
_tbLogn = this.FindByName<ContentPage>("TbLog");
RunTime();
}
private void RunTime()
{
_tbPage.CurrentPage = _tbLogn;
if (_tbPage.SelectedItem == _tbPage.Children[2])
{
DisplayAlert("Tab", "Hello World", "OK");
}
}
}
}
changing to
_tbPage.SelectedItem == _tbList
Its similar nothing happen, How to solve this? thanks a lot.
This may not be fancy but it works for me :D
this.CurrentPageChanged += (o, e) =>
{
var i = this.Children.indexOf(this.CurrentPage);
if(i == 1 && UsrType == 2)
{
DisplayAlert("Admin", "Administrator Privilege required!", "OK");
this.CurrentPage = _tbLog;
}
};
I set SelectedItem to null first, before setting it to the page I wanted to show.

Resources