Xamarin Forms - Update UI element from itself doesn't work - user-interface

I'm currently working on Map UI update.
I have a public List<string> PolylineAddressPoints which has a setter where a method is called. The method, which is called, updates public List<GeoPosition> PolylineCoordinates.
When the public List<GeoPosition> PolylineCoordinates has been update, then the renderer is updating the UI of my map.
However, it only works if I edit the list from the MainPage.xaml.cs.
I search during a long moment on the web why my map want not update by itself without find any solution. Does the elements needs to be updated from the UI thread?
My main goal is just to update it directly from the list..
My customMap:
public class CustomMap : Map
{
public static readonly BindableProperty PolylineAddressPointsProperty =
BindableProperty.Create(nameof(PolylineAddressPoints), typeof(List<string>), typeof(CustomMap), new List<string>(), BindingMode.TwoWay);
public List<string> PolylineAddressPoints
{
get { return (List<string>)GetValue(PolylineAddressPointsProperty); }
set
{
SetValue(PolylineAddressPointsProperty, value);
generatePolylineCoordinates(value);
}
}
public static readonly BindableProperty PolylineCoordinatesProperty =
BindableProperty.Create(nameof(PolylineCoordinates), typeof(List<GeoPosition>), typeof(CustomMap), new List<GeoPosition>(), BindingMode.TwoWay);
public List<GeoPosition> PolylineCoordinates
{
get { return (List<GeoPosition>)GetValue(PolylineCoordinatesProperty); }
set { SetValue(PolylineCoordinatesProperty, value); }
}
private async void generatePolylineCoordinates(List<string> value)
{
this.PolylineCoordinates = await GeneratePolylineCoordinates(value);
MessagingCenter.Send<App>((App)Application.Current, "PolylineUpdated");
}
}
I tried using the MessagingCenter but it doesn't works.. It look like the thread is cancel, I said that because I don't see the debug as well.. I don't understand what is happening..
There is the MainPage.xaml.cs:
public partial class MapPolylinePage : ContentPage
{
public List<string> AddressPointList { get; set; }
public MapPolylinePage()
{
base.BindingContext = this;
MessagingCenter.Subscribe<App>((App)Application.Current, "PolylineUpdated", (sender) =>
{
Debug.WriteLine("--DEBUG ADDRESS--");
foreach (string item in MapTest.PolylineAddressPoints)
{
Debug.WriteLine("Adress => [{0}]", item);
}
Debug.WriteLine("--DEBUG ADDRESS--");
Debug.WriteLine("--DEBUG POSITION--");
foreach (GeoPosition item in MapTest.PolylineCoordinates)
{
Debug.WriteLine("GeoPosition => [{0}/{1}]", item.Latitude, item.Longitude);
}
Debug.WriteLine("--DEBUG POSITION--");
});
AddressPointList = new List<string>()
{
"Le Mans, 72100, France",
"Arnages, 72230, France",
"Allonnes, 72700, France"
};
InitializeComponent();
}
}
and the MainPage.xaml:
<ContentPage.Content>
<local:CustomMap x:Name="MapTest" PolylineAutoUpdate="True" PolylineAddressPoints="{Binding AddressPointList}" PolylineColor="Aqua" PolylineWidth="5"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
</ContentPage.Content>
Thank in advance !

Related

ItemsSource doesn't show/bind when ObservableCollection is filled before loading the page

