DataTemplate से अभिभावक DataContext तक पहुँचें


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

सिल्वरलाइट में रिश्तेदार स्रोत के साथ मुझे समस्या थी। खोज और पढ़ने के बाद मुझे कुछ अतिरिक्त बाइंडिंग लाइब्रेरी का उपयोग किए बिना एक उपयुक्त समाधान नहीं मिला। लेकिन, यहां सीधे डेटा संदर्भ को जानने वाले एक तत्व को संदर्भित करके मूल 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 (फ़ाइनलाइज़र जिसे नहीं कहा जाता है, कमांड बाइंडिंग DataConitxtxt को बनाए रखता है) को लीक करता है। क्या आप यह सत्यापित कर सकते हैं कि यह समस्या आपके लिए भी मौजूद है?
जोरिस वीमर

@ यह काम करता है, लेकिन क्या ऐसा करना संभव है ताकि यह सभी वस्तुओं के लिए आग लग जाए जो एक ही टेम्पलेट को लागू करते हैं? नाम अद्वितीय है इसलिए हमें प्रत्येक के लिए एक अलग टेम्पलेट की आवश्यकता होगी, जब तक कि मुझे कुछ याद न हो।
क्रिस

1
@ मेरे अंतिम की अवहेलना, मैंने इसे खोजक के साथ रिश्तेदारों का उपयोग करके और पूर्वजों द्वारा खोज के साथ काम करने के लिए प्राप्त किया, (इसलिए नाम द्वारा खोज न करने के अलावा सभी समान)। ItemsControls की मेरे मामले मैं दोहराने उपयोग में हर एक को एक टेम्पलेट लागू करने तो मेरा इस तरह दिखता है: कमान = "{बाइंडिंग RelativeSource = {RelativeSource FindAncestor, AncestorType = {x: प्रकार ItemsControl}}, पथ = DataContext.OpenDocumentBtnCommand}"
क्रिस

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

RelativeSource बनाम ElementName

ये दोनों दृष्टिकोण समान परिणाम प्राप्त कर सकते हैं,

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) एक ही काम करने में सक्षम हैं, लेकिन निम्नलिखित एमएसडीएन लेख के अनुसार, हर एक को अपने स्वयं के विशेष क्षेत्र में उपयोग किया जाना चाहिए।

कैसे करें: बंधन स्रोत निर्दिष्ट करें

प्रत्येक प्लस के संक्षिप्त विवरण को पृष्ठ के निचले भाग की तालिका में एक अधिक विवरण के लिए लिंक खोजें।


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>

मुझे उम्मीद है कि यह किसी और के लिए काम करता है। मेरे पास एक डेटा संदर्भ है, जो स्वचालित रूप से ItemControls पर सेट है, और इस डेटा संदर्भ में दो गुण हैं: MyItems-जो एक संग्रह है- और एक कमांड 'CustomCommand'। ItemTemplateका उपयोग कर रहा है की वजह से DataTemplate, DataContextऊपरी स्तर की पहुंच सीधे सुलभ नहीं है। फिर माता-पिता के डीसी को प्राप्त करने के लिए समाधान एक रिश्तेदार पथ का उपयोग करें और 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}" >

क्योंकि डेटेटप्लेट लागू होने के बाद ग्रुपबॉक्स को पैरेंट में रखा जाता है और इसके कॉन्टेक्स्ट तक पहुंच होगी

तो आपको बस इतना करना है कि टेम्पलेट से शैली को हटा दें और इसे टेम्पलेट में एक तत्व में स्थानांतरित करें

ध्यान दें कि आइटमकंट्रोल के लिए संदर्भ वह आइटम है जो नियंत्रण नहीं है। ComboBox के लिए ComboBoxItem ComboBox ही नहीं है जिस स्थिति में आपको नियंत्रणों का उपयोग करना चाहिए इसके बजाय ItemContainerStyle


0

हां, आप इसका उपयोग करके हल कर सकते हैं ElementName=Something जूवे द्वारा सुझाए अनुसार ।

परंतु!

यदि कोई चाइल्ड एलिमेंट (जिस पर आप इस तरह के बाइंडिंग का उपयोग करते हैं) एक यूजर कंट्रोल है जो उसी तत्व के नाम का उपयोग करता है जैसा कि आप पेरेंट कंट्रोल में निर्दिष्ट करते हैं, तो बाइंडिंग गलत ऑब्जेक्ट में चली जाती है !!

मुझे पता है कि यह पोस्ट एक समाधान नहीं है, लेकिन मुझे लगा कि बंधन में एलीमेंटनाम का उपयोग करने वाले सभी को यह जानना चाहिए, क्योंकि यह एक संभावित रनटाइम बग है।

<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>
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.