2014-09-05 77 views
3

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

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

我能想到的3种方法去解决这个:

  1. FileSource,创建Visibility类型的其他财产,并返回每个控制适当的知名度。但这对我来说听起来很糟糕 - 这听起来像是我将“模型”(FileSource)与视图(控件)的行为紧密混合。
  2. 创建大量简单的数据转换类,然后使用模型的语义属性进行数据绑定(在本例中为File)。例如,对于其他组件,一个stringVisibility转换器和另一个stringVisibility转换器(其返回“Visibility”值)。这对于属性更改通知很有效,但对于我对子控件期望的每种不同响应都会创建一个新类,这听起来对我来说是不必要的复杂。
  3. 编写一个Update方法并订阅PropertyChanged事件。这听起来像我会在很大程度上击败数据绑定点。

这样做的正确方法是什么?有没有,也许是一种简单的方法来在XAML中内联数据“转换”(对于我打算读取的值,但不能回写到源)?

回答

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代码都将变得简单易读。

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

+0

通过'BoolToVisibilityConverter' ,你的意思是推出我自己的,或者在.NET中使用'BooleanToVisibilityConverter'? – 2014-09-05 21:07:16

+1

@TheodorosChatzigiannakis滚你自己的。 'BooleanToVisibilityConverter'只是转换为Collapsed/Visible,iirc,所以它不是非常有用和可重用的。 – Athari 2014-09-05 21:08:57

+0

您的建议帮助我更好地理解了XAML和CLR对象之间的关系,并向我展示了如何编写可重用转换器。我想我会用这个解决方案。 – 2014-09-05 21:30:36

1

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

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

它看起来像:

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}"/> 
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"); } 
} 
+1

该解决方案很好,但不幸的是,它依赖于XAML中可怕的动画语法。有什么方法可以获得更可读的语法吗?此外,如果想避免将部分视图逻辑放入虚拟机,仍然需要编写行为(在我看来,这种“状态”是演示的一部分)。 – Athari 2014-09-05 21:18:13

+0

你的建议对于更一般的情况看起来很棒,但在我的特殊情况下,恐怕会使事情变得复杂一点。不过,我将来会提到这一点。 – 2014-09-05 21:34:22

+0

@Athari个人我认为视觉状态的优势胜过冗长的语法(这不是*那*坏的IMO)。我明白了将视图逻辑放入虚拟机的意思,但这不是必需的 - 您可以轻松地将MVVM消息传递给“PropertyChanged”,并让视图更新自己的状态。 – McGarnagle 2014-09-05 21:34:33

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

为了让代码不那么可怕,你可以删除'Grid'和'Style。Setters'标签和几乎所有'使用'... – Athari 2014-09-05 21:28:39

相关问题