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

时间:2014-09-05 20:42:42

标签: c# .net wpf xaml data-binding

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

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

我可以想出三种解决方法:

  1. Visibility中,创建另一个FileSource类型的属性,并为每个控件返回正确的可见性。但这对我来说听起来非常糟糕 - 这听起来好像我正在紧密地混合#34;模型" (File)具有视图(控件)的行为。
  2. 创建大量简单的数据转换类,然后使用模型的语义属性进行数据绑定(在本例中为string)。例如,Visibility - >某些组件的string转换器和另一个Visibility - > Visibility转换器(返回"相反的" Update值)用于其他组件。这适用于属性更改通知,但是对于我期望从子控件发出的各种不同响应创建一个新类对我来说是不必要的。
  3. 编写PropertyChanged方法并订阅{{1}}事件。这听起来像我在很大程度上打败了数据绑定点。
  4. 这样做的正确方法是什么?也许是一种简单的方法来进行数据转换"在XAML中内联(对于我打算读取的值,但不写回源)?

4 个答案:

答案 0 :(得分:3)

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

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

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

最后,您可以使用链接多个值转换器的ChainConverter链接转换器。您可以找到sample implementation in my private frameworkpermalink)。有了它,您可以在XAML中创建转换器实例,如ConvertIsNotNullToVisibleInverted。样品用法:

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

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

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

答案 1 :(得分:2)

考虑使用visual states - 这些是针对您正在讨论的场景而设计的,其中您有一个需要在多个状态之间转换的控件。使用这种方法优于绑定的一个优点是它允许您使用动画(包括过渡)。


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

<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)。您还可以使用Blend 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 :(得分:1)

选项(2)基本上就是你要去的地方。您需要IValueConverter(或2,具体取决于实施方式)。

我会称之为NullToVisibilityConverter或类似的东西。如果Visiblity.Visible参数不为空,它将返回value,如果是Visibility.Collapsed,则返回public class NullToVisibilityConverter : IValueConverter { public object Convert(...) { return value != null ? Visibility.Visible : Visibility.Collapsed; } public object ConvertBack(...) { return Binding.DoNothing; } } 。要交换行为,您可以编写第二个转换器,或使用ConverterParameter。

看起来像是:

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

使用方法:

{{1}}

答案 3 :(得分:1)

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

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