DataTemplate'ten üst DataContext'e erişin


112

Bir var ListBoxbir ViewModel bir çocuk koleksiyonuna hangi bağlar. Liste kutusu öğeleri, ana ViewModel'deki bir özelliğe dayalı olarak bir veri şablonunda stilize edilir:

<Style x:Key="curveSpeedNonConstantParameterCell">
   <Style.Triggers>
      <DataTrigger Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, 
          ElementName=someParentElementWithReferenceToRootDataContext}" 
          Value="True">
          <Setter Property="Control.Visibility" Value="Hidden"></Setter>
      </DataTrigger>
   </Style.Triggers>
</Style>

Aşağıdaki çıktı hatasını alıyorum:

System.Windows.Data Error: 39 : BindingExpression path error: 
 'CurveSpeedMustBeSpecified' property not found on 
   'object' ''BindingListCollectionView' (HashCode=20467555)'. 
 BindingExpression:Path=DataContext.CurveSpeedMustBeSpecified; 
 DataItem='Grid' (Name='nonConstantCurveParametersGrid');
 target element is 'TextBox' (Name=''); 
 target property is 'NoTarget' (type 'Object')

Dolayısıyla, bağlama ifadesini değiştirirsem "Path=DataContext.CurrentItem.CurveSpeedMustBeSpecified"çalışır, ancak yalnızca ana kullanıcı denetiminin veri bağlamı bir BindingListCollectionView. Bu, kabul edilebilir olmadığı için özelliklerine kullanıcı kontrol bağlar geri kalanı CurrentItemile BindingListotomatik olarak.

Ana veri bağlamının bir koleksiyon görünümü veya tek bir öğe olmasına bakılmaksızın çalışması için stilin içindeki bağlama ifadesini nasıl belirtebilirim?

Yanıtlar:


161

Silverlight'taki ilgili kaynakla ilgili sorunlar yaşadım. Aradıktan ve okuduktan sonra, bazı ek Bağlama kitaplığı kullanmadan uygun bir çözüm bulamadım. Ancak, veri bağlamını bildiğiniz bir öğeye doğrudan başvurarak ana DataContext'e erişim elde etmek için başka bir yaklaşım burada . Bu kullanır Binding ElementNameve uzun kendi adlandırmaları saygı ve ağır yeniden kullanımını yok mu gibi oldukça iyi çalışıyor templates/ ' stylesbileşenlerinde:

<ItemsControl x:Name="level1Lister" ItemsSource={Binding MyLevel1List}>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Button Content={Binding MyLevel2Property}
              Command={Binding ElementName=level1Lister,
                       Path=DataContext.MyLevel1Command}
              CommandParameter={Binding MyLevel2Property}>
      </Button>
    <DataTemplate>
  <ItemsControl.ItemTemplate>
</ItemsControl>

Bu, düğmeyi Style/ içine koyarsanız da çalışır Template:

<Border.Resources>
  <Style x:Key="buttonStyle" TargetType="Button">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="Button">
          <Button Command={Binding ElementName=level1Lister,
                                   Path=DataContext.MyLevel1Command}
                  CommandParameter={Binding MyLevel2Property}>
               <ContentPresenter/>
          </Button>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</Border.Resources>

<ItemsControl x:Name="level1Lister" ItemsSource={Binding MyLevel1List}>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Button Content="{Binding MyLevel2Property}" 
              Style="{StaticResource buttonStyle}"/>
    <DataTemplate>
  <ItemsControl.ItemTemplate>
</ItemsControl>

İlk başta x:Namesana öğelerin şablonlu bir öğeden erişilemeyeceğini düşündüm , ancak daha iyi bir çözüm bulamadığım için denedim ve iyi çalışıyor.


1
Projemde bu tam kod var ama ViewModels sızdırıyor (Sonlandırıcı çağrılmadı, Komut bağlama DataContext'i koruyor gibi görünüyor). Bu sorunun sizin için de var olduğunu doğrulayabilir misiniz?
Joris Weimar

@Juve bu işe yarıyor, ancak aynı şablonu uygulayan tüm öğe kontrolleri için tetiklenecek şekilde bunu yapmak mümkün mü? Ad benzersizdir, bu nedenle bir şeyi kaçırmıyorsam, her biri için ayrı bir şablona ihtiyacımız olur.
Chris

1
@Juve sonuncuyu göz ardı et, findancestor ile akraba kaynağını kullanarak ve ata türüne göre arama yaparak çalıştım (yani adıyla arama dışında hepsi aynı). Benim durumumda, her biri bir şablon uygulayan ItemsControls kullanımını tekrar ediyorum, böylece benimki şöyle görünür: Command = "{Binding RelativeSource = {RelativeSource FindAncestor, AncestorType = {x: Type ItemsControl}}, Path = DataContext.OpenDocumentBtnCommand}"
Chris

48

Bunun RelativeSourcegibi ana öğeyi bulmak için kullanabilirsiniz -

Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, 
RelativeSource={RelativeSource AncestorType={x:Type local:YourParentElementType}}}"

Hakkında daha fazla ayrıntı için bu SO sorusuna bakın RelativeSource.


