Непонимание основ привязки данных и DataContexts - длинная история - PullRequest
2 голосов
/ 16 сентября 2010

Я использовал привязку данных в нескольких простых ситуациях с довольно хорошим успехом.Обычно я просто использую INotifyPropertyChanged, чтобы позволить моему codebehind изменять значения графического интерфейса на экране, а не реализовывать свойства зависимостей для всего.

Я играю со светодиодным элементом управления, чтобы узнать больше о привязке данных в пользовательских элементах управления, и меня заставилииспользовать свойства зависимостей, потому что VS2008 сказал мне, что я должен.Мое приложение простое - у меня есть окно, которое отображает несколько светодиодных элементов управления, каждый с номером над ним и, по желанию, один на своей стороне.Светодиоды должны быть определены с цветом по умолчанию, а также с измененным состоянием.

Я начал с написания элемента управления светодиодами, который, казалось, работал идеально.Сначала я начал с кода, подобного следующему:

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>

Это отлично рисует светодиод.Затем я привязал LEDSize, LEDLabel и LEDColor к свойствам эллипса, установив 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
}

AtВ этот момент я могу изменить значения свойств и увидеть, что светодиод меняет размер, цвет и метку.Отлично!

Я хочу, чтобы светодиодный элемент управления можно было многократно использовать в других виджетах, которые я пишу со временем, и следующим шагом для меня было создание другого 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>

Обратите внимание, что я могу изменять свойства светодиодов в XAML во время разработки, и все работаеткак и ожидалось:

alt text

Затем я вслепую попытался связать данные LEDColor с моим IOView, и VS2008 любезно сказал мне «Невозможно установить« Binding »наСвойство «LEDColor» типа «LED». «Привязка» может быть установлена ​​только для свойства DependencyProperty объекта DependencyObject. " Упс!Я даже не осознавал этого, так как раньше я не делал свои собственные элементы управления графическим интерфейсом.Так как LEDColor уже привязан к базе данных эллипса, я добавил свойство DependencyProperty под названием Color.

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

Обратите внимание, что я установилсвойство LEDColor в установщике, так как именно так Ellipse знает, каким должен быть его цвет.

Следующим детским шагом была установка цвета светодиода в моем IOView путем привязки к IOView.InputColor:

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 я изменил светодиод на это:

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

Но это не работает, из-за следующей ошибки в окне вывода:

Ошибка пути BindingExpression: свойство 'InputColor' не найдено в 'объекте' '' LED '(Name =' ')'.BindingExpression: Path = InputColor;DataItem = 'LED' (Name = '');целевой элемент - «LED» (Name = '');Свойство target - 'Color' (тип 'Brush')

Хмм ... поэтому по какой-то причине моя DataBinding испорчена.Я могу заставить светодиод работать самостоятельно с привязкой к данным, но как только я помещаю его в другой элемент управления и устанавливаю его текстовый текст, он не работает.Я не уверен, что попробовать в этом пункте.

Я хотел бы получить как можно более подробный ответ.Я знаю, что мог бы просто повторно проверить CheckBox, чтобы получить те же результаты, но для меня это эксперимент, и я пытаюсь понять, как привязать данные к потомкам элементов управления.

1 Ответ

5 голосов
/ 16 сентября 2010

Многое можно сказать обо всем этом, но позвольте мне посмотреть, могу ли я предоставить указатели, которые устранят некоторые ваши недоразумения:

  • Для того, чтобы свойство было target привязки, это свойство должно быть свойством зависимости.WPF (и Silverlight) используют свойства зависимостей в качестве средства отслеживания изменений, поддержки приоритета значений (для анимации и т. П.) И множества других полезных вещей.Обратите внимание, что я сказал «цель». источником привязки может быть любой старый объект, поддерживающий уведомление об изменении.
  • Установка UserControl 'DataContext внутри самого UserControl считается плохой практикой, поскольку любой потребительвашего контроля может изменить его, и это нарушит любые привязки в вашем контроле, которые зависят от этого контекста
  • В дополнение к вышеприведенному пункту, другая проблема заключается в том, что вы нарушите любые привязки в , потребляя код, зависящий от контекста данных «над» пользовательским элементом управления.Это объясняет проблему, с которой вы сталкиваетесь InputColor, но не связывается успешно.Свойство InputColor находится в контексте данных, предоставленном хост-элементом управления (IOView), но контекст данных светодиода установлен на сам светодиод, поэтому свойство не может быть найдено без дальнейшей привязки.

Следование этому совету приводит к следующей реализации (не тестировалась):

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 :

<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 :

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