I am using Xamarin Cross Platform and Windows 10.
Trying to add behavior on entry. Had class Behaviors:
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace CoTraveller
{
public class Behaviors
{
public class NumericValidationBehavior : Behavior<Entry>
{
protected override void OnAttachedTo(Entry entry)
{
entry.TextChanged += OnEntryTextChanged;
base.OnAttachedTo(entry);
}
protected override void OnDetachingFrom(Entry entry)
{
entry.TextChanged -= OnEntryTextChanged;
base.OnDetachingFrom(entry);
}
public void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
double result;
bool isValid = double.TryParse(args.NewTextValue, out result);
((Entry)sender).TextColor = isValid ? Color.Default : Color.Red;
}
}
}
}
Xaml page where I want to use this class method:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:CoTraveller"
x:Class="CoTraveller.RegistrationPage">
<ContentPage.Content>
<ScrollView>
<StackLayout VerticalOptions="CenterAndExpand" Padding="5">
<StackLayout Orientation="Horizontal">
<Entry x:Name="name_entry" Placeholder="First Name">
<Entry.Behaviors>
<local:NumericValidationBehavior />
</Entry.Behaviors>
</Entry>
<Entry x:Name="surname_entry" Placeholder="Surname"></Entry>
</StackLayout>
<Entry Placeholder="Login"></Entry>
<StackLayout Orientation="Horizontal">
<Entry x:Name="pass_entry" Placeholder="Password" IsPassword="True"></Entry>
<Entry x:Name="pass_confirm_entry" Placeholder="Confirm Password" IsPassword="True"></Entry>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Save Password"></Label>
<Switch IsToggled="False"></Switch>
</StackLayout>
<Label Text="Date Of Birth"></Label>
<DatePicker x:Name="birthday_dp"></DatePicker>
<Picker x:Name="sex_p" HorizontalOptions="FillAndExpand" Title="Click and select gender">
<Picker.Items>
<x:String>Male</x:String>
<x:String>Female</x:String>
</Picker.Items>
</Picker>
<Entry Placeholder="Email (optional)"></Entry>
<Entry Placeholder="Phone number (optional)"></Entry>
<Picker x:Name="type_entry" HorizontalOptions="FillAndExpand" Title="Click and select user type">
<Picker.Items>
<x:String>Driver</x:String>
<x:String>Pedestrian</x:String>
</Picker.Items>
</Picker>
<Button Text="Sign Up" Clicked="SignUpBtn"></Button>
<Label x:Name="sign_in_lbl" Text="Already have account? Sign In" TextColor="Blue"></Label>
</StackLayout>
</ScrollView>
</ContentPage.Content> </ContentPage>
Both are in the same namespace and CoTraveller is the name of my project.
I had errors like:
Severity Code Description Project File Line Suppression State
Error Position 12:30. Type local:NumericValidationBehavior not found in xmlns clr-namespace:CoTraveller CoTraveller D:\CoTraveller\CoTraveller\CoTraveller\RegistrationPage.xaml 12
Severity Code Description Project File Line Suppression State
Error XLS0414 The type 'local:NumericValidationBehavior' was not found. Verify that you are not missing an assembly reference and that all referenced assemblies have been built. CoTraveller RegistrationPage.xaml 12
What is the reason? I declared namespace in XAML file, but smth is wrong...
The namespace declaration xmlns:local="clr-namespace:CoTraveller" is correct, but because the behavior is a nested type, you need to qualify the name with the outer type where you use it. Use <local:Behaviors.NumericValidationBehavior />.
Related
I'm trying to create something like this, so I wrote this code:
This is the 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="alphatrial.MainPage"
BackgroundColor="#ffc40c"
xmlns:local="clr-namespace:alphatrial.Effects">
<ContentPage.Effects>
<local:StatusBarEffect
BackgroundColor="#ffc40c"/>
</ContentPage.Effects>
<StackLayout >
<Label Text="ISC - Alpha" TextColor="Black" FontSize="Large" FontAttributes="Bold" FontFamily="MyAwesomeCustomFont" HorizontalOptions="End" Margin="0,50,50,-14" />
<Line Stroke="black" X1="0" X2="340" StrokeThickness="3" />
<Label Text="App" TextColor="White" FontSize="Large" FontAttributes="Bold" FontFamily="MyAwesomeCustomFont" HorizontalOptions="End" Margin="0,-12,15,0"/>
<Frame Margin="0,80,0,0" BackgroundColor="Transparent">
<StackLayout >
<Label Text="Email" TextColor="Black" FontSize="Small" Margin="25,0,20,0" x:Name="emailAnimate" BindingContext="email" />
<Entry Margin="20,-40,20,0" x:Name="email"/>
</StackLayout>
</Frame>
<Frame Margin="0,-10,0,0" BackgroundColor="Transparent">
<StackLayout >
<Label Text="Password" TextColor="Black" FontSize="Small" Margin="25,0,20,0" x:Name="PassAnimate" />
<Entry Margin="20,-40,20,0" x:Name="password" IsPassword="True"/>
</StackLayout>
</Frame>
</StackLayout>
</ContentPage>
And this is the xaml.cs file:
public MainPage()
{
InitializeComponent();
email.Focused += Email_Focused;
email.Unfocused += Email_Unfocused;
password.Focused += Password_Focused;
password.Unfocused += Password_Unfocused;
}
private void Password_Unfocused(object sender, FocusEventArgs e)
{
if (password.Text == "")
{
PassAnimate.TranslateTo(0, 0, 100);
PassAnimate.ScaleTo(1, 100);
}
}
private void Password_Focused(object sender, FocusEventArgs e)
{
if (password.IsFocused)
{
PassAnimate.ScaleTo(0.8, 150);
PassAnimate.TranslateTo(-25, -30, 150);
}
}
private void Email_Unfocused(object sender, FocusEventArgs e)
{
if(email.Text=="")
{
emailAnimate.TranslateTo(0, 0, 100);
emailAnimate.ScaleTo(1, 100);
}
}
private void Email_Focused(object sender, FocusEventArgs e)
{
if (email.IsFocused)
{
emailAnimate.ScaleTo(0.8,150);
emailAnimate.TranslateTo(-25, -30,150);
}
}
}
}
But I obtained this.
The unfocus function doesn't work when I remove the focus from the entry. However, when I write something in the entry, then clear it and remove the focus, the animation works fine. What is the problem?
Update:
when i removed the code that checks if entry is empty:
if(email.Text=="")
the animation worked. it seems that this is not the correct way to check if entry is empty. would you please give me an idea about how to know of entry is empty?
okay i figured it out, i have to check if the entry is empty using the following:
if (string.IsNullOrEmpty(email.Text) )
I need to set two ViewModels from the code behind in the xaml code. Or if there is better way doing would be great to.
When I do it like this way the application crashes. When I set ProductDetailViewModel in the code behind (BindingContext = ViewModel) everything works fine.
update
It's not an good idea to pass viewModels as parameters.
I have now one class "ViewModelLocator" which contains all the ViewModels as static properties. Use Google for more info. This way things are way easier.
example
ViewModelLocator
public static class ViewModelLocator
{
public static AddProductViewModel AddProductViewModel { get; set; } = new AddProductViewModel(App.ProductDataStore, App.NavigationService);
}
end update
update 2
As #Waescher stated, it's better to use FreshMvvm. The static approach is simple and fast but not good for slow devices or larger apps. Thanks.
end update 2
**Xamarin.Forms.Xaml.XamlParseException:** 'Position 9:10. Can not find the object referenced by `ProductDetailViewModel`'
Since I can't set the ViewModels directly in the xaml I need to do it by reference from code behind.
See < *** First ViewModel *** > and < *** Second ViewModel *** > in the xaml 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"
xmlns:controls="clr-namespace:BoerPlaza.Controls"
xmlns:flv="clr-namespace:DLToolkit.Forms.Controls;assembly=DLToolkit.Forms.Controls.FlowListView"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
x:Class="BoerPlaza.Views.Product.ProductCustomerPictures">
<ContentPage.BindingContext>
<x:Reference Name="ProductDetailViewModel" /><!-- *** First ViewModel ***!-->
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<!-- Total image count -->
<Label Text="{Binding Product.UserImages.Total}"
Style="{StaticResource H2}" />
<!-- Title -->
<Label Text="{Binding Product.Title}"
Style="{StaticResource H1}" />
<!-- reviews -->
<StackLayout Orientation="Horizontal">
<controls:StarDisplayTemplateView x:Name="customRattingBar"
SelectedStarValue="{Binding Product.RatingTotal}" />
<Label Text="{Binding Product.RatingAmount, StringFormat='{0} reviews | '}" />
<Label Text="Schrijf een review" />
</StackLayout>
<Label Text="{Binding Product.Title, StringFormat='Heb je een productfoto van {0} die je wilt delen? '}" />
<Button Text="Foto's toevoegen"
Command="{Binding SelectImagesCommand}"
BackgroundColor="{StaticResource neutral-color}"
BorderColor="{StaticResource alt-color}"
BorderWidth="1"
TextColor="{StaticResource primary-color}"
HorizontalOptions="Start"
HeightRequest="40"
FontSize="12" />
<!-- hr -->
<BoxView Style="{StaticResource separator}" />
<flv:FlowListView FlowColumnCount="3"
x:Name="listItems"
FlowItemsSource="{Binding Media}"
SeparatorVisibility="None"
HasUnevenRows="false"
RowHeight="100"
HeightRequest="0">
<flv:FlowListView.BindingContext>
<x:Reference Name="MultiMediaPickerViewModel" /> <!-- *** Second ViewModel ***!-->
</flv:FlowListView.BindingContext>
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<Grid>
<ffimageloading:CachedImage DownsampleToViewSize="true"
HeightRequest="100"
Source="{Binding PreviewPath}"
Aspect="AspectFill"
HorizontalOptions="FillAndExpand">
</ffimageloading:CachedImage>
<Image Source="play"
IsVisible="false"
HorizontalOptions="End"
VerticalOptions="End">
<Image.Triggers>
<DataTrigger TargetType="Image"
Binding="{Binding Type}"
Value="Video">
<Setter Property="IsVisible"
Value="True" />
</DataTrigger>
</Image.Triggers>
</Image>
</Grid>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Code behind:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ProductCustomerPictures : ContentPage
{
public ProductDetailViewModel ProductDetailViewModel
{
get { return _productDetailViewModel; }
set { _productDetailViewModel = value; }
}
public MultiMediaPickerViewModel MultiMediaPickerViewModel
{
get { return _multiMediaPickerViewModel; }
set { _multiMediaPickerViewModel = value; }
}
private ProductDetailViewModel _productDetailViewModel;
private MultiMediaPickerViewModel _multiMediaPickerViewModel;
public ProductCustomerPictures(ProductDetailViewModel viewModel)
{
InitializeComponent();
ProductDetailViewModel = viewModel;
MultiMediaPickerViewModel = new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
}
}
If I understood this correctly and if you want to keep the pattern to pass in the view model as constructor argument ...
public ProductCustomerPictures(ProductDetailViewModel viewModel)
{
InitializeComponent();
ProductDetailViewModel = viewModel;
MultiMediaPickerViewModel = new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
}
... then you can remove this completely ...
<ContentPage.BindingContext>
...
</ContentPage.BindingContext>
... and this property ...
public ProductDetailViewModel ProductDetailViewModel
{
get { return _productDetailViewModel; }
set { _productDetailViewModel = value; }
}
Instead, just set the BindingContext directly in the constructor.
public ProductCustomerPictures(ProductDetailViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel; // <-- here
MultiMediaPickerViewModel = new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
}
Now, each and every control in the XAML is binding to the ProductDetailViewModel.
But you still have the FlowListView which should bind to the MultiMediaPickerViewModel. Instead of setting its binding context directly in XAML, it is common to use the binding with a reference, but first you have to give the whole page a name with which we can refer in the binding:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
...
...
x:Name="thisPage" <--- here
x:Class="BoerPlaza.Views.Product.ProductCustomerPictures">
Now, you can use the name as reference in the binding expression:
<flv:FlowListView FlowColumnCount="3"
x:Name="listItems"
FlowItemsSource="{Binding Source={x:Reference thisPage}, Path=MultiMediaPickerViewModel.Media}"
SeparatorVisibility="None"
HasUnevenRows="false"
RowHeight="100"
HeightRequest="0">
"{Binding Source={x:Reference thisPage}, Path=MultiMediaPickerViewModel.Media}" uses the page itself (by name thisPage) and binds to the property Media of the property MultiMediaPickerViewModel of the page.
With that, you can safely remove this code as well:
<flv:FlowListView.BindingContext>
...
</flv:FlowListView.BindingContext>
By the way, you can condense the properties in the code behind:
public MultiMediaPickerViewModel MultiMediaPickerViewModel { get; private set; }
public ProductCustomerPictures(ProductDetailViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
MultiMediaPickerViewModel = new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
}
I have a question. I created the following TabbedPage:
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage 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:views="clr-namespace:MyApp.Views"
mc:Ignorable="d"
x:Class="MyApp.Views.MainPage"
xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
android:TabbedPage.ToolbarPlacement="Bottom"
BarBackgroundColor="White"
BarTextColor="Black"
android:TabbedPage.BarItemColor="#B2B2B2"
android:TabbedPage.BarSelectedItemColor="#56D7A5"
android:TabbedPage.IsSwipePagingEnabled="False">
<TabbedPage.Children>
<NavigationPage Title="page1" IconImageSource="navbar_page1">
<x:Arguments>
<views:page1 NavigationPage.HasNavigationBar="False" />
</x:Arguments>
</NavigationPage>
<NavigationPage Title="page2" IconImageSource="navbar_page2">
<x:Arguments>
<views:page2 NavigationPage.HasNavigationBar="False" />
</x:Arguments>
</NavigationPage>
<NavigationPage Title="page3" IconImageSource="navbar_page3">
<x:Arguments>
<views:page3 NavigationPage.HasNavigationBar="False" />
</x:Arguments>
</NavigationPage>
</TabbedPage>
Now on every page I have added this custom FabMenu like this:
<c:FloatingMenu Margin="0, 0, 10, 10" BGColor="#56D7A5" OpenIcon="openFab_icon" CloseIcon="closeFab_icon"
AbsoluteLayout.LayoutBounds=".95,.95" AbsoluteLayout.LayoutFlags="PositionProportional">
<c:FloatingButton x:Name="btnAddHomework" BGColor="#59E1FF" IconSrc="add_homework_icon" OnClickCommand="{Binding btnAddHomeworkCommand}" />
<c:FloatingButton x:Name="btnAddDeadline" BGColor="#0FF1A0" IconSrc="add_deadline_icon"/>
<c:FloatingButton x:Name="btnAddTest" BGColor="#5988FF" IconSrc="add_test_icon"/>
</c:FloatingMenu>
The problem is that every page has his own FabMenu, so you see it dissapear and reappear on every page, so my question is: Is there some kind of root view that overlays all the tabs in the TabbedPage?
Please let me know how I do that!
Disclaimer
I came up with a way to create the effect wanted using only pure Xamarin.Forms. Read along and pay attention to the tricky parts of the solution.
Abstract
This solution is achieved implementing AbsoluteLayout, CarouselView, IndicatorView and DataTemplateSelector. Xamarin.Forms 4.8 is supposed in what follows. If a lower version is used, please take into account that features like CarouselView or IndicatorView could be in Preview status.
DataTemplateSelector, CarouselView and IndicatorView are used to simulate a TabbedPage, and AbsoluteLayout is used to provide the Overlay.
So, now with the solution:
Create your Views
Here you create a view for each of the pages you want. In this example i want my application to consist of two pages, so i create two views (code behind remains untouched):
View1.xaml
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="overlayTest.View1"
BackgroundColor="Black">
<ContentView.Content>
<StackLayout>
<Label Text="Welcome to Xamarin.Forms 1!"
TextColor="White"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentView.Content>
</ContentView>
View2.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="overlayTest.View2">
<ContentView.Content>
<StackLayout>
<Label Text="Welcome to Xamarin.Forms 2!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentView.Content>
</ContentView>
Create a DataTemplateSelector
This will be used by the CarouselView in order to select one view or the other depending on the current Position.
using System;
using Xamarin.Forms;
namespace overlayTest
{
class MyTemplateSelector : DataTemplateSelector
{
readonly DataTemplate view1, view2;
public MyTemplateSelector()
{
view1 = new DataTemplate(typeof(View1));
view2 = new DataTemplate(typeof(View2));
}
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
String s = item.ToString();
if(s == "1")
{
return view1;
}
return view2;
}
}
}
Create your Main Page
Page1.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:t="clr-namespace:overlayTest"
x:Class="overlayTest.Page1">
<ContentPage.Resources>
<ResourceDictionary>
<t:MyTemplateSelector x:Key="templateSelector"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<AbsoluteLayout>
<StackLayout AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
Padding="0"
Spacing="0">
<CarouselView ItemTemplate="{StaticResource templateSelector}"
IndicatorView="indicatorView">
<CarouselView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>1</x:String>
<x:String>2</x:String>
</x:Array>
</CarouselView.ItemsSource>
</CarouselView>
<IndicatorView x:Name="indicatorView">
<IndicatorView.IndicatorTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="FillAndExpand">
<Frame Margin="10">
<Label/>
</Frame>
</StackLayout>
</DataTemplate>
</IndicatorView.IndicatorTemplate>
</IndicatorView>
</StackLayout>
<ContentView
IsVisible="True" VerticalOptions="Start"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="Transparent">
<Frame CornerRadius="10"
Margin="20"
VerticalOptions="StartAndExpand"
HorizontalOptions="CenterAndExpand" InputTransparent="False">
<StackLayout Padding="0">
<Label
FontSize="Medium"
TextColor="Black"/>
<StackLayout Orientation="Horizontal"
HorizontalOptions="CenterAndExpand">
<Label Text="I am floating here"/>
<Switch IsToggled="True" />
</StackLayout>
<Button Text="Save"
BackgroundColor="Accent"/>
</StackLayout>
</Frame>
</ContentView>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
And in the code behind we set the name of the tabs. Here please put attention in the fact that i am supposing an element tree of a StackLayout -> Frame -> Label. If you change the IndicatorTemplate, you will have to also modify this part of the code!
Page1.xaml.cs
using System.Linq;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace overlayTest
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Page1 : ContentPage
{
public Page1()
{
InitializeComponent();
indicatorView.PropertyChanged += (s, a) =>
{
if (a.PropertyName == IndicatorView.HeightProperty.PropertyName)
{
var indicators = indicatorView.IndicatorLayout.Children.ToList();
int counter = 0;
foreach(var indicator in indicators)
{
var indicatorBaseStack = (StackLayout)indicator;
var indicatorFrame = (Frame)indicatorBaseStack.Children[0];
var indicatorFrameLabel = (Label)indicatorFrame.Content;
indicatorFrameLabel.Text = counter == 0 ? "View1" : "View2";
counter++;
}
}
};
}
}
}
Finally set that Page to the MainPage property of App:
public App()
{
InitializeComponent();
MainPage = new Page1();
}
The final result looks like this:
As a workaround, you could set ToolbarItem of each ContentPage (or you can define a base ContentPage).
<ContentPage.ToolbarItems>
<ToolbarItem Text="Example Item"
IconImageSource="xxx.png"
Order="Secondary"
Clicked="{Binding xx}"
Priority="0" />
<ToolbarItem Text="Example Item"
IconImageSource="xxx.png"
Order="Secondary"
Priority="1" />
<ToolbarItem Text="Example Item"
IconImageSource="xxx.png"
Order="Secondary"
Priority="2" />
</ContentPage.ToolbarItems>
I recommend creating a BaseContentPage that includes a static FloatingButton. This allows every page to inherit from BaseContentPage and use the same FloatingButton.
Code
BaseContentPage
abstract class BaseContentPage : ContentPage
{
protected static Button Button { get; } = new Button { Text = $"This button was created at {DateTimeOffset.UtcNow}" }.Invoke(button => button.Clicked += HandleButtonClicked);
static async void HandleButtonClicked(object sender, EventArgs e) =>
await Application.Current.MainPage.DisplayAlert("Button Clicked", "This is the same button on both pages", "OK");
}
Example LabelPage
class LabelPage : BaseButtonPage
{
public LabelPage()
{
Title = "LabelPage";
Content = new StackLayout
{
Children =
{
new Label { Text = "Label Page" }.TextCenter().Center(),
Button
}
}
}
}
Example ButtonPage
class ButtonPage : BaseButtonPage
{
public ButtonPage()
{
Title = "ButtonPage";
Content = Button;
}
}
Example App
public class App : Application
{
public App()
{
Device.SetFlags(new[] { "Markup_Experimental" });
MainPage = new TabbedPage
{
Children =
{
new ButtonPage(),
new LabelPage()
}
};
}
}
Sample App
Here is the sample app used to create the attached GIF:
https://github.com/brminnick/TabbedPageButton/
I have this class:
public partial class Calendar : ContentPage, INotifyPropertyChanged
{
public ICommand EventSelectedCommand => new Command(async (item) => await ExecuteEventSelectedCommand(item));
public ICommand DayTappedCommand => new Command<DateTime>(async (date) => await DayTapped(date));
public EventCollection Events { get; set; }
private void SetCalendar()
{
Events = new EventCollection
{
[DateTime.Now] = new List<EventModel>
{
new EventModel { Name = "Herrenabend, 10:00 - 12:00 – 5000 Scanns", ID = 100},
};
}
and I wanna do bind the data to my calendar.
I tried this:
<Grid Grid.Row="1" Grid.Column="1">
<controls:Calendar
x:Name="calender"
DayTappedCommand="{Binding DayTappedCommand}"
Events="{Binding Q2go.MainMenu_Partners.Calendar.Events}">
<controls:Calendar.EventTemplate>
<DataTemplate>
<StackLayout>
<Label
Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Medium" >
<Label.GestureRecognizers>
<TapGestureRecognizer
Tapped="ClickOnLabel"
/>
</Label.GestureRecognizers>
</Label>
<Label
Grid.Row="1"
Text="{Binding ID}"
FontSize="8" >
<Label.GestureRecognizers>
<TapGestureRecognizer
Tapped="ClickOnLabel"
/>
</Label.GestureRecognizers>
</Label>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding BindingContext.EventSelectedCommand, Source={x:Reference test}}"
CommandParameter="{Binding .}" />
</StackLayout.GestureRecognizers>
</StackLayout>
</DataTemplate>
</controls:Calendar.EventTemplate>
</controls:Calendar>
</Grid>
But the data isn't given into the calendar and non of the events fire.
Where is the missing link here?
I thought by: Events="{Binding Q2go.MainMenu_Partners.Calendar.Events}">
I am doing everything correctly. but nothing is displayed. Why is that?
I know that if I just give the data to the calendar inside code (calender.events = events) it works just fine (still no event handlers) but i want it to bind the data from code. So without this line (calender.events = events).
I read in every sample that this should work, but it doesn't for me. So where is the issue?
if your page's BindingContext is set to this then you should just use Events="{Binding Events}" as the Binding expression
I am new to Xamarin Form Application development and Want to try a simple app that will get string from textfield and place it in label by data binding.
Text field with 20 px margin from both side and vertically center.
Label will be below text field.
When typing in textField, the label will update (MVVM)
UI design will be from XAML.
Thank you.
If you are using Xamarin Forms to achieve this and using DataBinding (MVVM), first in your ViewModel (We will call it MainPageViewModel.cs) you need something like this:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SandboxForms.ViewModels
{
public class MainPageViewModel : INotifyPropertyChanged
{
private string _myTextField;
public string MyTextField
{
get { return _myTextField; }
set
{
_myTextField = value;
OnPropertyChanged(nameof(MyTextField));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then in our ContentPage (We will call this one MainPage.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="SandboxForms.Pages.MainPage"
xmlns:viewmodels="clr-namespace:SandboxForms.ViewModels;SandboxForms">
<ContentPage.BindingContext>
<viewmodels:MainPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout Padding="20">
<!-- I am applying EndAndExpand to the entry and
StartAndExpand to the label to center them each other -->
<Entry
HorizontalOptions="FillAndExpand"
VerticalOptions="EndAndExpand"
Placeholder="Write here and see the magic!!!"
Text="{Binding MyTextField}"/>
<Label
HorizontalTextAlignment="End"
HorizontalOptions="FillAndExpand"
VerticalOptions="StartAndExpand"
Text="{Binding MyTextField}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Here is a few screenshots of the results:
Application starting,
Entering text on your Entry
Hope this works for you, my best regards!
There are two approaches for data binding each of which has merits depending on the situation. The first is MVVM as mentioned previously. This works well for fields that your ViewModel should know about, such as the text in an entry field but this isn't always the case and it's important to have a complete understanding before choosing the right method for your needs.
MVVM Approach
ViewModel
public class MyPageViewModel : INotifyPropertyChanged
{
private string myTextField;
public string MyTextField
{
get { return myTextField; }
set
{
if( !myTextField.Equals( value ) )
{
myTextField = value;
OnPropertyChanged("MyTextField");
}
}
}
}
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"
x:Class="SandboxForms.Pages.MainPage"
xmlns:viewmodels="clr-namespace:SandboxForms.ViewModels;SandboxForms">
<ContentPage.BindingContext>
<viewmodels:MainPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout Padding="20">
<!-- I am applying EndAndExpand to the entry and
StartAndExpand to the label to center them each other -->
<Entry
HorizontalOptions="FillAndExpand"
VerticalOptions="EndAndExpand"
Placeholder="Write here and see the magic!!!"
Text="{Binding MyTextField}"/>
<Label
HorizontalTextAlignment="End"
HorizontalOptions="FillAndExpand"
VerticalOptions="StartAndExpand"
Text="{Binding MyTextField}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
This is generally a preferred approach by most developers as opposed to mixing business logic directly in the code behind of your UI.
There are a number of helpers, and frameworks out there that you can look at if you aren't familiar with this. The following are some of the more popular ones.
MvvmHelpers - James Montemagno
Prism Library (my personal favorite)
Mvvm Cross
Mvvm Light
View Centric Approach
Sometimes it actually would violate the MVVM pattern to directly bind to a property of our ViewModel, and other times we may want to display something in our View without the need of updating a backing field in the ViewModel. As an example we can look at Xamarin's guide to data binding.
<?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="XamlSamples.SliderBindingsPage"
Title="Slider Bindings Page">
<StackLayout>
<Label Text="ROTATION"
BindingContext="{x:Reference Name=slider}"
Rotation="{Binding Path=Value}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
<Label BindingContext="{x:Reference slider}"
Text="{Binding Value,
StringFormat='The angle is {0:F0} degrees'}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
I should note that one of the most common times I would recommend using this approach is with Context Actions in a ListView, since our ViewModel may contain the Command that we want to execute on the individual cell, however the cell in which we are executing the context action actually is bound to the object from our IEnumerable<T> and not our ViewModel. In this particular case we would do something like the following:
<?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:Name="someListPage"
x:Class="MyApp.Views.SomeListPage">
<ListView ItemsSource="{Binding Gear}"
CachingStrategy="RecycleElement"
IsRefreshing="{Binding IsRefreshing}"
IsPullToRefreshEnabled="True"
RefreshCommand="{Binding RefreshCommand}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Description}" Detail="{Binding Detail}">
<TextCell.ContextActions>
<MenuItem Text="Remove"
Command="{Binding BindingContext.RemoveItemCommand,Source={x:Reference someListPage}}"
CommandParameter="{Binding .}"
IsDestructive="True" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
You'll notice that for this to work we first give the page itself a name that we can then reference for our binding for the ContextAction Command property. This is only changing where we are looking for this single property. We then resume using the normal binding context for the CommandParameter property and pass in the actual object the cell is bound to with {Binding .}
Hope this helps you better understand your options for binding with Xaml. Happy Coding!