Xamarin Forms ToggleButton - c#

I have a PCL and I am using MVVM pattern.
I am building a togglebutton, that toggles when the related property in the viewmodel is activated, with behaviors and triggers.
This is my behavior
public class ToggleBehavior : Behavior<View>
{
TapGestureRecognizer tapRecognizer;
public static readonly BindableProperty IsToggledProperty = BindableProperty.Create<ToggleBehavior, bool>(tb => tb.IsToggled, false);
public bool IsToggled
{
set { SetValue(IsToggledProperty, value); }
get { return (bool)GetValue(IsToggledProperty); }
}
}
This is how I consume my behavior
<Button Text="AUTO" Style="{StaticResource SmallEllipseButton}" BackgroundColor="{StaticResource BackgroundColor}" cm:Message.Attach="Automatic($dataContext)">
<Button.Behaviors>
<local:ToggleBehavior x:Name="autoToggleBehavior" IsToggled="{Binding CurrentMode,Converter={StaticResource modeToBooleanConverter}, ConverterParameter=AUTO}"/>
</Button.Behaviors>
<Button.Triggers>
<DataTrigger TargetType="Button" Binding="{Binding Source={x:Reference autoToggleBehavior},Path=IsToggled}" Value="False" >
<Setter Property="BackgroundColor" Value="White"/>
<Setter Property="BorderColor" Value="White"/>
<Setter Property="TextColor" Value="Gray"/>
</DataTrigger>
<DataTrigger TargetType="Button" Binding="{Binding Source={x:Reference autoToggleBehavior},Path=IsToggled}" Value="True" >
<Setter Property="BackgroundColor" Value="{StaticResource BackgroundColor}"/>
<Setter Property="TextColor" Value="White"/>
</DataTrigger>
</Button.Triggers>
</Button>
The problem is that the property IsToggled is not binded correctly in this IsToggled="{Binding CurrentMode,Converter={StaticResource modeToBooleanConverter}, ConverterParameter=AUTO}"/>
If I set it statically to true or false it works.
The problem I think is that I can't bind dinamically this property.
I use the same binding with the same converter in the same page for an IsVisible property and it works.
If I put a breakpoint in the converter, the application doesn't break in it, but for the IsVisible property breaks.

Using a Button might not be the best option as it already has a tap handler and assigning one to it would still not trigger the event. I don't know if this helps but I changed the control to a Label to get the below to work. Of course if the Button is bound to a command that modifies the view model then you don't need the tap handler in the behaviour at all.
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:local="clr-namespace:BehTest" x:Class="BehTest.BehTestPage">
<Label Text="AUTO" HorizontalOptions="Center" VerticalOptions="Center">
<Label.Behaviors>
<local:ToggleBehavior x:Name="autoToggleBehavior" IsToggled="{Binding Toggled, Mode=TwoWay}"/>
</Label.Behaviors>
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding Source={x:Reference autoToggleBehavior},Path=IsToggled}" Value="False" >
<Setter Property="BackgroundColor" Value="White"/>
<Setter Property="TextColor" Value="Gray"/>
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding Source={x:Reference autoToggleBehavior},Path=IsToggled}" Value="True" >
<Setter Property="BackgroundColor" Value="Blue"/>
<Setter Property="TextColor" Value="White"/>
</DataTrigger>
</Label.Triggers>
</Label>
</ContentPage>
Behaviour:
public class ToggleBehavior : Behavior<View>
{
readonly TapGestureRecognizer tapRecognizer;
public ToggleBehavior()
{
tapRecognizer = new TapGestureRecognizer
{
Command = new Command(() => this.IsToggled = !this.IsToggled)
};
}
public static readonly BindableProperty IsToggledProperty = BindableProperty.Create<ToggleBehavior, bool>(tb => tb.IsToggled, false);
public bool IsToggled
{
set { SetValue(IsToggledProperty, value); }
get { return (bool)GetValue(IsToggledProperty); }
}
protected override void OnAttachedTo(View bindable)
{
base.OnAttachedTo(bindable);
bindable.GestureRecognizers.Add(this.tapRecognizer);
}
protected override void OnDetachingFrom(View bindable)
{
base.OnDetachingFrom(bindable);
bindable.GestureRecognizers.Remove(this.tapRecognizer);
}
protected override void OnAttachedTo(BindableObject bindable)
{
base.OnAttachedTo(bindable);
this.BindingContext = bindable.BindingContext;
bindable.BindingContextChanged += Bindable_BindingContextChanged;
}
protected override void OnDetachingFrom(BindableObject bindable)
{
base.OnDetachingFrom(bindable);
this.BindingContext = null;
bindable.BindingContextChanged -= Bindable_BindingContextChanged;
}
void Bindable_BindingContextChanged(object sender, EventArgs e)
{
var bobject = sender as BindableObject;
this.BindingContext = bobject?.BindingContext;
}
}

