Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xamarin.Forms: Customize button style on hover in UWP

I have defined some style in App.xaml of the Xamarin Forms project. But that doesn't affect the button if you hover over it or press it. The font color changes here to black and a gray border around the button appears. Now I want to overwrite this style.

First try: add definition to App.xaml of the UWP project

<Application
    x:Class="YourApp.UWP.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:YourApp.UWP"
    RequestedTheme="Light">

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.ThemeDictionaries>
                <ResourceDictionary x:Key="Light">
                    <SolidColorBrush x:Key="ButtonPointerOverBackgroundThemeBrush" Color="#00FF00" />
                </ResourceDictionary>
            </ResourceDictionary.ThemeDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Result: no changes at all

Second try: overwrite PointOver visual state in App.xamlof UWP project

<Application
    x:Class="YourApp.UWP.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:YourApp.UWP"
    RequestedTheme="Light">

    <Application.Resources>
        <ResourceDictionary>
            <Style TargetType="Button" x:Key="HoverButtonStyle">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Button">
                            <Grid>
                                <VisualStateManager.VisualStateGroups>
                                    <VisualStateGroup x:Name="CommonStates">
                                        <VisualState x:Name="PointerOver">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                                Storyboard.TargetProperty="Background">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="#00FF00" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                                Storyboard.TargetProperty="Foreground">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="#00FF00" />
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                    </VisualStateGroup>
                                </VisualStateManager.VisualStateGroups>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Result: no changes at all, I think I have to applay the style (if I do this the button seems not to be here)

Third try: add complete button style and apply it

<Style TargetType="Button" x:Key="HoverButtonStyle">
    <Setter Property="Background" Value="{ThemeResource ButtonBackgroundThemeBrush}" />
    <Setter Property="Foreground" Value="{ThemeResource ButtonForegroundThemeBrush}"/>
    <Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderThemeBrush}" />
    <Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
    <Setter Property="Padding" Value="12,4,12,4" />
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="VerticalAlignment" Value="Center" />
    <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
    <Setter Property="FontWeight" Value="SemiBold" />
    <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="PointerOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                       Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ButtonPointerOverBackgroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPointerOverForegroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                       Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPressedBackgroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPressedForegroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                       Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledBackgroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                       Storyboard.TargetProperty="BorderBrush">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledBorderThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledForegroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="FocusVisualWhite"
                                         Storyboard.TargetProperty="Opacity"
                                         To="1"
                                         Duration="0" />
                                    <DoubleAnimation Storyboard.TargetName="FocusVisualBlack"
                                         Storyboard.TargetProperty="Opacity"
                                         To="1"
                                         Duration="0" />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unfocused" />
                            <VisualState x:Name="PointerFocused" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Border x:Name="Border"
                Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                Margin="3">
                        <ContentPresenter x:Name="ContentPresenter"
                              Content="{TemplateBinding Content}"
                              ContentTransitions="{TemplateBinding ContentTransitions}"
                              ContentTemplate="{TemplateBinding ContentTemplate}"
                              Margin="{TemplateBinding Padding}"
                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                              AutomationProperties.AccessibilityView="Raw"/>
                    </Border>
                    <Rectangle x:Name="FocusVisualWhite"
                   IsHitTestVisible="False"
                   Stroke="{ThemeResource FocusVisualWhiteStrokeThemeBrush}"
                   StrokeEndLineCap="Square"
                   StrokeDashArray="1,1"
                   Opacity="0"
                   StrokeDashOffset="1.5" />
                    <Rectangle x:Name="FocusVisualBlack"
                   IsHitTestVisible="False"
                   Stroke="{ThemeResource FocusVisualBlackStrokeThemeBrush}"
                   StrokeEndLineCap="Square"
                   StrokeDashArray="1,1"
                   Opacity="0"
                   StrokeDashOffset="0.5" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Custom renderer:

protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
    base.OnElementChanged(e);

    if (this.Element != null)
    {
        this.Control.Style = Windows.UI.Xaml.Application.Current.Resources["HoverButtonStyle"] as Windows.UI.Xaml.Style;
    }
}

Result: style seems to be applied, but the background color I defined in Xamarin Forms doesn't take the full width of the button. Also the border color still isn't changed.

How is this done right?

like image 270
testing Avatar asked Oct 06 '16 12:10

testing


2 Answers

Now I found out how this styling works. First you have to find the base UWP class (by holding Ctrl and clicking on the class name or by looking here). E.g. for Picker it is ComboBox. If you use Google you come to this page, where you find everything you need to know about overwriting the default layout of a ComboBox. For a Button it is this page and so on. So the solution is to have a App.xaml (UWP project) like this (take the color of your choice):

