误解数据绑定基础和DataContexts - 长篇大论

Dav*_*ave 2 c# data-binding wpf datacontext .net-3.5

我一直在几个简单的情况下使用数据绑定,取得了相当不错的成功.通常我只使用INotifyPropertyChanged来启用我的代码隐藏来修改屏幕上的GUI值,而不是为所有内容实现依赖项属性.

我正在使用LED控件来了解有关用户控件中数据绑定的更多信息,并被迫使用依赖属性,因为VS2008告诉我必须这样做.我的应用程序很简单 - 我有一个窗口,显示几个LED控件,每个控件上面都有一个数字,可选择一个控件.LED应该可以使用默认颜色进行定义,以及更改状态.

我开始写一个LED控制器,看起来非常好.首先,我开始使用这样的代码:

LED.xaml

<UserControl x:Class="LEDControl.LED"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="Auto" Width="Auto">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <!-- LED portion -->
        <Ellipse Grid.Column="0" Margin="3" Height="{Binding LEDSize}" Width="{Binding LEDSize}" Fill="{Binding LEDColor}" StrokeThickness="2" Stroke="DarkGray" />
        <Ellipse Grid.Column="0" Margin="3" Height="{Binding LEDSize}" Width="{Binding LEDSize}">
            <Ellipse.Fill>
                <RadialGradientBrush GradientOrigin="0.5,1.0">
                    <RadialGradientBrush.RelativeTransform>
                        <TransformGroup>
                            <ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="1.5" ScaleY="1.5"/>
                            <TranslateTransform X="0.02" Y="0.3"/>
                        </TransformGroup>
                    </RadialGradientBrush.RelativeTransform>
                    <GradientStop Offset="1" Color="#00000000"/>
                    <GradientStop Offset="0.4" Color="#FFFFFFFF"/>
                </RadialGradientBrush>
            </Ellipse.Fill>
        </Ellipse>
        <!-- label -->
        <TextBlock Grid.Column="1" Margin="3" VerticalAlignment="Center" Text="{Binding LEDLabel}" />
    </Grid>
</UserControl>
Run Code Online (Sandbox Code Playgroud)

这样可以很好地吸收LED.然后,我将LEDSize,LEDLabel和LEDColor绑定到Ellipse属性,方法是this.DataContext = this像我一样设置:

LED.xaml.cs

/// <summary>
/// Interaction logic for LED.xaml
/// </summary>
public partial class LED : UserControl, INotifyPropertyChanged
{
    private Brush state_color_;
    public Brush LEDColor
    {
        get { return state_color_; }
        set { 
            state_color_ = value;
            OnPropertyChanged( "LEDColor");
        }
    }

    private int led_size_;
    public int LEDSize
    {
        get { return led_size_; }
        set {
            led_size_ = value;
            OnPropertyChanged( "LEDSize");
        }
    }

    private string led_label_;
    public string LEDLabel
    {
        get { return led_label_; }
        set {
            led_label_ = value;
            OnPropertyChanged( "LEDLabel");
        }
    }