I have a carouselview, in that view I have an ObservableCollection binded as an itemssource. I am able to bind the collection and it would show when I execute the viewmodel's command in the OnAppearing event.
Code that works:
Second Page
public partial class SecondPage : ContentPage
{
public Coll(bool hard, string subject)
{
InitializeComponent();
var vm = (DataSelectionViewModel)BindingContext;
vm.Hard = hard;
vm.Subject = subject;
/* had to set "hard" and "subject" here again, otherwise data won't load */
}
protected override async void OnAppearing()
{
var vm = (DataSelectionViewModel)BindingContext;
base.OnAppearing();
await vm.LoadData.ExecuteAsync().ConfigureAwait(false);
}
}
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
private string subject;
public string Subject { get => subject; set => SetProperty(ref subject, value); }
private bool hard;
public bool Hard { get => hard; set => SetProperty(ref hard, value); }
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
foreach (var data in filtereddata)
{
FilteredData.Add(data);
}
}
}
First Page where second page gets Hard and Subject values
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
await Navigation.PushAsync(new SecondPage(vm.Hard, vm.Subject));
}
So I want to change my code so that if I press the button on the first page, data instantly starts to filter and add to the ObservableCollection and when it's finished, then navigate to the second page. However if I try to load it to the BaseViewModel and then get the data from the second viewmodel it won't show the data.
Code that doesn't work:
Second Page
public partial class SecondPage : ContentPage
{
public SecondPage()
{
InitializeComponent();
}
}
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
FilteredData = new ObservableCollection<Items>();
}
}
BaseViewModel
public class BaseViewModel : INotifyPropertyChanged
{
private string subject;
public string Subject { get => subject; set => SetProperty(ref subject, value); }
private bool hard;
public bool Hard { get => hard; set => SetProperty(ref hard, value); }
public ObservableCollection<Items> FilteredData { get; set; }
/* BaseViewModel has implementation of SetProperty */
}
First Page where second page gets Hard and Subject values
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
}
First Page viewmodel
public class FirstPageViewModel : BaseViewModel
{
public IAsyncCommand MehetButtonClickedCommand { get; }
readonly IPageService pageService;
readonly IFeladatokStore _feladatokStore;
public FeladatValasztoViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
ButtonClickedCommand = new AsyncCommand(ButtonClicked);
pageService = DependencyService.Get<IPageService>();
}
private async Task ButtonClicked()
{
await FilterData();
await pageService.PushAsync(new SecondPage());
}
private async Task FilterData()
{
FilteredData.Clear();
var datas = await _dataStore.SearchData(Subject, Hard).ConfigureAwait(false);
foreach (var data in datas)
{
FilteredData.Add(data);
}
}
So basically this gives a null exception error. I also tried giving the ObservableCollection as an argument for SecondPage(ObservableCollection x) and that did work, but because I had to make another ObservableCollection for it and copy from one to another it stopped being async and froze for a couple of seconds. So my question is how can I make this async?
To avoid delay, build the new collection in a private variable. Then set the property to that variable:
// Constructor with parameter
public SomeClass(IList<Items> data)
{
SetFilteredDataCopy(data);
}
public ObservableCollection<Items> FilteredData { get; set; }
private void SetFilteredDataCopy(IList<Items> src)
{
var copy = new ObservableCollection<Items>();
foreach (var item in src)
copy.Add(item);
FilteredData = copy;
//MAYBE OnPropertyChanged(nameof(FilteredData));
}

Xamarin ListView binding is not working

