我遇到了一个常见的场景,我有一个“安装”按钮,当某些东西被“安装”时它应该变成绿色并且不可点击(我猜这应该是视图模型中的一个
bool
属性)。
我知道实现这一目标的一种方法:
为我需要更改的每个属性定义一个转换器,在本例中,一个
bool->string
转换器(用于文本),一个 bool -> color
转换器(用于颜色)
使用VisualState(我还没有完全理解),据我所知,我需要在代码隐藏中切换状态,但是我如何在代码隐藏中监听viewmodel的属性变化?
但这似乎并不理想,比如我突然有第三种状态,如“安装暂停”,我使用枚举作为状态,然后突然我需要更改所有转换器。
请给我一个此类案例的工作示例。
假设您有这个代表安装状态的枚举:
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>
这是另一个使用
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>
我不确定项目类型到底是什么。我通过数据绑定提供 UWP 解决方案。这也适用于 WinUI3 应用程序。
我想做的是为不同的状态创建一个
enum
,然后让Enabled属性和Background属性绑定到带有转换器的enum
。
详细步骤:
Disabled
状态的后台更改代码。enum
来表示不同的状态。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>