2009-07-05 4 views
106

मैं एक व्यूमोडेल लिखना चाहता हूं जो हमेशा दृश्य से कुछ पढ़ने-योग्य निर्भरता गुणों की वर्तमान स्थिति को जानता है।रीड-ओनली जीयूआई गुणों को वापस देखें ViewModel

विशेष रूप से, मेरे जीयूआई में FlowDocumentPageViewer है, जो FlowDocument से एक समय में एक पृष्ठ प्रदर्शित करता है। FlowDocumentPageViewer CanGoToPreviousPage और CanGoToNextPage नामक दो पढ़ने-योग्य निर्भरता गुणों का खुलासा करता है। मैं चाहता हूं कि मेरा व्यूमोडेल हमेशा इन दो दृश्य गुणों के मानों को जान सके।

मैं सोचा मैं एक OneWayToSource डेटा बाइंडिंग के साथ ऐसा कर सकता है:

<FlowDocumentPageViewer 
    CanGoToNextPage="{Binding NextPageAvailable, Mode=OneWayToSource}" ...> 

यदि यह अनुमति दी गई थी, यह सही होगा: जब भी FlowDocumentPageViewer के CanGoToNextPage संपत्ति बदल गया है, नया मान नीचे ViewModel के NextPageAvailable में धकेल दिया जायेगा संपत्ति, जो वही है जो मैं चाहता हूं।

दुर्भाग्य से, यह संकलित नहीं होता है: मुझे 'CanGoToPreviousPage' संपत्ति केवल पढ़ने के लिए त्रुटि मिलती है और मार्कअप से सेट नहीं किया जा सकता है। स्पष्ट रूप से केवल पढ़ने योग्य गुण किसी भी प्रकार के डाटाबेसिंग का समर्थन नहीं करते हैं, यहां तक ​​कि उस संपत्ति के संबंध में केवल पढ़ने योग्य डेटाबेस भी नहीं।

मैं अपना व्यूमोडेल गुणों को निर्भरता प्रॉपर्टीज बना सकता हूं, और एकवेवे बाध्यकारी को दूसरी तरफ जा रहा हूं, लेकिन मैं अलग-अलग चिंताओं के उल्लंघन के बारे में पागल नहीं हूं (व्यूमोडेल को दृश्य के संदर्भ की आवश्यकता होगी, जो एमवीवीएम डाटाबेसिंग से बचने के लिए माना जाता है)।

FlowDocumentPageViewer एक CanGoToNextPageChanged घटना का खुलासा नहीं करता, और मैं किसी भी अच्छा तरीका एक DependencyProperty से सूचनाएं, एक और DependencyProperty बनाने के लिए है, जो overkill यहाँ की तरह लगता है यह बाध्य करने के लिए की कमी बदल पाने के लिए के बारे में पता नहीं है।

मैं अपने व्यूमोडेल को दृश्य के केवल पढ़ने योग्य गुणों में परिवर्तनों के बारे में सूचित कैसे रख सकता हूं?

उत्तर

129

हां, मैंने इसे अतीत में ActualWidth और ActualHeight गुणों के साथ किया है, जिनमें से दोनों केवल पढ़ने के लिए हैं। मैंने एक संलग्न व्यवहार बनाया है जिसमें ObservedWidth और ObservedHeight संलग्न गुण हैं। इसमें Observe संपत्ति भी है जिसका प्रारंभिक हुक-अप करने के लिए उपयोग किया जाता है। प्रयोग इस तरह दिखता है:

<UserControl ... 
    SizeObserver.Observe="True" 
    SizeObserver.ObservedWidth="{Binding Width, Mode=OneWayToSource}" 
    SizeObserver.ObservedHeight="{Binding Height, Mode=OneWayToSource}" 

तो दृश्य मॉडल Width और Height गुण है कि ObservedWidth और ObservedHeight संलग्न गुण के साथ सिंक में हमेशा से रहे हैं है। Observe संपत्ति FrameworkElement की घटना SizeChanged घटना से जुड़ी है। हैंडल में, यह ObservedWidth और ObservedHeight गुणों को अपडेट करता है। एर्गो, Width और Height व्यू मॉडल के ActualWidth और ActualHeightUserControl के साथ हमेशा समन्वयित होता है।

