web-dev-qa-db-ja.com

実際のオブジェクトではなくインターフェイスへのWPFデータバインディング-キャストは可能ですか?

私がこのようなインターフェースを持っているとしましょう:

public interface ISomeInterface
{
...
}

このインターフェイスを実装するクラスもいくつかあります。

public class SomeClass : ISomeInterface
{
...
}

これで、カスタムDataTemplateを使用して、ISomeInterfaceの項目をリストするWPF ListBoxができました。

データバインディングエンジンは明らかに(私が理解できた)インターフェイスプロパティにバインドすることを許可しません-オブジェクトがSomeClassオブジェクトであることがわかり、SomeClassがバインドされたプロパティをたまたま利用できる場合にのみデータが表示されます非インターフェイスプロパティ。

すべてのオブジェクトが、SomeClassなどではなく、ISomeInterfaceであるかのように動作するようにDataTemplateに指示するにはどうすればよいですか?

ありがとう!

49
Rune Jacobsen

明示的に実装されたインターフェイスメンバーにバインドするには、括弧を使用するだけです。例えば:

暗黙:

{Binding Path=MyValue}

明示的:

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

この答え Microsoftフォーラムから Beatriz Costa-MSFT は読む価値があります(かなり古い):

データバインディングチームは、しばらく前にインターフェイスのサポートを追加することについて話し合いましたが、適切な設計を思い付かなかったため、実装されませんでした。問題は、オブジェクト型のようにインターフェイスに階層がないことでした。データソースがIMyInterface1IMyInterface2の両方を実装し、リソースの両方のインターフェイスにDataTemplatesがあるシナリオを考えてみましょう。どのDataTemplateを取得する必要があると思いますか?

オブジェクトタイプの暗黙的なデータテンプレートを作成する場合、最初に正確なタイプのDataTemplateを検索し、次にその親、祖父母などを検索します。適用するタイプの順序は非常に明確に定義されています。インターフェースのサポートの追加について説明したとき、リフレクションを使用してすべてのインターフェースを見つけ、タイプのリストの最後に追加することを検討しました。私たちが遭遇した問題は、型が複数のインターフェースを実装するときにインターフェースの順序を定義することでした。

もう1つ覚えておかなければならないことは、反射はそれほど安くはないということです。これにより、このシナリオではパフォーマンスが少し低下します。

それで、解決策は何ですか?これをすべてXAMLで行うことはできませんが、少しのコードで簡単に行うことができます。 ItemTemplateSelectorItemsControlプロパティを使用して、各アイテムに使用するDataTemplateを選択できます。テンプレートセレクターのSelectTemplateメソッドでは、テンプレート化するアイテムをパラメーターとして受け取ります。ここでは、実装するインターフェースを確認し、それに一致するDataTemplateを返すことができます。

18

簡単に言えば、DataTemplateはインターフェースをサポートしていません(多重継承、明示的対暗黙的などについて考えてください)。私たちがこれを回避する傾向がある方法は、基本クラスを拡張して、DataTemplateの特殊化/一般化を可能にすることです。これはまともな、しかし必ずしも最適ではないということを意味します。

public abstract class SomeClassBase
{

}

public class SomeClass : SomeClassBase
{

}

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

別のオプションがあります。 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

9
Pieter Breed

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>

これには欠点がありますが、私にはかなりうまくいくようです。

8
MikeKulls

注:インターフェースプロパティがパス内にある場合は、次のようなより複雑なマルチパートパスを使用することもできます。

_ <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="(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}"/>
_
4
Simon_Weaver