10
Mode=FindAncestorÇalışması için belirtmek zorunda kaldım , ancak bu işe yarıyor ve bir MVVM senaryosunda çok daha iyi çünkü adlandırma kontrollerinden kaçınıyor. Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:YourParentElementType}}}"
Aphex

1
bir cazibe gibi çalışır <3 ve modu belirtmek zorunda kalmadı, .net 4.6.1
user2475096

30

RelativeSource ve ElementName karşılaştırması

Bu iki yaklaşım aynı sonucu elde edebilir,

RelativeSrouce

Binding="{Binding Path=DataContext.MyBindingProperty, 
          RelativeSource={RelativeSource AncestorType={x:Type Window}}}"

Bu yöntem görsel ağacında (bu örnekte) bir tür Pencere bir kontrol arar ve bunu bulduğunda işlemi temelde oluyor erişebilirsiniz DataContextkullanarak Path=DataContext..... Bu yöntemin Artıları, bir isme bağlı olmanıza gerek olmaması ve bir tür dinamik olmasıdır, ancak görsel ağacınızda yapılan değişiklikler bu yöntemi etkileyebilir ve muhtemelen bozabilir.

ElementName

Binding="{Binding Path=DataContext.MyBindingProperty, ElementName=MyMainWindow}

Bu yöntem katı bir Namedurağanlığa atıfta bulunur, bu nedenle kapsamınız onu görebildiği sürece, iyisiniz.Bu yöntemi elbette bozmamak için adlandırma kuralınıza bağlı kalmalısınız. a Name="..."Window / UserControl'ünüz için.

Her üç tip de ( RelativeSource, Source, ElementName) aynı şeyi yapma yeteneğine sahip olsa da, aşağıdaki MSDN makalesine göre, her biri kendi uzmanlık alanlarında daha iyi kullanılmalıdır.

Nasıl yapılır: Bağlama Kaynağını Belirtme

Sayfanın altındaki tabloda her birinin kısa açıklamasını ve daha fazla ayrıntıya giden bir bağlantıyı bulun.


18

WPF'de benzer bir şeyin nasıl yapılacağını araştırıyordum ve şu çözümü aldım:

<ItemsControl ItemsSource="{Binding MyItems,Mode=OneWay}">
<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <StackPanel Orientation="Vertical" />
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
    <DataTemplate>
        <RadioButton 
            Content="{Binding}" 
            Command="{Binding Path=DataContext.CustomCommand, 
                        RelativeSource={RelativeSource Mode=FindAncestor,      
                        AncestorType={x:Type ItemsControl}} }"
            CommandParameter="{Binding}" />
    </DataTemplate>
</ItemsControl.ItemTemplate>

Umarım bu başka biri için işe yarar. ItemsControls'ye otomatik olarak ayarlanan bir veri içeriğim var ve bu veri bağlamının iki özelliği var: MyItems-bu bir koleksiyon- ve bir 'CustomCommand' komutu. A ItemTemplatekullanıyor olduğu DataTemplateiçin DataContext, üst seviyelere doğrudan erişilemez. Ardından, üst öğenin DC'sini almak için geçici çözüm, göreli bir yol kullanmak ve ItemsControltüre göre filtrelemektir .


0

sorun, bir DataTemplate'in kendisine uygulanan bir öğenin parçası olmamasıdır.

bu, şablona bağlarsanız, bağlamı olmayan bir şeye bağladığınız anlamına gelir.

ancak şablonun içine bir öğe koyarsanız, o öğe üst öğeye uygulandığında bir bağlam kazanır ve daha sonra bağlama çalışır

yani bu işe yaramayacak

<DataTemplate >
    <DataTemplate.Resources>
        <CollectionViewSource x:Key="projects" Source="{Binding Projects}" >

ama bu mükemmel çalışıyor

<DataTemplate >
    <GroupBox Header="Projects">
        <GroupBox.Resources>
            <CollectionViewSource x:Key="projects" Source="{Binding Projects}" >

çünkü veri şablonu uygulandıktan sonra grup kutusu üst öğeye yerleştirilir ve Bağlamına erişebilir.

Bu nedenle tek yapmanız gereken, stili şablondan kaldırmak ve şablondaki bir öğeye taşımak

Not Bir itemscontrol için bağlam ComboBox öğesi değil kontrol yani ComboBoxItem olduğunu değil denetimleri yerine ItemContainerStyle kullanmalıdır bu durumda ComboBox kendisi


0

Evet, bunu kullanarak çözebilirsiniz. ElementName=Something Juve'nin önerdiği .

FAKAT!

Bir alt öğe (üzerinde bu tür bir bağlamayı kullandığınız), üst denetimde belirttiğiniz öğe adını kullanan bir kullanıcı denetimiyse, bağlama yanlış nesneye gider !!

Bu yazının bir çözüm olmadığını biliyorum, ancak bağlamada ElementName'i kullanan herkesin bunu bilmesi gerektiğini düşündüm, çünkü bu olası bir çalışma zamanı hatası.

<UserControl x:Class="MyNiceControl"
             x:Name="TheSameName">
   the content ...
</UserControl>

<UserControl x:Class="AnotherUserControl">
        <ListView x:Name="TheSameName">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <MyNiceControl Width="{Binding DataContext.Width, ElementName=TheSameName}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
</UserControl>
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.