<Application
    x:Class="YourApp.UWP.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:YourApp.UWP"
    RequestedTheme="Light">

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.ThemeDictionaries>
                <ResourceDictionary x:Key="Light">
                    <SolidColorBrush x:Key="SystemControlHighlightBaseMediumLowBrush" Color="White" />
                    <SolidColorBrush x:Key="SystemControlHighlightBaseHighBrush" Color="White" />
                </ResourceDictionary>
            </ResourceDictionary.ThemeDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

To apply a style only for some buttons, you have to do the following steps:

In App.xaml of your UWP project you need the following entry:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Styles/DefaultButtonControlTemplate.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Here you register a style, which is in a separate file. I have a folder called Styles, where the file DefaultButtonControlTemplate.xaml is placed in. The content of the files taken from MSDN and looks like this:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyApp.UWP.ControlTemplates">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="ColorsAndBrushes.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <ControlTemplate x:Key="DefaultButtonControlTemplate" TargetType="Button">
        <!-- here is the content of the file -->
    </ControlTemplate>

</ResourceDictionary>

As you can see I'm referencing a common file, which contains all my colors (or brushes in UWP world).

Finally, you need a custom renderer like this:

public class DefaultButtonRenderer : ButtonRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
    {
        base.OnElementChanged(e);

        if (this.Control != null)
        {
            this.Control.Template = Windows.UI.Xaml.Application.Current.Resources["DefaultButtonControlTemplate"] as Windows.UI.Xaml.Controls.ControlTemplate;
        }
    }
}
like image 124
testing Avatar answered Oct 21 '22 08:10

testing


Found a way to keep it all within the UWP custom renderer, not having to worry about modifying anything else or if other button settings will conflict. In my case, I created a custom PillButton so obviously update your classes and colors or what not. If you don't see a corner radius on the button then it will be a normal button using the below.

[assembly: ExportRenderer(typeof(PillButton), typeof(PillButtonRenderer))]
namespace YourProject.UWP.Renderers
{
    public class PillButtonRenderer : ButtonRenderer
    {
        public PillButton PillButtonElement => Element as PillButton;

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
        {
            base.OnElementChanged(e);

            if (Control != null)
            {
                Windows.UI.Xaml.Controls.Button button = Control;

                Resources = (Windows.UI.Xaml.ResourceDictionary)XamlReader.Load(PillButtonStyleDictionary);

                Resources["PillCornerRadius"] = PillButtonElement.CornerRadius;
                Resources["PillBorderWidth"] = PillButtonElement.BorderWidth;

                // if hover color not supplied, then hover color will be lighter version of background color, unless background color is transparent in which case it will be the border color
                var hoverColor = PillButtonElement.UwpHoverColor != default(Color) ? PillButtonElement.UwpHoverColor
                    : (PillButtonElement.BackgroundColor == Color.Transparent
                        ? PillButtonElement.BorderColor
                        : PillButtonElement.BackgroundColor.ChangeColorBrightness(0.15));
                Resources["PillFillColorOnHover"] = new SolidColorBrush(hoverColor.ToUwp());

                // if pressed color not supplied, then make it a darker shade of the hover color
                var pressedColor = PillButtonElement.UwpPressedColor != default(Color) ? PillButtonElement.UwpPressedColor : hoverColor.ChangeColorBrightness(-0.09);
                Resources["PillFillColorOnPressed"] = new SolidColorBrush(pressedColor.ToUwp());

                // if text color on hover/press not supplied, then make it black or white depending on how dark the hover color is
                var textColor = PillButtonElement.PressedTextColor != default(Color) ? PillButtonElement.PressedTextColor : hoverColor.BlackOrWhiteForegroundTextColor();
                Resources["PillTextColorOnHoverOrPressed"] = new SolidColorBrush(textColor.ToUwp());

                // set normal style
                Resources["PillBackgroundColor"] = new SolidColorBrush(PillButtonElement.BackgroundColor.ToUwp());
                Resources["PillTextColor"] = new SolidColorBrush(PillButtonElement.TextColor.ToUwp());
                PillButtonElement.BackgroundColor = Color.Transparent; // hack

                button.Style = Resources["PillButtonStyle"] as Windows.UI.Xaml.Style;
            }
        }

        private const string PillButtonStyleDictionary = @"<ResourceDictionary
    xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
    xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">

    <x:Double x:Key=""PillCornerRadius"">0</x:Double>
    <x:Double x:Key=""PillBorderWidth"">0</x:Double>

