I have created a ContentView with a single Label (I plan to add more later).
PageHeadingView.xaml
<ContentView.Content>
<StackLayout Orientation="Vertical" BackgroundColor="Red">
<Label x:Name="HeadingLabel" Text="{Binding HeadingText}" />
</StackLayout>
</ContentView.Content>
I defined a BindableProperty in my code behind. I also set the BindingContext of my view to be itself.
PageHeadingView.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PageHeadingView : ContentView
{
public PageHeadingView()
{
InitializeComponent();
this.BindingContext = this;
}
public static readonly BindableProperty HeadingTextProperty = BindableProperty.Create(nameof(HeadingText), typeof(string), typeof(PageHeadingView), default(string));
public string HeadingText { get => (string)GetValue( HeadingTextProperty); set => SetValue(HeadingTextProperty, value); }
}
I then added the View to my ContentPage. I also added a test Label inside a StackLayout to ensure my bindings were working correctly.
HomePage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MyProject.Views"
x:Class="MyProject.HomePage">
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<views:PageHeadingView HeadingText="{Binding Name}" />
<Label Text="{Binding Name}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
And set my BindingContext in code.
HomePage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
//ViewModel contains a string property named: Name;
this.BindingContext = new ViewModel();
}
}
When I run my code, my PageHeadingView does not display any text. I can see the red background color, so I know the control has been added to the Page correctly. The test Label I placed in StackLayout also works correctly, and I am able to see the bound value.
What do I need to do to make my CustomView display Bindable content?
From your code, you may have some problems when using bindableproperty bidning, I create simple sample that you can take a look:
PageHeadingView.xaml:
<ContentView.Content>
<StackLayout BackgroundColor="Red" Orientation="Vertical">
<Label x:Name="HeadingLabel" />
</StackLayout>
</ContentView.Content>
PageHeadingView.cs, you can update HeadingText value by HeadingTextPropertyChanged, then display HeadingLabel.
public partial class PageHeadingView : ContentView
{
public static BindableProperty HeadingTextProperty= BindableProperty.Create(
propertyName: "HeadingText",
returnType: typeof(string),
declaringType: typeof(PageHeadingView),
defaultValue: "",
defaultBindingMode: BindingMode.OneWay,
propertyChanged: HeadingTextPropertyChanged);
public string HeadingText
{
get { return base.GetValue(HeadingTextProperty).ToString(); }
set { base.SetValue(HeadingTextProperty, value); }
}
private static void HeadingTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (PageHeadingView)bindable;
control.HeadingLabel.Text = newValue.ToString();
}
public PageHeadingView()
{
InitializeComponent();
}
}
<StackLayout>
<customcontrol:PageHeadingView HeadingText="{Binding Name}" />
<Label Text="{Binding Name}" />
</StackLayout>
You are binding the wrong element in PageHeadingView.
Option 1:
Add "Content" in PageHeadingView.xaml.cs
this.Content.BindingContext = this;
Option 2:
Remove "Content" in PageHeadingView.xaml
<!--<ContentView.Content>-->
<StackLayout Orientation="Vertical" BackgroundColor="Red">
<Label x:Name="HeadingLabel" Text="{Binding HeadingText}" />
</StackLayout>
<!--</ContentView.Content>-->
Edited as quoted the wrong class previously.
Related
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 resuable control like this to display a loading spinner:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Framework.Controls.Loading" x:Name="LoadingControl" IsVisible="{Binding LoadingIndicator}"
HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<ContentView.Content>
<ActivityIndicator HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" Color="DarkBlue"
IsVisible="{Binding LoadingIndicator}"
IsRunning="{Binding LoadingIndicator}">
</ActivityIndicator>
</ContentView.Content>
</ContentView>
I am trying to consume it on a page like this:
<controls:Loading LoadingIndicator="{Binding IsLoading}"></controls:Loading>
However, the loading spinner fails to appear on-screen.
When I set the LoadingIndicator property to true, it appears just fine:
<controls:Loading LoadingIndicator="true"></controls:Loading>
My 'IsLoading' binding is definitely working properly, because if I place the following code directly in my XAML page it also works fine:
<ActivityIndicator HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
Color="DarkBlue" IsVisible="{Binding IsLoading}" IsRunning="{Binding IsLoading}">
</ActivityIndicator>
Therefore, what is it about this that's wrong?
<controls:Loading LoadingIndicator="{Binding IsLoading}"></controls:Loading>
The 'IsLoading' property gets set on each of my pages from my view model. Here is a snippet from the view model:
public ICommand OnSave => new Command(async () =>
{
IsLoading = true;
await CreateItem();
IsLoading = false;
});
The code-behind for my control looks like this:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Loading : ContentView
{
public static readonly BindableProperty LoadingIndicatorProperty =
BindableProperty.Create(
propertyName: nameof(LoadingIndicator), typeof(bool),
typeof(Loading), default(string), BindingMode.OneWayToSource);
public bool LoadingIndicator
{
get => (bool)GetValue(LoadingIndicatorProperty);
set => SetValue(LoadingIndicatorProperty, value);
}
public Loading()
{
InitializeComponent();
BindingContext = this;
}
}
Do I need to write code to handle the change if the IsLoading binding gets updated?
This is the full code for the page where I am using the control:
ItemCreatePage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage Title="{Binding PageTitle}"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:userControls="clr-namespace:Framework.UserControls"
xmlns:converters="clr-namespace:Framework.ValueConverters"
xmlns:controls="clr-namespace:Framework.Controls;assembly=Framework.Android"
x:Class="Framework.Views.Item.ItemCreatePage">
<ContentPage.Resources>
<ResourceDictionary>
<converters:DoubleConverter x:Key="DoubleConverter"></converters:DoubleConverter>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<Grid>
<ScrollView>
<Grid RowSpacing="0" VerticalOptions="Start">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackLayout Grid.Row="1" Padding="20,20,20,0" VerticalOptions="Start">
<Label Text="Category" />
<userControls:BindablePicker
ItemsSource="{Binding Categories}"
SelectedItem="{Binding Path=Item.CategoryName, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=Item.CategoryId, Mode=TwoWay}"/>
<Label Text="Description" />
<Editor Text="{Binding Item.Description}" HeightRequest="100"/>
<Label Text="Area"/>
<Entry Text="{Binding Item.LineNumber}"/>
<Label Text="Identifier"/>
<Entry Text="{Binding Item.Identifier}"/>
<Label Text="Code"/>
<Entry Text="{Binding Item.Code}"/>
<Label Text="Priority" />
<userControls:BindablePicker
ItemsSource="{Binding Priorities}"
SelectedItem="{Binding Path=Item.ItemPriority, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=Item.ItemPriorityCode, Mode=TwoWay}"/>
<Label Text="Owner" />
<userControls:BindablePicker
ItemsSource="{Binding Users}"
SelectedItem="{Binding Path=Item.OwnerName, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=Item.OwnerId, Mode=TwoWay}"/>
<Label Text="Due Date" />
<DatePicker Date="{Binding Item.DateDue}" />
<Label Text="Date Identified" />
<DatePicker Date="{Binding Item.DateIdentified}" />
<Label Text="Status" />
<userControls:BindablePicker
ItemsSource="{Binding Statuses}"
SelectedItem="{Binding Path=Item.Status, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=Item.StatusCode, Mode=TwoWay}"/>
<Label Text="Comment" />
<Editor Text="{Binding Item.Comment}" HeightRequest="100"/>
<Label Text="IOM" />
<Entry Text="{Binding Item.OutcomeMeasurementInitial, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />
<Label Text="FOM" />
<Entry Text="{Binding Item.OutcomeMeasurementFinal, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />
<Label Text="Longitude" />
<Entry Text="{Binding Item.Longitude, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />
<Label Text="Latitude" />
<Entry Text="{Binding Item.Latitude, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />
<Button Margin="0,20,0,20" Command="{Binding OnSave}" BackgroundColor="{StaticResource Primary}"
BorderRadius="2" Text="Save" VerticalOptions="End" TextColor="White" ></Button>
</StackLayout>
</Grid>
</ScrollView>
<controls:Loading LoadingIndicator="{Binding IsLoading}"></controls:Loading>
</Grid>
</ContentPage.Content>
</ContentPage>
ItemCreatePage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ItemCreatePage : ContentPage
{
public ItemCreatePage ()
{
InitializeComponent ();
}
protected override async void OnAppearing()
{
var vm = BindingContext as ItemCreateViewModel;
vm.Item = new Data.Entities.Item();
await vm?.GetDeviceLocation();
base.OnAppearing();
}
}
The view model code:
public class ItemCreateViewModel : FormViewModel<Data.Entities.Item>
{
public async Task GetDeviceLocation()
{
this.Item = await this.Item.AddDeviceLocation();
OnPropertyChanged(nameof(this.Item));
}
public ILookupService LookupService { get; set; }
public IItemService ItemService { get; set; }
#region selectLists
public List<EnumListItem<ItemPriority>> Priorities => EnumExtensions.ToEnumList<ItemPriority>();
public List<EnumListItem<ItemStatus>> Statuses => EnumExtensions.ToEnumList<ItemStatus>();
public string PageTitle => $"{PageTitles.ItemCreate}{this.OfflineStatus}";
public List<Data.Entities.User> Users => UserService.GetAll(this.Offline);
public List<Data.Entities.Lookup> Categories => LookupService.GetLookups(this.Offline, LookupTypeCode.ItemCategories);
#endregion
public Data.Entities.Item Item { get; set; }
public ICommand OnSave => new Command(async () =>
{
await Loading(CreateItem);
});
private async Task CreateItem()
{
// ... Save logic is here
}
FormViewModel:
public class FormViewModel<T> : BaseViewModel
{
public IValidator<T> Validator => Resolve<IValidator<T>>();
public bool IsLoading { get; set; }
/// <summary>
/// Render a loading spinner whilst we process a request
/// </summary>
/// <param name="method"></param>
/// <returns></returns>
public async Task Loading(Func<Task> method)
{
IsLoading = true;
await method.Invoke();
IsLoading = false;
}
}
BaseViewModel:
public class BaseViewModel : IViewModelBase
{
public BaseViewModel()
{
if (this.GetCurrentUserToken() != null && !UserService.IsActive())
{
SettingService.ClearToken();
Bootstrapper.MasterDetailPage.IsPresented = false;
Application.Current.MainPage = new LoginPage();
}
}
public T Resolve<T>() => AutofacBootstrapper.Container.Resolve<T>();
public IUserService UserService => Resolve<IUserService>();
public INavigator Navigator => AutofacBootstrapper.Navigator;
public IDisplayAlertFactory DisplayAlert { get; set; }
public INavigation MasterNavigation => Bootstrapper.MasterDetailPage?.Detail?.Navigation;
public bool Offline => SettingService.GetSetting<bool>(CacheProperties.Offline);
public string OfflineStatus => this.Offline ? " - Offline" : string.Empty;
public Token GetCurrentUserToken() => SettingService.GetToken() ?? null;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
You don't need to set your custom control's BindingContext here:
public Loading()
{
InitializeComponent();
BindingContext = this;//It's wrong!
//because the custom control's BindingContext will
//automatically be set to the BindingContext of
//the page where it's used which is what we usually want.
}
Here is a way to achieve what you want:
Your Custom Control's XAML:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Framework.Controls.Loading" x:Name="LoadingControl"
HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<ContentView.Content>
<ActivityIndicator x:Name="TheIndicator" HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand" Color="DarkBlue"/>
</ContentView.Content>
</ContentView>
And here is its code-behind:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Loading : ContentView
{
public static readonly BindableProperty LoadingIndicatorProperty =
BindableProperty.Create(propertyName:nameof(LoadingIndicator),
returnType: typeof(bool), declaringType: typeof(Loading), defaultValue: default(bool),
defaultBindingMode:BindingMode.Default, propertyChanged:LoadingBindingChanged);
private static void LoadingBindingChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var view = (Loading)(bindable);
view.SetLoadingVisibility((bool)newvalue);
}
public Loading()
{
InitializeComponent();
IsVisible = false; // we do this because by default a view' IsVisible property is true
}
public bool LoadingIndicator
{
get => (bool)GetValue(LoadingIndicatorProperty);
set => SetValue(LoadingIndicatorProperty, value);
}
public void SetLoadingVisibility(bool show)
{
IsVisible = show;
TheIndicator.IsVisible = show;
TheIndicator.IsRunning = show;
}
}
You are not invoking PropertyChanged event when you change IsLoading property. If you want UI to refresh you need to invoke this event for the chosen property.
Change implementation of IsLoading property to:
private bool _isLoading;
public bool IsLoading
{
get=> _isLoading;
set
{
_isLoading=value;
OnPropertyChanged(nameof(IsLoading));
}
}
and it should work
I have a Switch bound to a property of an element in a List. I want to bind IsVisible of a button to the same property, but the button's visibility is not changed when the property is changed by the Switch. What am I missing?
XAML:
<StackLayout>
<ListView HorizontalOptions="FillAndExpand" ItemsSource="{Binding EquipmentList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" />
<Switch IsToggled="{Binding State}" />
<Button
Command="{Binding BindingContext.DoCommand, Source={x:Reference TestPage}}"
CommandParameter="{Binding .}"
IsVisible="{Binding State}"
Text="Click" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
ViewModel:
private Command<Equipment> _doCommand;
public Command<Equipment> DoCommand => _doCommand ??
(_doCommand = new Command<Equipment>((Equipment obj) => HandleEquipment(obj)));
// Outputs correct Name and State of the list item
private void HandleEquipment(Equipment obj)
{
System.Diagnostics.Debug.WriteLine(obj.Name + ", " + obj.State);
}
Model:
class Equipment
{
public int Id { get; set; }
public string Name { get; set; }
public bool State { get; set; }
public Equipment(int Id, string Name, bool State)
{
this.Id = Id;
this.Name = Name;
this.State = State;
}
}
As Gerald wrote in his first comment: You have to implement the INotifyPropertyChanged interface on your Equipment model (and not just in the ViewModel).
Without this implementation, the elements in the view have no chance to know, that the state changed (in your case the button).
Implementation:
public class Equipment: INotifyPropertyChanged
{
public bool State
{
get => _state;
set =>
{
_state = value;
OnPropertyChanged();
}
}
private bool _state;
// OTHER PROPERTIES
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The call of the method OnPropertyChanged() is important. The IsVisible property of the button recognizes the change and updates his value.
Instead of binding two things to a property, why not have the single item bound (i.e. the switch) and use XAML to show or hide the button:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibility" />
</Window.Resources>
<StackLayout>
<ListView HorizontalOptions="FillAndExpand" ItemsSource="{Binding EquipmentList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" />
<Switch Name="toggleSwitch" IsToggled="{Binding State}" />
<Button
Command="{Binding BindingContext.DoCommand, Source={x:Reference TestPage}}"
CommandParameter="{Binding .}"
IsVisible="{Binding ElementName=toggleSwitch, Path=IsToggled, Converter={StaticResource BooleanToVisibilityConverter}"
Text="Click" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
It may not be a Window that your StackLayout is in, but if you place a BooleanToVisibilityConverter in your Resources section you'll then be able to use it in your XAML file.
This will mean that if the property name changes in the future you only have one place you need to update in the user interface and you're also using the power of the XAML language.
Also as correctly pointed out by everyone, you need to implement INotifyPropertyChanged in the model in order for the Switch to be updated too.
I am working with xamarin forms and facing problems with the layout. Now, with the fontsize..
I have two labels and one of them is Micro. I need that the other get the size of the Micro label / 2 as its size...I am reading about relative layout, but I don't know if it's the best way to do that...does someone have any idea to help me?
This is my labels:
<StackLayout Spacing="0">
<Label x:Name="menu_lbl_promocoes" Text="0000" FontAttributes="Bold" TextColor="Black" HorizontalOptions="Center" Style="{Binding labelsfont}"/>
<Label x:Name="menu_lbl_disponiveis" Text="Disponíveis" TextColor="Black" HorizontalOptions="Center" FontSize="Small" Style="{Binding labelsfont}"/>
</StackLayout>
</StackLayout> //yeah, there is another stacklayout
<Label Text="Promoções" FontSize="Micro" TextColor="White" HorizontalOptions="Center" Style="{Binding labelsfont}"/>
I need the second label get the half size of the third (which has a micro size)...
Simply extend Label to have two bindable properties - FontSizeFactor and NamedFontSize - and have them calculate the font size for you:
public class MyLabel : Label
{
public static readonly BindableProperty FontSizeFactorProperty =
BindableProperty.Create(
"FontSizeFactor", typeof(double), typeof(MyLabel),
defaultValue: 1.0, propertyChanged: OnFontSizeFactorChanged);
public double FontSizeFactor
{
get { return (double)GetValue(FontSizeFactorProperty); }
set { SetValue(FontSizeFactorProperty, value); }
}
private static void OnFontSizeFactorChanged(BindableObject bindable, object oldValue, object newValue)
{
((MyLabel)bindable).OnFontSizeChangedImpl();
}
public static readonly BindableProperty NamedFontSizeProperty =
BindableProperty.Create(
"NamedFontSize", typeof(NamedSize), typeof(MyLabel),
defaultValue: NamedSize.Small, propertyChanged: OnNamedFontSizeChanged);
public NamedSize NamedFontSize
{
get { return (NamedSize)GetValue(NamedFontSizeProperty); }
set { SetValue(NamedFontSizeProperty, value); }
}
private static void OnNamedFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
{
((MyLabel)bindable).OnFontSizeChangedImpl();
}
protected virtual void OnFontSizeChangedImpl()
{
if (this.FontSizeFactor != 1)
this.FontSize = (this.FontSizeFactor * Device.GetNamedSize(NamedFontSize, typeof(Label)));
}
}
Sample Usage:
<Label FontSize="Large" Text="Large Size" />
<local:MyLabel NamedFontSize="Large" FontSizeFactor="0.9" Text="90% Large Size" />
<Label FontSize="Medium" Text="Medium Size" />
<local:MyLabel NamedFontSize="Medium" FontSizeFactor="0.75" Text="75% Medium Size" />
<Label FontSize="Micro" Text="Micro Size" />
<local:MyLabel NamedFontSize="Micro" FontSizeFactor="0.5" Text="50% Micro Size" />
So ima give an example on how to do it:
We got 2 labels.
First one with micro
<Label x:Name="statuslabel" FontSize="Micro" Text="Filter status:" VerticalOptions="Center" WidthRequest="100" />
Second one without size
Than in the codein the constructur, at the last line you write:
typelabel.FontSize = statuslabel.FontSize / 2;
the result:
I would like to bind an Entry with a Slider and vice versa. I wrote something like this:
<Entry x:Name="myEntry" Text="{Binding Value, Mode=TwoWay}" BindingContext="{x:Reference slider}"/>
<Slider x:Name="slider" Maximum="100" Minimum="0" BindingContext="{x:Reference myEntry}"/>
When I use the slider, the value in the entry is updated, but when I put manually some value in the Entry, the value append a 0 or change to 0. What can be the problem. I am working on android.
You should bind both your Slider and Entry to string/ integer in a backing View Model.
class MyViewModel
{
private int _sliderValue;
public string EntryText
{
get => _sliderValue.ToString();
set => SetProperty(ref _sliderValue, int.Parse(value) );
}
public int SliderValue
{
get => _sliderValue;
set => (ref _sliderValue, value);
}
}
And in the view
<Entry Text="{Binding EntryText}" />
<Slider Value="{Binding SliderValue}" />
More MVVM Info
Fresh MVVM for Xamarin
Caliburn Micro for Xamarin
please refer the following xaml code
<Frame HorizontalOptions="FillAndExpand" VerticalOptions="StartAndExpand">
<StackLayout>
<Entry Text="{Binding Path=Value}"
FontSize="18"
x:Name="label"
BindingContext="{x:Reference Name=slider}"/>
<Slider x:Name="slider"
Maximum="1500"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</Frame>
The same can be achieved by using the view model please refer the code
Xaml
<Frame HorizontalOptions="FillAndExpand" VerticalOptions="StartAndExpand">
<StackLayout>
<Entry x:Name="NameEntry" Placeholder="Enter Name" Text="{Binding Forename,Mode=TwoWay}" />
<Slider Value="{Binding Forename}" Minimum="0" Maximum="10"/>
</StackLayout>
</Frame>
------------------- now see the model in a c# file-----------------------------------
public partial class MainPage : ContentPage
{
public class DetailsViewModel : INotifyPropertyChanged
{
int forename;
public int Forename
{
get
{
return forename;
}
set
{
if (forename != value)
{
forename = value;
OnPropertyChanged ("Forename");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public MainPage()
{
InitializeComponent();
BindingContext = new DetailsViewModel();
}
}