Ответ 1
Чтобы привязываться к явным реализациям элементов интерфейса, все, что вам нужно сделать, это использовать круглые скобки. Например:
неявный:
{Binding Path=MyValue}
Явный:
{Binding Path=(mynamespacealias:IMyInterface.MyValue)}
Скажем, у меня есть такой интерфейс:
public interface ISomeInterface
{
...
}
У меня также есть пара классов, реализующих этот интерфейс;
public class SomeClass : ISomeInterface
{
...
}
Теперь у меня есть элементы перечисления ListBox WPF из ISomeInterface, используя пользовательский DataTemplate.
Механизм привязки данных, по-видимому, не будет (что я смог выяснить) позволяет мне привязываться к свойствам интерфейса - он видит, что объект является объектом SomeClass, а данные отображаются только в том случае, если SomeClass должен иметь связанную свойство доступно как свойство без интерфейса.
Как я могу заставить DataTemplate действовать так, как будто каждый объект является ISomeInterface, а не SomeClass и т.д.?
Спасибо!
Чтобы привязываться к явным реализациям элементов интерфейса, все, что вам нужно сделать, это использовать круглые скобки. Например:
неявный:
{Binding Path=MyValue}
Явный:
{Binding Path=(mynamespacealias:IMyInterface.MyValue)}
Этот ответ от форумов Microsoft от Беатрис Коста - MSFT стоит прочитать (довольно старый):
Команда по связыванию данных обсудила добавление поддержки интерфейсов некоторое время назад, но в итоге не реализовала ее, потому что мы не смогли создать хороший дизайн для нее. Проблема заключалась в том, что интерфейсы не имеют такой иерархии, как типы объектов. Рассмотрим сценарий, в котором ваш источник данных реализует как
IMyInterface1
, так иIMyInterface2
, и у вас есть DataTemplates для обоих этих интерфейсов в ресурсах: какой DataTemplate вы думаете, что мы должны подобрать?При выполнении неявных шаблонов данных для типов объектов мы сначала пытаемся найти
DataTemplate
для точного типа, затем для его родителя, дедушки и т.д. Для нас очень хорошо определен порядок. Когда мы говорили о добавлении поддержки интерфейсов, мы рассмотрели использование рефлексии, чтобы узнать все интерфейсы и добавить их в конец списка типов. Проблема, с которой мы столкнулись, заключалась в определении порядка интерфейсов, когда тип реализует несколько интерфейсов.Другая вещь, о которой мы должны были иметь в виду, - это то, что размышление не так дешево, и это уменьшит наши возможности для этого сценария.
Итак, какое решение? Вы не можете сделать все это в XAML, но вы можете сделать это легко с небольшим количеством кода. Свойство
ItemTemplateSelector
ItemsControl
может использоваться для выбора, которыйDataTemplate
вы хотите использовать для каждого элемента. В методеSelectTemplate
для вашего селектора шаблонов вы получаете в качестве параметра элемент, который вы создадите. Здесь вы можете проверить, какой интерфейс он реализует, и вернутьDataTemplate
, который соответствует ему.
Короткий ответ: DataTemplate не поддерживает интерфейсы (подумайте о множественном наследовании, явном v) неявном и т.д.). Способ, которым мы стремимся обойти это, состоит в том, чтобы расширить возможности базового класса, чтобы дать возможность специализации/обобщения DataTemplate. Это означает, что это достойное, но не обязательно оптимальное решение:
public abstract class SomeClassBase
{
}
public class SomeClass : SomeClassBase
{
}
<DataTemplate DataType="{x:Type local:SomeClassBase}">
<!-- ... -->
</DataTemplate>
У вас есть другой вариант. Установите ключ на свой DataTemplate и укажите этот ключ в ItemTemplate. Вот так:
<DataTemplate DataType="{x:Type documents:ISpecificOutcome}"
x:Key="SpecificOutcomesTemplate">
<Label Content="{Binding Name}"
ToolTip="{Binding Description}" />
</DataTemplate>
затем ссылайтесь на шаблон по ключу, где вы хотите его использовать, например:
<ListBox ItemsSource="{Binding Path=SpecificOutcomes}"
ItemTemplate="{StaticResource SpecificOutcomesTemplate}"
>
</ListBox>
Ответ, предложенный dummyboy, является лучшим ответом (он должен быть отдан на первое место). У этого есть проблема, которую конструктор ему не нравится (дает ошибку "Object null не может использоваться как параметр accessor для PropertyPath), но есть хорошее обходное решение. Обходной путь состоит в том, чтобы определить элемент в datatemplate, а затем установите шаблон в метку или другой элемент управления содержимым. В качестве примера я пытался добавить изображение, подобное этому
<Image Width="120" Height="120" HorizontalAlignment="Center" Source="{Binding Path=(starbug:IPhotoItem.PhotoSmall)}" Name="mainImage"></Image>
Но он продолжал давать мне ту же ошибку. Решением было создание ярлыка и использование шаблона данных для показа моего контента
<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>
У этого есть свои недостатки, но он, кажется, работает очень хорошо для меня.
Примечание. Вы можете использовать более сложные многочастные пути, подобные этому, если свойство интерфейса находится внутри пути:
<TextBlock>
<TextBlock.Text>
<Binding Path="Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode"/>
</TextBlock.Text>
</TextBlock>
Или непосредственно с директивой Binding
.
<TextBlock Text="{Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>
Или при использовании нескольких свойств интерфейса вы можете переопределить DataContext локально, чтобы сделать код более удобочитаемым.
<StackPanel DataContext={Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod)}">
<TextBlock Text="{Binding CarrierName}"/>
<TextBlock Text="{Binding CarrierServiceCode}"/>
</StackPanel>
Совет. Следите за тем, чтобы случайно закончилось с )}
в конце выражения Path. Глупая ошибка копирования/вставки. Я продолжаю делать.
Path="(myNameSpace:IShippingPackage.ShippingMethod)}"
Path=
Обнаружено, что если я явно не использую Path=
, тогда он не сможет проанализировать привязку.
Обычно я просто пишу что-то вроде этого:
Text="{Binding FirstName}"
вместо
Text="{Binding Path=FirstName}"
Но с более сложной привязкой интерфейса я обнаружил, что Path=
необходимо было избежать этого исключения:
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)
то есть. не делайте этого:
<TextBlock Text="{Binding Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>