    <SolidColorBrush
        x:Key=""PillBackgroundColor""
        Color=""Black"" />
    <SolidColorBrush
        x:Key=""PillTextColor""
        Color=""Black"" />
    <SolidColorBrush
        x:Key=""PillFillColorOnHover""
        Color=""Black"" />
    <SolidColorBrush
        x:Key=""PillFillColorOnPressed""
        Color=""Black"" />
    <SolidColorBrush
        x:Key=""PillTextColorOnHoverOrPressed""
        Color=""Black"" />

    <Style
        x:Key=""PillButtonStyle""
        TargetType=""Button"">
        <Setter
            Property=""Background""
            Value=""{ThemeResource SystemControlBackgroundBaseLowBrush}"" />
        <Setter
            Property=""Foreground""
            Value=""{ThemeResource SystemControlForegroundBaseHighBrush}"" />
        <Setter
            Property=""BorderBrush""
            Value=""{ThemeResource SystemControlForegroundTransparentBrush}"" />
        <Setter
            Property=""BorderThickness""
            Value=""{ThemeResource ButtonBorderThemeThickness}"" />
        <Setter
            Property=""Padding""
            Value=""8,4,8,4"" />
        <Setter
            Property=""HorizontalAlignment""
            Value=""Left"" />
        <Setter
            Property=""VerticalAlignment""
            Value=""Center"" />
        <Setter
            Property=""FontFamily""
            Value=""{ThemeResource ContentControlThemeFontFamily}"" />
        <Setter
            Property=""FontWeight""
            Value=""Normal"" />
        <Setter
            Property=""FontSize""
            Value=""{ThemeResource ControlContentThemeFontSize}"" />
        <Setter
            Property=""UseSystemFocusVisuals""
            Value=""True"" />
        <Setter Property=""Template"">
            <Setter.Value>
                <ControlTemplate TargetType=""Button"">
                    <Grid x:Name=""RootGrid"">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name=""CommonStates"">
                                <VisualState x:Name=""Normal"">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""Pill""
                                            Storyboard.TargetProperty=""Fill"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillBackgroundColor}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""ContentPresenter""
                                            Storyboard.TargetProperty=""Foreground"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillTextColor}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <PointerUpThemeAnimation Storyboard.TargetName=""RootGrid"" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name=""PointerOver"">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""Pill""
                                            Storyboard.TargetProperty=""Fill"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillFillColorOnHover}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""ContentPresenter""
                                            Storyboard.TargetProperty=""Foreground"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillTextColorOnHoverOrPressed}"" />
                                        </ObjectAnimationUsingKeyFrames>

                                        <PointerUpThemeAnimation Storyboard.TargetName=""RootGrid"" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name=""Pressed"">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""Pill""
                                            Storyboard.TargetProperty=""Fill"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillFillColorOnPressed}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""ContentPresenter""
                                            Storyboard.TargetProperty=""Foreground"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillTextColorOnHoverOrPressed}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <PointerDownThemeAnimation Storyboard.TargetName=""RootGrid"" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name=""Disabled"">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""Pill""
                                            Storyboard.TargetProperty=""Fill"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{ThemeResource SystemControlBackgroundBaseLowBrush}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""ContentPresenter""
                                            Storyboard.TargetProperty=""Foreground"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{ThemeResource SystemControlDisabledBaseMediumLowBrush}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""Pill""
                                            Storyboard.TargetProperty=""Stroke"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{ThemeResource SystemControlDisabledTransparentBrush}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Rectangle
                            x:Name=""Pill""
                            RadiusX=""{StaticResource PillCornerRadius}""
                            RadiusY=""{StaticResource PillCornerRadius}""
                            Stroke=""{TemplateBinding BorderBrush}""
                            StrokeThickness=""{StaticResource PillBorderWidth}"" />
                        <ContentPresenter
                            x:Name=""ContentPresenter""
                            Padding=""{TemplateBinding Padding}""
                            HorizontalContentAlignment=""{TemplateBinding HorizontalContentAlignment}""
                            VerticalAlignment=""Center""
                            VerticalContentAlignment=""{TemplateBinding VerticalContentAlignment}""
                            AutomationProperties.AccessibilityView=""Raw""
                            Content=""{TemplateBinding Content}""
                            ContentTemplate=""{TemplateBinding ContentTemplate}""
                            ContentTransitions=""{TemplateBinding ContentTransitions}"" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>";
    }
}

In case it comes up, here's the ToUwp color extension for converting from Xamarin Color to UWP Color that's being used in that code:

internal static class ColorExtensions
{
    public static Color ToUwp(this Xamarin.Forms.Color color)
    {
        return Color.FromArgb((byte)(color.A * 255),
                              (byte)(color.R * 255),
                              (byte)(color.G * 255),
                              (byte)(color.B * 255));
    }
}
like image 45
Mark Z. Avatar answered Oct 21 '22 08:10

Mark Z.