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

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

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

public interface ISomeInterface
{
...
}

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

public class SomeClass : ISomeInterface
{
...
}

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

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

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

Спасибо!

4b9b3361

Ответ 1

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

неявный:

{Binding Path=MyValue}

Явный:

{Binding Path=(mynamespacealias:IMyInterface.MyValue)}

Ответ 2

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

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

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

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

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

Ответ 3

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

public abstract class SomeClassBase
{

}

public class SomeClass : SomeClassBase
{

}

<DataTemplate DataType="{x:Type local:SomeClassBase}">
    <!-- ... -->
</DataTemplate>

Ответ 4

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

Ответ 5

Ответ, предложенный 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>

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

Ответ 6

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

 <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}"/>