c# - etiqueta - alt html5
Enlace de datos WPF a la interfaz y no a objetos reales-¿posible conversión? (6)
Digamos que tengo una interfaz como esta:
public interface ISomeInterface
{
...
}
También tengo un par de clases implementando esta interfaz;
public class SomeClass : ISomeInterface
{
...
}
Ahora tengo un ListBox de WPF que enumera los elementos de ISomeInterface, usando un DataTemplate personalizado.
El motor de enlace de datos aparentemente no (que he podido descifrar) me permite vincular las propiedades de la interfaz: ve que el objeto es un objeto SomeClass, y los datos solo aparecen si SomeClass tiene la propiedad enlazada disponible como una propiedad sin interfaz.
¿Cómo puedo decirle a DataTemplate que actúe como si cada objeto fuera un ISomeInterface, y no un SomeClass, etc.?
¡Gracias!
La respuesta corta es que DataTemplate no admite interfaces (piense en herencia múltiple, explícita v. Implícita, etc.). La forma en que tendemos a evitar esto es hacer que una clase base se extienda para permitir la especialización / generalización de DataTemplate. Esto significa que una solución decente, pero no necesariamente óptima, sería:
public abstract class SomeClassBase
{
}
public class SomeClass : SomeClassBase
{
}
<DataTemplate DataType="{x:Type local:SomeClassBase}">
<!-- ... -->
</DataTemplate>
La respuesta sugerida por dummyboy es la mejor respuesta (debe votarse a la parte superior imo). Tiene un problema que al diseñador no le gusta (da un error "Object null no se puede usar como un parámetro de acceso para un PropertyPath) pero hay una buena solución. La solución consiste en definir el elemento en una plantilla de datos y luego establecer la plantilla en una etiqueta u otro control de contenido. Como ejemplo, estaba tratando de agregar una imagen como esta
<Image Width="120" Height="120" HorizontalAlignment="Center" Source="{Binding Path=(starbug:IPhotoItem.PhotoSmall)}" Name="mainImage"></Image>
Pero me siguió dando el mismo error. La solución fue crear una etiqueta y usar una plantilla de datos para mostrar mi contenido
<Label Content="{Binding}" HorizontalAlignment="Center" MouseDoubleClick="Label_MouseDoubleClick">
<Label.ContentTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=(starbug:IPhotoItem.PhotoSmall)}" Width="120" Height="120" Stretch="Uniform" ></Image>
</StackPanel>
</DataTemplate>
</Label.ContentTemplate>
</Label>
Esto tiene sus desventajas, pero parece funcionar bastante bien para mí.
Nota: Puede usar rutas de varias partes más complejas como esta también si la propiedad de la interfaz se encuentra dentro de una ruta:
<TextBlock>
<TextBlock.Text>
<Binding Path="Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode"/>
</TextBlock.Text>
</TextBlock>
O directamente con la directiva de Binding
.
<TextBlock Text="{Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>
O al usar múltiples propiedades de una interfaz, puede redefinir el DataContext localmente para hacer que el código sea más legible.
<StackPanel DataContext={Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod)}">
<TextBlock Text="{Binding CarrierName}"/>
<TextBlock Text="{Binding CarrierServiceCode}"/>
</StackPanel>
Consejo: cuidado con terminar accidentalmente con )}
al final de una expresión de ruta. Estúpido error de copiar / pegar que sigo haciendo.
Path="(myNameSpace:IShippingPackage.ShippingMethod)}"
Asegúrese de usar Path=
Descubrí que si no uso explícitamente Path=
entonces es posible que no pueda analizar el enlace. Normalmente escribiré algo como esto:
Text="{Binding FirstName}"
en lugar de
Text="{Binding Path=FirstName}"
Pero con el enlace de interfaz más complejo, encontré que Path=
era necesario para evitar esta excepción:
System.ArgumentNullException: Key cannot be null.
Parameter name: key
at System.Collections.Specialized.ListDictionary.get_Item(Object key)
at System.Collections.Specialized.HybridDictionary.get_Item(Object key)
at System.ComponentModel.PropertyChangedEventManager.RemoveListener(INotifyPropertyChanged source, String propertyName, IWeakEventListener listener, EventHandler`1 handler)
at System.ComponentModel.PropertyChangedEventManager.RemoveHandler(INotifyPropertyChanged source, EventHandler`1 handler, String propertyName)
at MS.Internal.Data.PropertyPathWorker.ReplaceItem(Int32 k, Object newO, Object parent)
at MS.Internal.Data.PropertyPathWorker.UpdateSourceValueState(Int32 k, ICollectionView collectionView, Object newValue, Boolean isASubPropertyChange)
es decir, no hagas esto:
<TextBlock Text="{Binding Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>
Para enlazar a los miembros explícitos de la interfaz implementada, todo lo que necesita hacer es usar los paréntesis. Por ejemplo:
implícito:
{Binding Path=MyValue}
explícito:
{Binding Path=(mynamespacealias:IMyInterface.MyValue)}
Tienes otra opción Establezca una clave en su DataTemplate y haga referencia a esa clave en ItemTemplate. Me gusta esto:
<DataTemplate DataType="{x:Type documents:ISpecificOutcome}"
x:Key="SpecificOutcomesTemplate">
<Label Content="{Binding Name}"
ToolTip="{Binding Description}" />
</DataTemplate>
luego haz referencia a la plantilla por clave donde quieras usarla, así:
<ListBox ItemsSource="{Binding Path=SpecificOutcomes}"
ItemTemplate="{StaticResource SpecificOutcomesTemplate}"
>
</ListBox>
Esta respuesta de los foros de Microsoft por Beatriz Costa - MSFT vale la pena leerla (bastante antigua):
El equipo de enlace de datos discutió la posibilidad de agregar soporte para interfaces hace un tiempo, pero terminó no implementándolo porque no pudimos encontrar un buen diseño para él. El problema era que las interfaces no tienen una jerarquía como hacen los tipos de objetos. Considere el escenario donde su fuente de datos implementa tanto
IMyInterface1
comoIMyInterface2
y tiene DataTemplates para ambas interfaces en los recursos: ¿qué plantilla de datos cree que deberíamos recuperar?Al hacer plantillas de datos implícitos para tipos de objetos, primero tratamos de encontrar un
DataTemplate
para el tipo exacto, luego para su padre, abuelo y demás. Hay un orden muy bien definido de tipos para que nosotros apliquemos. Cuando hablamos sobre la adición de soporte para interfaces, consideramos el uso de la reflexión para descubrir todas las interfaces y agregarlas al final de la lista de tipos. El problema que encontramos fue definir el orden de las interfaces cuando el tipo implementa múltiples interfaces.La otra cosa que tuvimos que tener en cuenta es que el reflejo no es tan barato, y esto disminuiría un poco nuestro rendimiento en este escenario.
Entonces, ¿cuál es la solución? No puedes hacer todo esto en XAML, pero puedes hacerlo fácilmente con un poco de código. La propiedad
ItemTemplateSelector
deItemsControl
se puede utilizar para elegir quéDataTemplate
desea usar para cada elemento. En el métodoSelectTemplate
para su selector de plantilla, recibe como parámetro el elemento que va a crear. Aquí puede verificar qué interfaz implementa y devolver elDataTemplate
que coincide con él.