शायद नहीं सही समाधान (मैं सहमत हूँ - केवल पढ़ने के लिए ड्राफ्ट पैरा चाहिए समर्थन OneWayToSource बाइंडिंग), लेकिन यह काम करता है और यह MVVM पैटर्न की पुष्टि की। जाहिर है, ObservedWidth और ObservedHeight डीपी केवल पढ़ने के लिए नहीं हैं।

अद्यतन:

class SizeObserver 
{ 
    #region " Observe " 

    public static bool GetObserve(FrameworkElement elem) 
    { 
     return (bool)elem.GetValue(ObserveProperty); 
    } 

    public static void SetObserve(
     FrameworkElement elem, bool value) 
    { 
     elem.SetValue(ObserveProperty, value); 
    } 

    public static readonly DependencyProperty ObserveProperty = 
     DependencyProperty.RegisterAttached("Observe", typeof(bool), typeof(SizeObserver), 
     new UIPropertyMetadata(false, OnObserveChanged)); 

    static void OnObserveChanged(
     DependencyObject depObj, DependencyPropertyChangedEventArgs e) 
    { 
     FrameworkElement elem = depObj as FrameworkElement; 
     if (elem == null) 
      return; 

     if (e.NewValue is bool == false) 
      return; 

     if ((bool)e.NewValue) 
      elem.SizeChanged += OnSizeChanged; 
     else 
      elem.SizeChanged -= OnSizeChanged; 
    } 

    static void OnSizeChanged(object sender, RoutedEventArgs e) 
    { 
     if (!Object.ReferenceEquals(sender, e.OriginalSource)) 
      return; 

     FrameworkElement elem = e.OriginalSource as FrameworkElement; 
     if (elem != null) 
     { 
      SetObservedWidth(elem, elem.ActualWidth); 
      SetObservedHeight(elem, elem.ActualHeight); 
     } 
    } 

    #endregion 

    #region " ObservedWidth " 

    public static double GetObservedWidth(DependencyObject obj) 
    { 
     return (double)obj.GetValue(ObservedWidthProperty); 
    } 

    public static void SetObservedWidth(DependencyObject obj, double value) 
    { 
     obj.SetValue(ObservedWidthProperty, value); 
    } 

    // Using a DependencyProperty as the backing store for ObservedWidth. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty ObservedWidthProperty = 
     DependencyProperty.RegisterAttached("ObservedWidth", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0)); 

    #endregion 

    #region " ObservedHeight " 

    public static double GetObservedHeight(DependencyObject obj) 
    { 
     return (double)obj.GetValue(ObservedHeightProperty); 
    } 

    public static void SetObservedHeight(DependencyObject obj, double value) 
    { 
     obj.SetValue(ObservedHeightProperty, value); 
    } 

    // Using a DependencyProperty as the backing store for ObservedHeight. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty ObservedHeightProperty = 
     DependencyProperty.RegisterAttached("ObservedHeight", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0)); 

    #endregion 
} 

इसका इस्तेमाल करने के लिए स्वतंत्र महसूस:

public static class SizeObserver 
{ 
    public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached(
     "Observe", 
     typeof(bool), 
     typeof(SizeObserver), 
     new FrameworkPropertyMetadata(OnObserveChanged)); 

    public static readonly DependencyProperty ObservedWidthProperty = DependencyProperty.RegisterAttached(
     "ObservedWidth", 
     typeof(double), 
     typeof(SizeObserver)); 

    public static readonly DependencyProperty ObservedHeightProperty = DependencyProperty.RegisterAttached(
     "ObservedHeight", 
     typeof(double), 
     typeof(SizeObserver)); 

    public static bool GetObserve(FrameworkElement frameworkElement) 
    { 
     frameworkElement.AssertNotNull("frameworkElement"); 
     return (bool)frameworkElement.GetValue(ObserveProperty); 
    } 