We added a ToggleButton into our NuGet!
Its free to use.
xmlns:aw="clr-namespace:AscendantWare.Xamarin.Essentials.Controls"
<aw:AWToggleButton TextColor="White" ToggleBackgroundColor="DarkGoldenrod" Margin="0" VerticalOptions="FillAndExpand" HorizontalOptions="Fill" CornerRadius="15" IsToggled="{Binding IsNoteEnabled, Mode=TwoWay}" Command="{Binding MyCommand}"/>
More Informations in our online documentation here!

Related

Deselect ListBoxItem with delay

I want to achieve an effect, where the ListBoxItem gets selected (highlighted) for a few seconds and after that it gets deselected. Trying to achieve a simple control highlight with fade effects but in order to make things work, I need a property to be changed accordingly.
I have a IsSelected property bound to my view model property:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Style>
My property looks like so:
public bool IsSelected
{
get => _isSelected;
set
{
// Update value
_isSelected = value;
// Raise property changed
OnPropertyChanged(nameof(IsSelected));
}
}
I have tried using a delayed binding:
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ControlTemplate.Triggers>
<!-- Deselect after 5 seconds -->
<DataTrigger Binding="{Binding IsSelected, Delay=5000}" Value="True">
<Setter Property="IsSelected" Value="False" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
I have also tried using storyboards:
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ControlTemplate.Triggers>
<!-- Deselect after 5 seconds -->
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected">
<DiscreteBooleanKeyFrame KeyTime="00:00:05" Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Unfortunately, none of the above approach seem to update my IsSelected property and the ListBoxItem remains selected and highlighted.
I want this to be done in XAML (or extensions), MVVM style, no code behind and no wasteful timers - is this possible? If so, how can I properly deselect a ListBoxItem with a delay?
Here is an example of an attached behaviour that should do what you want, more or less:
public class DeselectBehavior
{
public static bool GetIsEnabled(ListBox listBox)
{
return (bool)listBox.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(ListBox listBox, bool value)
{
listBox.SetValue(IsEnabledProperty, value);
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached(
"IsEnabled",
typeof(bool),
typeof(DeselectBehavior),
new UIPropertyMetadata(false, OnChanged));
private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ListBox listBox = d as ListBox;
if((bool)e.NewValue)
{
listBox.AddHandler(ListBoxItem.SelectedEvent, (RoutedEventHandler)OnListBoxItemSelected, true);
}
else
{
listBox.RemoveHandler(ListBoxItem.SelectedEvent, (RoutedEventHandler)OnListBoxItemSelected);
}
}
private static async void OnListBoxItemSelected(object sender, RoutedEventArgs e)
{
await Task.Delay(2000);
ListBoxItem listBoxItem = e.OriginalSource as ListBoxItem;
if (listBoxItem != null)
listBoxItem.IsSelected = false;
}
}
XAML:
<ListBox ... local:DeselectBehavior.IsEnabled="True">
...

Modify button background through triggers Xamarin forms

I have two buttons. I want change second Button BackgroundColor using Triggers When I click first button but I am unable to do it. The code I am trying
<Button Text="button 1" x:Name="btn1" HorizontalOptions="Fill">
<Button.Triggers>
<Trigger TargetType="Button" Binding="{Binding Source={x:Reference btn2} Property="IsEnabled">
<Setter Property="BackgroundColor" Value="Red"></Setter>
</Trigger>
</Button.Triggers>
</Button>
<Button Text="button 2" x:Name="btn2" HorizontalOptions="Fill" />
I even have no idea where to write click event for this.
The best way to do that is to use ViewModel rather than the Code base.
Approach 1 : using the ViewModel
public class YourViewModel : BaseViewModel
{
public ICommand Button1Command { get; set; }
private bool _enableButton2;
public bool EnableButton2
{
get
{
return _enableButton2;
}
set
{
_enableButton2= value;
RaisePropertyChanged();
}
}
public YourViewModel()
{
Button1Command =new Command(Button1Clicked);
}
private void Button1Clicked()
{
EnableButton2=true; //Whenever you need to enable or disable set it true/false
}
}
Now you have your ViewModel, You need to implement your UI like this :
<Button x:Name="button1" Text="Button 1" Command="{Binding Button1Command }" />
<Button x:Name="button2" Text="Button 2">
<Button.Triggers>
<DataTrigger TargetType="Button" Binding="{Binding EnableButton2}" Value="false">
<Setter Property="BackgroundColor" Value="#dbe1e5" />
<Setter Property="TextColor" Value="#bfcfd5" />
</DataTrigger>
<DataTrigger TargetType="Button" Binding="{Binding EnableButton2" Value="true">
<Setter Property="BackgroundColor" Value="Red" />
<Setter Property="TextColor" Value="#FFFFFF" />
</DataTrigger>
</Button.Triggers>
</Button>
This is the MVVM way to do this; let me know if you want Code Base style.

Creating a self-updating Textblock user control in WPF

I'm trying to create a re-usable textblock user control in WPF. The basic idea is as follows:
User does not directly specify the content of the textblock
There are three dependancy properties in my user control called IsToggled, ToggleTrueText, and ToggleFalseText.
The control will display ToggleTrueText if IsToggled is true; or display ToggleFalseText if IsToggled is false.
When IsToggled changes during runtime, the text automatically changes to either ToggleTrueText or ToggleFalseText
I started by adding a PropertyChangedCallback to the IsToggled DP:
Code-behind of the UserControl:
public static readonly DependencyProperty IsToggledProperty =
DependencyProperty.Register("IsToggled", typeof(bool),
typeof(TagToggle), new PropertyMetadata(new
PropertyChangedCallback(OnToggleStateChanged)));
public bool IsToggled
{
get { return (bool)GetValue(IsToggledProperty); }
set { SetValue(IsToggledProperty, value); }
}
//ToggleTrueText and ToggleFalseText are declared similarly to IsToggled
...
private static void OnToggleStateChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
...
}
Xaml of the user control:
<Grid x:Name="LayoutRoot">
<TextBlock x:Name="TheTextBlock" Text="{Binding WhatDoIBindTo}"/>
</Grid>
However, I'm not sure what would be the best way to ensure that TheTextBlock updates its text whenever IsToggled changes during runtime.
Try this:
private static void OnToggleStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TagToggle ctrl = d as TagToggle;
if (ctrl != null)
{
TheTextBlock.Text = ctrl.IsToggled ? ToggleTrueText. : ToggleFalseText;
}
}
If you want to bind the Text property of the TextBlock you need to make sure that you are binding to properties of the UserControl. You could do this by setting the DataContext property of the TextBlock:
<TextBlock x:Name="TheTextBlock" DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding ToggleTrueText}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsToggled}" Value="False">
<Setter Property="Text" Value="{Binding ToggleFalseText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
You can used trigger for this
Please check below code
<TextBlock x:Name="TheTextBlock">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsToggled}" Value="True">
<Setter Property="Text" Value="{Binding ToggleTrueText}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsToggled}" Value="False">
<Setter Property="Text" Value="{Binding ToggleFalseText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

