DataTemplate에서 부모 DataContext에 액세스


112

나는이 ListBox뷰 모델에 자식 컬렉션에있는 바인딩을. 목록 상자 항목은 상위 ViewModel의 속성에 따라 데이터 템플릿에서 스타일이 지정됩니다.

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

다음과 같은 출력 오류가 발생합니다.

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

따라서 바인딩 식을 변경하면 "Path=DataContext.CurrentItem.CurveSpeedMustBeSpecified"작동하지만 부모 사용자 정의 컨트롤의 데이터 컨텍스트가 BindingListCollectionView. 이 허용되지 않기 때문에의 속성에 대한 사용자 제어 귀속 나머지 CurrentItem상의 BindingList자동.

컬렉션 뷰 또는 단일 항목 인 상위 데이터 컨텍스트에 관계없이 작동하도록 스타일 내에서 바인딩 식을 지정하려면 어떻게해야합니까?

답변:


161

Silverlight의 상대 소스에 문제가 있습니다. 검색하고 읽은 후 추가 바인딩 라이브러리를 사용하지 않고는 적절한 솔루션을 찾지 못했습니다. 그러나 데이터 컨텍스트를 알고있는 요소를 직접 참조 하여 상위 DataContext대한 액세스 권한을 얻는 또 다른 방법 이 있습니다. 그것은 사용 Binding ElementName하고 당신이 당신의 자신의 이름을 존중하고 무거운 재사용이없는만큼, 아주 잘 작동 templates/ styles구성 요소에서를 :

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

버튼을 Style/에 넣는 경우에도 작동합니다 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>

처음 x:Names에는 템플릿 항목 내에서 부모 요소에 액세스 할 수 없다고 생각 했지만 더 나은 솔루션을 찾지 못했기 때문에 방금 시도했고 제대로 작동합니다.


1
내 프로젝트 에이 정확한 코드가 있지만 ViewModels가 누출되고 있습니다 (Finalizer가 호출되지 않고 명령 바인딩이 DataContext를 유지하는 것 같습니다). 이 문제가 귀하에게도 존재하는지 확인할 수 있습니까?
Joris Weimar

@Juve이 작동하지만 동일한 템플릿을 구현하는 모든 항목 컨트롤에 대해 실행되도록 할 수 있습니까? 이름은 고유하므로 누락되지 않는 한 각각에 대해 별도의 템플릿이 필요합니다.
크리스

1
@Juve는 내 마지막을 무시하고 findancestor와 함께 relativesource를 사용하고 ancestortype으로 검색하여 작동하도록했습니다 (이름으로 검색하지 않는 것을 제외하고 모두 동일합니다). 제 경우에는 템플릿을 구현하는 각각의 ItemsControls를 반복해서 사용하므로 다음과 같습니다. Command = "{Binding RelativeSource = {RelativeSource FindAncestor, AncestorType = {x : Type ItemsControl}}, Path = DataContext.OpenDocumentBtnCommand}"
Chris

48

다음 RelativeSource과 같이 부모 요소를 찾는 데 사용할 수 있습니다.

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

자세한 내용은 이 SO 질문 을 참조하십시오.RelativeSource .


10
Mode=FindAncestor작동 하도록 지정해야 했지만 이름 지정 제어를 방지하기 때문에 작동하며 MVVM 시나리오에서 훨씬 좋습니다. Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:YourParentElementType}}}"
Aphex

1
마법 같은 일 <3 모드를 지정하지 않았다, .NET 4.6.1
user2475096

30

RelativeSourceElementName

이 두 가지 접근 방식은 동일한 결과를 얻을 수 있습니다.

RelativeSrouce

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

이 방법은 시각적 트리에서 (이 예에서는) 타입 윈도우의 컨트롤을 찾고 그것을 발견하면 당신은 기본적으로는 접근 할 수 DataContext를 사용하여 Path=DataContext..... 이 방법의 장점은 이름에 묶일 필요가 없으며 일종의 동적이지만 시각적 트리를 변경하면이 방법에 영향을 미치고 깨질 수 있다는 것입니다.

ElementName

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

이 방법은 솔리드 정적 Name 은 스코프가 볼 수 을 참조하므로 괜찮습니다. 물론이 방법을 위반하지 않도록 명명 규칙을 고수해야합니다. 접근 방식은 간단하며 필요한 것은 지정하는 것입니다. ㅏName="..." 창 / UserControl에 대한 .

세 가지 유형 모두 (RelativeSource, Source, ElementName ) 모두 동일한 작업을 수행 할 수 있지만 다음 MSDN 문서에 따르면 각각 고유 한 전문 분야에서 사용하는 것이 좋습니다.

방법 : 바인딩 소스 지정

페이지 하단의 표에서 각 항목에 대한 간략한 설명과 자세한 내용에 대한 링크를 찾으십시오.


18

WPF에서 비슷한 작업을 수행하는 방법을 찾고 있었는데이 솔루션을 얻었습니다.

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

나는 이것이 다른 누군가에게 효과가 있기를 바랍니다. ItemsControls에 자동으로 설정된 데이터 컨텍스트가 있으며이 데이터 컨텍스트에는 두 가지 속성이 있습니다.MyItems 있으며이 컬렉션 인-와 하나의 명령 'CustomCommand'라는 이 있습니다. (가)의 때문에 ItemTemplate을 사용하고 DataTemplateDataContext상위 레벨의 직접 액세스 할 수 없습니다. 그런 다음 부모의 DC를 얻는 해결 방법은 상대 경로를 사용하고 ItemsControl유형별로 필터링하는 것 입니다.


0

문제는 DataTemplate이 적용된 요소의 일부가 아니라는 것입니다.

즉, 템플릿에 바인딩하면 컨텍스트가없는 항목에 바인딩됩니다.

그러나 템플릿 내부에 요소를 넣으면 해당 요소가 부모에 적용될 때 컨텍스트를 얻고 바인딩이 작동합니다.

그래서 이것은 작동하지 않습니다

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

하지만 이것은 완벽하게 작동합니다

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

데이터 템플릿이 적용된 후 그룹 상자는 부모에 배치되고 컨텍스트에 액세스 할 수 있기 때문입니다.

따라서 템플릿에서 스타일을 제거하고 템플릿의 요소로 이동하기 만하면됩니다.

참고 ItemsControl에 대한 컨텍스트가 콤보 상자의 항목이 아닌 컨트롤 즉, ComboBoxItem인지하지 당신이 컨트롤 대신 ItemContainerStyle을 사용해야하는 경우에는 콤보 자체


0

예, 다음을 사용하여 해결할 수 있습니다. ElementName=Something Juve가 제안한대로을 .

그러나!

이러한 종류의 바인딩을 사용하는 자식 요소가 부모 컨트롤에서 지정한 것과 동일한 요소 이름을 사용하는 사용자 컨트롤 인 경우 바인딩은 잘못된 개체로 이동합니다 !!

이 게시물이 해결책이 아니라는 것을 알고 있지만 가능한 런타임 버그이므로 바인딩에서 ElementName을 사용하는 모든 사람이 이것을 알아야한다고 생각했습니다.

<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>
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.