如何使用数据绑定正确更改 WPF 中控件的状态

本文关键字:WPF 控件 状态 何使用 数据绑定 | 更新日期: 2023-09-27 18:20:53

我对WPF中的数据绑定很陌生。

假设我有一个名为 FileSource 的类,它有一个名为 File(字符串(的属性以及从中派生的其他一些属性。在我的 GUI 中,我有一个控件,其外观应在两种"模式"之间更改:一种模式(如果null File,另一种模式(如果它不null(。假设一种模式将某些子组件的可见性设置为 Visible,将其他子组件设置为 Collapsed ,而另一种模式则相反。

我可以想到 3 种方法来解决这个问题:

  1. FileSource 中,创建另一个类型为 Visibility 的属性,并返回每个控件的正确可见性。但这对我来说听起来很糟糕 - 听起来我会将"模型"(FileSource(与视图(控件(的行为紧密混合在一起。
  2. 创建大量简单的数据转换类,然后使用模型的语义属性(在本例中为 File(进行数据绑定。例如,一个string -> Visibility 转换器用于某些组件,另一个string -> Visibility 转换器(返回"相反"的Visibility值(用于其他组件。这适用于属性更改通知,并且可以很好地使用,但是为我期望从子控件中获得的每种不同响应创建一个新类对我来说听起来不必要地复杂。
  3. 编写一个Update方法并订阅PropertyChanged事件。在我看来,这听起来像是我将在很大程度上击败数据绑定的重点。

正确的方法是什么?也许有一种简单的方法可以在 XAML 中内联执行数据"转换"(对于我打算读取但不写回源的值(?

如何使用数据绑定正确更改 WPF 中控件的状态

你不需要太多的转换器类。您只需要一个BoolToVisibilityConverter,但具有指定truefalse可见性值的属性。您可以创建如下实例:

<BoolToVisibilityConverter x:Key="ConvertBoolToVisible"
    FalseVisibility="Collapsed" TrueVisibility="Visible" />
<BoolToVisibilityConverter x:Key="ConvertBoolToVisibleInverted"
    FalseVisibility="Visible" TrueVisibility="Collapsed" />

另一个转换器是 IsNullConverter .您可以使用类似 bool InvertValue 的属性对其进行参数化。在资源字典中,实例可以称为 ConvertIsNullConvertIsNotNull 。或者,如果您愿意,可以创建两个类。

最后,您可以将转换器与链接多个值转换器的ChainConverter链接在一起。您可以在我的私有框架(永久链接(中找到示例实现。有了它,您可以在 XAML 中创建转换器实例,例如 ConvertIsNotNullToVisibleInverted .示例用法:

<a:ChainConverter x:Key="ConvertIsNotNullToVisible">
    <a:ValueConverterRef Converter="{StaticResource ConvertIsNotNull}"/>
    <a:ValueConverterRef Converter="{StaticResource ConvertBoolToVisible}"/>
</a:ChainConverter>

另一种方法是使用触发器。不过,XAML 代码会更复杂,所以我更喜欢转换器。它需要编写一些类,但这是值得的。使用这样的体系结构,不需要为每个组合提供数十个类,并且 C# 和 XAML 代码都将简单易读。

并且不要添加所有可能的转换器组合。仅在需要时添加它们。最有可能的是,您只需要几个。

请考虑使用视觉状态 - 这些状态专为您正在谈论的方案而设计,其中您有一个需要在多个状态之间转换的控件。 与绑定相比,使用此方法的一个优点是它允许您使用动画(包括过渡(。


若要使其正常工作,请在控件的根元素下声明视觉状态组和视觉状态:

<UserControl>
    <Grid x:Name="LayoutRoot">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="DefaultStates">
                <VisualState x:Name="State1" />
                <VisualState x:Name="State2">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="textBlock2"
                                                       Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0" To="Visible" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <TextBlock x:Name="textBlock1" Text="state #1" />
        <TextBlock x:Name="textBlock2" Text="state #2" Visibility="Collapsed" />
    </Grid>
</UserControl>

要在状态之间转换,可以调用VisualStateManager.GoToState(this, "State2", true) 。 还可以使用混合 SDK 通过触发器从 XAML 进行转换。 可能最有用的转换方法是使用 DataStateBehavior ,它将状态绑定到视图模型属性:

    <Grid x:Name="LayoutRoot">
        <i:Interaction.Behaviors>
            <ei:DataStateBehavior Binding="{Binding CurrentState}" 
                                  Value="State2" 
                                  TrueState="State2" FalseState="State1" />
        </i:Interaction.Behaviors>

这样,您只需更新视图模型中的属性,UI 状态就会自动更新。

public string File
{
    get { return _file; }
    set
    {
        _file = value;
        RaisePropertyChanged();
        RaisePropertyChanged(() => CurrentState);
    }
}
private string _file;
public string CurrentState
{
    get { return (File == null ? "State1" : "State2"); }
}

选项 (2( 本质上就是您要找的。您需要一个IValueConverter(或 2,具体取决于实现(。

我会称它为NullToVisibilityConverter或类似的东西。如果value参数不为 null,它将返回 Visiblity.Visible,如果是,则返回 Visibility.Collapsed。要交换行为,您可以编写第二个转换器,或使用转换器参数。

它看起来像:

public class NullToVisibilityConverter : IValueConverter
{
   public object Convert(...)
   {
       return value != null ? Visibility.Visible : Visibility.Collapsed;
   }
   public object ConvertBack(...)
   {
       return Binding.DoNothing;
   }
}

使用:

<Button Visibility="{Binding File, Converter={StaticResource MyConverter}"/>

而且....以下是使用样式/触发器的另一种方法:

MainWindow.xaml

<Window x:Class="WpfApplication19.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel>
            <StackPanel.Resources>
                <Style TargetType="TextBlock" x:Key="FileIsNull">
                    <Setter Property="Visibility" Value="Collapsed" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding File}" Value="{x:Null}">
                            <Setter Property="Visibility" Value="Visible" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
                <Style TargetType="TextBlock" x:Key="FileIsNotNull">
                    <Setter Property="Visibility" Value="Visible" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding File}" Value="{x:Null}">
                            <Setter Property="Visibility" Value="Collapsed" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </StackPanel.Resources>
            <TextBlock Text="Filename is null" Style="{StaticResource FileIsNull}" MinHeight="50" Background="Beige" />
            <TextBlock Text="{Binding File}" Style="{StaticResource FileIsNotNull}" MinHeight="50" Background="Bisque" />
            <Button Name="btnSetFileToNull" Click="btnSetFileToNull_Click" Content="Set File To Null" Margin="5" />
            <Button Name="btnSetFileToNotNull" Click="btnSetFileToNotNull_Click"  Content="Set File To Not Null" Margin="5" />
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.ComponentModel;
using System.Windows;
namespace WpfApplication19
{
    public partial class MainWindow : Window
    {
        public FileSource fs { get; set; }
        public MainWindow()
        {
            InitializeComponent();
            fs = new FileSource();
            this.DataContext = fs;
        }
        private void btnSetFileToNull_Click(object sender, RoutedEventArgs e)
        {
            fs.File = null;
        }
        private void btnSetFileToNotNull_Click(object sender, RoutedEventArgs e)
        {
            fs.File = @"C:'abc.123";
        }
    }
    public class FileSource : INotifyPropertyChanged
    {
        private string _file;
        public string File { get { return _file; } set { _file = value; OnPropertyChanges("File"); } }
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanges(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}