如何根据 WinUI 3 中 viewModel 中的值更改按钮的外观

问题描述 投票:0回答:3

我遇到了一个常见的场景,我有一个“安装”按钮,当某些东西被“安装”时它应该变成绿色并且不可点击(我猜这应该是视图模型中的一个

bool
属性)。 我知道实现这一目标的一种方法:

  • 为我需要更改的每个属性定义一个转换器,在本例中,一个

    bool->string
    转换器(用于文本),一个
    bool -> color
    转换器(用于颜色)

  • 使用VisualState(我还没有完全理解),据我所知,我需要在代码隐藏中切换状态,但是我如何在代码隐藏中监听viewmodel的属性变化?

但这似乎并不理想,比如我突然有第三种状态,如“安装暂停”,我使用枚举作为状态,然后突然我需要更改所有转换器。

请给我一个此类案例的工作示例。

uwp winui-3 winui
3个回答
0
投票

假设您有这个代表安装状态的枚举:

public enum InstallationStatus
{
    Unknown,
    NotInstalled,
    Installed,
}

然后,您可以像这样创建一个自定义按钮:

StatusButton.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections.Generic;

namespace ButtonTests;

// We need this to use Dicionary in XAML.
public class StringToStyleDictionary : Dictionary<string, Style>
{
}

public sealed class StatusButton : Button
{
    public StatusButton()
    {
        this.DefaultStyleKey = typeof(StatusButton);
        // This line will register a callback 
        //that will be called every time the "Content" property is changed.
        this.RegisterPropertyChangedCallback(Button.ContentProperty, OnContentPropertyChanged);
    }

    public Dictionary<string, Style> StyleDictionary
    {
        get => (Dictionary<string, Style>)GetValue(StyleDictionaryProperty);
        set => SetValue(StyleDictionaryProperty, value);
    }

    public static readonly DependencyProperty StyleDictionaryProperty =
        DependencyProperty.Register(
            nameof(StyleDictionary),
            typeof(Dictionary<string, Style>),
            typeof(StatusButton),
            new PropertyMetadata(default));

    private void OnContentPropertyChanged(DependencyObject sender, DependencyProperty dp)
    {
        if (Content?.ToString() is string styleKey &&
            StyleDictionary?.TryGetValue(styleKey.ToString(), out Style? style) is true)
        {
            Style = style;
        }
    }
}

Generic.xaml

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

    <Style
        BasedOn="{StaticResource DefaultButtonStyle}"
        TargetType="local:StatusButton" />

</ResourceDictionary>

并像这样使用它:

MainPage.xaml

<Page
    x:Class="ButtonTests.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:ButtonTests"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">

    <Page.Resources>
        <local:StringToStyleDictionary x:Key="StyleDictionary">
            <Style
                x:Key="Unknown"
                TargetType="local:StatusButton">
                <Setter Property="Background" Value="HotPink" />
            </Style>
            <Style
                x:Key="NotInstalled"
                TargetType="local:StatusButton">
                <Setter Property="Background" Value="LightGreen" />
            </Style>
            <Style
                x:Key="Installed"
                TargetType="local:StatusButton">
                <Setter Property="Background" Value="SkyBlue" />
            </Style>
        </local:StringToStyleDictionary>
    </Page.Resources>

    <StackPanel>
        <local:StatusButton
            Content="{x:Bind ViewModel.Status, Mode=OneWay}"
            StyleDictionary="{StaticResource StyleDictionary}" />
    </StackPanel>
</Page>

0
投票

这是另一个使用

AttachedProperty
的选项:

StyleSelector.cs

using Microsoft.UI.Xaml;
using System.Collections.Generic;

namespace ButtonTests;

public class StyleSelector : DependencyObject
{
    public static readonly DependencyProperty StyleDictionaryProperty =
        DependencyProperty.RegisterAttached(
            "StyleDictionary",
            typeof(Dictionary<string, Style>),
            typeof(StyleSelector),
            new PropertyMetadata(default));

    public static readonly DependencyProperty KeyProperty =
        DependencyProperty.RegisterAttached(
            "Key",
            typeof(string),
            typeof(StyleSelector),
            new PropertyMetadata(default, OnKeyPropertyChanged));

    public static Dictionary<string, Style> GetStyleDictionary(DependencyObject obj)
        => (Dictionary<string, Style>)obj.GetValue(StyleDictionaryProperty);

    public static void SetStyleDictionary(DependencyObject obj, Dictionary<string, Style> value)
        => obj.SetValue(StyleDictionaryProperty, value);

    public static string GetKey(DependencyObject obj)
        => (string)obj.GetValue(KeyProperty);

    public static void SetKey(DependencyObject obj, string value)
        => obj.SetValue(KeyProperty, value);

    private static void OnKeyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is FrameworkElement target &&
            GetStyleDictionary(target) is Dictionary<string, Style> styleDictionary &&
            e.NewValue is string styleKey &&
            styleDictionary.TryGetValue(styleKey, out Style? style) is true)
        {
            target.Style = style;
        }
    }
}
public class StringToDataTemplateDictionary : Dictionary<string, DataTemplate>
{
}

MainPage.xaml