    public LED()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged( string property_name)
    {
        if( PropertyChanged != null)
            PropertyChanged( this, new PropertyChangedEventArgs( property_name));
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

此时,我可以更改属性值,并看到LED改变了大小,颜色和标签.大!

我希望LED控件可以在我编写的其他小部件中重复使用,下一步是创建另一个UserControl(在单独的程序集中),称为IOView. IOView在这一点上是非常基本的:

IOView.xaml

<UserControl x:Class="IOWidget.IOView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:led="clr-namespace:LEDControl;assembly=LEDControl"
    Height="Auto" Width="Auto">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" HorizontalAlignment="Center" Text="{Binding Path=Index}" />
        <led:LED Grid.Row="1" HorizontalContentAlignment="Center" HorizontalAlignment="Center" LEDSize="30" LEDColor="Green" LEDLabel="Test" />
    </Grid>
</UserControl>
Run Code Online (Sandbox Code Playgroud)

请注意,我可以在设计时修改XAML中的LED属性,一切都按预期工作:

替代文字

然后我盲目地尝试将LEDColor数据绑定到我的IOView,并且VS2008告诉我"A'Binding'不能设置在'LED'类型的'LEDColor'属性上.'绑定'只能在DependencyObject的DependencyProperty上设置". 哎呀!我甚至没有意识到,因为我之前没有制作过自己的GUI控件.由于LEDColor已经数据绑定到Ellipse,我添加了一个名为Color的DependencyProperty.

LED.xaml.cs

    public static DependencyProperty ColorProperty = DependencyProperty.Register( "Color", typeof(Brush), typeof(LED));
    public Brush Color
    {
        get { return (Brush)GetValue(ColorProperty); }
        set { 
            SetValue( ColorProperty, value);
            LEDColor = value;
        }
    }
Run Code Online (Sandbox Code Playgroud)

请注意,我LEDColor在setter中设置了属性,因为Ellipse知道它应该是什么颜色.

接下来的步骤涉及通过绑定到IOView.InputColor来设置IOView中LED的颜色:

IOView.xaml.cs:

/// <summary>
/// Interaction logic for IOView.xaml
/// </summary>
public partial class IOView : UserControl, INotifyPropertyChanged
{
    private Int32 index_;
    public Int32 Index
    {
        get { return index_; }
        set {
            index_ = value;
            OnPropertyChanged( "Index");
        }
    }

    private Brush color_;
    public Brush InputColor
    {
        get { return color_; }
        set {
            color_ = value;
            OnPropertyChanged( "InputColor");
        }
    }

    private Boolean state_;
    public Boolean State
    {
        get { return state_; }
        set {
            state_ = value;
            OnPropertyChanged( "State");
        }
    }

    public IOView()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged( string property_name)
    {
        if( PropertyChanged != null)
            PropertyChanged( this, new PropertyChangedEventArgs( property_name));
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

在IOView.xaml中,我将LED更改为:

<led:LED Grid.Row="1" HorizontalContentAlignment="Center" HorizontalAlignment="Center" LEDSize="30" Color="{Binding InputColor}" />
Run Code Online (Sandbox Code Playgroud)

但它不起作用,因为输出窗口中出现以下错误:

BindingExpression路径错误:'object'''LED'(Name ='')'上找不到'InputColor'属性.BindingExpression:路径= InputColor; DataItem ='LED'(Name =''); 目标元素是'LED'(Name =''); target属性是'Color'(类型'Brush')

嗯......所以出于某种原因,我的DataBinding搞砸了.我可以通过数据绑定使LED自行工作,但是一旦我将其包装在另一个控件中并设置其datacontext,它就不起作用.我不知道在这一点上要尝试什么.

我希望得到尽可能详细的答案.我知道我可以重新模仿一个CheckBox来获得相同的结果,但这对我来说是一个实验,我试图了解如何数据绑定到控件的后代.

Ken*_*art 5

关于这一切有很多话要说,但让我看看我是否可以提供解决你的一些误解的指针:

  • 为了使属性成为绑定的目标,该属性必须是依赖属性.WPF(和Silverlight)使用依赖项属性作为跟踪更改,支持值优先级(用于动画等)以及一堆其他有用的东西的方法.请注意,我说"目标".绑定的可以是支持更改通知的任何旧对象.
  • 设置UserControlDataContext该内UserControl本身被认为是不好的做法,因为你无法控制的任何消费可以改变它,这样做会打破这种依赖于这方面的控制范围之内的任何绑定
  • 除了上述观点之外,另一个问题是您将在使用依赖于用户控件"上方"的数据上下文的代码时中断任何绑定.这解释了您在InputColor未成功绑定时看到的问题.该InputColor属性位于主机控件(IOView)提供的数据上下文中,但LED的数据上下文设置为LED本身,因此如果不进一步限定绑定,则无法找到该属性.

遵循此建议会导致以下实施(未经测试):

LED.xaml.cs:

public partial class LED : UserControl
{
    public static readonly DependencyProperty LEDColorProperty = DependencyProperty.Register(
        "LEDColor",
        typeof(Brush),
        typeof(LED));

    public Brush LEDColor
    {
        get { return this.GetValue(LEDColorProperty) as Brush; }
        set { this.SetValue(LEDColorProperty, value); }
    }

    // LEDSize and LEDLabel omitted for brevity, but they're very similar to LEDColor

    public LED()
    {
        InitializeComponent();
    }
}
Run Code Online (Sandbox Code Playgroud)

LED.xaml:

<UserControl
    x:Name="root"
    x:Class="LEDControl.LED"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="Auto" Width="Auto">

    <Grid DataContext="{Binding ElementName=root}>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <!-- LED portion -->
        <Ellipse Grid.Column="0" Margin="3" Height="{Binding LEDSize}" Width="{Binding LEDSize}" Fill="{Binding LEDColor}" StrokeThickness="2" Stroke="DarkGray" />
        <Ellipse Grid.Column="0" Margin="3" Height="{Binding LEDSize}" Width="{Binding LEDSize}">
            <Ellipse.Fill>
                <RadialGradientBrush GradientOrigin="0.5,1.0">
                    <RadialGradientBrush.RelativeTransform>
                        <TransformGroup>
                            <ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="1.5" ScaleY="1.5"/>
                            <TranslateTransform X="0.02" Y="0.3"/>
                        </TransformGroup>
                    </RadialGradientBrush.RelativeTransform>
                    <GradientStop Offset="1" Color="#00000000"/>
                    <GradientStop Offset="0.4" Color="#FFFFFFFF"/>
                </RadialGradientBrush>
            </Ellipse.Fill>
        </Ellipse>
        <!-- label -->
        <TextBlock Grid.Column="1" Margin="3" VerticalAlignment="Center" Text="{Binding LEDLabel}" />
    </Grid>
</UserControl>
Run Code Online (Sandbox Code Playgroud)

IOView.xaml:

<UserControl x:Name="root"
    x:Class="IOWidget.IOView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:led="clr-namespace:LEDControl;assembly=LEDControl"
    Height="Auto" Width="Auto">

    <Grid DataContext="{Binding ElementName=root}">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" HorizontalAlignment="Center" Text="{Binding Path=Index}" />
        <led:LED Grid.Row="1" HorizontalContentAlignment="Center" HorizontalAlignment="Center" LEDSize="{Binding I_Can_Bind_Here_All_I_Like}" LEDColor="{Binding I_Can_Bind_Here_All_I_Like}" LEDLabel="{Binding I_Can_Bind_Here_All_I_Like}" />
    </Grid>
</UserControl>
Run Code Online (Sandbox Code Playgroud)