WPF привязывает данные к интерфейсу, а не к реальному объекту - возможно ли приведение? - PullRequest
49 голосов
/ 29 ноября 2008

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

public interface ISomeInterface
{
...
}

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

public class SomeClass : ISomeInterface
{
...
}

Теперь у меня есть WPF ListBox, перечисляющий элементы ISomeInterface с использованием пользовательского шаблона данных.

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

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

Спасибо!

Ответы [ 6 ]

60 голосов
/ 01 декабря 2009

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

неявное:

{Binding Path=MyValue}

Явный:

{Binding Path=(mynamespacealias:IMyInterface.MyValue)}
18 голосов
/ 14 сентября 2013

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

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

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

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

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

13 голосов
/ 29 ноября 2008

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

public abstract class SomeClassBase
{

}

public class SomeClass : SomeClassBase
{

}

<DataTemplate DataType="{x:Type local:SomeClassBase}">
    <!-- ... -->
</DataTemplate>
9 голосов
/ 12 сентября 2009

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

Rendering

8 голосов
/ 20 мая 2011

Ответ, предложенный dummyboy, является лучшим ответом (за него следует проголосовать за верхнюю часть ИМО). У него есть проблема, которая не нравится конструктору (выдает ошибку «Нулевой объект не может быть использован в качестве параметра метода доступа для 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>

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

4 голосов
/ 18 января 2017

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

 <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}"/>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...