    public static void SetObserve(FrameworkElement frameworkElement, bool observe) 
    { 
     frameworkElement.AssertNotNull("frameworkElement"); 
     frameworkElement.SetValue(ObserveProperty, observe); 
    } 

    public static double GetObservedWidth(FrameworkElement frameworkElement) 
    { 
     frameworkElement.AssertNotNull("frameworkElement"); 
     return (double)frameworkElement.GetValue(ObservedWidthProperty); 
    } 

    public static void SetObservedWidth(FrameworkElement frameworkElement, double observedWidth) 
    { 
     frameworkElement.AssertNotNull("frameworkElement"); 
     frameworkElement.SetValue(ObservedWidthProperty, observedWidth); 
    } 

    public static double GetObservedHeight(FrameworkElement frameworkElement) 
    { 
     frameworkElement.AssertNotNull("frameworkElement"); 
     return (double)frameworkElement.GetValue(ObservedHeightProperty); 
    } 

    public static void SetObservedHeight(FrameworkElement frameworkElement, double observedHeight) 
    { 
     frameworkElement.AssertNotNull("frameworkElement"); 
     frameworkElement.SetValue(ObservedHeightProperty, observedHeight); 
    } 

    private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) 
    { 
     var frameworkElement = (FrameworkElement)dependencyObject; 

     if ((bool)e.NewValue) 
     { 
      frameworkElement.SizeChanged += OnFrameworkElementSizeChanged; 
      UpdateObservedSizesForFrameworkElement(frameworkElement); 
     } 
     else 
     { 
      frameworkElement.SizeChanged -= OnFrameworkElementSizeChanged; 
     } 
    } 

    private static void OnFrameworkElementSizeChanged(object sender, SizeChangedEventArgs e) 
    { 
     UpdateObservedSizesForFrameworkElement((FrameworkElement)sender); 
    } 

    private static void UpdateObservedSizesForFrameworkElement(FrameworkElement frameworkElement) 
    { 
     // WPF 4.0 onwards 
     frameworkElement.SetCurrentValue(ObservedWidthProperty, frameworkElement.ActualWidth); 
     frameworkElement.SetCurrentValue(ObservedHeightProperty, frameworkElement.ActualHeight); 

     // WPF 3.5 and prior 
     ////SetObservedWidth(frameworkElement, frameworkElement.ActualWidth); 
     ////SetObservedHeight(frameworkElement, frameworkElement.ActualHeight); 
    } 
} 
+2

मुझे आश्चर्य है कि क्यों मैं OnSourceChanged विधि के लिए एक लाइन जोड़ा कि यदि आप निरीक्षण के बिना, गुणों को स्वचालित रूप से संलग्न करने के लिए कुछ चालबाजी कर सकते हैं। लेकिन यह एक अच्छा समाधान की तरह दिखता है। धन्यवाद! –

+1

धन्यवाद केंट। मैंने इस "आकार ऑब्सर्वर" वर्ग के लिए नीचे एक कोड नमूना पोस्ट किया है। इस भावना के लिए –

+42

+1: "केवल-पढ़ने वाले डीपी को OneWayToSource बाइंडिंग का समर्थन करना चाहिए" – Tristan

20

तो किसी और रुचि रखता है, मैं केंट के समाधान के एक सन्निकटन यहाँ कोडित: यहाँ कोड है कि ऊपर वर्णित कार्यक्षमता लागू करता है अपने ऐप्स में यह अच्छा काम करता है। (धन्यवाद केंट!)

49

मैं एक सार्वभौमिक समाधान का उपयोग करता हूं जो न केवल वास्तविक विड्थ और वास्तविकता के साथ काम करता है, बल्कि किसी भी डेटा के साथ आप कम से कम पढ़ने मोड में बाध्य कर सकते हैं।

मार्कअप इस तरह दिखता है, ViewportWidth और ViewportHeight प्रदान की दृश्य मॉडल

