I am trying to update the visibility of a stacklayout of a tabbed page on user interaction and it the inverse boolean doesn't seem to work right away. i have to goto another page and come back again to see the inverse boolean converter work properly. Can anyone suggest if i am missing anything.
XAML:
<page:ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:page="clr-namespace:My.Pages"
x:Class="Home"
xmlns:converter="clr-namespace:My.Pages.ValueConverters"
.....//....>
<StackLayout x:Name="stack1" Spacing="4" IsVisible="{Binding IsSomethingVisible, Converter={x:Static converter:InverseBoolConverter.Create}}">
<Label Text="Hello" IsVisible="{Binding IsSomethingelseVisible}"/>
<Label Text="Hi" IsVisible="{Binding IsSomethingelse2Visible}"/>
</StackLayout>
<StackLayout x:Name="stack2" Spacing="4" IsVisible="{Binding IsSomethingVisible}">
<Label Text="Hello" IsVisible="{Binding IsSomethingelseVisible}"/>
<Label Text="Hi" IsVisible="{Binding IsSomethingelse2Visible}"/>
</StackLayout>
Converter:
public class InverseBoolConverter : IValueConverter
{
public static InverseBoolConverter Create {
get {
return new InverseBoolConverter();
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !((bool)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return !((bool)value);
}
}
ViewModel:
private bool _isSomethingVisible;
public bool IsSomethingVisible
{
get { return _isSomethingVisible; }
set
{
_isSomethingVisible = value;
RaisePropertyChanged(nameof(IsSomethingVisible));
}
}
Public Void OnUserInteractionCommand()
{
DoSomething().ContinueWith((task) => {
Device.BeginInvokeOnMainThread(() =>{ IsSomethingVisible = true;});
});
}
Current scenario:
By default stack1 is visible but when OnUserInteractionCommand is called stack2 is visible and stack1 is also visible, user needs to go to different page and come back again then just stack2 is visible and stack1 is not visible.
Expected :
When OnUserInteractionCommand is called stack2 should be visible and stack1 is not visible.
Expected : When OnUserInteractionCommand is called stack2 should be visible and stack1 is not visible.
I modify your code about your Converter and create simple sample to test, having no problem.
<ContentPage
x:Class="FormsSample.simplecontrol.Page19"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converter="clr-namespace:FormsSample.converter">
<ContentPage.Resources>
<converter:Visibleconverter x:Key="VisibleConverter" />
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<StackLayout x:Name="stack1" IsVisible="{Binding IsSomethingVisible, Converter={StaticResource VisibleConverter}}">
<Label Text="Hello" />
<Label Text="Hi" />
<Label Text="stack1" />
</StackLayout>
<StackLayout x:Name="stack2" IsVisible="{Binding IsSomethingVisible}">
<Label Text="Hello" />
<Label Text="Hi" />
<Label Text="stack2" />
</StackLayout>
<Button
x:Name="btn1"
Clicked="btn1_Clicked"
Text="change visible" />
</StackLayout>
</ContentPage.Content>
Visibleconverter.cs:
public class Visibleconverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !((bool)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I change isSomethingVisible value by button.click.
public partial class Page19 : ContentPage, INotifyPropertyChanged
{
private bool _isSomethingVisible;
public bool IsSomethingVisible
{
get { return _isSomethingVisible; }
set
{
_isSomethingVisible = value;
RaisePropertyChanged(nameof(IsSomethingVisible));
}
}
public Page19()
{
InitializeComponent();
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private void btn1_Clicked(object sender, EventArgs e)
{
Device.BeginInvokeOnMainThread(() => { IsSomethingVisible = !IsSomethingVisible; });
}
}
I have no idea what you're trying to do with this line of code.
<StackLayout x:Name="stack1" Spacing="4" IsVisible="{Binding IsSomethingVisible, Converter={x:Static converter:InverseBoolConverter.Create}}">
Typically, you would register it in App.xaml so that it can be used throughout the app. That won't be the last time you need an inverse bool converter. Yoiu could also register it in the page resources.
<ContentPage.Resources>
<converter:Visibleconverter x:Key="VisibleConverter" />
</ContentPage.Resources>
Then just access the converter as a static resource:
<StackLayout x:Name="stack1" IsVisible="{Binding IsSomethingVisible, Converter={StaticResource VisibleConverter}}">
Related
I have:
<Grid.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding Source={RelativeSource AncestorType={x:Type locals:OneViewModel}},
Path=OneTappedView}" CommandParameter="{Binding .}" />
</Grid.GestureRecognizers>
<Button x:Name="bt_one" Clicked="bt_one_Clicked"/>
When I do Grid Tap, Command and bt_one_Clicked execute concurrently? Thank you
When I do Grid Tap, Command and bt_one_Clicked execute concurrently?
Yes, you can add the button's clicked code in your grid tap event when tapping your grid.
You can refer to the following code:
OnePage.xml
<?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:formapp1="clr-namespace:FormApp1"
x:Class="FormApp1.OnePage">
<ContentPage.BindingContext>
<formapp1:OneViewModel></formapp1:OneViewModel>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<Grid WidthRequest="300" HeightRequest="600" BackgroundColor="Yellow">
<Grid.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding OneTappedViewCommand}" />
</Grid.GestureRecognizers>
</Grid>
<Button x:Name="bt_one" Text="one button" Command="{Binding BtnOneClickedCommand}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
OneViewModel.cs
public class OneViewModel: INotifyPropertyChanged
{
public ICommand OneTappedViewCommand { private set; get; }
public ICommand BtnOneClickedCommand { private set; get; }
public OneViewModel() {
OneTappedViewCommand = new Command(GridTapped);
BtnOneClickedCommand= new Command(btnOneClickedMthod);
}
private void GridTapped()
{
System.Diagnostics.Debug.WriteLine("----111------> GridTapped is triggered......");
//add one clicked method here
btnOneClickedMthod();
}
private void btnOneClickedMthod()
{
System.Diagnostics.Debug.WriteLine("----222------> GridTapped is triggered......");
}
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));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Note:
In model OneViewModel, you can add bt_one event(btnOneClickedMthod) in grid tap function GridTapped.
I'm trying to visualize the state of an object, called MinRepresentation, in the ListView below.
I want to bind the function ImageSource stateImage(MinRepresentationState state), where i handover the state and get back the Imagesource.
My Problem is, accsessing the function and passing the parameter via xaml.
Visible Orders is an Collection of MinRepresentation each includes State
<ListView HasUnevenRows="True" SelectionMode="Single" ItemsSource="{Binding VisibleOrders}" ItemSelected="OnListViewItemSelected" ItemTapped="OnListViewItemTapped">
***OTHER STUFF***
<StackLayout Orientation="Horizontal" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3" Padding= "0,5,0,5" BackgroundColor="#CC3F6E3F">
<Image Source="{Binding stateImage(State)" Margin="10,0,0,0" />
<Label Text="{Binding Id, StringFormat='ID: [{0}]'}" FontSize="Small" Margin="5,0,0,0" FontAttributes="Bold" TextColor="#FFFFFF"/>
</StackLayout>
***OTHER STUFF***
</ListView>
public ImageSource stateImage(MinRepresentationState state)
{
switch (state)
{
case MinRepresentationState.Assigned:
return ImageSource.FromResource("state_assigned.png");
}
}
You can use IValueConverter to achieve that. Create an IValueConverter class and place your logic code there:
public class StateToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
MinRepresentationState representationState = (MinRepresentationState)value;
if (representationState == MinRepresentationState.Assigned)
{
return "state_assigned.png";
}
return "otherImage.png";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then consume it in XAML like:
<!--Define it in resources-->
<ContentPage.Resources>
<local:StateToImageConverter x:Key="StateToImageConverter"/>
</ContentPage.Resources>
<!--Use converter-->
<Image Source="{Binding State, Converter={StaticResource StateToImageConverter}}" Margin="10,0,0,0"/>
You can only bind to public properties. Create a StateImage property on your model
public string StateImage
{
get {
switch (State)
{
case MinRepresentationState.Assigned:
return "state_assigned.png";
}
}
}
You could also use a DataTrigger to set the image in your XAML
<Image Source="state_default.png" Margin="10,0,0,0">
<Image.Triggers>
<DataTrigger TargetType="Image" Binding="{Binding State}" Value="Assigned">
<Setter Property="Source" Value="state_assigned.png" />
</DataTrigger>
</Image.Triggers>
</Image>
Reference: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/triggers#data
Here is the method that I have come up with so far. However it seems like not such a clean solution.
Does anyone have any suggestions how I could come up with a better solution for doing this?
<?xml version="1.0" encoding="utf-8"?>
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Japanese;assembly=Japanese"
x:Class="Japanese.Templates.HeaderTemplate"
x:Name="this" HorizontalOptions="FillAndExpand" Orientation="Vertical" Spacing="0" Margin="0">
<StackLayout IsVisible="{Binding HeaderType, Converter={StaticResource HeaderType1BoolConverter}, Source={x:Reference this} }" >
<!-- code -->
</StackLayout>
<StackLayout IsVisible="{Binding HeaderType, Converter={StaticResource HeaderType2BoolConverter}, Source={x:Reference this} }" >
<!-- code -->
</StackLayout>
</StackLayout>
In my bank end CS:
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace Japanese.Templates
{
public partial class HeaderTemplate : StackLayout
{
public HeaderTemplate()
{
InitializeComponent();
}
public static readonly BindableProperty HeaderTypeProperty =
BindableProperty.Create(
nameof(HeaderType),
typeof(string),
typeof(DataViewCellTemplate),
default(string));
public string HeaderType
{
get { return (string)GetValue(HeaderTypeProperty); }
set { SetValue(HeaderTypeProperty, value); }
}
}
}
Converter Code:
public class HeaderType1BoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return object.Equals(value, "1");
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class HeaderType2BoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return object.Equals(value, "2");
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In the calling code:
<template:HeaderTemplate Header="Application" HeaderType="1" />
Using triggers is an option.
For e.g.: You can add property-triggers to check the value on HeaderType and accordingly update the Content (or layout) in your custom control HeaderView.
Please note in this case we are extending from ContentView not StackLayout on the assumption that only one layout/control is visible at a time.
XAML
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SampleApp"
x:Class="SampleApp.HeaderView">
<ContentView.Triggers>
<!-- if header type is 1, use header1 layout -->
<Trigger TargetType="local:HeaderView" Property="HeaderType" Value="1">
<Setter Property="Content">
<Setter.Value>
<Label Text="Header1" />
</Setter.Value>
</Setter>
</Trigger>
<!-- if header type is 2, use header2 layout -->
<Trigger TargetType="local:HeaderView" Property="HeaderType" Value="2">
<Setter Property="Content">
<Setter.Value>
<StackLayout>
<Label Text="Header2" />
<BoxView HeightRequest="1" BackgroundColor="Gray" />
</StackLayout>
</Setter.Value>
</Setter>
</Trigger>
<!-- you can add more layouts here if you need -->
</ContentView.Triggers>
<!-- add default content that can be displayed in case of no match -->
<StackLayout>
<Label Text="DefaultHeader" />
<BoxView HeightRequest="1" BackgroundColor="Gray" />
</StackLayout>
</ContentView>
Code-behind
public partial class HeaderView : ContentView
{
public HeaderView()
{
InitializeComponent();
}
public static readonly BindableProperty HeaderTypeProperty =
BindableProperty.Create(
nameof(HeaderType), typeof(string), typeof(HeaderView),
defaultValue: default(string));
public string HeaderType
{
get { return (string)GetValue(HeaderTypeProperty); }
set { SetValue(HeaderTypeProperty, value); }
}
}
Usage
<local:HeaderView HeaderType="1" />
I believe that what you are looking is a ControlTemplate here is an implementation.
App.xaml
<Application xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SimpleTheme.App">
<Application.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="Header1Template">
<StackLayout>
<!-- code -->
</StackLayout>
</ControlTemplate>
<ControlTemplate x:Key="Header2Template">
<StackLayout>
<!-- code -->
</StackLayout>
</ControlTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
Usage (This will make header1 your default, you can pick any of the both)
<ContentView x:Name="contentView" ControlTemplate="{StaticResource Header1Template}">
Now there are many ways to change the header, either by defining as the default code above or with an action which can be a button click like the example given on the link or with a propertyChange event, if you want to depend on a property to choose the header like:
public int HeaderNumber { get; set; }
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "HeaderNumber")
{
contentView.ControlTemplate = (HeaderNumber == 1) ? Header1Template : Header2Template;
}
}
Note that i never implemented a ControlTemplate but i think this solves your problem.
I code that I use often in my application so I created this template. With a lot of help from the user gannaway the code I have so far is this:
<?xml version="1.0" encoding="utf-8" ?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Japanese;assembly=Japanese"
x:Class="Japanese.SwitchViewCellTemplate"
x:Name="this">
<Grid VerticalOptions="CenterAndExpand" Padding="20,0" >
<local:StyledLabel Text="{Binding Text, Source={x:Reference this}}" HorizontalOptions="StartAndExpand" />
<local:StyledLabel IsVisible="{Binding IsVisible, Source={x:Reference this}}" TextColor="Gray" HorizontalOptions="End" Text="✓" />
</Grid>
</ViewCell>
Here's the code behind:
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace Japanese
{
public partial class SwitchViewCellTemplate : ViewCell
{
public event EventHandler SelectAction;
public SwitchViewCellTemplate()
{
InitializeComponent();
}
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(SwitchViewCellTemplate));
public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwitchViewCellTemplate));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public bool IsVisible
{
get { return (bool)GetValue(IsVisibleProperty); }
set { SetValue(IsVisibleProperty, value); }
}
protected override void OnTapped()
{
base.OnTapped();
this.SelectAction?.Invoke(this, new EventArgs());
}
}
}
and the way I would like to use it:
<template:SwitchViewCellTemplate Text="{Binding [1].Name}"
IsVisible="{Binding [1].IsSelected}"
SelectAction="selectValue" />
Here is the method used to handle toggled:
void Handle_SelectAction(object sender, System.EventArgs e)
{
var viewCell = sender as ViewCell;
if (viewCell == null)
return;
}
Where selectValue is a function in the CS code behind of the pages where I use the template.
The code gives an error:
The type initializer for 'Japanese.SwitchViewCellTemplate' threw an exception.
Can anyone give me advice on what might be wrong that is causing this error.
To solve, an event can be added to the template.
XAML Template
<?xml version="1.0" encoding="utf-8" ?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Japanese;assembly=Japanese"
x:Class="Japanese.SwitchViewCellTemplate"
x:Name="this">
<Grid VerticalOptions="CenterAndExpand" Padding="20,0" >
<local:StyledLabel Text="{Binding Text, Source={x:Reference this}}" HorizontalOptions="StartAndExpand" />
<local:StyledLabel IsVisible="{Binding IsVisible, Source={x:Reference this}}" TextColor="Gray" HorizontalOptions="End" Text="✓" />
</Grid>
</ViewCell>
Add an event to your template's code-behind:
public partial class SwitchViewCellTemplate : ViewCell
{
public event EventHandler SelectAction;
public SwitchViewCellTemplate()
{
InitializeComponent();
}
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
nameof(Text),
typeof(string),
typeof(SwitchViewCellTemplate),
default(string));
public static readonly BindableProperty IsVisibleProperty =
BindableProperty.Create(
nameof(IsVisible),
typeof(bool),
typeof(SwitchViewCellTemplate),
default(bool));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public bool IsVisible
{
get { return (bool)GetValue(IsVisibleProperty); }
set { SetValue(IsVisibleProperty, value); }
}
protected override void OnTapped()
{
base.OnTapped();
this.SelectAction?.Invoke(this, new EventArgs());
}
}
Page XAML
<template:SwitchViewCellTemplate Text="{Binding [1].Name}"
IsVisible="{Binding [1].IsSelected}"
SelectAction="Handle_SelectAction" />
Page Code-Behind
void Handle_SelectAction(object sender, System.EventArgs e)
{
}
I have a switch and a label on my XAML page:
<Switch x:Name="CpSwitch" Toggled="CpSwitch" />
<ViewCell x:Name="aBtn" Tapped="openPicker">
<Grid VerticalOptions="CenterAndExpand" Padding="20, 0">
<Label Text="Don't Know" />
<Picker x:Name="aBtnPicker" SelectedIndexChanged="aBtnPickerSelectedIndexChanged" ItemsSource="{Binding Points}">
</Picker>
<Label Text="{Binding ABtnLabel}"/>
</Grid>
</ViewCell>
My ViewModel
public class SettingsViewModel: ObservableProperty
{
public SettingsViewModel()
{
}
What I would like to do is to use a ViewModel and in that view model have the switched state either make the ViewCell visible or not.
Can someone give me some suggestions on how I can bind the switch and the visible property for the ViewCell using a ViewModel.
Basically you bind them both to the same Boolean variable in your view model.
<Switch IsToggled="{Binding ShowViewCell}" />
<ViewCell IsVisible="{Binding ShowViewCell}">
...
</ViewCell>
And in your view model:
public class SettingsViewModel
{
public bool ShowViewCell {get;set;}
}
This ensures that when the Switch is turned ON the ViewCell becomes visible. If you want to achieve the opposite you could write a custom converter:
public class InverseBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value;
}
}
Add it to your App.xaml resource dictionary:
<Application.Resources>
<ResourceDictionary>
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
</ResourceDictionary>
</Application.Resources>
And apply that to the ViewCell:
<Switch IsToggled="{Binding ShowViewCell, Converter={StaticResource InverseBoolConverter}}" />