Xamarin.Формы: настройка стиля кнопок при наведении в UWP

#button #xamarin #xamarin.forms #uwp #windows-10-universal

#кнопка #xamarin #xamarin.forms #uwp #windows-10-универсальный

Вопрос:

Я определил некоторый стиль в App.xaml проекте Xamarin Forms. Но это не влияет на кнопку, если вы наведете на нее курсор или нажмете ее. Цвет шрифта здесь меняется на черный, а вокруг кнопки появляется серая рамка. Теперь я хочу перезаписать этот стиль.

Первая попытка: добавьте определение в App.xaml проект UWP

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

Результат: никаких изменений вообще

Вторая попытка: перезаписать PointOver визуальное состояние в App.xaml проекте UWP

 <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, не занимает всю ширину кнопки. Также цвет границы по-прежнему не изменяется.

Как это делается правильно?

Ответ №1:

Теперь я узнал, как работает этот стиль. Сначала вам нужно найти базовый класс UWP (удерживая Ctrlи щелкая по имени класса или просматривая здесь). Например, для Picker него. ComboBox Если вы используете Google, вы попадаете на эту страницу, где найдете все, что вам нужно знать о перезаписи макета a по умолчанию ComboBox . Для a Button это эта страница и так далее. Итак, решение состоит в том, чтобы иметь App.xaml (проект UWP), подобный этому (выберите цвет по вашему выбору):

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

Чтобы применить стиль только для некоторых кнопок, необходимо выполнить следующие действия:

В App.xaml вашем проекте UWP вам нужна следующая запись:

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

Комментарии:

1. Что, если вы хотите применить его только к некоторым кнопкам?

2. Один из способов, который я нашел, — использовать пользовательский рендерер (см. Отредактированный ответ). Не знаю, есть ли лучший / более простой способ.

3. Что происходит внутри ColorsAndBrushes.XAML и <!— here is the content of the file —>?

4. @iupchris10: это ResourceDictionary с цветами (например <Color x:Key="Primary">#FF0000</Color> ) и кистями (например <SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}" /> ).

5. @SuperJMN Уверен, вы могли бы использовать клавиши в качестве селекторов для применения только к некоторым кнопкам

Ответ №2:

Найден способ сохранить все это в пользовательском средстве визуализации 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>";
    }
}
  

На случай, если это произойдет, вот расширение ToUwp color для преобразования цвета Xamarin в цвет UWP, который используется в этом коде:

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