Truy cập DataContext mẹ từ DataTemplate


112

Tôi có một ListBoxliên kết với một tập hợp con trên ViewModel. Các mục trong hộp danh sách được tạo kiểu trong một mẫu dữ liệu dựa trên một thuộc tính trên ViewModel chính:

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

Tôi gặp lỗi đầu ra sau:

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

Vì vậy, nếu tôi thay đổi biểu thức liên kết thành "Path=DataContext.CurrentItem.CurveSpeedMustBeSpecified"nó hoạt động, nhưng chỉ với điều kiện là văn bản dữ liệu của điều khiển người dùng chính là a BindingListCollectionView. Điều này là không thể chấp nhận được vì phần còn lại của kiểm soát người dùng liên kết với các thuộc tính của CurrentItemtrên BindingListtự động.

Làm cách nào để chỉ định biểu thức liên kết bên trong kiểu để nó hoạt động bất kể ngữ cảnh dữ liệu gốc là dạng xem bộ sưu tập hay một mục duy nhất?

Câu trả lời:


161

Tôi gặp sự cố với nguồn tương đối trong Silverlight. Sau khi tìm kiếm và đọc, tôi không tìm thấy giải pháp phù hợp mà không sử dụng một số thư viện Binding bổ sung. Tuy nhiên, đây là một cách tiếp cận khác để giành quyền truy cập vào DataContext mẹ bằng cách tham chiếu trực tiếp đến một phần tử mà bạn biết bối cảnh dữ liệu. Nó sử dụng Binding ElementNamevà hoạt động khá tốt, miễn là bạn tôn trọng cách đặt tên của riêng mình và không sử dụng lại nhiều templates/ stylestrên các thành phần:

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

Điều này cũng hoạt động nếu bạn đặt nút vào 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>

Lúc đầu, tôi nghĩ rằng x:Nameskhông thể truy cập các phần tử gốc từ bên trong một mục mẫu, nhưng vì tôi không tìm thấy giải pháp nào tốt hơn nên tôi đã thử và nó hoạt động tốt.


1
Tôi có mã chính xác này trong dự án của mình nhưng nó bị rò rỉ ViewModels (Finalizer không được gọi, liên kết lệnh dường như giữ lại DataContext). Bạn có thể xác minh rằng vấn đề này cũng tồn tại cho bạn không?
Joris Weimar

@Juve này hoạt động, nhưng có thể làm điều này để nó kích hoạt tất cả các điều khiển mục triển khai cùng một mẫu không? Tên là duy nhất vì vậy chúng tôi sẽ cần một mẫu riêng cho mỗi tên, trừ khi tôi thiếu thứ gì đó.
Chris

1
@Juve bỏ qua cuối cùng của tôi, tôi đã làm cho nó hoạt động bằng cách sử dụng nguồn thân nhân với findancestor và tìm kiếm theo loại tổ tiên, (vì vậy tất cả đều giống nhau ngoại trừ việc không tìm kiếm theo tên). Trong trường hợp của tôi, tôi sử dụng lặp lại các ItemsControls, mỗi cái thực hiện một mẫu để tôi trông giống như sau: Command = "{Binding RelativeSource = {RelativeSource FindAncestor, AncestorType = {x: Type ItemsControl}}, Path = DataContext.OpenDocumentBtnCommand}"
Chris

48

Bạn có thể sử dụng RelativeSourceđể tìm phần tử mẹ, như thế này -

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

Xem câu hỏi SO này để biết thêm chi tiết về RelativeSource.


10
Tôi đã phải chỉ định Mode=FindAncestorđể nó hoạt động, nhưng điều này hoạt động và tốt hơn nhiều trong kịch bản MVVM vì nó tránh các điều khiển đặt tên. Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:YourParentElementType}}}"
Aphex

1
làm việc như một nét duyên dáng <3 và không có để xác định chế độ, .net 4.6.1
user2475096

30

RelativeSource so với ElementName

Hai cách tiếp cận này có thể đạt được cùng một kết quả,

RelativeSrouce

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

Phương thức này tìm kiếm một điều khiển của kiểu Cửa sổ (trong ví dụ này) trong cây trực quan và khi nó tìm thấy về cơ bản, bạn có thể truy cập nó DataContextbằng cách sử dụng Path=DataContext..... Ưu điểm của phương pháp này là bạn không cần phải gắn với một cái tên và nó thuộc loại động, tuy nhiên, những thay đổi được thực hiện đối với cây trực quan của bạn có thể ảnh hưởng đến phương pháp này và có thể phá vỡ nó.

ElementName

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

Phương thức này tham chiếu đến một tĩnh vững chắc Namemiễn là phạm vi của bạn có thể nhìn thấy nó, bạn vẫn ổn. Bạn nên tuân thủ quy ước đặt tên của mình để không phá vỡ phương thức này. a Name="..."cho Window / UserControl của bạn.

Mặc dù cả ba loại ( RelativeSource, Source, ElementName) đều có khả năng hoạt động giống nhau, nhưng theo bài báo MSDN sau đây, mỗi loại tốt hơn nên được sử dụng trong lĩnh vực chuyên môn của riêng chúng.

Cách thực hiện: Chỉ định Nguồn ràng buộc

Tìm mô tả ngắn gọn của từng thứ cộng với liên kết đến chi tiết hơn trong bảng ở cuối trang.


18

Tôi đang tìm kiếm cách thực hiện điều gì đó tương tự trong WPF và tôi nhận được giải pháp này:

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

Tôi hy vọng điều này làm việc cho người khác. Tôi có một ngữ cảnh dữ liệu được đặt tự động thành ItemsControls và ngữ cảnh dữ liệu này có hai thuộc tính: MyItems-which là một bộ sưu tập- và một lệnh 'CustomCommand'. Bởi vì ItemTemplateđang sử dụng a DataTemplate, các DataContextcấp trên không thể truy cập trực tiếp. Sau đó, giải pháp để lấy DC của cha mẹ là sử dụng một đường dẫn tương đối và lọc theo ItemsControlloại.


0

vấn đề là DataTemplate không phải là một phần của một phần tử được áp dụng cho nó.

điều này có nghĩa là nếu bạn liên kết với mẫu, bạn đang liên kết với một thứ không có ngữ cảnh.

tuy nhiên nếu bạn đặt một phần tử bên trong mẫu thì khi phần tử đó được áp dụng cho phần tử gốc, nó sẽ nhận được một ngữ cảnh và khi đó liên kết sẽ hoạt động

vì vậy điều này sẽ không hoạt động

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

nhưng điều này hoạt động hoàn hảo

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

bởi vì sau khi áp dụng phương thức dữ liệu, hộp nhóm được đặt trong hộp chính và sẽ có quyền truy cập vào Ngữ cảnh của nó

vì vậy tất cả những gì bạn phải làm là xóa kiểu khỏi mẫu và di chuyển nó vào một phần tử trong mẫu

lưu ý rằng ngữ cảnh cho một điều khiển mục là mục không phải điều khiển tức là ComboBoxItem cho ComboBox không phải là chính ComboBox, trong trường hợp đó bạn nên sử dụng các điều khiển ItemContainerStyle thay thế


0

Có, bạn có thể giải quyết nó bằng cách sử dụng ElementName=Somethingtheo đề xuất của Juve.

NHƯNG!

Nếu một phần tử con (mà bạn sử dụng loại liên kết này) là một điều khiển người dùng sử dụng cùng tên phần tử mà bạn chỉ định trong điều khiển mẹ, thì ràng buộc sẽ chuyển đến đối tượng sai !!

Tôi biết bài đăng này không phải là một giải pháp nhưng tôi nghĩ rằng tất cả những người sử dụng ElementName trong liên kết nên biết điều này, vì nó có thể là một lỗi thời gian chạy.

<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>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.