<Page
    x:Class="ButtonTests.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:ButtonTests"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">

    <Page.Resources>
        <local:StringToStyleDictionary x:Key="StyleDictionary">
            <Style
                x:Key="Unknown"
                BasedOn="{StaticResource DefaultButtonStyle}"
                TargetType="Button">
                <Setter Property="Background" Value="HotPink" />
            </Style>
            <Style
                x:Key="NotInstalled"
                BasedOn="{StaticResource DefaultButtonStyle}"
                TargetType="Button">
                <Setter Property="Background" Value="LightGreen" />
            </Style>
            <Style
                x:Key="Installed"
                BasedOn="{StaticResource DefaultButtonStyle}"
                TargetType="Button">
                <Setter Property="Background" Value="SkyBlue" />
            </Style>
        </local:StringToStyleDictionary>
    </Page.Resources>

    <StackPanel>
        <Button
            local:StyleSelector.Key="{x:Bind ViewModel.Status, Mode=OneWay}"
            local:StyleSelector.StyleDictionary="{StaticResource StyleDictionary}"
            Content="{x:Bind ViewModel.Status, Mode=OneWay}" />
    </StackPanel>
</Page>

0
投票

我不确定项目类型到底是什么。我通过数据绑定提供 UWP 解决方案。这也适用于 WinUI3 应用程序。

我想做的是为不同的状态创建一个

enum
,然后让Enabled属性和Background属性绑定到带有转换器的
enum

详细步骤:

  1. 为您需要的按钮创建默认按钮样式。然后注释掉
    Disabled
    状态的后台更改代码。
  2. 创建一个
    enum
    来表示不同的状态。
  3. Enabled属性和Background属性
  4. 创建两个ValueConverter类
  5. 将 Button 的这些属性绑定到 ViewModel 的
    enum
    属性

这里是详细的代码,大家可以参考一下

值转换器:

   public class StatusToBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        IStatus status = (IStatus)value;

        switch (status)
        {
                case IStatus.InstallPaused:
                return false;
                case IStatus.NotInstalled:
                return true;
                case IStatus.Installed:
                return false;
        }
        //default value
        return true;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}


public class StatusToColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        IStatus status = (IStatus)value;

        switch (status)
        {
            case IStatus.InstallPaused:
                return new SolidColorBrush(Colors.Green);
            case IStatus.NotInstalled:
                return new SolidColorBrush(Colors.Yellow); 
            case IStatus.Installed:
                return new SolidColorBrush(Colors.Green); 
        }
        //default value
        return new SolidColorBrush(Colors.Yellow);
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

MainPage.CS

    public enum IStatus
{
    NotInstalled,
    Installed,
    InstallPaused,
}

/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
    public MainViewModel SourceModel = new MainViewModel();
    public MainPage()
    {
        this.InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        SourceModel.Status = IStatus.Installed;
    }

   
}

public class MainViewModel :INotifyPropertyChanged
{
    public IStatus _status { get; set; }

    public IStatus Status 
    {
        get { return _status; }
        set { 
            _status = value;
            RaisePropertychanged("Status");
        }
       
    }

    public MainViewModel()
    {
        Status = IStatus.NotInstalled;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertychanged(string propertyName) 
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

主页.Xaml:

    <Page.Resources>
    <local:StatusToBoolConverter x:Key="StatusToBoolConverter"/>
    <local:StatusToColorConverter x:Key="StatusToColorConverter"/>
    <Style x:Key="ButtonStyle1" TargetType="Button">
        <Setter Property="Background" Value="{ThemeResource ButtonBackground}"/>
        <Setter Property="BackgroundSizing" Value="OuterBorderEdge"/>
        <Setter Property="Foreground" Value="{ThemeResource ButtonForeground}"/>
        <Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderBrush}"/>
        <Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}"/>
        <Setter Property="Padding" Value="{StaticResource ButtonPadding}"/>
        <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="{StaticResource UseSystemFocusVisuals}"/>
        <Setter Property="FocusVisualMargin" Value="-3"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <ContentPresenter x:Name="ContentPresenter" AutomationProperties.AccessibilityView="Raw" Background="{TemplateBinding Background}" BackgroundSizing="{TemplateBinding BackgroundSizing}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" ContentTemplate="{TemplateBinding ContentTemplate}" CornerRadius="{TemplateBinding CornerRadius}" Content="{TemplateBinding Content}" ContentTransitions="{TemplateBinding ContentTransitions}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal">
                                    <Storyboard>
                                        <PointerUpThemeAnimation Storyboard.TargetName="ContentPresenter"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="PointerOver">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPointerOver}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBorderBrushPointerOver}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonForegroundPointerOver}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <PointerUpThemeAnimation Storyboard.TargetName="ContentPresenter"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPressed}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBorderBrushPressed}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonForegroundPressed}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <PointerDownThemeAnimation Storyboard.TargetName="ContentPresenter"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <!--<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundDisabled}"/>
                                        </ObjectAnimationUsingKeyFrames>-->
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBorderBrushDisabled}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonForegroundDisabled}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </ContentPresenter>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Page.Resources>

<StackPanel>
    <Button x:Name="TargetButton" Style="{StaticResource ButtonStyle1}" Content="TargetButton" 
            IsEnabled="{x:Bind SourceModel.Status,Converter={StaticResource StatusToBoolConverter},Mode=OneWay}" 
            Background="{x:Bind SourceModel.Status,Converter={StaticResource StatusToColorConverter},Mode=OneWay}" 
            >
    </Button>

    <Button Content="ChangeStates" Click="Button_Click"/>
</StackPanel>
© www.soinside.com 2019 - 2024. All rights reserved.