简体   繁体   English

误解的数据绑定基础知识和DataContexts-长话不说

[英]Misunderstanding databinding fundamentals and DataContexts — long story

I've been using databinding in several simple situations with pretty good success. 我一直在几种简单的情况下使用数据绑定,并取得了很好的成功。 Usually I just use INotifyPropertyChanged to enable my codebehind to modify the GUI values on screen, rather than implement dependency properties for everything. 通常,我只是使用INotifyPropertyChanged来启用我的代码隐藏功能来修改屏幕上的GUI值,而不是为所有内容实现依赖项属性。

I am playing with an LED control to learn more about databinding in user controls, and was forced to use dependency properties because VS2008 told me I had to. 我正在玩LED控件,以了解有关用户控件中数据绑定的更多信息,并且由于VS2008告诉我必须这样做,因此不得不使用依赖项属性。 My application is straightforward -- I have a window that displays several LED controls, each with a number above it and optionally, one to its side. 我的应用程序很简单-我有一个窗口,其中显示几个LED控件,每个控件上方都有一个数字,还可以在侧面显示一个。 The LEDs should be definable with a default color, as well as change state. LED应使用默认颜色以及更改状态进行定义。

I started by writing an LED control, which seemed to go perfectly fine. 我首先编写了一个LED控件,看起来还不错。 First, I started with code like this: 首先,我从这样的代码开始:

LED.xaml 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>

This draws an LED just fine. 这样就可以画一个LED了。 I then bound LEDSize, LEDLabel, and LEDColor to the Ellipse properties by setting this.DataContext = this like I always do: 然后,通过设置this.DataContext = this像我一直一样,将LEDSize,LEDLabel和LEDColor绑定到Ellipse属性。

LED.xaml.cs 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
}

At this point, I can change the property values and see that the LED changes size, color and its label. 在这一点上,我可以更改属性值,并看到LED更改大小,颜色及其标签。 Great! 大!

I want the LED control to be reusable in other widgets that I write over time, and the next step for me was to create another UserControl (in a separate assembly), called IOView . 我希望LED控件可以随着时间的推移在其他小部件中重复使用,而我的下一步是创建另一个名为IOView UserControl(在单独的程序IOView IOView is pretty basic at this point: IOView在这一点上非常基础:

IOView.xaml 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>

Notice that I can modify the LED properties in XAML at design time and everything works as expected: 请注意,我可以在设计时修改XAML中的LED属性,一切都会按预期进行:

替代文字

I then blindly tried to databind LEDColor to my IOView, and VS2008 kindly told me "A 'Binding' cannot be set on the 'LEDColor' property of type 'LED'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject." 然后,我盲目地尝试将LEDColor数据绑定到我的IOView,VS2008友好地告诉我“不能在类型为“ LED”的“ LEDColor”属性上设置“绑定”。只能在DependencyObject的DependencyProperty上设置“ Binding” “。 Oops! 哎呀! I hadn't even realized that since I haven't made my own GUI controls before. 我什至没有意识到,因为我以前没有做过自己的GUI控件。 Since the LEDColor is already databound to the Ellipse, I added a DependencyProperty called Color. 由于LEDColor已经与Ellipse进行数据绑定,因此我添加了一个名为Color的DependencyProperty。

LED.xaml.cs 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;
        }
    }

Note that I set the property LEDColor in the setter, since that's how the Ellipse knows what color it should be. 请注意,我在设置器中设置了属性LEDColor ,因为这就是Ellipse知道其应为哪种颜色的方式。

The next baby step involved setting the color of the LED in my IOView by binding to IOView.InputColor: 下一步是通过绑定到IOView.InputColor设置IOView中LED的颜色:

IOView.xaml.cs: 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
}

and in IOView.xaml, I changed the LED to this: 在IOView.xaml中,我将LED更改为此:

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

But it's not working, because of the following error in the Output window: 但这不起作用,因为“输出”窗口中出现以下错误:

BindingExpression path error: 'InputColor' property not found on 'object' ''LED' (Name='')'. BindingExpression路径错误:在“对象”“ LED”(Name =“)”上找不到“ InputColor”属性。 BindingExpression:Path=InputColor; BindingExpression:路径= InputColor; DataItem='LED' (Name=''); DataItem ='LED'(Name =''); target element is 'LED' (Name=''); 目标元素是'LED'(Name =''); target property is 'Color' (type 'Brush') 目标属性为“颜色”(类型为“画笔”)

Hmm... so for some reason, my DataBinding is messed up. 嗯...由于某种原因,我的DataBinding搞砸了。 I can get the LED to work on its own with databinding, but once I wrap it in another control and set its datacontext, it doesn't work. 我可以使LED与数据绑定一起独立工作,但是一旦将它包装在另一个控件中并设置了它的datacontext后,它就不起作用了。 I'm not sure what to try at this point. 我不确定目前该如何尝试。

I'd love to get as detailed an answer as possible. 我希望得到尽可能详细的答案。 I know that I could have just retemplated a CheckBox to get the same results, but this is an experiment for me and I'm trying to understand how to databind to controls' descendants. 我知道我可以重新设计一个CheckBox以获得相同的结果,但这对我来说是一个实验,我试图了解如何将数据绑定到控件的后代。

There's a lot to say about all this, but let me see if I can provide pointers that addresses some of your misunderstandings: 关于这一切,有很多要说的,但让我看看我是否可以提供解决您一些误解的指针:

  • In order for a property to be the target of a binding, that property must be a dependency property. 为了使属性成为绑定的目标 ,该属性必须是依赖项属性。 WPF (and Silverlight) use dependency properties as a means for tracking changes, supporting value precedence (for animations and the like), and a bunch of other useful things. WPF(和Silverlight)使用依赖项属性来跟踪更改,支持值优先级(用于动画等)以及其他许多有用的东西。 Note that I said "target". 请注意,我说的是“目标”。 The source of a binding can be any old object that supports change notification. 绑定的来源可以是任何支持更改通知的旧对象。
  • Setting a UserControl 's DataContext within the UserControl itself is considered bad practice because any consumer of your control can change it, and doing so will break any bindings within your control that depend on that context UserControl本身中设置UserControlDataContext被认为是不好的做法,因为UserControl任何使用者都可以更改它,并且这样做会破坏控件中依赖于该上下文的任何绑定。
  • In addition to the above point, the other issue is that you will break any bindings in consuming code that depend on the data context "above" the user control. 除了上述要点之外,另一个问题是,您将破坏依赖于用户控件“上方”的数据上下文的使用代码中的所有绑定。 This explains the issue you're seeing with InputColor not successfully binding. 这就解释了您遇到的与InputColor未成功绑定的问题。 The InputColor property is in the data context provided by the host control ( IOView ) but the data context of the LED is set to the LED itself, so the property can't be found without qualifying the binding further. InputColor属性位于主机控件( IOView )提供的数据上下文中,但是LED的数据上下文设置为LED本身,因此,如果不进一步限制绑定,就无法找到该属性。

Following this advice leads to the following implementation (not tested): 遵循此建议可导致以下实现(未经测试):

LED.xaml.cs : 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();
    }
}

LED.xaml : 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>

IOView.xaml : 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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM