เข้าถึง DataContext หลักจาก DataTemplate


112

ฉันมีสิ่งListBoxที่เชื่อมโยงกับคอลเลกชันลูกบน ViewModel รายการในกล่องรายการมีสไตล์ในแผ่นข้อมูลตามคุณสมบัติบน 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 หลังจากค้นหาและอ่านแล้วฉันไม่พบวิธีแก้ปัญหาที่เหมาะสมโดยไม่ใช้ไลบรารี Binding เพิ่มเติม แต่นี่เป็นอีกแนวทางหนึ่งในการเข้าถึง 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

@ ให้มันใช้งานได้ แต่เป็นไปได้ไหมที่จะทำเช่นนี้เพื่อให้มันเริ่มทำงานสำหรับรายการควบคุมทั้งหมดที่ใช้เทมเพลตเดียวกัน ชื่อไม่ซ้ำกันดังนั้นเราจึงต้องมีเทมเพลตแยกกันสำหรับแต่ละเทมเพลตเว้นแต่ฉันจะพลาดบางอย่าง
คริส

1
@ จู๋ไม่สนใจคนสุดท้ายของฉันฉันทำให้มันใช้งานได้โดยใช้ relativeource กับ findancestor และค้นหาตามประเภทบรรพบุรุษ (เหมือนกันทั้งหมดยกเว้นไม่ได้ค้นหาด้วยชื่อ) ในกรณีของฉันฉันใช้ ItemsControls ซ้ำโดยแต่ละรายการใช้เทมเพลตดังนั้นของฉันจึงมีลักษณะดังนี้ Command = "{Binding RelativeSource = {RelativeSource FindAncestor, AncestorType = {x: Type ItemsControl}}, Path = DataContext.OpenDocumentBtnCommand}"
คริส

48

คุณสามารถใช้RelativeSourceเพื่อค้นหาองค์ประกอบหลักเช่นนี้ -

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

ดูคำถามนี้ดังนั้น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

RelativeSourceเทียบกับElementName

สองแนวทางนี้สามารถบรรลุผลลัพธ์เดียวกัน

ญาติ

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

วิธีนี้มองหาตัวควบคุมประเภท Window (ในตัวอย่างนี้) ในแผนผังภาพและเมื่อพบว่าโดยทั่วไปแล้วคุณสามารถเข้าถึงได้DataContextโดยใช้Path=DataContext..... ข้อดีของวิธีนี้คือคุณไม่จำเป็นต้องผูกติดกับชื่อและเป็นแบบไดนามิกอย่างไรก็ตามการเปลี่ยนแปลงที่เกิดขึ้นกับแผนภูมิภาพของคุณอาจส่งผลต่อวิธีการนี้และอาจทำลายมันได้

ElementName

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

วิธีนี้อ้างอิงถึงค่าคงที่ที่มั่นคงNameตราบเท่าที่ขอบเขตของคุณสามารถมองเห็นได้คุณก็สบายดีคุณควรยึดมั่นในหลักการตั้งชื่อของคุณไม่ให้ทำลายวิธีนี้แน่นอนวิธีการนั้นง่ายมากและสิ่งที่คุณต้องระบุก็คือ a Name="..."สำหรับ Window / 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-which คือ collection- และคำสั่ง 'CustomCommand' หนึ่งคำสั่ง เนื่องจากการItemTemplateใช้DataTemplateที่DataContextระดับบนไม่สามารถเข้าถึงได้โดยตรง จากนั้นวิธีแก้ปัญหาเพื่อรับ 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}" >

เนื่องจากหลังจากใช้ datatemplate แล้ว groupbox จะถูกวางไว้ในพาเรนต์และจะสามารถเข้าถึงบริบทได้

ดังนั้นสิ่งที่คุณต้องทำคือลบสไตล์ออกจากเทมเพลตและย้ายไปไว้ในองค์ประกอบในเทมเพลต

โปรดทราบว่าบริบทสำหรับ itemscontrol คือรายการที่ไม่ใช่ตัวควบคุมเช่น ComboBoxItem สำหรับ ComboBox ไม่ใช่ ComboBox เองซึ่งในกรณีนี้คุณควรใช้ตัวควบคุม 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.