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
-
Un independiente
UserControl
que se puede utilizar en cualquier lugar sin una especificaciónDataContext
siendo requerido.Este tipo de
UserControl
normalmente exponeDependencyProperties
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.
-
A
UserControl
que está destinado a ser utilizado con un específicoModel
oViewModel
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ícitoDataTemplate
es unDataTemplate
con unDataType
y noKey
y 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
oItemsControl.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”.