Inside my Xamarin application I have added a Picker control to a page. The setup of that Picker looks like this in xaml:
<controls:ExtendedPicker x:Name="GenderPicker" Style="{StaticResource PickerControl}" Margin="0,0,0,20"
ItemsSource="{Binding GenderItems}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding Gender, Mode=TwoWay}" />
In my ViewModel I have this code to populate the picker with some values:
public class GenderItem
{
public string Name { get; set; }
}
public class RegisterViewModel : BaseViewModel
{
private DateTime _birthdate;
public DateTime Birthdate { get => _birthdate; set => SetProperty(ref _birthdate, value); }
public GenderItem Gender { get => _gender; set => SetProperty(ref _gender, value); }
public List<GenderItem> GenderItems { get; set; } = new List<GenderItem>
{
new GenderItem { Naam = "Male" },
new GenderItem { Naam = "Female" },
new GenderItem { Naam = "Confused" }
};
public RegisterViewModel()
{
// This doesn't work
Gender = new GenderItem { Naam = "Female" };
// This works
Birthdate = DateTime.Today.AddYears(-40);
}
}
From within the constructor I try to set a default selected value for the Picker control. But for some reason the value isn't selected in the Picker.
I also set a datetime property with a value and I do see that this value is picked up by the Date control.
Why isn't this working for the Picker control? What can I do to make this work?
SetProperty method
The SetProperty method lives inside my BaseViewModel class. You get this with every new Xamarin project.
public class BaseViewModel : INotifyPropertyChanged
{
public IDataStore<Item> DataStore => DependencyService.Get<IDataStore<Item>>();
bool _isBusy = false;
public bool IsBusy
{
get => _isBusy;
set => SetProperty(ref _isBusy, value);
}
string _title = string.Empty;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Gender needs to be an element in GenderItems. Right now you are creating a new object that has the same value as an item in GenderItems, but it is not the same object
Related
I have a CalendarioView from this Xamarin.Plugin.Calendar nuget package.
I've been following this tutorial, and I want to have the same result. Instead of assigning the EventCollection list manually, as in the example, I have my List.
How to fill it in the EventCollection? I've searched and didn't find anything that worked.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:c ="clr-namespace:Minha_Carteira_Hospitalar.Controls"
x:Class="Minha_Carteira_Hospitalar.Views.PlanoReceita"
xmlns:controls="clr-namespace:Xamarin.Plugin.Calendar.Controls;assembly=Xamarin.Plugin.Calendar">
<controls:Calendar
Events="{Binding Events}"
>
<controls:Calendar.EventTemplate>
<DataTemplate>
<StackLayout Padding="15,0,0,0">
<Label
Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Medium" />
</StackLayout>
</DataTemplate>
</controls:Calendar.EventTemplate>
</controls:Calendar>
MVVM code
public EventCollection Events;
public ObservableCollection<Plans> myPlans= new ObservableCollection<Plans>();
public ObservableCollection<Plans> MyPlans
{
get => myPlans;
set => myPlans= value;
}
public MyPlansViewModel()
{
Events = new EventCollection();
}
public ICommand LoadingMyPlans
{
get
{
return new Command(async () =>
{
try
{
List<Plans> tmp = await App.Database.GetMyPlans();
foreach(var item in tmp)
{
MyPlans.Clear();
tmp.ForEach(i => MyPlans.Add(i));
Events.Add(item.DatePlan, MyPlans);
}
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Error", ex.Message, "OK");
}
});
}
}
I am not sure where you use the LoadingMyPlans for. I make a simple example about how to fill your own list into EventCollection for your reference.
The same Xaml as yours.
Model:
public class Plans
{
public DateTime dateTime { get; set; }
public List<Plan> plans { get; set; }
}
public class Plan
{
public string Name { get; set; }
public string Desc { get; set; }
}
ViewModel:
public class CalendarViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
{
return false;
}
storage = value;
OnPropertyChanged(propertyName);
return true;
}
public EventCollection Events { get; set; }
public ObservableCollection<Plans> plans { get; set; }
public CalendarViewModel()
{
plans = new ObservableCollection<Plans>()
{
new Plans(){ dateTime=DateTime.Now, plans=new List<Plan>() { new Plan() { Name = "Plan_A", Desc = "aaaaa" }, new Plan() { Name = "Plan_A2", Desc = "aaaaa2" } }},
new Plans(){ dateTime=DateTime.Now.AddDays(5), plans=new List<Plan>() { new Plan() { Name = "Plan_B", Desc = "bbbbb" }, new Plan() { Name = "Plan_B2", Desc = "aaaaa2" } }},
new Plans(){ dateTime=DateTime.Now.AddDays(-3), plans=new List<Plan>() { new Plan() { Name = "Plan_C", Desc = "ccccc" }}}
};
Events = new EventCollection();
foreach (var item in plans)
{
Events.Add(item.dateTime, item.plans);
}
}
}
Code behind:
public Page2()
{
InitializeComponent();
this.BindingContext = new CalendarViewModel();
}
got a question on how to convert this code behind into a mvvm style, Here's a sample
void CameraView_MediaCaptured(object sender, MediaCapturedEventArgs e)
{
switch (cameraView.CaptureMode)
{
default:
case CameraCaptureMode.Default:
case CameraCaptureMode.Photo:
previewVideo.IsVisible = false;
previewPicture.IsVisible = true;
previewPicture.Rotation = e.Rotation;
previewPicture.Source = e.Image;
doCameraThings.Text = "Snap Picture";
break;
case CameraCaptureMode.Video:
previewPicture.IsVisible = false;
previewVideo.IsVisible = true;
previewVideo.Source = e.Video;
doCameraThings.Text = "Start Recording";
break;
}
}
A combination of Jason's and Karas's answer .
Bind with the corresponding properties between xaml and viewmodel .
//xaml
IsVisible = "{Binding IsVisible}"
Implement INotifyPropertyChanged inside viewmodel .
public class ViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool isVisible;
public bool IsVisible {
get
{
return isVisible;
}
set
{
isVisible = value;
NotifyPropertyChanged();
}
}
Replace the event with EventToCommandBehavior and change the properties value.
//xaml
<xct:EventToCommandBehavior
EventName="MediaCaptured"
Command="{Binding MyCommand}" />
//viewmodel
public ICommand MyCommand { get; set; }
public ViewModel()
{
MyCommand = new Command((obj)=> {
var cameraView = obj as CameraView;
switch (cameraView.CaptureMode)
{
default:
case CameraCaptureMode.Default:
case CameraCaptureMode.Photo:
IsVisible = false;
break;
case CameraCaptureMode.Video:
IsVisible = false;
break;
}
});
}
First you create a BaseViewModel, which you can now inherit on every other ViewModel. Then your ViewModel and bind it via BindingContext = new YourPageVM() to your page via YourPage.cs. You can now create properties in your ViewModel and Binding them in the XAML. e.g .:
Here Is the BaseViewModel:
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string
propertyName =
"")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
}
//In Your VM:
public class YourPageVM : BaseViewModel
{
bool isVisPreVideo;
public bool IsVisPreVideo{
get=> isVisPreVideo;
set=> SetProperty(ref isVisPreVideo,value);}
//set the Value in Constructor or in Your Method
public YourPageVM()
{
IsVisPreVideo = false;
}
//........
}
//At Xaml:
xmlns:viewmodel="clr-namespace:YourProject.ViewModel"
x:DataType="viewmodel:YourPageVM"
IsVisible = "{Binding IsVisPreVideo}"
And this you can do also with the other values Rotation, Source and Text.
I am trying to make simple app which will provide features to read/write data to database trough an Web API.
I have view model which is bind to view, but it is not working properly after web api get request, even that call was successfully done. I've tried to check value with display alert, value is correct, but it is not presented in view part, exactly in one label. Here is my code:
<?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="SOSEUApp.Pages.NotePage"
Title="Dnevnik">
<ContentPage.ToolbarItems>
<ToolbarItem Text="GET" Clicked="ToolbarItem_Clicked"></ToolbarItem>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}"></ActivityIndicator>
<StackLayout Orientation="Vertical">
<Label Text="{Binding Date,StringFormat='Date: {0}'}"></Label>
</StackLayout>
<StackLayout>
</StackLayout>
</StackLayout>
</ContentPage.Content>
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class NotePage : ContentPage
{
NoteViewModel nvm = new NoteViewModel();
public NotePage()
{
InitializeComponent();
BindingContext = nvm;
}
private async void ToolbarItem_Clicked(object sender, EventArgs e)
{
nvm.IsBusy = true;
nvm.Notes = await App.NotesWebApiService.GetAll(nvm.CurrentActiveNote.Route);
nvm.GetLastNote();
nvm.IsBusy = false;
await DisplayAlert("Info", nvm.Date.ToString(), "Close");
}
}
public class NoteViewModel : BaseViewModel
{
IList<Note> notes = new List<Note>();
public IList<Note> Notes
{
get { return notes; }
set { SetProperty(ref notes, value); }
}
private Note currentActiveNote = new Note();
public Note CurrentActiveNote { get { return currentActiveNote; } }
public string Date { get { return currentActiveNote.Date.ToString("dd.MM.yyyy"); } }
public string OrderedNumber
{
get { return currentActiveNote.OrderNumber.ToString(); }
set
{
string v = currentActiveNote.OrderNumber.ToString();
SetProperty(ref v, value);
currentActiveNote.OrderNumber = Convert.ToInt16(v);
}
}
public string Description
{
get { return currentActiveNote.Description; }
set
{
string v = currentActiveNote.Description;
SetProperty(ref v, value);
currentActiveNote.Description = v;
}
}
public void GetLastNote()
{
notes.OrderBy(a => a.Date);
currentActiveNote = notes.Last();
}
}
public class BaseViewModel : DataModel, INotifyPropertyChanged
{
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Thank you in advance!
just called SetProperty method and passed desired values there. It works
Said as Jason, you need to fire a PropertyChanged event when Date changes.Here is official document for reference.
Generally, usually writed in Set methods.As follow:
private string propertyname;
public string PropertyName
{
set { SetProperty(ref propertyname, value); }
get { return propertyname; }
}
Or write as follow:
public string PropertyName
{
set
{
if (propertyname!= value)
{
propertyname= value;
OnPropertyChanged("PropertyName");
}
}
get
{
return propertyname;
}
}
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When data of model change , this will be invoked automatically.
I use the TimePicker and appear with a default value of "12:00 AM", I want to set the field in blank.
<TimePicker x:Name="timepicker" />
You must create your own class of TimePicker that will allow a NullableTime on XAML.
The Class should be as following:
public class MyTimePicker : TimePicker
{
private string _format = null;
public static readonly BindableProperty NullableDateProperty =
BindableProperty.Create<MyTimePicker, TimeSpan?>(p => p.NullableTime, null);
public TimeSpan? NullableTime
{
get { return (TimeSpan?)GetValue(NullableDateProperty); }
set { SetValue(NullableDateProperty, value); UpdateTime(); }
}
private void UpdateTime()
{
if (NullableTime.HasValue) { if (null != _format) Format = _format; Time = NullableTime.Value; }
else { _format = Format; Format = "pick ..."; }
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
UpdateTime();
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "Time") NullableTime = Time;
}
}
Then on XAML you should create your control and use it as below:
<local:MyTimePicker NullableTime="{x:Null}" />
If you use this sample you will see that the default value should be pick...
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); }
}
}