Entendendo mal os fundamentos de ligação de dados e DataContexts - uma longa história

Eu tenho usado a ligação de dados em várias situações simples com muito bom sucesso. Normalmente, eu apenas uso INotifyPropertyChanged para permitir que meu código traseiro modifique os valores da GUI na tela, em vez de implementar propriedades de dependência para tudo.

Estou brincando com um controle de LED para saber mais sobre a ligação de dados nos controles do usuário e fui forçado a usar propriedades de dependência porque o VS2008 me disse que precisava. Meu aplicativo é direto - tenho uma janela que exibe vários controles de LED, cada um com um número acima e, opcionalmente, um ao lado. Os LEDs devem ser definíveis com uma cor padrão, bem como alterar o estado.

Comecei escrevendo um controle de LED, que parecia estar perfeitamente bem. Primeiro, comecei com um código como este:

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>

Isso chama um LED muito bem. Em seguida, vinculei LEDSize, LEDLabel e LEDColor às propriedades da Elipse, definindothis.DataContext = this como eu sempre faço:

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
}

Neste ponto, eu posso alterar os valores das propriedades e ver se o LED muda de tamanho, cor e seu rótulo. Ótimo!

Desejo que o controle de LED seja reutilizável em outros widgets que escrevo com o tempo e a próxima etapa para mim foi criar outro UserControl (em um assembly separado), chamadoIOView. IOView é bastante básico neste momento:

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>

Observe que eu posso modificar as propriedades do LED no XAML em tempo de design e tudo funciona conforme o esperado:

Tentei cegamente conectar o LEDColor ao meu IOView, e o VS2008 me disse"Uma 'Ligação' não pode ser configurada na propriedade 'LEDColor' do tipo 'LED'. Uma 'Ligação' pode ser configurada apenas em uma DependencyProperty de um DependencyObject." Opa! Eu nem tinha percebido isso, já que não tinha feito meus próprios controles de GUI antes. Desde oLEDColor já está vinculado ao Ellipse, adicionei um DependencyProperty chamado 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;
        }
    }

Observe que eu defino a propriedadeLEDColor no setter, já que é assim que o Ellipse sabe que cor deve ser.

A próxima etapa do bebê envolveu definir a cor do LED no meu IOView vinculando-o ao 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
}

e no IOView.xaml, mudei o LED para este:

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

Mas não está funcionando, devido ao seguinte erro na janela Saída:

Erro no caminho BindingExpression: propriedade 'InputColor' não encontrada no 'objeto' '' LED '(Nome =' ')'. BindingExpression: Path = InputColor; DataItem = 'LED' (Nome = ''); o elemento de destino é 'LED' (Nome = ''); a propriedade target é 'Color' (digite 'Brush')

Hmm ... então, por algum motivo, meu DataBinding está bagunçado. Posso fazer com que o LED trabalhe sozinho com a ligação de dados, mas depois que o envolvo em outro controle e defino seu contexto de dados, ele não funciona. Não tenho certeza do que tentar neste momento.

Eu adoraria obter uma resposta o mais detalhada possível. Eu sei que eu poderia ter apenas redemplificado um CheckBox para obter os mesmos resultados, mas este é um experimento para mim e estou tentando entender como vincular dados aos descendentes dos controles.

questionAnswers(1)

yourAnswerToTheQuestion