Saltar al contenido

Cómo enlazar correctamente a una propiedad de dependencia de un control de usuario en un marco MVVM

Solución:

Ésa es una de las muchas razones por las que nunca debe configurar el DataContext directamente desde el UserControl sí mismo.

Cuando lo haga, ya no podrá utilizar ningún otro DataContext con él porque el UserControl’s DataContext está codificado en una instancia que solo el UserControl tiene acceso, lo que anula una de las mayores ventajas de WPF de tener una interfaz de usuario y capas de datos independientes.

Hay dos formas principales de utilizar UserControls en WPF

  1. Un independiente UserControl que se puede utilizar en cualquier lugar sin una especificación DataContext siendo requerido.

    Este tipo de UserControl normalmente expone DependencyProperties para cualquier valor que necesite, y se usaría así:

    <v:InkStringView TextInControl="{Binding SomeValue}" />
    

    Los ejemplos típicos en los que puedo pensar serían cualquier cosa genérica, como un control de calendario o un control emergente.

  2. A UserControl que está destinado a ser utilizado con un específico Model o ViewModel solamente.

    Estas UserControls son mucho más comunes para mí, y probablemente sea lo que está buscando en su caso. Un ejemplo de cómo usaría un UserControl de este tipo sería este:

    <v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
    

    O más frecuentemente, se usaría con un implícito DataTemplate. Un implícito DataTemplate es un DataTemplate con un DataType y no Keyy WPF utilizará automáticamente esta plantilla cada vez que desee representar un objeto del tipo especificado.

    <Window.Resources>
        <DataTemplate DataType="{x:Type m:InkStringViewModel}">
            <v:InkStringView />
        </DataTemplate>
    <Window.Resources>
    
    <!-- Binding to a single ViewModel -->
    <ContentPresenter Content="{Binding MyInkStringViewModelProperty}" />
    
    <!-- Binding to a collection of ViewModels -->
    <ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" />
    

    No ContentPresenter.ItemTemplate o ItemsControl.ItemTemplate es necesario cuando se utiliza este método.

No mezcles estos dos métodos, no va bien 🙂


Pero de todos modos, para explicar su problema específico con un poco más de detalle

Cuando crea su UserControl así

<v:InkStringView TextInControl="{Binding text}"  />

básicamente estás diciendo

var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;

vw.DataContext no se especifica en ninguna parte del XAML, por lo que se hereda del elemento principal, lo que da como resultado

vw.DataContext = Strings[x];

entonces tu atadura que pone TextInControl = vw.DataContext.text es válido y se resuelve bien en tiempo de ejecución.

Sin embargo, cuando ejecuta esto en su constructor UserControl

this.DataContext = new InkStringViewModel();

los DataContext se establece en un valor, por lo que ya no se hereda automáticamente del padre.

Entonces, ahora el código que se ejecuta se ve así:

var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;

y naturalmente, InkStringViewModel no tiene una propiedad llamada text, por lo que el enlace falla en tiempo de ejecución.

Parece que está mezclando el modelo de la vista principal con el modelo de UC.

Aquí hay una muestra que coincide con su código:

El MainViewModel:

using System.Collections.Generic;

namespace UCItemsControl
{
    public class MyString
    {
        public string text { get; set; }
    }

    public class MainViewModel
    {
        public ObservableCollection<MyString> Strings { get; set; }

        public MainViewModel()
        {
            Strings = new ObservableCollection<MyString>
            {
                new MyString{ text = "First" },
                new MyString{ text = "Second" },
                new MyString{ text = "Third" }
            };
        }
    }
}

La ventana principal que la usa:

<Window x:Class="UCItemsControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:v="clr-namespace:UCItemsControl"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <v:MainViewModel></v:MainViewModel>
    </Window.DataContext>
    <Grid>
        <ItemsControl 
                ItemsSource="{Binding Strings}" x:Name="self" >

            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>


            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <DataTemplate.Resources>
                        <Style TargetType="v:InkStringView">
                            <Setter Property="FontSize" Value="25"/>
                            <Setter Property="HorizontalAlignment" Value="Left"/>
                        </Style>
                    </DataTemplate.Resources>

                    <v:InkStringView TextInControl="{Binding text}"  />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

Su UC (sin conjunto de DataContext):

public partial class InkStringView : UserControl
{
    public InkStringView()
    {
        InitializeComponent();
    }

    public String TextInControl
    {
        get { return (String)GetValue(TextInControlProperty); }
        set { SetValue(TextInControlProperty, value); }
    }

    public static readonly DependencyProperty TextInControlProperty =
        DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}

(Tu XAML está bien)

Con eso puedo obtener lo que supongo que es el resultado esperado, una lista de valores:

First
I am row 1
Second
I am row 1
Third
I am row 1

Ya casi estás ahí. El problema es que está creando un ViewModel para su UserControl. Este es un olor.

UserControls debe verse y comportarse como cualquier otro control, visto desde el exterior. Tiene propiedades expuestas correctamente en el control y está vinculando controles internos a estas propiedades. Eso es correcto.

Donde fallas es intentar crear un ViewModel para todo. Así que deshazte de ese estúpido InkStringViewModel y deja que quien esté usando el control vincule su modelo de vista a él.

Si tiene la tentación de preguntar “¿qué pasa con la lógica en el modelo de vista? Si me deshago de ella, tendré que poner código en el código subyacente”. Yo respondo, “¿es lógica de negocios? Eso no debería estar incrustado en su UserControl de todos modos. ¡Y MVVM! = Sin código subyacente. Use código subyacente para su lógica de interfaz de usuario. Es donde pertenece”.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *