Xamarin.Forms iOS Picker jumping to last item after closing dialog - c#

I just encountered this issue on a project i am working on, and am stumped. As you can see in the gif below, when selecting an item with the Picker dialog on iOS (Emulator), the selected value jumps to the last item in the list after confirming (no matter if I tap out of the dialog or use the Done button). On Android the corresponding dialog behaves properly. I am using Xamarin.Forms 4.5.0.356 and Xamarin.Essentials 1.5.1 for this.
Minimum Bug replica
public class PickerItemModel
{
public string TestProp { get; set; }
public PickerItemModel(string t) => TestProp = t;
public override string ToString()
{
return TestProp;
}
}
public class MainViewModel : ComputedBindableBase
{
public List<PickerItemModel> PickerItemModels { get; set; } = new List<PickerItemModel> {
new PickerItemModel("Hello"),
new PickerItemModel("Stackoverflow")
};
public PickerItemModel SelectedModel { get; set; }
The ComputedBindableBase does implemented the INotifyPropertyChanged Event and automatically raise it when a property is changed.
<Picker Title="Test"
ItemsSource="{Binding PickerItemModels}"
SelectedItem="{Binding SelectedModel}" />
The question is now, either how do i fix this behavior, or what is valid workaround for it. I am willing to use some custom packages for this, but I cannot implement the complete dialog on my own as I have some restrictions on the project in terms of time spent on it.
Thank you in advance for your time. :-)
EDIT:
I recompiled the code now after a few hours and it works now. I assume it was some bug with old code not being properly redeployed or something... I am also using VS on Mac so that may also just be the cause since it has behaved buggy since day one...
Also, here is the BindableBase class (ComputedBindableBase does nothing but add an utility function):
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T target, T value, [CallerMemberName] string propertyName = "")
{
target = value;
RaisePropertyChanged(propertyName);
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

I can not repro this issue using Xamarin.Forms 4.5.0.356 and Xamarin.Essentials 1.5.1. My code is functionally the same as yours with the one exception that I did not derive my viewmodel from ComputedBindableBase. The following code works as expected
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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:app2="clr-namespace:App3"
mc:Ignorable="d"
x:Class="App3.MainPage">
<ContentPage.BindingContext>
<app2:MainPageViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<Picker Title="Please select..."
ItemsSource="{Binding PickerItemModels}"
SelectedItem="{Binding SelectedModel}" />
</ContentPage.Content>
</ContentPage>
ViewModel
public class PickerItemModel
{
public string TestProp { get; set; }
public PickerItemModel(string t) => TestProp = t;
public override string ToString() => TestProp;
}
public class MainPageViewModel
{
public List<PickerItemModel> PickerItemModels { get; set; } = new List<PickerItemModel> {
new PickerItemModel("Hello"),
new PickerItemModel("Stackoverflow")
};
public PickerItemModel SelectedModel { get; set; }
}

Related

Xamarin.Forms ListView Binding Issue

We are a newbie for Xamarin. We are having an issue in binding the response data from a web service to a ListView.
We debugged and we can see the the web service is successfully responding with the data but it never gets populated.
Any ideas?
It's gotta be a small thing that we are missing. We have managed to display a single entry from the data with other views (in other parts of the project) BUT not in IEnumerable<> or List<>
Here's the code:
View - RoundsPage.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:viewModels="clr-namespace:AthlosifyMobile.ViewModels"
x:Class="AthlosifyMobile.Views.RoundsPage">
<ContentPage.BindingContext>
<viewModels:RoundsViewModel />
</ContentPage.BindingContext>
<StackLayout>
<Entry Text="{Binding AccessToken}" />
<Button Command="{Binding GetRoundsCommand}" Text="Get all rounds" />
<Label Text="Rounds: "></Label>
<ListView ItemsSource="{Binding Rounds}" HasUnevenRows="true" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="Round 1:"></Label>
<Label Text="{Binding Name}"></Label>
<Label Text="{Binding DailyHandicap}"></Label>
<Label Text="{Binding PlayedUTC}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>`
ViewModel - RoundsViewModel.cs :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using AthlosifyMobile.Annotations;
using Xamarin.Forms;
using AthlosifyMobile.Services;
using AthlosifyMobile.Models;
namespace AthlosifyMobile.ViewModels
{
public class RoundsViewModel : INotifyPropertyChanged
{
ApiServices _apiServices = new ApiServices();
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<Round> _rounds;
public string AccessToken { get; set; }
public IEnumerable<Round> Rounds
{
get
{
return _rounds;
}
set
{
_rounds = value;
OnPropertyChanged();
}
}
public ICommand GetRoundsCommand
{
get
{
return new Command(async() =>
{
Rounds = await _apiServices.GetRoundsAsync(AccessToken);
});
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Model - Course.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace AthlosifyMobile.Models
{
public class Round : EntityBase
{
public Guid RoundID { get; set; }
public Guid UserID { get; set; }
public Guid RoundCategoryID { get; set; }
public Guid CourseID { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public int DailyHandicap { get; set; }
public DateTime PlayedUTC { get; set; }
public RoundCategory RoundCategory { get; set; }
public Course Course { get; set; }
public ICollection<RoundHole> RoundHoles { get; set; }
}
public abstract class EntityBase
{
public DateTime CreatedUTC { get; set; }
public DateTime LastModifiedUTC { get; set; }
}
}
Services - apiservices.cs:
using AthlosifyMobile.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace AthlosifyMobile.Services
{
public async Task<IEnumerable<Round>> GetRoundsAsync(string accessToken)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var json = await client.GetStringAsync("http://localhost:5609/api/Rounds");
var list = JsonConvert.DeserializeObject<IEnumerable<Round>>(json);
return list;
}
}
}
You will need to diagnose whether this is an issue with connecting the View to the ViewModel or whether your Data Service isn't working correctly. Either way, there are a few things you should do to fix this!
Firstly you are using IEnumerable, instead you should be using ObservableCollection<T>. You should always be using ObservableCollection<T> for Binded list views. This is explained in the xamarin docs here (they automatically notify the view when their contents changed & update).
So you should make this change:
public ObservableCollection<Round> Rounds { get; }
Next you should verify that the bindings are correct. I would not recommend your approach of going straight to live data if you aren't familiar with xamarin. Firstly you should try adding some static objects to the view model and trying to bind them!
Disconnect your API code and call a method that creates some of your Round objects. Here is an example method (i use methods like these all the time when designing my ListViews UI).
public RoundsViewModel()
{
Rounds = CreateSampleData();
}
private ObservableCollection<Round> CreateSampleData()
{
ObservableCollection<Round> dummyData = new ObservableCollection<Round>();
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
return dummyData;
}
At this point you will either see items in your ListView, meaning you have an issue with your API code / Implementation of INotifyPropertyChanged. If you don't see anything then you likely have an issue with binding and will need to verify that your view is actually connected to the View Model.
Mvvm Helpers
Seeing some of this code makes me feel very sorry for you, you definitely should looking into using an MVVM helper such as Prism or MVVMCross. I personally use Prism which provides a ViewModelBase which all ViewModels inherit from. This means all of the INotifyPropertyChanged code is hidden away from you (less boilerplate). It also has a dependancy service which means hooking views up to view models is as simple as registering it in the app.cs.
If you are interested in prism, watch this video with Brian Lagunas to see what Prism can do for you!
Update: There are now a few helpful libraries aside from Prism that will help with the MVVM stuff. Refractored.MVVMHelpers and Xamarin.CommunityToolkit both contain an essential object: ObservableRangeCollection.
ALL code using an ObservableCollection should be replaced with ObservableRangeCollection, it is an essential object and really belongs in a microsoft maintained namespace at this point. It creates a performance benefit for updating larger collections & reduces the need for alot of boilerplate when updating the ObservableCollection

Xamarin: Binding property not found

This app works just fine in UWP. I have ripped out everything except one of the more basic properties that is failing on Android. It looks like this:
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:ViewModels="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.Views.MyApp">
<ContentPage.BindingContext>
<ViewModels:MyViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<ScrollView>
<StackLayout Style="{StaticResource PageForm}">
<Picker ItemsSource="{Binding Modes}"
ItemDisplayBinding="{Binding Value}"
SelectedItem="{Binding SelectedMode}" />
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
MyPage.cs
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace MyApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyApp : ContentPage
{
public MyApp ()
{
InitializeComponent ();
}
}
}
MyViewModel.cs
using MyApp.Models;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace MyApp.ViewModels
{
public class MyViewModel: INotifyPropertyChanged
{
List<Mode> _modes;
Mode _selectedMode;
public event PropertyChangedEventHandler PropertyChanged;
public MyViewModel()
{
Modes = new List<Mode>()
{
new Mode() { Key=ModeType.Mode1, Value="Mode1" },
new Mode() { Key=ModeType.Mode2, Value="Mode2" }
};
SelectedMode = Modes.Single(m => m.Key == ModeType.Mode1);
}
public List<Mode> Modes {
get { return _modes; }
set {
_modes = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Modes"));
}
}
public Mode SelectedMode {
get {
return _selectedMode;
}
set {
_selectedMode = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedMode"));
}
}
}
}
Mode.cs
namespace MyApp.Models
{
public enum ModeType { Mode1, Mode2 };
public class Mode
{
public ModeType _key;
public string _value;
public ModeType Key {
get
{
return _key;
}
set
{
_key = value;
}
}
public string Value {
get
{
return _value;
}
set
{
_value = value;
}
}
}
}
and what I see in the Debug console is
[0:] Binding: 'Value' property not found on 'MyApp.Models.Mode', target property: 'Xamarin.Forms.Picker.Display'
[0:] Binding: 'Value' property not found on 'MyApp.Models.Mode', target property: 'Xamarin.Forms.Picker.Display'
[0:] Binding: 'SelectedMode' property not found on 'MyApp.ViewModels.'MyApp', target property: 'Xamarin.Forms.Picker.SelectedItem'
Like I said this works if I run it as a UWP app but when I try it on Android it just doesn't work. That's about all I can say since it doesn't really say what the problem is other than the errors above which don't make sense.
The rest of the view model actually works. The main part of the app works, I can even run the code on this view model. If I create a simple string binding that will work, even on Android.
Any help is appreciated.
The answer is total magic to me. If someone can please explain this I will mark your answer as the accepted one.
Anroid Project File > Properties > Linking > Set to None.
It still didn't work so I closed Visual Studio and deleted the bin and obj directories in the PCL and Android projects. Finally it worked.
One other thing is this seems like I've now lost the ability to have linking be set to sdk and user assemblies. What if I need that at some point?
Use a one way binding to avoid having these binding errors in the debug console.
Text="{Binding [Name], Source={x:Static i18n:Translator.Instance}, Mode=OneWay}"
If you need TwoWay binding, make sure the bound model objects implement INotifyPropertyChanged as Markus Michel indicated.
Your mode model class also needs to implement INotifyPropertyChanged

Button command binding doesn't work in Xamarin.Forms

I'd like to bind a command to the command property of my button. This seemed pretty straightforward since I've done this many times before in WPF and the method here is very similar. Let me show some code snippets.
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="MyApp.View.CustomPage"
Title="Something">
<ContentPage.Content>
<StackLayout>
<Button x:Name="numBtn" Text="Increase number" Command="{Binding IncreaseCommand}" />
<Label x:Name="numLabel" Text="{Binding numberText}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
Code-behind
public partial class CustomPage : ContentPage
{
public CustomPage ()
{
InitializeComponent ();
BindingContext = ViewModelLocator.ViewModel(); //ViewModelLocator is singleton, gives
//you a ViewModel instance
}
}
ViewModel
public ICommand IncreaseCommand { get; private set; }
private int number;
public string numberText { get; private set;}
the constructor:
public ViewModel()
{
IncreaseCommand = new Command (() => IncreaseExecuted ());
number = 0;
numberText = number.ToString ();
OnPropertyChanged (numberText);
}
and then
private void IncreaseExecuted()
{
number++;
numberText = number.ToString ();
OnPropertyChanged (numberText);
}
When I run the app using the Xamarin Android Player (KitKat) I see the button, and the label reading 0. Then I press the button and nothing happens. I tried checking what happens with breakpoints but the app doesn't pause, not even when they're in the constructor of my ViewModel. I guess it's something to do with the emulator. Anyway, I think the binding is ok since I can see a "0" on the screen. What could be the problem? Let me show my ViewModelBase class just in case:
ViewModelBase
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Maybe my numberText property doesn't get updated when I call OnPropertyChanged? But I've used the exact same ViewModelBase class several times before and it always worked fine. One last thing, my CustomPage page is wrapped inside a NavigationPage which is a child of a TabbedPage:
MainPage.xaml.cs
this.Children.Add (new NavigationPage (new CustomPage ()) {Title="Something"} );
This shouldn't affect anything but there it is just in case. So what's wrong with my command binding? Thank you in advance!
This answer is not directly related to the problem of the original question, however this question is the one that ranks highest in search engines and the title of this question is ambiguous to the question that this answer answers.
I was having issues with a Command that wouldn't fire the associated command action. My problem was that I had defined the Command as a field instead of a property.
Works:
public Command MyCommand { get; set; }
Doesn't work:
public Command MyCommand;
Hope this helps someone else.
You are almost there. Take a close look at your call to the OnPropertyChanged method; you are passing the value of numberText and not the name. If you change your code to pass "numberText" I expect it shall work properly.
Edit: I should add that the the OnPropertyChanged call in the constructor has the same problem. The reason you see "0" at startup is that the view is simply using the existing binding to retrieve the value.
Edit 2: Now that Xamarin supports C# 6.0, you can use the new "nameof" expression that eliminates the need for a hard-coded string. Alternately, you can use MvvmCross, MvvmLight, or XLabs' MVVM classes.

WPF Display fake Data in designer with Collection of Interface

I want display fake data in Visual Studio Designer. I use View Service Interface way with this aborecence (it is minimal example) :
-ServiceView
-IMainWindow.cs
-ICustomer.cs
-SampleData
-MainWindowDesign.cs
-CustomerDesign.cs
-Data.xaml
IMainWindow.cs
namespace TestDesignSampleData.ServiceView
{
interface IMainWindow
{
ObservableCollection<ICustomer> Customers { get; }
}
}
ICustomer.cs
namespace TestDesignSampleData.ServiceView
{
interface ICustomer
{
string Name { get; set; }
}
}
MainWindowDesign.cs
namespace TestDesignSampleData.SampleData
{
class MainWindowDesign : IMainWindow
{
public ObservableCollection<ICustomer> Customers { get; set; }
public MainWindowDesign()
{
//Customers = new ObservableCollection<ICustomer>();
}
}
}
CustomerDesign.cs
namespace TestDesignSampleData.SampleData
{
class CustomerDesign : ICustomer
{
public string Name { get; set; }
}
}
Data.xaml
<sd:MainWindowDesign xmlns:sd="clr-namespace:TestDesignSampleData.SampleData">
<sd:MainWindowDesign.Customers>
<sd:CustomerDesign Name="Charly Sparow"/>
<sd:CustomerDesign Name="Jacky Chan"/>
<sd:CustomerDesign Name="Dora Exploring"/>
</sd:MainWindowDesign.Customers>
</sd:MainWindowDesign>
This can build and execute, but the data are not displayed and the designer send this error for each line of the collection in Data.xaml :
Error 1 The specified value cannot be assigned to the collection.
The following type was expected: "ICustomer".
For some reason, Customer Design is not recognized as ICustomer.
EDIT:
I think it is Visual Studio designer bug. I dont have this problem with Blend. Maybe the Blend designer's compiler is more advanced.
Minimal Project to reproduce the error :
https://github.com/Orwel/DesignerError/releases/tag/CompiledProject
I think XAML designer has some issue with interpreting arrays/collections with interface type. If interface is not necessity, you can use inheritance instead.
Define your ICustomer -> create base class Customer that implements ICustomer and then create your special classes by inheriting Customer not ICustomer.
As you will have base class you can create array/collection of Customers instead of ICustomer which works correctly with XAML designer in Visual Studio.
I can't quite determine if/when/how you're binding to your MainWindowDesign class, but I've had some luck in using the following structure (customised for your example) to show design time data in WPF.
public class MainWindowDesignFactory
{
public static IMainWindow ViewModel
{
get
{
return new MainWindowDesignFactory().Create();
}
}
public IMainWindow Create()
{
var vm = new MainWindowDesign();
vm.Customers = new ObservableCollection<ICustomer>()
{
new CustomerDesign() { Name = "Charly Sparow" },
new CustomerDesign() { Name = "Jacky Chan" },
new CustomerDesign() { Name = "Dora Exploring" }
};
return vm;
}
private class MainWindowDesign : IMainWindow
{
public ObservableCollection<ICustomer> Customers { get; set; }
}
private class CustomerDesign : ICustomer
{
public string Name { get; set; }
}
}
And then in the XAML file:
<Window
x:Class="DesignTimeDataExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:TestDesignSampleData_SampleData="clr-namespace:TestDesignSampleData.SampleData"
Title="MainWindow"
Height="350"
Width="525"
d:DataContext="{x:Static TestDesignSampleData_SampleData:MainWindowDesignFactory.ViewModel}">
<Grid>
<ListBox ItemsSource="{Binding Customers}" />
</Grid>
</Window>
The important elements here are the d:DataContext attribute in the XAML (which does pretty much what you expect it to, set a DataContext while you are in the designer) and the actual MainWindowDesignFactory class, which has a static ViewModel property to bind to.
There are other ways, including using:
d:DataContext="{d:DesignInstance Type={x:Type TestDesignSampleData_SampleData:MainWindowDesign}, IsDesignTimeCreatable=True}"
But I've had more luck with the Factory/Static property combination (the design time creation of an instance using the DesignInstance attribute is a bit wonky, using a manually created Factory gave me more control and less arcane designer errors).
The code above is a little rough but it should give you a starting point.

ListBox of dead simple MVVM application stays empty - what am I missing?

The XAML of my window looks like this:
<Window x:Class="Binding1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Cronjobs" Height="350" Width="525">
<Grid>
<ListBox HorizontalAlignment="Stretch" Margin="10" VerticalAlignment="Stretch" ItemsSource="{Binding Cronjobs}" />
</Grid>
</Window>
As visible I bind the ListBox's ItemsSource to the Cronjobs property of the current DataContext. The DataContext is set to an instance of the ViewModel below in the constructor of the code-behind:
public partial class MainWindow : Window
{
private CronjobViewModel cronjobViewModel;
public MainWindow()
{
InitializeComponent();
this.cronjobViewModel = new CronjobViewModel();
this.DataContext = cronjobViewModel;
}
}
The ViewModel looks like this:
class CronjobViewModel : DependencyObject
{
public ObservableCollection<Cronjob> Cronjobs;
public CronjobViewModel( )
{
this.Cronjobs = new ObservableCollection<Cronjob>();
this.Cronjobs.Add( new Cronjob() );
this.Cronjobs.Add( new Cronjob() );
}
}
For quick and simple debugging I manually add some items to the collection for now. That Cronjob class is the actual model which is nothing more than a class with some simple string properties, cut down to the essential part:
class Cronjob {
private string name;
public string Name { get { return this.name; } set { this.name = value; } }
public Cronjob( ) { this.Name = "Herp"; }
}
I am mainly experienced in web development and new to the combination of WPF and MVVM. I spent nearly 10 hours figuring this out now but still do not see the cause. I also tried the DataGrid. I watched the first half of Jason Dolingers Video about MVVM about three times and took a close look on how did it, but it does not work for me, even though I understood the abstract concept of MVVM. I am pretty sure I just unintendedly omitted something in the XAML which should be there, but messing around with display property names and item templates did not help (according to what I found here and there around the internet they are not even necessary). Does anybody see the error in this code?
Sorry for the large code dump, I formatted the "boring" parts in a more compact way.
It's because Cronjobs is a field and you cannot bind to fields. Try changing it into property:
public ObservableCollection<Cronjob> Cronjobs { get; set; }
This should work ;)
public class CronjobViewModel
{
public ObservableCollection<Cronjob> Cronjobs { get; private set; }
public CronjobViewModel()
{
this.Cronjobs = new ObservableCollection<Cronjob>();
this.Cronjobs.Add(new Cronjob());
this.Cronjobs.Add(new Cronjob());
}
}

Categories

Resources