WPF databinding aan interface en niet echt object – casten mogelijk?

Stel dat ik een interface heb zoals deze:

public interface ISomeInterface
{
...
}

Ik heb ook een aantal klassen die deze interface implementeren;

public class SomeClass : ISomeInterface
{
...
}

Nu heb ik een WPF ListBox met items van ISomeInterface, met een aangepaste DataTemplate.

De databinding-engine zal me blijkbaar niet (dat heb ik kunnen achterhalen) toestaan ​​om te binden aan interface-eigenschappen – hij ziet dat het object een SomeClass-object is en gegevens worden alleen weergegeven als SomeClass de gebonden eigenschap beschikbaar als een niet-interface-eigenschap.

Hoe kan ik de DataTemplate vertellen om te doen alsof elk object een ISomeInterface is, en niet een SomeClass enz.?

Bedankt!


Antwoord 1, autoriteit 100%

Om te binden aan expliciet geïmplementeerde interfaceleden, hoef je alleen maar de haakjes te gebruiken. Bijvoorbeeld:

impliciet:

{Binding Path=MyValue}

expliciet:

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

Antwoord 2, autoriteit 29%

Dit antwoordvan Microsoft-forums door Beatriz Costa – MSFTis het lezen waard (vrij oud):

Het team voor gegevensbinding besprak een tijdje geleden het toevoegen van ondersteuning voor interfaces, maar implementeerde het uiteindelijk niet omdat we er geen goed ontwerp voor konden bedenken. Het probleem was dat interfaces geen hiërarchie hebben zoals objecttypes. Overweeg het scenario waarin uw gegevensbron zowel IMyInterface1als IMyInterface2implementeert en u DataTemplates voor beide interfaces in de bronnen hebt: welke DataTemplate moeten we volgens u oppikken?

Als we impliciete gegevenstemplates uitvoeren voor objecttypen, proberen we eerst een DataTemplatete vinden voor het exacte type, dan voor zijn bovenliggende, grootouderlijke enzovoort. Er is een zeer goed gedefinieerde volgorde van typen die we kunnen toepassen. Toen we het hadden over het toevoegen van ondersteuning voor interfaces, hebben we overwogen om reflectie te gebruiken om alle interfaces te achterhalen en ze aan het einde van de lijst met typen toe te voegen. Het probleem dat we tegenkwamen was het definiëren van de volgorde van de interfaces wanneer het type meerdere interfaces implementeert.

Het andere dat we in gedachten moesten houden, is dat reflectie niet zo goedkoop is, en dit zou onze prestatie een beetje verminderen voor dit scenario.

Dus wat is de oplossing? Je kunt dit niet allemaal in XAML doen, maar je kunt het gemakkelijk doen met een klein beetje code. De eigenschap ItemTemplateSelectorvan ItemsControlkan worden gebruikt om te kiezen welke DataTemplateu voor elk item wilt gebruiken. In de SelectTemplatemethode voor uw sjabloonkiezer, ontvangt u als parameter het item dat u wilt sjabloon. Hier kunt u controleren welke interface het implementeert en de DataTemplateretourneren die daarmee overeenkomt.


Antwoord 3, autoriteit 22%

Het korte antwoord is dat DataTemplate’s geen interfaces ondersteunen (denk aan meervoudige overerving, expliciet v. impliciet, enz.). De manier waarop we dit meestal omzeilen, is om een ​​basisklasse te hebben om dingen uit te breiden om de DataTemplate-specialisatie/-generalisatie mogelijk te maken. Dit betekent dat een fatsoenlijke, maar niet noodzakelijk optimale oplossing zou zijn:

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

Antwoord 4, autoriteit 14%

Je hebt een andere optie. Stel een sleutel in op uw DataTemplate en verwijs naar die sleutel in de ItemTemplate. Zoals dit:

<DataTemplate DataType="{x:Type documents:ISpecificOutcome}"
              x:Key="SpecificOutcomesTemplate">
    <Label Content="{Binding Name}"
           ToolTip="{Binding Description}" />
</DataTemplate>

verwijs vervolgens met de sleutel naar het sjabloon waar u het wilt gebruiken, zoals dit:

<ListBox ItemsSource="{Binding Path=SpecificOutcomes}"
         ItemTemplate="{StaticResource SpecificOutcomesTemplate}"
         >
</ListBox>

Rendering


Antwoord 5, autoriteit 12%

Het door dummyboy gesuggereerde antwoord is het beste antwoord (het zou bovenaan moeten worden gestemd). Het heeft een probleem dat de ontwerper het niet leuk vindt (geeft de foutmelding “Object null kan niet worden gebruikt als een accessor-parameter voor een PropertyPath), maar er is een goede oplossing. De oplossing is om het item in een gegevenssjabloon te definiëren en vervolgens stel de sjabloon in op een label of ander inhoudsbeheer. Ik probeerde bijvoorbeeld een afbeelding als deze toe te voegen

<Image Width="120" Height="120" HorizontalAlignment="Center" Source="{Binding Path=(starbug:IPhotoItem.PhotoSmall)}" Name="mainImage"></Image>

Maar ik kreeg steeds dezelfde foutmelding. De oplossing was om een ​​label te maken en een gegevenssjabloon te gebruiken om mijn inhoud weer te geven

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

Dit heeft zijn nadelen, maar het lijkt redelijk goed te werken voor mij.


Antwoord 6, autoriteit 6%

Opmerking: u kunt ook complexere meerdelige paden zoals deze gebruiken als de interface-eigenschap zich binnen een pad bevindt:

<TextBlock>
    <TextBlock.Text>
        <Binding Path="Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode"/>
    </TextBlock.Text>
 </TextBlock>

Of rechtstreeks met de Binding-instructie.

<TextBlock Text="{Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>

Of als u meerdere eigenschappen van een interface gebruikt, kunt u de DataContext lokaal opnieuw definiëren om de code leesbaarder te maken.

<StackPanel DataContext={Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod)}">
    <TextBlock Text="{Binding CarrierName}"/>
    <TextBlock Text="{Binding CarrierServiceCode}"/>
  </StackPanel>

Tip: pas op voor het per ongeluk eindigen met )}aan het einde van een Path-expressie. Stomme kopieer-/plakfout die ik blijf maken.

Path="(myNameSpace:IShippingPackage.ShippingMethod)}"


Zorg ervoor dat u Path=

. gebruikt

Ontdekt dat als ik Path=niet expliciet gebruik, het de binding mogelijk niet kan ontleden.
Meestal schrijf ik zoiets als dit:

Text="{Binding FirstName}"

in plaats van

Text="{Binding Path=FirstName}"

Maar met de complexere interface-binding ontdekte ik dat Path=nodig was om deze uitzondering te vermijden:

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)

d.w.z. doe dit niet:

<TextBlock Text="{Binding Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>

Other episodes