I am making a custom slider called scalar_slider using a UserControl. I would like to two way bind one of it's DependencyProperty.
The problem is that the value changes made inside scalar_slider are not updated back to the parent's view model vm which correctly implements INotifyPropertyChanged. I assume the problem has to do with how I am setting the binding in MainPage.xaml.
// MainPage.xaml
<local:scalar_slider scalar_value="{x:Bind vm.my_scalar_value Mode=TwoWay}" />
// scalar_slider.xaml
<UserControl
x:Class="transformations.scalar_slider"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:transformations"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel>
<TextBox Text="{x:Bind scalar_value, Mode=TwoWay}"/>
<Slider Value="{x:Bind scalar_value, Mode=TwoWay}"/>
</StackPanel>
</UserControl>
//scalar_slider.idl
namespace transformations
{
[default_interface]
runtimeclass scalar_slider : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged
{
scalar_slider();
static Windows.UI.Xaml.DependencyProperty scalar_valueProperty;
Single scalar_value;
}
}
//scalar_slider.h
namespace winrt::transformations::implementation
{
struct scalar_slider : scalar_sliderT<scalar_slider>
{
scalar_slider();
static Windows::UI::Xaml::DependencyProperty scalar_valueProperty();
static void scalar_valueProperty(Windows::UI::Xaml::DependencyProperty value);
float scalar_value();
void scalar_value(float value);
winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
void PropertyChanged(winrt::event_token const& token) noexcept;
template <class T>
void update_value(hstring const& property_name, T & var, T value)
{
if (var != value)
{
var = value;
raise_property_changed(property_name);
}
}
private:
event<Windows::UI::Xaml::Data::PropertyChangedEventHandler> m_property_changed;
void raise_property_changed(hstring const& property_name)
{
m_property_changed(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs(property_name));
}
static Windows::UI::Xaml::DependencyProperty m_scalar_value_property;
float m_scalar_value = 0.0f;
};
}
//scalar_slider.cpp
namespace winrt::transformations::implementation
{
scalar_slider::scalar_slider()
{
InitializeComponent();
}
winrt::event_token scalar_slider::PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
{
return m_property_changed.add(handler);
}
void scalar_slider::PropertyChanged(winrt::event_token const& token) noexcept
{
m_property_changed.remove(token);
}
Windows::UI::Xaml::DependencyProperty scalar_slider::m_scalar_value_property = Windows::UI::Xaml::DependencyProperty::Register(
L"scalar_value",
winrt::xaml_typename<float>(),
winrt::xaml_typename<winrt::transformations::scalar_slider>(),
Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
Windows::UI::Xaml::DependencyProperty scalar_slider::scalar_valueProperty()
{
return m_scalar_value_property;
}
void scalar_slider::scalar_valueProperty(Windows::UI::Xaml::DependencyProperty value)
{
m_scalar_value_property = value;
}
float scalar_slider::scalar_value()
{
return m_scalar_value;
}
void scalar_slider::scalar_value(float value)
{
update_value(L"scalar_value", m_scalar_value, value);
}
}
About your code, I find the get and set function of scalar_slider property you write are not correct in scalar_slider.cpp, you can change them like below code.
You can also read XAML custom (templated) controls with C++/WinRT to understand better.
float scalar_slider::scalar_value()
{
return winrt::unbox_value<float>(GetValue(m_scalar_value_property));
}
void scalar_slider::scalar_value(float value)
{
SetValue(m_scalar_value_property, winrt::box_value(value));
m_property_changed(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs(L"scalar_value"));
}
Related
I am using WinUI3 with C++/WinRT and I am trying to bind an object like "User" to a collection like "ListViewItem".
Below you can see the xaml and the code behind creating a list view item and the binding part.
XAML:
<StackPanel>
<Button x:Name="button" Click="button_Click" Content="Add" />
<ListView x:Name="listView" />
</StackPanel>
CPP:
class User : public ::winrt::Microsoft::UI::Xaml::Data::INotifyPropertyChanged
{
public:
User() {}
~User() {}
void Name(const ::winrt::hstring& aName) {
if (mName != aName) {
mName = aName;
mPropertyChanged(*this, ::winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Name" });
}
}
::winrt::hstring Name() {
return mName;
}
::winrt::event_token PropertyChanged(::winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& aHandler) {
return mPropertyChanged.add(aHandler);
}
void PropertyChanged(::winrt::event_token const& aToken) noexcept {
mPropertyChanged.remove(aToken);
}
private:
::winrt::hstring mName = L"NoName";
event<::winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> mPropertyChanged;
};
::winrt::Microsoft::UI::Xaml::Controls::ListViewItem CreateLVI() {
::winrt::Microsoft::UI::Xaml::Controls::TextBlock myText;
User myDataObject;
::winrt::Microsoft::UI::Xaml::Data::Binding myBinding;
myBinding.Path(::winrt::Microsoft::UI::Xaml::PropertyPath(L"Name"));
myBinding.Source(myDataObject);
::winrt::Microsoft::UI::Xaml::Data::BindingOperations::SetBinding(myText, ::winrt::Microsoft::UI::Xaml::Controls::TextBlock::TextProperty(), myBinding);
::winrt::Microsoft::UI::Xaml::Controls::ListViewItem lvi;
lvi.Content(myText);
return lvi;
}
MainWindow::MainWindow()
{
InitializeComponent();
}
void MainWindow::button_Click(::winrt::Windows::Foundation::IInspectable const& sender, ::winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e)
{
listView().Items().Append(CreateLVI());
}
But it doesnt work, the user's name doesnt show in the list view item. The list view item is created tho.
I am trying to two way bind data inside of a UserControl. The code below works for one way binding, but when I try to use two way binding I get the following error from the xaml compiler:
XamlCompiler error WMC1118: TwoWay binding target 'value_prop' must be a dependency property
However it's very confusing to me because I did make value_prop a dependency property. How can I make this work?
// MainPage.xaml
<local:attribute_slider Label="Scale" value_prop="{x:Bind texture_showcase_vm.current_texture.scale, Mode=TwoWay}"/>
// attribute_slider.xaml
<UserControl
x:Class="wzrd_editor.attribute_slider"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:wzrd_editor"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" >
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" >
<TextBlock x:Name="scale_label" Text="{x:Bind Label, Mode=OneWay}" HorizontalAlignment="Left" />
<TextBlock x:Name="scale_text_value" Text="{x:Bind value_prop, Mode=TwoWay}" HorizontalAlignment="Right"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.RightOf="scale_label" />
<Slider x:Name="Scale" Value="{x:Bind value_prop, Mode=TwoWay}" Width="200" Orientation="Horizontal" Minimum="0" Maximum="10" StepFrequency="0.01" TickFrequency="0.01" SnapsTo="StepValues" TickPlacement="None"
RelativePanel.Below="scale_label"
RelativePanel.AlignRightWith="scale_text_value"
RelativePanel.AlignLeftWith="scale_label"
RelativePanel.AlignRightWithPanel="True" />
</StackPanel>
</UserControl>
//attribute_slider.idl
namespace wzrd_editor
{
[bindable]
[default_interface]
runtimeclass attribute_slider : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged
{
attribute_slider();
static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
static Windows.UI.Xaml.DependencyProperty value_property{ get; set; };
String Label;
Single value_prop;
}
}
attribute_slider.h
namespace winrt::wzrd_editor::implementation
{
struct attribute_slider : attribute_sliderT<attribute_slider>
{
attribute_slider();
static Windows::UI::Xaml::DependencyProperty LabelProperty();
static Windows::UI::Xaml::DependencyProperty value_property();
static void value_property(Windows::UI::Xaml::DependencyProperty const& value);
hstring Label();
void Label(hstring const& value);
float value_prop();
void value_prop(float const& value);
winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
void PropertyChanged(winrt::event_token const& token) noexcept;
private:
static Windows::UI::Xaml::DependencyProperty m_labelProperty;
static Windows::UI::Xaml::DependencyProperty m_value_property;
hstring m_label = L"";
float m_value = 0.0f;
winrt::event<Windows::UI::Xaml::Data::PropertyChangedEventHandler> m_property_changed;
void raise_property_changed(hstring const& property_name)
{
m_property_changed(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs(property_name));
}
template <class T>
void update_value(hstring const& property_name, T & var, T value)
{
if (var != value)
{
var = value;
raise_property_changed(property_name);
}
}
};
}
//attribute_slider.cpp
namespace winrt::wzrd_editor::implementation
{
Windows::UI::Xaml::DependencyProperty attribute_slider::m_labelProperty =
Windows::UI::Xaml::DependencyProperty::Register(
L"Label",
winrt::xaml_typename<winrt::hstring>(),
winrt::xaml_typename<wzrd_editor::attribute_slider>(),
Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
Windows::UI::Xaml::DependencyProperty attribute_slider::m_value_property =
Windows::UI::Xaml::DependencyProperty::Register(
L"value_prop",
winrt::xaml_typename<winrt::hstring>(),
winrt::xaml_typename<wzrd_editor::attribute_slider>(),
Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
attribute_slider::attribute_slider()
{
InitializeComponent();
}
hstring attribute_slider::Label()
{
return m_label;
}
void attribute_slider::Label(hstring const& value)
{
update_value(L"Label", m_label, value);
}
float attribute_slider::value_prop()
{
return m_value;
}
void attribute_slider::value_prop(float const& value)
{
update_value(L"value_prop", m_value, value);
}
Windows::UI::Xaml::DependencyProperty attribute_slider::LabelProperty()
{
return m_labelProperty;
}
Windows::UI::Xaml::DependencyProperty attribute_slider::value_property()
{
return m_value_property;
}
void attribute_slider::value_property(Windows::UI::Xaml::DependencyProperty const& value)
{
m_value_property = value;
}
winrt::event_token attribute_slider::PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
{
return m_property_changed.add(handler);
}
void attribute_slider::PropertyChanged(winrt::event_token const& token) noexcept
{
m_property_changed.remove(token);
}
}
I have a page
<?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:PassSystem.Controls;assembly=PassSystem"
x:Class="PassSystem.Views.CreatePassPage"
Title="Оформление пропуска">
<ScrollView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout>
<FlexLayout Direction="Column" BackgroundColor="White">
<controls:ActionOption Title="Название" Value="1" LeftMargin="18">
</controls:ActionOption>
</FlexLayout>
</StackLayout>
</ScrollView>
</ContentPage>
and i have contentview
<?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:controls="clr-namespace:PassSystem.Controls;assembly=PassSystem"
xmlns:effects="clr-namespace:PassSystem.Effects;assembly=PassSystem"
xmlns:local="clr-namespace:PassSystem"
x:Class="PassSystem.Controls.ActionOption">
<FlexLayout x:Name="MainLayout" Direction="Row" JustifyContent="Center" AlignContent="Center" AlignItems="Center" HeightRequest="52"
effects:ClickableEffect.ClickCommand="!!"
effects:ClickableEffect.CancelCommand="{Binding UpCommand}"
effects:ClickableEffect.UpCommand="{Binding UpCommand}"
effects:ClickableEffect.DownCommand="{Binding DownCommand}">
<Label Text="{Binding Title}"
TextColor="{StaticResource PrimaryTextColor}"
FontSize="15"
VerticalTextAlignment="Center"
VerticalOptions="FillAndExpand"
FlexLayout.Grow="1"
Margin="{Binding TitleMargin, Mode=TwoWay}">
</Label>
<StackLayout Orientation="Horizontal" HeightRequest="16" VerticalOptions="CenterAndExpand" Margin="0,0,8,0">
<Label Text="{Binding Value}" FontSize="12" TextColor="{StaticResource SecondaryTextColor}" VerticalTextAlignment="Center"/>
<controls:IconView Source="forward" HeightRequest="14" WidthRequest="14" ForegroundColor="{StaticResource AdditionalTextColor}"/>
</StackLayout>
<FlexLayout.Effects>
<effects:ClickableEffect />
</FlexLayout.Effects>
</FlexLayout>
</ContentView>
.cs file
public partial class ActionOption : ContentView
{
public ActionOption()
{
InitializeComponent();
BindingContext = this;
}
private string _title;
public string Title
{
get => _title;
set
{
if(value == _title) return;
_title = value;
OnPropertyChanged();
}
}
private string _value;
public string Value
{
get => _value;
set
{
if (value == _value) return;
_value = value;
OnPropertyChanged();
}
}
public Thickness TitleMargin => new Thickness(_leftMargin, 0, 6, 0);
private double _leftMargin;
public double LeftMargin
{
get => _leftMargin;
set
{
if (Math.Abs(value - _leftMargin) < 0.01d) return;
_leftMargin = value;
OnPropertyChanged();
OnPropertyChanged(nameof(TitleMargin));
}
}
private ICommand _clicked;
public ICommand Clicked
{
get => _clicked;
set
{
if (value == _clicked) return;
_clicked = value;
OnPropertyChanged();
}
}
public ICommand UpCommand => new Command(() => MainLayout.BackgroundColor = Color.FromHex("#fff"));
public ICommand DownCommand => new Command(() => MainLayout.BackgroundColor = (Color)((App)Application.Current).Resources["HighlightingColor"]);
}
And I need to bind the ClickableEffect.ClickCommand from the page. I.e.
<controls:ActionOption Title="TitleHere" Value="1" LeftMargin="18" Clicked="{Binding ClickedCommand}">
</controls:ActionOption>
And in control
`effects:ClickableEffect.ClickCommand="{Binding ClickCommand (FromPage)}"`
Additional information. ClickableEffect
{
public ClickableEffect() : base("PassSystem.ClickableEffect")
{
}
#region Click
public static readonly BindableProperty ClickCommandProperty = BindableProperty
.CreateAttached("ClickCommand", typeof(ICommand), typeof(ClickableEffect), (object)null);
public static ICommand GetClickCommand(BindableObject view)
{
return (ICommand)view.GetValue(ClickCommandProperty);
}
public static void SetClickCommand(BindableObject view, ICommand value)
{
view.SetValue(ClickCommandProperty, value);
}
public static readonly BindableProperty ClickCommandParameterProperty = BindableProperty
.CreateAttached("ClickCommandParameter", typeof(object), typeof(ClickableEffect), (object)null);
public static object GetClickCommandParameter(BindableObject view)
{
return view.GetValue(ClickCommandParameterProperty);
}
public static void SetClickCommandParameter(BindableObject view, object value)
{
view.SetValue(ClickCommandParameterProperty, value);
}
#endregion
}
Implementation of Clickable Effect on Android:
public class ClickableListener : Java.Lang.Object, View.IOnTouchListener, View.IOnClickListener, View.IOnLongClickListener
{
private Element Element { get; }
private View View { get; }
public ClickableListener(Element element, View view)
{
Element = element;
View = view;
}
...
public void OnClick(View v)
{
Tap();
}
private void Tap()
{
var command = ClickableEffect.GetClickCommand(Element);
var parameter = ClickableEffect.GetClickCommandParameter(Element);
command?.Execute(parameter);
}
}
[assembly: ResolutionGroupName("PassSystem")]
[assembly: ExportEffect(typeof(AndroidClickableEffect), "ClickableEffect")]
namespace PassSystem.Droid.Native.Effects
{
public class AndroidClickableEffect : PlatformEffect
{
private bool _attached;
protected override void OnAttached()
{
//because an effect can be detached immediately after attached (happens in listview), only attach the handler one time.
if(!_attached)
{
var control = Control ?? Container;
var listener = new ClickableListener(Element, control);
control.SetOnClickListener(listener);
...
_attached = true;
}
}
}
How to implement this? Thank you.
P.S. It's hard for me to explain and I hope you understand. :)
In order to achieve the desired behaviour:
ClickCommand have to be an attached property of a ClickableEffect. More information can be found in the official doc: Passing Effect Parameters as Attached Properties.
Since you have a ContentView in between the effect and the page, you have to make sure that BindindContext of the ContentView is set correctly. I am afraid that you will have to define a ClickCommand bindable property on the ContentView level and bind it to the effect's command. So: Page.BindingContext.ClickCommand => ContentView.ClickCommand => ClickableEffect.ClickCommand (where => is binding).
Which basically raise a question - why do you need an effect?
I have a view that uses the SearchBox user control, The SearchBox has two radio buttons to select the search modes - Instant and delayed. I have binded the searchmodes to SearchMode property, and also I have created a custom dependency property for the Search Mode.
View
<controls:SearchBox Grid.Row="0"
HorizontalAlignment="Right"
Margin="2" Width="200"
SearchMode="{Binding DataContext.SearchMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged }" />
ViewModel.cs
private Mode mSearchMode;
public Mode SearchMode
{
get
{
return mSearchMode;
}
set
{
mSearchMode = value;
NotifyOfPropertyChange();
}
}
// Called when application is restarted.
private void ActivateLastSelectedSearchMode(Mode lastselectedMode)
{
// Sets the last selected mode to the search mode
SearchMode = lastselectedMode;
}
public enum Mode
{
Instant,
Delayed,
}
SearchBox.xaml
<UserControl x:Class = "abc.SearchBox"
DataContext="{Binding RelativeSource={RelativeSource Self}}" >
<UserControl.Resources>
<converters:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<RadioButton Content="{lex:Loc SearchBox:SearchModelInstatOption}"
IsChecked="{Binding Path=SearchMode, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:Mode.Instant}}" />
<RadioButton Content="{lex:Loc SearchBox:SearchModeDelayedOption}"
IsChecked="{Binding Path=SearchMode, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:Mode.Delayed}}" />
</StackPanel>
</UserControl>
SearchBox.xaml.cs
public partial class SearchBox : UserControl
{
public static DependencyProperty SearchModeProperty =
DependencyProperty.Register(
"SearchMode",
typeof(Mode),
typeof(SearchBox),
new FrameworkPropertyMetadata(default(Mode), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsSearchModeChanged));
static void OnIsSearchModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var searchBox = obj as SearchBox;
searchBox.SearchMode = (Mode)e.NewValue;
}
public Mode SearchMode
{
get { return (Mode)GetValue(SearchModeProperty); }
set { SetValue(SearchModeProperty, value); }
}
}
I want the OnIsSearchModeChanged() to be fired each time when SearchMode is set during call back i e, ActivateLastSelectedSearchMode() is invoked in ViewModel.cs. I am absolutely clueless..where I am missing, I am unable to achieve success.
//snip
private Mode mSearchMode;
public Mode SearchMode
{
get
{
return mSearchMode;
}
set
{
mSearchMode = value;
NotifyOfPropertyChange(()=>SearchMode); //Change
}
}
does the reflected change make any difference? Other option would be to create a custom convention for your user control
You should create an Event in you View Model and subscribe to it from your code behind.
In your View Model :
public event SearchModeAction SearchModeChanged;
public delegate void SearchModeAction(object sender, EventArgs e);
public void SearchModeHasChanged()
{
SearchModeAction Handler = SearchModeChanged;
if (Handler != null)
{
Handler(this, null);
}
}
private void ActivateLastSelectedSearchMode(Mode lastselectedMode)
{
// Sets the last selected mode to the search mode
SearchMode = lastselectedMode;
SearchModeHasChanged()
}
In your Code Behind :
private void Window_Loaded(object sender, RoutedEventArgs e)
{
((YourViewModelClass)DataContext).SearchModeChanged += OnIsSearchModeChanged;
}
private void OnIsSearchModeChanged(object sender, EventArgs e)
{
var searchBox = obj as SearchBox;
searchBox.SearchMode = (Mode)e.NewValue;
}
This way each time you arrive in your ActivateLastSelectedSearchMode method in your View Model, you will call your OnIsSearchModeChanged method in your View.
Ahh..the reason was the EnumToBooleanConverter.
Though the value of 'parameter' and 'value' was same, There was a difference between their object types as both were referencing to different namespaces. So I created a public enum called 'Mode' and ensured that the 'Instant' and 'Delayed' reference to the same namespace.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
{
return false;
}
return value.Equals(parameter); // This always returned false despite the values being the same
}
I have the following XAML Xamarin.Forms.Button
<Button Text="Cancel" BackgroundColor="#3079a8" TextColor="White" />
I tried to add padding to it via the Padding property but that didn't work. After checking the forums and the docs, I realised there is no padding property in the documentation for Xamarin.Forms.Button (link to the docs)
, is there some other type of quick fix to add just a little bit more padding to a button? A code example would be greatly appreciated.
usage:
<controls:EnhancedButton Padding="1,2,3,4"/>
advantages:
no nasty sideeffects
no problem with alignments
no viewtree uglyness
no view depth incrementation
ios:
[assembly: ExportRenderer(typeof(EnhancedButton), typeof(EnhancedButtonRenderer))]
namespace YOURNAMESPACE.iOS
{
public class EnhancedButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
UpdatePadding();
}
private void UpdatePadding()
{
var element = this.Element as EnhancedButton;
if (element != null)
{
this.Control.ContentEdgeInsets = new UIEdgeInsets(
(int)element.Padding.Top,
(int)element.Padding.Left,
(int)element.Padding.Bottom,
(int)element.Padding.Right
);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(EnhancedButton.Padding))
{
UpdatePadding();
}
}
}
}
android:
[assembly: ExportRenderer(typeof(EnhancedButton), typeof(EnhancedButtonRenderer))]
namespace YOURNAMESPACE.Droid
{
public class EnhancedButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
UpdatePadding();
}
private void UpdatePadding()
{
var element = this.Element as EnhancedButton;
if (element != null)
{
this.Control.SetPadding(
(int)element.Padding.Left,
(int)element.Padding.Top,
(int)element.Padding.Right,
(int)element.Padding.Bottom
);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(EnhancedButton.Padding))
{
UpdatePadding();
}
}
}
}
pcl:
public class EnhancedButton : Button
{
#region Padding
public static BindableProperty PaddingProperty = BindableProperty.Create(nameof(Padding), typeof(Thickness), typeof(EnhancedButton), default(Thickness), defaultBindingMode:BindingMode.OneWay);
public Thickness Padding
{
get { return (Thickness) GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
}
#endregion Padding
}
Solution using effects instead of renderers, to allow easy usage for more than one control:
XAML:
<Label Text="Welcome to Xamarin.Forms!" BackgroundColor="Red">
<Label.Effects>
<xamTest:PaddingEffect Padding="20,40,20,40"></xamTest:PaddingEffect>
</Label.Effects>
</Label>
PCL:
[assembly: ResolutionGroupName("ComponentName")]
namespace XamTest
{
public class PaddingEffect : RoutingEffect
{
/// <inheritdoc />
protected PaddingEffect(string effectId) : base($"ComponentName.{nameof(PaddingEffect)}")
{
}
public Thickness Padding { get; set; }
}
}
Android:
[assembly: ResolutionGroupName("ComponentName")]
[assembly: ExportEffect(typeof(XamTest.Droid.PaddingEffect), "PaddingEffect")]
namespace XamTest.Droid
{
public class PaddingEffect : PlatformEffect
{
/// <inheritdoc />
protected override void OnAttached()
{
if (this.Control != null)
{
var firstMatch = this.Element.Effects.FirstOrDefault(d => d is XamTest.PaddingEffect);
if (firstMatch is XamTest.PaddingEffect effect)
{
this.Control.SetPadding((int)effect.Padding.Left, (int)effect.Padding.Top, (int)effect.Padding.Right, (int)effect.Padding.Bottom);
}
}
}
/// <inheritdoc />
protected override void OnDetached()
{
}
}
}
Update:
Padding has been added to the XF Button control in Xamarin.Forms v3.2+
<Button Padding="10,20,10,20" />
Old:
The best way to do it would be to increase the size of the button.
Then align the text as you see fit. Unfortunately it is about the best you can do. It works well if you have your text center aligned. Not so much if its left or right aligned.
you can wrap your button in a StackLayout and add padding to the StackLayout
<StackLayout Padding="10,10,10,10">
<Button Text="Cancel" BackgroundColor="#3079a8" TextColor="White" />
</StackLayout>
In iOS, you can use a simple custom renderer to add a bit of padding to the button:
[assembly: ExportRenderer(typeof(Button), typeof(iOSButtonRenderer))]
namespace My.App.iOS.Renderers.Button
{
/// <summary>
/// A custom renderer that adds a bit of padding to either side of a button
/// </summary>
public class iOSButtonRenderer : ButtonRenderer
{
public iOSButtonRenderer() { }
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
{
base.OnElementChanged(e);
Control.ContentEdgeInsets = new UIEdgeInsets(Control.ContentEdgeInsets.Top, Control.ContentEdgeInsets.Left + 10, Control.ContentEdgeInsets.Bottom, Control.ContentEdgeInsets.Right + 10);
}
}
}
It doesn't work for me in android.
Looking at SetPadding function, I see that the Control has a minimum height of 132 and a minimum width of 242.
I changed the SetPadding function in:
private void SetPadding()
{
var element = Element as ExtendedButton;
if (element != null)
{
Control.SetMinHeight(-1);
Control.SetMinimumHeight(-1);
Control.SetMinWidth(-1);
Control.SetMinimumWidth(-1);
Control.SetPadding(
(int)element.Padding.Left,
(int)element.Padding.Top - 6,
(int)element.Padding.Right,
(int)element.Padding.Bottom - 6);
}
}
The simplest you can do is
<StackLayout Margin="0,20,0,0" Orientation="Horizontal" Spacing="20">
<Switch VerticalOptions="CenterAndExpand" ></Switch>
<Label VerticalOptions="CenterAndExpand" TextColor="White">Remember Me</Label>
<StackLayout HorizontalOptions="FillAndExpand"><Button VerticalOptions="CenterAndExpand" HorizontalOptions="FillAndExpand" Text="Sign In" TextColor="White" BackgroundColor="#5cb85c" ></Button>
</StackLayout>
</StackLayout>
[such a big headache working with xamarin forms :'(]