<Canvas> 
    <u:DataPiping.DataPipes> 
     <u:DataPipeCollection> 
      <u:DataPipe Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualWidth}" 
         Target="{Binding Path=ViewportWidth, Mode=OneWayToSource}"/> 
      <u:DataPipe Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualHeight}" 
         Target="{Binding Path=ViewportHeight, Mode=OneWayToSource}"/> 
      </u:DataPipeCollection> 
    </u:DataPiping.DataPipes> 
<Canvas> 

यहाँ के गुणों कस्टम तत्वों

public class DataPiping 
{ 
    #region DataPipes (Attached DependencyProperty) 

    public static readonly DependencyProperty DataPipesProperty = 
     DependencyProperty.RegisterAttached("DataPipes", 
     typeof(DataPipeCollection), 
     typeof(DataPiping), 
     new UIPropertyMetadata(null)); 

    public static void SetDataPipes(DependencyObject o, DataPipeCollection value) 
    { 
     o.SetValue(DataPipesProperty, value); 
    } 

    public static DataPipeCollection GetDataPipes(DependencyObject o) 
    { 
     return (DataPipeCollection)o.GetValue(DataPipesProperty); 
    } 

    #endregion 
} 

public class DataPipeCollection : FreezableCollection<DataPipe> 
{ 

} 

public class DataPipe : Freezable 
{ 
    #region Source (DependencyProperty) 

    public object Source 
    { 
     get { return (object)GetValue(SourceProperty); } 
     set { SetValue(SourceProperty, value); } 
    } 
    public static readonly DependencyProperty SourceProperty = 
     DependencyProperty.Register("Source", typeof(object), typeof(DataPipe), 
     new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnSourceChanged))); 

    private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     ((DataPipe)d).OnSourceChanged(e); 
    } 

    protected virtual void OnSourceChanged(DependencyPropertyChangedEventArgs e) 
    { 
     Target = e.NewValue; 
    } 

    #endregion 

    #region Target (DependencyProperty) 

    public object Target 
    { 
     get { return (object)GetValue(TargetProperty); } 
     set { SetValue(TargetProperty, value); } 
    } 
    public static readonly DependencyProperty TargetProperty = 
     DependencyProperty.Register("Target", typeof(object), typeof(DataPipe), 
     new FrameworkPropertyMetadata(null)); 

    #endregion 

    protected override Freezable CreateInstanceCore() 
    { 
     return new DataPipe(); 
    } 
} 
+0

(उपयोगकर्ता543564 से एक उत्तर के माध्यम से): यह उत्तर नहीं है लेकिन दिमित्री को एक टिप्पणी - मैंने आपके समाधान का उपयोग किया और यह बहुत अच्छा काम किया। अच्छा सार्वभौमिक समाधान जिसे विभिन्न स्थानों पर सामान्य रूप से उपयोग किया जा सकता है। मैंने इसे अपने व्यूमोडेल में कुछ यूई तत्व गुण (वास्तविक हाइट और एक्टुअलविड्थ) को धक्का देने के लिए उपयोग किया। –

+2

धन्यवाद! इससे मुझे सामान्य संपत्ति के लिए बाध्य करने में मदद मिली। दुर्भाग्य से संपत्ति ने INotifyPropertyChanged घटनाओं को प्रकाशित नहीं किया था। मैंने इसे डेटापाइप बाध्यकारी में एक नाम निर्दिष्ट करके हल किया और नियंत्रण में निम्नलिखित जोड़ों को जोड़ दिया: बाइंडिंगऑपरेशंस.गेटबाइंडिंग एक्सपेरेशनबेस (बाइंडिंगनाम, डेटापाइप। सोर्सप्रोपर्टी) .UpdateTarget(); – chilltemp

+3

इस समाधान ने मेरे लिए अच्छा काम किया। मेरा एकमात्र ट्विक टाइंड्सवोवोवेडफॉल्ट को फ़्रेमवर्कप्रॉपर्टी मेटाडेटा के लिए लक्ष्यप्रॉपर्टी निर्भरता प्रॉपर्टी पर सही स्थापित कर रहा था। –

9

के लिए स्रोत कोड रहे हैं यहाँ इस के लिए एक समाधान है " बग "जिसे मैंने यहां ब्लॉग किया था:
OneWayToSource Binding for ReadOnly Dependency Property