I have been trying to bind my ListView to my View model. The view model successfully retrieves 5 records from the database and the Listview seems to display 5 blank rows, however it is not showing binding for each field within each row.
I have spent a couple of days searching internet but I don't seem to be doing anything different. I was using master detail pages so I thought that it may be the issue so I set my Events page as first navigation page without master/detail scenario but to no avail. Please note that I am using Portable Ninject for my dependencies/IoC.
My App.Xamal.cs is is as follows:
public App (params INinjectModule[] platformModules)
{
InitializeComponent();
var eventsPage = new NavigationPage(new EventsPage());
//Register core services
Kernel = new StandardKernel(new MyAppCoreModule(), new MyAppNavModule(eventsPage.Navigation));
//Register platform specific services
Kernel.Load(platformModules);
//Get the MainViewModel from the IoC
eventsPage.BindingContext = Kernel.Get<EventsViewModel>();
((BaseViewModel)(eventsPage.BindingContext)).Init();
MainPage = eventsPage;
}
My EventsPage.Xaml is provided 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="MyApp.Views.EventsPage"
Title="Events">
<ContentPage.Content>
<ListView x:Name="Events" ItemsSource="{Binding Events}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding EventID}" BackgroundColor="Red" TextColor="White"
FontAttributes="Bold" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
My EventsPage.xaml.cs is provided below:
namespace MyApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class EventsPage : ContentPage, IBaseViewFor<EventsViewModel>
{
public EventsPage ()
{
InitializeComponent ();
}
EventsViewModel _vm;
public EventsViewModel ViewModel
{
get => _vm;
set
{
_vm = value;
BindingContext = _vm;
}
}
}
}
My EventsViewModel is as follows, it successfully retrieves 5 records and OnPropertyChanged is fired for Events property:
namespace MyApp.ViewModels
{
public class EventsViewModel : BaseViewModel, IBaseViewModel
{
ObservableCollection<Event> _events;
readonly IEventDataService _eventDataService;
public ObservableCollection<Event> Events
{
get { return _events; }
set
{
_events = value;
OnPropertyChanged();
}
}
public EventsViewModel(INavService navService, IEventDataService eventDataService) : base(navService)
{
_eventDataService = eventDataService;
Events = new ObservableCollection<Event>();
}
public override async Task Init()
{
LoadEntries();
}
async void LoadEntries()
{
try
{
var events = await _eventDataService.GetEventsAsync();
Events = new ObservableCollection<Event>(events);
}
finally
{
}
}
}
}
My BaseViewModel is as follows:
namespace MyApp.ViewModels
{
public abstract class BaseViewModel : INotifyPropertyChanged
{
protected INavService NavService { get; private set; }
protected BaseViewModel(INavService navService)
{
NavService = navService;
}
bool _isBusy;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
OnPropertyChanged();
OnIsBusyChanged();
}
}
protected virtual void OnIsBusyChanged()
{
}
public abstract Task Init();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// Secod BaseViewModel abstract base class with a generic type that will be used to pass strongly typed parameters to the Init method
public abstract class BaseViewModel<TParameter> : BaseViewModel
{
protected BaseViewModel(INavService navService) : base(navService)
{
}
public override async Task Init()
{
await Init(default(TParameter));
}
public abstract Task Init(TParameter parameter);
}
}
IBaseViewModel is just a blank interface:
public interface IBaseViewModel
{
}
IBaseViewFor is given below:
namespace MyApp.ViewModels
{
public interface IBaseViewFor
{
}
public interface IBaseViewFor<T> : IBaseViewFor where T : IBaseViewModel
{
T ViewModel { get; set; }
}
}
My Event model is as follows:
namespace MyApp.Models
{
public class Event
{
public int EventID;
}
}
Finally, the image of the output, as you can see that 5 rows are created with red background but EventID is not binding in each row. I have checked the data and EventID is returned. I have even tried to manually add records into Events list but to no avail, see the manual code and image below:
async void LoadEntries()
{
try
{
Events.Add((new Event() { EventID = 1 }));
Events.Add((new Event() { EventID = 2 }));
Events.Add((new Event() { EventID = 3 }));
Events.Add((new Event() { EventID = 4 }));
Events.Add((new Event() { EventID = 5 }));
}
finally
{
}
}
I have spent a lot of time on it but unable to find a reason for this anomaly, can someone please cast a fresh eye and provide help!?
You can only bind to public properties - ie, you need a getter
public class Event
{
public int EventID { get; set; }
}

ListView stops updating after few seconds when I bind a ListView to ReactiveList