CanUserAddRows New Row not saving in DataGrid

Created DataGrid and set CanUserAddRows="True"
Have a button which saves updates in the cs file:
private void Save_Click(object sender, RoutedEventArgs e)
{
UnitService unitService = new UnitService();
unitService.SaveUpdates(valuationCase);
MainWindow mainWin = new MainWindow();
mainWin.Show();
this.Close();
}
There is also a textbox not in the datagrid on the window which is editable and this is correctly saving edits with the save click button. Just the new rows aren't.
Any ideas??
datagrid definition:
<DataGrid Name="dgCommentsList" AutoGenerateColumns="False" Margin="10,196,9.953,38.204" CanUserAddRows="True" FontSize="18">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontSize" Value="20" />
<Setter Property="FontWeight" Value="bold" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Type" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="Type" Text="{Binding Type}" >
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsReadOnly}" Value="False">
<Setter Property="TextBox.IsReadOnly" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsReadOnly}" Value="True">
<Setter Property="TextBox.IsReadOnly" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid>
I think you need to set the mode of the binding for it to write back to the underlying object.Plus I noticed your DataGrid does not have an ItemsSource. I'm guessing as this was just a snippet that you left it out.
<TextBox x:Name="Type" Text="{Binding Type, Mode=TwoWay}">
You should commit the edit on the row using dataGrid.CommitEdit()
Edit: After diagnosing the issue here goes
You either need to implement INotifyPropertyChanged on your DataContext class (i.e: Viewmodel) like so:
public class ViewModel: INotifyPropertyChanged
{
private string _type;
public string Type
{
get { return _type; }
set
{
_type = value;
OnPropertyChanged("Type");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Or you extend from DependencyObject and use Dependency Properties, like so:
public class ViewModel: DependencyObject
{
public static readonly DependencyProperty TypeProperty = DependencyProperty.Register(
"Type", typeof (string), typeof (ViewModel), new PropertyMetadata(default(string)));
public int Type
{
get { return (int) GetValue(TypeProperty ); }
set { SetValue(TypeProperty , value); }
}
}
Hope it helps ;)

Fired datatrigger is not changing custom control's newly added property

I am creating an animation control and where I am trying to use data triggers. The issue is the dp property which I created is not getting changed/called when the trigger is fired. Here is the summary of behaviour I noticed.
1) The code behind of never gets called.
2) Property appears in XAML intellisense but the changes given in XAML never gets applied (design/runtime). But if I replace 'IsSpinning' in "public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning", typeof(bool), typeof(ProgressWaitSpinner), new UIPropertyMetadata(false));" to something else( say 'xyz') it starts working for property assignment, but throws runtime exception if styles are enabled.
3) When running the sample, The rectangle should be hidden instead of showing as Chocolate color, which is not happening.
4) Setter for changing color is working, which is from the user control, however the setter property on newly created property is not working.
I created a simplified sample here which shows the problem. Anyone got a clue what is going on please?
UserControl XAML:
<UserControl x:Class="CustomControls.ProgressWaitSpinner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControls"
Height="191" Width="191">
<Grid x:Name="LayoutRoot">
<Label Height="32" Name="label1" VerticalAlignment="Top" />
</Grid>
</UserControl>
UserControl Code:
using System.Windows;
using System.Windows.Controls;
namespace CustomControls
{
public partial class ProgressWaitSpinner : UserControl
{
public ProgressWaitSpinner(){InitializeComponent();}
public bool IsSpinning
{
get
{
return (bool)GetValue(IsSpinningProperty);
}
set
{
if (value == true)
{
this.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.Visibility = System.Windows.Visibility.Hidden;
}
SetValue(IsSpinningProperty, value);
}
}
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning", typeof(bool), typeof(ProgressWaitSpinner), new UIPropertyMetadata(false));
}
}
MainWindow XAML:
<Window x:Class="WPFSpinnerWait.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:usrctrl="clr-namespace:CustomControls"
Title="MainWindow" Height="208" Width="228">
<Grid>
<usrctrl:ProgressWaitSpinner Height="40" x:Name="WaitSpinner" Margin="110,103,0,0" HorizontalAlignment="Left" Width="84" VerticalAlignment="Top">
<usrctrl:ProgressWaitSpinner.Style>
<Style>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=label1, Path=Content}" Value="NotStarted"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="usrctrl:ProgressWaitSpinner.Background" Value="Red" />
<Setter Property="usrctrl:ProgressWaitSpinner.IsSpinning" Value="false"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=label1, Path=Content}" Value="Running"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="usrctrl:ProgressWaitSpinner.Background" Value="Chocolate" />
<Setter Property="usrctrl:ProgressWaitSpinner.IsSpinning" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</usrctrl:ProgressWaitSpinner.Style>
</usrctrl:ProgressWaitSpinner>
<Button Content="NotStarted" Height="28" HorizontalAlignment="Left" Margin="38,22,0,0" Name="checkBox1" VerticalAlignment="Top" Width="136" Click="checkBox1_Checked" />
<Button Content="Running" Height="30" HorizontalAlignment="Left" Margin="38,56,0,0" Name="checkBox2" VerticalAlignment="Top" Width="136" Click="checkBox1_Checked" />
<Label Content="NotStarted" DataContext="usrctrl:ProgressWaitSpinner" Height="25" HorizontalAlignment="Left" Margin="38,92,0,0" Name="label1" VerticalAlignment="Top" Width="114" />
</Grid>
</Window>
MainWindow Code:
using System.Windows;
using System.Windows.Controls;
namespace WPFSpinnerWait
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void checkBox1_Checked(object sender, RoutedEventArgs e)
{
label1.Content = ((Button)sender).Content.ToString();
}
}
}
The code behind won't get called, DependancyProperties do not use the backing property when the property is change/used in Xaml, thay are only there for use in code behind as a helper, thay have no use in Xaml bindings
You can use the PropertyChanged event of the DependancyProperty instead
public bool IsSpinning
{
get { return (bool)GetValue(IsSpinningProperty); }
set { SetValue(IsSpinningProperty, value); }
}
// Using a DependencyProperty as the backing store for IsSpinning. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsSpinningProperty =
DependencyProperty.Register("IsSpinning", typeof(bool), typeof(ProgressWaitSpinner), new PropertyMetadata(false, OnIsSpinningChanged));
private static void OnIsSpinningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (((bool)e.NewValue) == true)
{
(d as ProgressWaitSpinner).Visibility = System.Windows.Visibility.Visible;
}
else
{
(d as ProgressWaitSpinner).Visibility = System.Windows.Visibility.Hidden;
}
}
Edit:
For your second question, Try adding the TargetType for your Style so you can access the properties directly
<Style TargetType="{x:Type usrctrl:ProgressWaitSpinner}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=label1, Path=Content}" Value="NotStarted"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Red" />
<Setter Property="IsSpinning" Value="false"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=label1, Path=Content}" Value="Running"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Chocolate" />
<Setter Property="IsSpinning" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>

Categories

Resources