यह टी का उपयोग करके काम करता है डब्ल्यू निर्भरता गुण, श्रोता और मिरर। श्रोता लक्ष्यप्रॉपर्टी के लिए OneWay और PropertyChangedCallback में यह मिरर प्रॉपर्टी को अपडेट करता है जो बाध्यकारी में निर्दिष्ट किए गए किसी भी चीज़ को OneWayToSource को बाध्य करता है। मैं इसे PushBinding फोन और यह किसी भी केवल पढ़ने के लिए निर्भरता संपत्ति पर इस

<TextBlock Name="myTextBlock" 
      Background="LightBlue"> 
    <pb:PushBindingManager.PushBindings> 
     <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/> 
     <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/> 
    </pb:PushBindingManager.PushBindings> 
</TextBlock> 

Download Demo Project Here की तरह सेट किया जा सकता है।
इसमें स्रोत कोड और लघु नमूना उपयोग शामिल है, या यदि आप कार्यान्वयन विवरण में रुचि रखते हैं तो my WPF blog पर जाएं।

एक आखिरी टिप्पणी, .NET 4.0 के बाद से हम भी आगे निर्मित-समर्थन इस के लिए, एक OneWayToSource Binding reads the value back from the Source after it has updated it

4

के बाद से मैं दिमित्री Tashkinov के समाधान की तरह से दूर कर रहे हैं! हालांकि यह मेरे वीएस डिजाइन मोड में दुर्घटनाग्रस्त हो गया।

 
    private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     if (!((bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue)) 
      ((DataPipe)d).OnSourceChanged(e); 
    } 
0

मुझे लगता है कि यह थोड़ा सरल किया जा सकता है:

XAML:

behavior:ReadOnlyPropertyToModelBindingBehavior.ReadOnlyDependencyProperty="{Binding ActualWidth, RelativeSource={RelativeSource Self}}" 
behavior:ReadOnlyPropertyToModelBindingBehavior.ModelProperty="{Binding MyViewModelProperty}" 

सीएस:

public class ReadOnlyPropertyToModelBindingBehavior 
{ 
    public static readonly DependencyProperty ReadOnlyDependencyPropertyProperty = DependencyProperty.RegisterAttached(
    "ReadOnlyDependencyProperty", 
    typeof(object), 
    typeof(ReadOnlyPropertyToModelBindingBehavior), 
    new PropertyMetadata(OnReadOnlyDependencyPropertyPropertyChanged)); 

    public static void SetReadOnlyDependencyProperty(DependencyObject element, object value) 
    { 
    element.SetValue(ReadOnlyDependencyPropertyProperty, value); 
    } 

    public static object GetReadOnlyDependencyProperty(DependencyObject element) 
    { 
    return element.GetValue(ReadOnlyDependencyPropertyProperty); 
    } 

    private static void OnReadOnlyDependencyPropertyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
    { 
    SetModelProperty(obj, e.NewValue); 
    } 


    public static readonly DependencyProperty ModelPropertyProperty = DependencyProperty.RegisterAttached(
    "ModelProperty", 
    typeof(object), 
    typeof(ReadOnlyPropertyToModelBindingBehavior), 
    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); 

    public static void SetModelProperty(DependencyObject element, object value) 
    { 
    element.SetValue(ModelPropertyProperty, value); 
    } 

    public static object GetModelProperty(DependencyObject element) 
    { 
    return element.GetValue(ModelPropertyProperty); 
    } 
} 
+0

थोड़ा आसान हो सकता है, लेकिन अगर मैं इसे अच्छी तरह से पढ़ता हूं, तो यह तत्व पर केवल ** ** बाध्यकारी की अनुमति देता है। मेरा मतलब है, मुझे लगता है कि इस दृष्टिकोण के साथ, आप वास्तविक Width ** और ** ActualHeight दोनों को बाध्य करने में सक्षम नहीं होंगे। उनमें से सिर्फ एक। – quetzalcoatl

संबंधित मुद्दे