Привязка данных WPF к интерфейсу, а не к фактическому объекту - возможно ли приведение?

Скажем, у меня есть такой интерфейс:

public interface ISomeInterface
{
...
}

У меня также есть несколько классов, реализующих этот интерфейс;

public class SomeClass : ISomeInterface
{
...
}

Теперь у меня есть WPF ListBox со списком элементов ISomeInterface с использованием настраиваемого DataTemplate.

Механизм привязки данных, по-видимому, не позволит мне (что я смог выяснить) привязать к свойствам интерфейса - он видит, что объект является объектом SomeClass, и данные отображаются только в том случае, если SomeClass должен иметь связанное свойство, доступное как свойство, не связанное с интерфейсом.

Как я могу указать DataTemplate действовать так, как будто каждый объект является ISomeInterface, а не SomeClass и т. Д.?

Спасибо!


person Rune Jacobsen    schedule 29.11.2008    source источник
comment
Спасибо за уведомление @slugster, я обновил принятый ответ. :)   -  person Rune Jacobsen    schedule 05.12.2013


Ответы (6)


Чтобы выполнить привязку к явно реализованным членам интерфейса, все, что вам нужно сделать, это использовать круглые скобки. Например:

скрытый:

{Binding Path=MyValue}

явный:

{Binding Path=(mynamespacealias:IMyInterface.MyValue)}
person dummyboy    schedule 01.12.2009
comment
Рад, что я наткнулся на это, это также помогло мне решить проблему с привязкой к объектам, созданным PostSharp; stackoverflow.com/questions/13636962/ - person RJ Lohan; 30.11.2012
comment
К сожалению, на самом деле это не работает для вопроса OP (я знаю, что этот вопрос очень старый). Он не спрашивал о привязке, но DataType="{x:Type local:SomeClassBase}". - person Steve; 29.08.2013
comment
@Steve, этот ответ правильный - OP говорит о путях привязки в шаблоне данных, явные свойства интерфейса для конкретного типа по умолчанию не видны механизму привязки (неявные). Я считаю, что принятый ответ на самом деле был не тем, чем был OP, тем более что этот ответ был опубликован год спустя. - person slugster; 05.12.2013
comment
Это работает, но, к сожалению, движок недостаточно умен, чтобы добавить обработчик к событию IMyInterface.MyValueChanged, если он есть. Это означает, что если вы хотите получать уведомления об изменениях, реализующий класс также должен реализовывать INotifyPropertyChanged и использовать то же имя свойства интерфейса. Это может быть проблемой, если несколько интерфейсов реализованы явно и одно и то же имя свойства существует более чем на одном. Используйте с умом. - person Crono; 27.01.2015
comment
"Path=" очень важен. Это не сработало, пока я не добавил это. - person bwall; 19.11.2020

Это ответ на форумах Microsoft от Беатрис Коста - MSFT стоит прочитать (довольно старая):

Группа связывания данных некоторое время назад обсуждала добавление поддержки интерфейсов, но в итоге не реализовала ее, потому что мы не смогли придумать для нее хороший дизайн. Проблема заключалась в том, что у интерфейсов нет иерархии, как у объектных типов. Рассмотрим сценарий, в котором ваш источник данных реализует как IMyInterface1, так и IMyInterface2, и у вас есть шаблоны данных для обоих этих интерфейсов в ресурсах: какой шаблон данных, по вашему мнению, нам следует выбрать?

При создании неявных шаблонов данных для типов объектов мы сначала пытаемся найти DataTemplate для точного типа, затем для его родителя, прародителя и так далее. Для нас существует очень четко определенный порядок типов. Когда мы говорили о добавлении поддержки интерфейсов, мы рассматривали возможность использования отражения для обнаружения всех интерфейсов и добавления их в конец списка типов. Проблема, с которой мы столкнулись, заключалась в определении порядка интерфейсов, когда тип реализует несколько интерфейсов.

Еще мы должны помнить, что отражение не так уж и дешево, и это немного снизит нашу производительность для этого сценария.

Так в чем же выход? Вы не можете сделать все это в XAML, но вы можете легко сделать это с помощью небольшого количества кода. Свойство ItemTemplateSelector ItemsControl можно использовать для выбора того, DataTemplate вы хотите использовать для каждого элемента. В методе SelectTemplate для селектора шаблона вы получаете в качестве параметра элемент, который будет шаблоном. Здесь вы можете проверить, какой интерфейс он реализует, и вернуть соответствующий ему DataTemplate.

person Mohammad Dehghan    schedule 14.09.2013

Короткий ответ: DataTemplate не поддерживает интерфейсы (подумайте о множественном наследовании, явном v. Неявном и т. Д.). Способ, которым мы стараемся обойти это, - это расширить базовый класс, чтобы разрешить специализацию / обобщение DataTemplate. Это означает, что достойным, но не обязательно оптимальным решением будет:

public abstract class SomeClassBase
{

}

public class SomeClass : SomeClassBase
{

}

<DataTemplate DataType="{x:Type local:SomeClassBase}">
    <!-- ... -->
</DataTemplate>
person user7116    schedule 29.11.2008
comment
Спасибо - я приму ваш ответ, хотя это означает, что мне придется переделать немало вещей, чтобы делать то, что я хочу. Иногда жизнь отстой, когда вам задают WPF UI поверх библиотеки существующих бизнес-объектов .. :) - person Rune Jacobsen; 30.11.2008
comment
@Rune Jacobsen: В нашем магазине мы тоже сталкиваемся с такими же проблемами роста. - person user7116; 30.11.2008

У вас есть другой вариант. Задайте ключ в шаблоне 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>

Рендеринг

person Pieter Breed    schedule 12.09.2009
comment
К сожалению, это означает, что вы должны явно указать каждой привязке, какой шаблон использовать. Это раздражает, потому что в моем случае я хочу привязаться к тому же свойству интерфейса, но изменить базовый объект во время выполнения. Я не хочу явно указывать привязке для реализации только одного шаблона данных, потому что я хочу, чтобы он изменял этот шаблон данных во время выполнения в зависимости от базового объекта, поддерживающего это свойство. - person Jason Ridge; 24.07.2012

Ответ, предложенный dummyboy, - лучший ответ (он должен быть поставлен на первое место, imo). У него есть проблема, которая не нравится дизайнеру (выдает ошибку "Объект null не может использоваться в качестве параметра доступа для PropertyPath), но есть хорошее обходное решение. Временное решение состоит в том, чтобы определить элемент в табличке данных, а затем установите для шаблона метку или другой элемент управления содержимым. В качестве примера я пытался добавить изображение, подобное этому

<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>

У этого есть свои недостатки, но мне кажется, что это работает очень хорошо.

person MikeKulls    schedule 20.05.2011

Примечание. Вы также можете использовать более сложные пути из нескольких частей, подобные этому, если свойство интерфейса находится внутри пути:

 <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}"/>
person Simon_Weaver    schedule 17.01.2017