Xamarin.Forms:如何在UWP中自定义按钮悬停样式

8

我在Xamarin Forms项目的App.xaml中定义了一些样式。但这并不影响鼠标悬停或按下按钮时的按钮。此时,字体颜色变为黑色,按钮周围出现灰色边框。现在我想覆盖这个样式。

第一次尝试:在UWP项目的App.xaml中添加定义

<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>

结果:完全没有任何变化

第二次尝试:覆盖UWP项目的App.xaml中的PointOver可视状态。

<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>

结果:没有任何变化,我想我必须应用样式(如果这样做,按钮似乎就不在这里)

第三次尝试:添加完整的按钮样式并应用它

<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>

自定义渲染器:

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;
    }
}

结果:样式似乎被应用了,但我在Xamarin Forms中定义的背景颜色没有覆盖整个按钮的宽度。此外,边框颜色仍然没有改变。
如何正确处理这个问题?
2个回答

9

我发现了这个样式是如何工作的。首先,你需要找到基本的UWP类(通过按住Ctrl键并单击类名或查看此处)。例如,对于Picker来说,它是ComboBox。如果你使用谷歌搜索,你会来到这个网页,在这里你可以找到有关重写ComboBox默认布局的所有信息。对于一个Button来说,可以参考这个网页等。因此,解决方案就是创建一个像这样的App.xaml文件(选择你喜欢的颜色):

<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>

要仅对某些按钮应用样式,您需要执行以下步骤:
在您的UWP项目的“App.xaml”中,您需要添加以下条目:
<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Styles/DefaultButtonControlTemplate.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

在这里,您可以注册一个独立文件中的样式。我有一个名为Styles的文件夹,其中放置了DefaultButtonControlTemplate.xaml文件。该文件的内容取自MSDN,并且如下所示:

<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>

如您所见,我正在引用一个常见的文件,其中包含所有我的颜色(在 UWP 世界中称为画笔)。

最后,您需要像这样的自定义渲染器:

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;
        }
    }
}

如果您只想将其应用于某些按钮怎么办? - SuperJMN
我发现一种方法是使用自定义渲染器(请参见编辑后的答案)。不知道是否有更好/更容易的方法。 - testing
ColorsAndBrushes.xaml 文件中包含什么?<!-- 这是文件的内容 -->? - iupchris10
1
@iupchris10:这是一个包含颜色(例如<Color x:Key="Primary">#FF0000</Color>)和画刷(例如<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}" />)的ResourceDictionary - testing
@SuperJMN 你可以使用键作为选择器,仅将其应用于某些按钮。 - Chris Moschini
附加信息:对于包含透明图像的ToolbarItems,我需要覆盖SystemControlHighlightAltBaseHighBrush以防止在悬停时将其颜色更改为黑色。 - Markus S.

1

我找到了一种方法,可以在UWP自定义渲染器中保持所有内容,无需担心修改其他内容或是否会与其他按钮设置冲突。在我的情况下,我创建了一个自定义的PillButton,所以请更新您的类和颜色等内容。如果您在按钮上看不到角半径,则它将是使用以下内容的普通按钮。

[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>";
    }
}

如果需要,这里是用于将Xamarin颜色转换为UWP颜色的ToUwp颜色扩展,该代码中正在使用:

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));
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接