C# 误解数据绑定基础和数据上下文——说来话长

C# 误解数据绑定基础和数据上下文——说来话长,c#,wpf,data-binding,.net-3.5,datacontext,C#,Wpf,Data Binding,.net 3.5,Datacontext,我已经在几种简单的情况下使用了数据绑定,并取得了相当好的成功。通常我只是使用INotifyPropertyChanged来允许我的codebehind修改屏幕上的GUI值,而不是实现所有内容的依赖属性 我正在玩一个LED控件,以了解更多关于用户控件中数据绑定的信息,并被迫使用依赖属性,因为VS2008告诉我必须这样做。我的应用程序很简单——我有一个窗口,显示几个LED控件,每个控件上面都有一个数字,还可以选择在其旁边显示一个。LED应可定义为默认颜色,并可更改状态 我开始写一个LED控制器,它看

我已经在几种简单的情况下使用了数据绑定,并取得了相当好的成功。通常我只是使用INotifyPropertyChanged来允许我的codebehind修改屏幕上的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>
<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>
此时,我可以更改属性值,并看到LED改变大小、颜色及其标签。太好了

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

IOView.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>
<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>
请注意,我在setter中设置了属性
LEDColor
,因为椭圆就是这样知道它应该是什么颜色的

下一步是通过绑定到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
}
//
///IOView.xaml的交互逻辑
/// 
公共部分类IOView:UserControl,INotifyPropertyChanged
{
私有Int32索引;
公共Int32索引
{
获取{返回索引}
设置{
指数=数值;
不动产变更(“指数”);
}
}
私人画笔颜色;
公共画笔输入颜色
{
获取{返回颜色}
设置{
颜色=值;
OnPropertyChanged(“InputColor”);
}
}
私有布尔状态;
公共布尔状态
{
获取{返回状态}
设置{
状态=值;
不动产变更(“州”);
}
}
公共视图()
{
初始化组件();
this.DataContext=this;
}
#区域INotifyProperty更改成员
公共事件属性更改事件处理程序属性更改;
受保护的void OnPropertyChanged(字符串属性\u名称)
{
if(PropertyChanged!=null)
PropertyChanged(这是新PropertyChangedEventArgs(property_name));
}
#端区
}
在IOView.xaml中,我将LED改为:

<led:LED Grid.Row="1" HorizontalContentAlignment="Center" HorizontalAlignment="Center" LEDSize="30" Color="{Binding InputColor}" />

但由于输出窗口中的以下错误,它无法工作:

BindingExpression路径错误:在“对象”“LED”(名称=“”)上找不到“InputColor”属性。BindingExpression:Path=InputColor;DataItem='LED'(名称='');目标元素为“LED”(名称=“”);目标属性为“颜色”(类型为“笔刷”)

嗯。。。所以出于某种原因,我的数据绑定被搞砸了。我可以让LED通过数据绑定自行工作,但一旦我将其包装到另一个控件中并设置其datacontext,它就不工作了。我不确定在这一点上该尝试什么


我想得到尽可能详细的答案。我知道我可以重新设置一个复选框以获得相同的结果,但这对我来说是一个实验,我正在尝试了解如何将数据绑定到控件的后代。

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

  • 为了使属性成为绑定的目标,该属性必须是依赖项属性。WPF(和Silverlight)使用依赖性属性作为跟踪更改、支持值优先级(用于动画等)和一系列其他有用内容的手段。注意,我说的是“目标”。绑定的源可以是任何支持更改通知的旧对象
  • UserControl
    本身内设置
    UserControl
    DataContext
    被认为是不好的做法,因为控件的任何使用者都可以更改它,这样做会破坏控件内依赖于该上下文的任何绑定
  • 除上述问题外,另一个问题是,在使用依赖于“高于”用户控件的数据上下文的代码时,您将破坏任何绑定。这解释了
    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();
    }
}
<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>
<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>
LED.xaml

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


谢谢你的详细解释。我肯定不会像对ViewModels那样盲目地设置datacontext,因为这是一种完全不同的情况。我应用了您的更改,并且想知道如何使用XAML方法来设置DataContext,以防止UserControl的使用者仍然从代码隐藏中更改它(可能不会)。你的改变几乎奏效了,也许我遗漏了什么。我有一个计时器可以更改IOView的State属性,这应该会根据其值将InputColor从绿色更改为红色。它只改变一次…@戴夫:不客气。重要的不是使用XAML设置
DataContext
,而是我在
UserControl
的子级上设置了它,而不是
UserControl
本身。您也可以在代码中这样做,但XAML更简单。至于你的另一个问题,我想我需要看看代码才能准确地回答…我完全按照你的代码示例,一个