I am developing an app with Xamarin.Forms, trying to leverage ReactiveUI, but Xamarin.Forms ListView does not behave as expected.
The test setup is an Entry field which I input values into. I subscribe to changes on the ReactiveList and add the value to a ListView.
The problem: The ListView updates for a few seconds and then just stops.
Console still logs inputs.
Sample code below:
<!-- xaml layouts omitted for brevity -->
<Entry x:Name="searchbox" HorizontalOptions="FillAndExpand"
Text="{Binding SearchQuery, Mode=TwoWay}"
/>
<ListView x:Name="ResultView">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Address}"></TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel + SearchResult class:
public class SearchViewModel : ReactiveObject
{
public ReactiveList<SearchResult> SearchResults { get; set; }
private string searchQuery;
public string SearchQuery
{
get { return searchQuery; }
set { this.RaiseAndSetIfChanged(ref searchQuery, value); }
}
public ReactiveCommand<List<SearchResult>> Search { get; set; }
public SearchViewModel()
{
// Set up our ListView data list
this.SearchResults = new ReactiveList<SearchResult>();
this.SearchResults.ChangeTrackingEnabled = true;
Search = ReactiveCommand.CreateAsyncTask(async _ => {
return await GenerateSearchResultAsync(this.SearchQuery);
});
Search.Subscribe(results => {
SearchResults.Clear(); // just replace output every time
SearchResults.AddRange(results);
// output results to console
results.ForEach(r => Console.WriteLine(r.Address));
});
// this used to contain a condition I removed for brevity
this.WhenAnyValue(x => x.SearchQuery).InvokeCommand(this, x => x.Search);
}
// create a new result list and return it, async code removed for demo-simplicity
private static async Task<List<SearchResult>> GenerateSearchResultAsync(string value)
{
var rv = new List<SearchResult>();
rv.Add(new SearchResult(value + " " + DateTime.Now.ToString("hh:mm:ss.FFFF")));
return rv;
}
}
public class SearchResult
{
private string address;
public SearchResult(string s)
{
this.Address = s;
}
public string Address { get; set; }
}
This seems to be a bug when WeakReferences are mistakenly collected during GC.
I'm not knowledgable enough to do the debugging, but some other smart guys did:
https://github.com/reactiveui/ReactiveUI/issues/806
https://bugzilla.xamarin.com/show_bug.cgi?id=31415 (open xamarin bug report)
Workaround:
Add this class to your project:
public class ReactiveObservableCollection<T> : ReactiveList<T>
{
public ObservableCollection<T> ObservableCollection { private set; get; }
public ReactiveObservableCollection()
{
this.ObservableCollection = new ObservableCollection<T>();
ItemsAdded.Subscribe(ObservableCollection.Add);
ItemsRemoved.Subscribe((x) => ObservableCollection.Remove(x));
}
}
and use it instead of the ReactiveList:
public class ViewModel : ReactiveObject {
public ReactiveObservableCollection<SearchResult> SearchResults { set; get; }
public ViewModel()
{
// use ReactiveObservableCollection instead of ReactiveList
this.SearchResults = new ReactiveObservableCollection<SearchResult>()
{
ChangeTrackingEnabled = true
};
}
Also, make sure to use the .ObservableCollection property on the collection as ItemSource (instead of just the collection)!
this.ResultView.ItemsSource = viewModel.SearchResults.ObservableCollection;
instead of
this.ResultView.ItemsSource = viewModel.SearchResults;
This should solve the problem.
HTH!

Create an ICommand Bindable property on Xamarin Forms

I have a custom checkbox control that I created with an ICommand property and the corresponding bindable property (my checkbox is a Xamarin.Forms XAML Page), the code is:
CheckBox.xaml
<Image x:Name="imgCheckBox"
WidthRequest="20"
HeightRequest="20"/>
<Label x:Name="lblCheckBox"
TextColor="Black"
VerticalOptions="CenterAndExpand"/>
<TapGestureRecognizer Tapped="OnCheckBoxTapped"/>
CheckBox.xaml.cs
public partial class CheckBox : ContentView
{
private static ImageSource uncheckedImage;
private static ImageSource checkedImage;
public CheckBox()
{
InitializeComponent();
uncheckedImage = ImageSource.FromResource("cbUnchecked.png");
checkedImage = ImageSource.FromResource("cbChecked.png");
imgCheckBox.Source = uncheckedImage;
}
public static readonly BindableProperty IsCheckedProperty =
BindableProperty.Create<CheckBox, bool>(
checkbox =>
checkbox.IsChecked,
false,
propertyChanged: (bindable, oldValue, newValue) =>
{
CheckBox checkbox = (CheckBox)bindable;
EventHandler<bool> eventHandler = checkbox.CheckedChanged;
if (eventHandler != null)
{
eventHandler(checkbox, newValue);
}
});
public bool IsChecked
{
set { SetValue(IsCheckedProperty, value); }
get { return (bool)GetValue(IsCheckedProperty); }
}
void OnCheckBoxTapped(object sender, EventArgs args)
{
IsChecked = !IsChecked;
if (IsChecked)
{
imgCheckBox.Source = checkedImage;
}
else
{
imgCheckBox.Source = uncheckedImage;
}
}
public static readonly BindableProperty CheckBoxCommandProperty =
BindableProperty.Create<CheckBox, ICommand>(
checkbox =>
checkbox.CheckBoxCommand,
null,
BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) =>
{
CheckBox checkbox = (CheckBox)bindable;
EventHandler<bool> eventHandler = checkbox.CheckedChanged;
if (eventHandler != null)
{
eventHandler(checkbox, checkbox.IsChecked);
}
});
public event EventHandler<bool> CheckedChanged;
public ICommand CheckBoxCommand
{
get { return (ICommand)GetValue(CheckBoxCommandProperty); }
set { SetValue(CheckBoxCommandProperty, value); }
}
}
This checkbox implementation is on another Page called TermsAndConditionsPage, that is also a a Xamarin.Forms XAML Page, the code of the implementation is:
<toolkit:CheckBox Text="{Binding txtCheckBox}"
FontSize="Small"
CheckBoxCommand="{Binding OnCheckBoxTapChanged}"
IsChecked="{Binding IsCheckedChanged, Mode=TwoWay}"/>
<Button Text="Next"
Command="{Binding Next_OnClick}"
IsEnabled="{Binding Next_IsEnabled}"
HorizontalOptions="CenterAndExpand"
Clicked="OnNextClicked"/>
The Code Behind of this page is empty (Constructur with InitializeComponent()).
I also have the ViewModel of this page with this code:
TermsAndConditionsViewModel.cs
private string _txtCheckBox;
public string txtCheckBox
{ get { return _txtCheckBox; }
set
{
_txtCheckBox = value;
OnPropertyChanged("txtCheckBox");
}
}
private bool _Next_IsEnabled;
public bool Next_IsEnabled
{
get { return _Next_IsEnabled; }
set
{
_Next_IsEnabled = value;
OnPropertyChanged("Next_IsEnabled");
}
}
private bool _IsCheckedChanged;
public bool IsCheckedChanged
{
get { return _IsCheckedChanged; }
set
{
_IsCheckedChanged = value;
OnPropertyChanged("IsCheckedChanged");
}
}
public ICommand Next_OnClick { get; set; }
public ICommand OnCheckBoxTapChanged { get; set; }
public TermsAndConditionsViewModel()
{
txtCheckBox = "I agree with the terms and conditions";
Next_OnClick = new Command(NextClicked);
OnCheckBoxTapChanged = new Command(CheckBoxTapped);
}
private void CheckBoxTapped()
{
if (IsCheckedChanged)
{ Next_IsEnabled = true; }
else
{ Next_IsEnabled = false; }
}
private void NextClicked()
{ App.Current.MainPage = new Views.HelloWorld(); }
#region INPC
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{ PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
Now, the question time: the problem I'm having is the CheckBoxTapped Command is not working, I mean, it doesn't do anything, although the checkbox image changes every time I touch it, it does not change the Next_IsEnabled property of my button. I'd like to know what I am missing here to make this command work properly.
EDIT
What I'm looking for is a Command that behaves similarly to the one that Buttons have.
Thanks all for your time!
Since the original answer is now obsolete, here is the new method:
using System.Windows.Input;
public partial class MyControlExample : ContentView
{
// BindableProperty implementation
public static readonly BindableProperty CommandProperty =
BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(MyControlExample), null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// Helper method for invoking commands safely
public static void Execute(ICommand command)
{
if (command == null) return;
if (command.CanExecute(null))
{
command.Execute(null);
}
}
public MyControlExample()
{
InitializeComponent();
}
// this is the command that gets bound by the control in the view
// (ie. a Button, TapRecognizer, or MR.Gestures)
public Command OnTap => new Command(() => Execute(Command));
}
Something like that (pseudocode):
public class YourClassName : View
{
public YourClassName()
{
var gestureRecognizer = new TapGestureRecognizer();
gestureRecognizer.Tapped += (s, e) => {
if (Command != null && Command.CanExecute(null)) {
Command.Execute(null);
}
};
var label = new Label();
label.GestureRecognizers.Add(gestureRecognizer);
}
public static readonly BindableProperty CommandProperty =
BindableProperty.Create<YourClassName, ICommand>(x => x.Command, null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}

Xamarin.Forms PCL MVVM Light > Custom Control > Best Practice?

Hy
I would like to share my approach for a custom xamarin.forms control within a Xamarin PCL Project with MVVM-Light. What do you think about it?
Custom Control -> PersonPanel.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"
x:Class="xxx.PersonPanel">
<StackLayout Orientation="Vertical">
<Label x:Name="titleLabel" Text="{Binding TitleLabel}"/>
<Entry x:Name="filterText" Placeholder="{Binding FilterPlaceholderText}" Text="{Binding Filter.Lookup}" TextChanged="OnFilterTextChanged"/>
<Label x:Name="resultText" Text="{Binding ResultText}" IsVisible="{Binding ResultTextVisible}"/>
</StackLayout>
</ContentView>
Code-Behind -> PersonPanel.xaml.cs:
public partial class PersonPanel : ContentView
{
public PersonPanel()
{
InitializeComponent();
//Init ViewModel
BindingContext = ServiceLocator.Current.GetInstance<PersonPanelViewModel>();
}
private PersonPanelViewModel PersonPanelViewModel
{
get
{
return (PersonPanelViewModel)BindingContext;
}
}
public string TitleLabel
{
get
{
return PersonPanelViewModel.TitleLabel;
}
set
{
PersonPanelViewModel.TitleLabel = value;
}
}
public string FilterPlaceholderText
{
get
{
return PersonPanelViewModel.FilterPlaceholderText;
}
set
{
PersonPanelViewModel.FilterPlaceholderText = value;
}
}
private void OnFilterTextChanged(object sender, EventArgs e)
{
PersonPanelViewModel.SearchCommand.Execute(null);
}
}
ViewModel -> PersonPanelViewModel:
public class PersonPanelViewModel : ViewModelBase
{
private IPersonService _personService;
private decimal _personId = 0;
private string _titleLabel = string.Empty;
private string _filterPlaceholderText = string.Empty;
private string _resultText = string.Empty;
private bool _resultTextVisible = true;
public PersonPanelViewModel(IPersonService personService)
{
_personService = personService;
// Init Filter
Filter = new PersonFilter();
// Init Commands
SearchCommand = new RelayCommand(Search);
}
public ICommand SearchCommand { get; set; }
public PersonFilter Filter
{
get;
private set;
}
public string ResultText
{
get
{
return _resultText;
}
set
{
Set(() => ResultText, ref _resultText, value);
}
}
public bool ResultTextVisible
{
get
{
return _resultTextVisible;
}
set
{
Set(() => ResultTextVisible, ref _resultTextVisible, value);
}
}
public string FilterPlaceholderText
{
get
{
return _filterPlaceholderText;
}
set
{
Set(() => FilterPlaceholderText, ref _filterPlaceholderText, value);
}
}
public string TitleLabel
{
get
{
return _titleLabel;
}
set
{
Set(() => TitleLabel, ref _titleLabel, value);
}
}
public decimal PersonId
{
get
{
return _PersonId;
}
set
{
Set(() => PersonId, ref _PersonId, value);
}
}
private async void Search()
{
//Reset
ResultText = string.Empty;
ResultTextVisible = false;
PersonId = 0;
if (Filter.PersonLookup != null && Filter.PersonLookup.Length >= 3)
{
//Call to Person Service
List<PersonResult> Person = await _personService.FindpersonByFilter(Filter);
if (Person.Count == 1)
{
PersonId = Person[0].PersonId;
ResultText = Person[0].PersonName;
ResultTextVisible = true;
}
}
}
}
Using of Control in another 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:components="clr-namespace:xxx.ViewElements.Components"
x:Class="xxx.MainPage">
<StackLayout Orientation="Vertical">
<components:PersonPanel x:Name="personPanel" TitleLabel="Person" FilterPlaceholderText="Type your search criteria here..."/>
</StackLayout>
</ContentPage>
I'm using Autofac as the IOC Container.
What do you think about it? I am using MVVM the right way (it's very new to me)?
Is there a better way to deal with calling the Command from the Event (TextChanged) on the view?
What's about the properties in the Code-Behind (which do a routing to the ViewModel)?
Edit:
I'll try to describe, what I want to achieve:
Creating our own control (reusable in different views, cross-platform) -> PersonPanel.xaml
Written in XAML in our PCL Project with pure Xamarin.Forms controls in it
The control should have it's own commands (Search) and properties
One of the commands is using a Service
The control should get the Service (as an Interface) injected through IOC
The Service itself is also implemented in the PCL Project and makes a REST Call to a Webservice
The Result of the Call is than set to a property of the control -> ResultText Property
The Result is visible to the User
-> With the above implementation, all of that works, but I'm not sure if this is the right way...
Thanks for your help!
Kind regards,
Peter
The approach for mapping the event to the command is exactly as I would perform.
The rest is a little bit confusing. The general pattern is to create bindable properties in your control that are exposed to the view model when instantiated within the host view. A very basic sample structure is below:
public class TestLabelControl : Label
{
public static readonly BindableProperty TestTitleProperty = BindableProperty.Create< TestLabelControl, string> (p => p.TestTitle, null);
public string TestTitle {
get {
return (object)GetValue (TestTitleProperty);
}
set {
SetValue (TestTitleProperty, value);
}
}
}
public class TestContentPage : ContentPage
{
public TestContentPage()
{
var testLabel = new TestLabel();
testLabel.SetBinding<TestContentPageViewModel>(TestLabel.TestTitleProperty, vm => vm.TestLabelTitle, BindingMode.Default);
Content = testLabel;
}
}
public class TestContentPageViewModel
{
public string TestLabelTitle{get;set;}
public TestContentPageViewModel()
{
TestLabelTitle = "Something random";
}
}
You would then create the native renderers to handle the drawing on each platform.
By following this approach you keep the code separated and concise. It does seem a slightly long winded way of getting things done but it is highly scalable and configurable.

Resources