2012-01-11 19 views
29

में कोड में सेट मैं एक सूची बॉक्स के साथ एक XAML दृश्य के लिए ListBox:स्क्रॉल WPF SelectedItem एक दृश्य मॉडल

<control:ListBoxScroll ItemSource="{Binding Path=FooCollection}" 
         SelectedItem="{Binding SelectedFoo, Mode=TwoWay}" 
         ScrollSelectedItem="{Binding SelectedFoo}"> 
    <!-- data templates, etc. --> 
</control:ListBoxScroll> 

चयनित आइटम मेरे विचार में एक संपत्ति के लिए बाध्य है। जब उपयोगकर्ता सूची बॉक्स में कोई आइटम चुनता है तो दृश्य मॉडल में मेरी चुनीFoo संपत्ति अपडेट हो जाती है। जब मैं अपने दृश्य मॉडल में SelectedFoo प्रॉपर्टी सेट करता हूं तो सूची आइटम में सही आइटम चुना जाता है।

समस्या यह है कि यदि कोड में सेट किया गया चयनित फ़ूज़ वर्तमान में दृश्यमान नहीं है तो मुझे सूची बॉक्स पर ScrollIntoView को अतिरिक्त रूप से कॉल करने की आवश्यकता है। चूंकि मेरा लिस्टबॉक्स एक दृश्य के अंदर है और मेरा तर्क मेरे दृश्य मॉडल के अंदर है ... मुझे इसे करने का एक सुविधाजनक तरीका नहीं मिला।

class ListBoxScroll : ListBox 
{ 
    public static readonly DependencyProperty ScrollSelectedItemProperty = DependencyProperty.Register(
     "ScrollSelectedItem", 
     typeof(object), 
     typeof(ListBoxScroll), 
     new FrameworkPropertyMetadata(
      null, 
      FrameworkPropertyMetadataOptions.AffectsRender, 
      new PropertyChangedCallback(onScrollSelectedChanged))); 
    public object ScrollSelectedItem 
    { 
     get { return (object)GetValue(ScrollSelectedItemProperty); } 
     set { SetValue(ScrollSelectedItemProperty, value); } 
    } 

    private static void onScrollSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var listbox = d as ListBoxScroll; 
     listbox.ScrollIntoView(e.NewValue); 
    } 
} 

यह मूल रूप से एक नया निर्भरता संपत्ति ScrollSelectedItem जो मैं मेरे विचार मॉडल पर SelectedFoo संपत्ति के लिए बाध्य को उजागर करता है: तो मैं ListBoxScroll बढ़ाया। मैं फिर संपत्ति में हुक आश्रित संपत्ति के कॉलबैक बदल दिया और नए चुने हुए आइटम को देखने के लिए स्क्रॉल करें।

क्या किसी और को एक दृश्य मॉडल द्वारा समर्थित XAML दृश्य पर उपयोगकर्ता नियंत्रण पर फ़ंक्शंस कॉल करने का एक आसान तरीका पता है? यह करने के लिए चारों ओर एक रन का एक सा है:

  1. एक आश्रित संपत्ति
  2. बनाने संपत्ति के लिए एक कॉलबैक जोड़ने स्थिर कॉलबैक अंदर कॉलबैक बदल
  3. संभाल समारोह मंगलाचरण

यह अच्छा होगा ScrollSelectedItem { set { विधि में तर्क को सही करने के लिए, लेकिन निर्भरता ढांचा वास्तव में इसे कॉल किए बिना काम करने के लिए घूमता है और काम करता है।

+0

'चयनित इंडेक्स' सेट करना बहुत आसान होगा। –

+1

यह व्यूमोडेल के बजाय एक दृश्य "चिंता" जैसा लगता है। मुझे कुछ ऐसा करना पड़ा लेकिन मैंने दृश्य में कोड छोड़ा। Http://matthamilton.net/focus-a-virtualized-listboxitem –

+0

@MattHamilton देखें - यह कोड तकनीकी रूप से दृश्य (नियंत्रण में) है। एक दृश्य (कहीं भी) में आप कौन सा कोड लिखेंगे जो ScrollIntoView को कॉल करना पूरा करेगा? ध्यान रखें कि मैं चयनित इटैम पर सेट को ओवरराइड नहीं कर सकता क्योंकि यह वर्चुअल नहीं है। –

उत्तर

30

उत्तरों की समीक्षा करने के बाद एक आम विषय आया: बाहरी वर्ग सूची को सुनकर सूची बॉक्स की बदल गई घटना।

class ListBoxScroll : ListBox 
{ 
    public ListBoxScroll() : base() 
    { 
     SelectionChanged += new SelectionChangedEventHandler(ListBoxScroll_SelectionChanged); 
    } 

    void ListBoxScroll_SelectionChanged(object sender, SelectionChangedEventArgs e) 
    { 
     ScrollIntoView(SelectedItem); 
    } 
} 

मुझे लगता है यह सबसे सरल समाधान है करता है कि जो मैं चाहता: यह मुझे पता है कि निर्भर संपत्ति दृष्टिकोण overkill था और मैं बस उप-वर्ग खुद को सुनने के हो सकता था बनाया है।

माननीय उल्लेख व्यवहार को लाने के लिए adcool2007 पर जाता है। यहाँ उन दिलचस्पी के लिए लेख के एक जोड़े हैं:

http://blogs.msdn.com/b/johngossman/archive/2008/05/07/the-attached-behavior-pattern.aspx
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx

मैं सामान्य व्यवहार है कि कई अलग अलग उपयोगकर्ता नियंत्रण में जोड़ दिया जाएगा (जैसे व्यवहार क्लिक करें, खींचें व्यवहार, एनीमेशन व्यवहार, आदि) के लिए लगता है तो संलग्न व्यवहार बहुत समझ में आता है। कारण मैं इन विशेष मामलों में उनका उपयोग नहीं करना चाहता हूं यह है कि व्यवहार के कार्यान्वयन (ScrollIntoView पर कॉल करना) एक सामान्य क्रिया नहीं है जो किसी सूची बॉक्स के अलावा किसी भी नियंत्रण के साथ हो सकती है।

+0

सरल और प्रभावी, गहराई से तकनीकी नहीं होने के कारण कम ध्यान मिल सकता है लेकिन मेरा वोट मिला :) –

+0

+1 सरल होने के लिए और मिश्रण एसडीके पर निर्भर नहीं है, धन्यवाद! – kbo4sho88

+2

घटना से सदस्यता समाप्त करने के लिए मत भूलना: पी – adminSoftDK

12

क्योंकि यह कड़ाई से एक दृश्य समस्या है, इस कारण के लिए आपके दृश्य के पीछे कोड में ईवेंट हैंडलर नहीं होने का कोई कारण नहीं है। ListBox.SelectionChanged के लिए सुनो और नए चयनित आइटम को देखने के लिए स्क्रॉल करें।

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    ((ListBox)sender).ScrollIntoView(e.AddedItems[0]); 
} 

तुम भी एक व्युत्पन्न ListBox ऐसा करने की जरूरत नहीं है। बस एक मानक नियंत्रण का उपयोग करें और जब ListBox.SelectedItem मान परिवर्तन (जैसा कि आपके मूल प्रश्न में वर्णित है), उपरोक्त हैंडलर निष्पादित किया जाएगा और आइटम को दृश्य में स्क्रॉल किया जाएगा।

<ListBox 
     ItemsSource="{Binding Path=FooCollection}" 
     SelectedItem="{Binding Path=SelectedFoo}" 
     SelectionChanged="ListBox_SelectionChanged" 
     /> 

एक और दृष्टिकोण एक संलग्न संपत्ति है कि ICollectionView.CurrentChanged के लिए सुनता लिखने के लिए होगा और फिर नए वर्तमान आइटम के लिए ListBox.ScrollIntoView invokes। यदि आपको कई सूची बॉक्सों के लिए इस कार्यक्षमता की आवश्यकता है तो यह एक और "पुन: प्रयोज्य" दृष्टिकोण है। आपको प्रारंभ करने के लिए यहां एक अच्छा उदाहरण मिल सकता है: http://michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/

+0

स्क्रॉल नहीं करता है मुझे किसी ईवेंट को सुनने में कोई फर्क नहीं पड़ता है लेकिन गतिविधि "चयनित आइटम पर स्क्रॉल करें" वास्तव में ऐसा लगता है कि यह नियंत्रण का हिस्सा है और आस-पास के दृश्य नहीं। मैं बस अपने ListBoxScroll सबक्लास के अंदर इवेंट श्रोता लिख ​​सकता था - और यह मुझे एक निर्भर संपत्ति कॉलबैक से एक क्लीनर इवेंट प्रतिनिधि में दूर ले जाएगा। –

+0

मुझे यह दृष्टिकोण पसंद है लेकिन यदि आपके पास कई सूची बॉक्स हैं, तो यह थोड़ा बोझिल है, मैं इस कार्यक्षमता को ले सकता हूं और अपना खुद का सूची बॉक्स प्राप्त कर सकता हूं। मैं मानता हूं कि यह सख्ती से एक दृश्य समस्या है और कोड दृश्य में होना ठीक है ... –

+1

@AdriaanDavel बस सभी सूची बॉक्स चयन को बाध्य करें एक ही हैंडलर में बदलें, या शायद आप पहले से ही ऐसा कर रहे हैं, और बस इसके बारे में बात कर रहे हैं सभी घटना हैंडलर सदस्यता बयान बोझिल हो रहा है? – Zack

43

क्या आपने व्यवहार का उपयोग करने का प्रयास किया है ... यहां एक स्क्रॉल इन व्यूअर है। मैंने इसे सूची दृश्य और डेटाग्रिड के लिए उपयोग किया है ..... मुझे लगता है कि इसे ListBox के लिए काम करना चाहिए ......

आप के रूप xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

अपने Control

में तो

System.Windows.Interactivity के लिए एक संदर्भ जोड़ने के लिए XAML को Behavior<T> class

व्यवहार

public class ScrollIntoViewForListBox : Behavior<ListBox> 
{ 
    /// <summary> 
    /// When Beahvior is attached 
    /// </summary> 
    protected override void OnAttached() 
    { 
     base.OnAttached(); 
     this.AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged; 
    } 

    /// <summary> 
    /// On Selection Changed 
    /// </summary> 
    /// <param name="sender"></param> 
    /// <param name="e"></param> 
    void AssociatedObject_SelectionChanged(object sender, 
              SelectionChangedEventArgs e) 
    { 
     if (sender is ListBox) 
     { 
      ListBox listBox = (sender as ListBox); 
      if (listBox .SelectedItem != null) 
      { 
       listBox.Dispatcher.BeginInvoke(
        (Action) (() => 
            { 
             listBox.UpdateLayout(); 
             if (listBox.SelectedItem != 
              null) 
              listBox.ScrollIntoView(
               listBox.SelectedItem); 
            })); 
      } 
     } 
    } 
    /// <summary> 
    /// When behavior is detached 
    /// </summary> 
    protected override void OnDetaching() 
    { 
     base.OnDetaching(); 
     this.AssociatedObject.SelectionChanged -= 
      AssociatedObject_SelectionChanged; 

    } 
} 

प्रयोग

अन्य नाम जोड़ें उपयोग करने के लिए

 <ListBox ItemsSource="{Binding Path=MyList}" 
        SelectedItem="{Binding Path=MyItem, 
             Mode=TwoWay}" 
        SelectionMode="Single"> 
      <i:Interaction.Behaviors> 
       <Behaviors:ScrollIntoViewForListBox /> 
      </i:Interaction.Behaviors> 
     </ListBox> 

अब जब "MyItem" प्रॉपर्टी ViewModel में सेट की जाती है तो परिवर्तनों को फिर से चुनने पर सूची स्क्रॉल की जाएगी।

+0

यह वास्तव में बहुत अच्छा है। यह crazyarabian द्वारा वर्णित संलग्न संपत्ति दृष्टिकोण के समान है लेकिन यह काफी साफ क्लीनर लगता है। उनके संलग्न संपत्ति समाधान और आपके व्यवहार समाधान दोनों के साथ मुझे एक नई कक्षा लिखनी होगी। ऐसा लगता है कि लिस्टबॉक्स नियंत्रण को विस्तारित करना और उप-वर्ग में ईवेंट हैंडलिंग करना अधिक समझ में आता है। मैं आज कोशिश कर रहा हूं और देख रहा हूं कि यह आपके व्यवहार समाधान के रूप में साफ है या नहीं। –

+0

@JamesFassett Behavoir आमतौर पर उस कार्यक्षमता को नियंत्रित करने के लिए उपयोग किया जाता है जो वर्तमान में नहीं है .... आईई .... नियंत्रण को बढ़ाने से बचने के लिए .... लेकिन मेरा मानना ​​है कि विस्तार करना उतना ही अच्छा है .. – Ankesh

+0

बस एक्सप्रेस संस्करण का उपयोग कर। क्या इस समाधान को मिश्रण की आवश्यकता है? – paul

10

मुझे पता है कि यह एक पुराना सवाल है, लेकिन मेरी हाल की खोज ने मुझे इस समस्या के लिए लाया है। मैं व्यवहार दृष्टिकोण का उपयोग करना चाहते थे लेकिन ब्लेंड एसडीके पर निर्भरता सिर्फ मुझे देने के लिए नहीं करना चाहता था Behavior<T> इसलिए यहाँ यह बिना मेरे समाधान है:

public static class ListBoxBehavior 
{ 
    public static bool GetScrollSelectedIntoView(ListBox listBox) 
    { 
     return (bool)listBox.GetValue(ScrollSelectedIntoViewProperty); 
    } 

    public static void SetScrollSelectedIntoView(ListBox listBox, bool value) 
    { 
     listBox.SetValue(ScrollSelectedIntoViewProperty, value); 
    } 

    public static readonly DependencyProperty ScrollSelectedIntoViewProperty = 
     DependencyProperty.RegisterAttached("ScrollSelectedIntoView", typeof (bool), typeof (ListBoxBehavior), 
              new UIPropertyMetadata(false, OnScrollSelectedIntoViewChanged)); 

    private static void OnScrollSelectedIntoViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var selector = d as Selector; 
     if (selector == null) return; 

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

     if ((bool) e.NewValue) 
     { 
      selector.AddHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler)); 
     } 
     else 
     { 
      selector.RemoveHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler)); 
     } 
    } 

    private static void ListBoxSelectionChangedHandler(object sender, RoutedEventArgs e) 
    { 
     if (!(sender is ListBox)) return; 

     var listBox = (sender as ListBox); 
     if (listBox.SelectedItem != null) 
     { 
      listBox.Dispatcher.BeginInvoke(
       (Action)(() => 
        { 
         listBox.UpdateLayout(); 
         if (listBox.SelectedItem !=null) 
          listBox.ScrollIntoView(listBox.SelectedItem); 
        })); 
     } 
    } 
} 

और फिर उपयोग सिर्फ

<ListBox ItemsSource="{Binding Path=MyList}" 
     SelectedItem="{Binding Path=MyItem, Mode=TwoWay}" 
     SelectionMode="Single" 
     behaviors:ListBoxBehavior.ScrollSelectedIntoView="True"> 
+0

बहुत अच्छी तरह से काम करता है, मैं भी उस निर्भरता में खींच नहीं करना पसंद करता हूं। – angularsen

+0

क्या आपको 'if (e.NewValue bool == false) 'कोड नहीं हटाया जाना चाहिए? यदि 'e.NewValue' गलत हो जाता है, तो हैंडलर को हटाया जाना चाहिए? – Ziriax

+0

@Ziriax यह एक परीक्षण है कि e.NewValue का प्रकार बूलियन है, यह नहीं कि यह झूठा है। – Dutts

7

है इस प्रयास करें:

private void lstBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    lstBox.ScrollIntoView(lstBox.SelectedItem); 
} 
+0

यह एक समाधान है, लेकिन मुझे लगता है कि सवाल एमवीवीएम पैटर्न के साथ संगत समाधान चाहता था। – user672951

+2

@ user672951 यह एमवीवीएम पैटर्न को इस प्रश्न में अन्य समाधानों के जितना ही फिट करता है। – Zack

2

विभिन्न तरीकों बांधने के बाद मैंने पाया सबसे आसान और सबसे अच्छा होना करने के लिए निम्नलिखित

lstbox.Items.MoveCurrentToLast(); 
lstbox.ScrollIntoView(lstbox.Items.CurrentItem); 
0

मैंने अंकेश के जवाब को लिया और इसे मिश्रण एसडीके पर निर्भर नहीं किया। मेरे समाधान का नकारात्मक पक्ष यह है कि यह आपके ऐप के सभी सूची बॉक्स पर लागू होगा। लेकिन उल्टा कोई कस्टम वर्ग की आवश्यकता नहीं है।

जब अपने अनुप्रयोग आरंभ कर रहा है ...

internal static void RegisterFrameworkExtensionEvents() 
    { 
     EventManager.RegisterClassHandler(typeof(ListBox), ListBox.SelectionChangedEvent, new RoutedEventHandler(ScrollToSelectedItem)); 
    } 

    //avoid "async void" unless used in event handlers (or logical equivalent) 
    private static async void ScrollToSelectedItem(object sender, RoutedEventArgs e) 
    { 
     if (sender is ListBox) 
     { 
      var lb = sender as ListBox; 
      if (lb.SelectedItem != null) 
      { 
       await lb.Dispatcher.BeginInvoke((Action)delegate 
       { 
        lb.UpdateLayout(); 
        if (lb.SelectedItem != null) 
         lb.ScrollIntoView(lb.SelectedItem); 
       }); 
      } 
     } 
    } 

यह आपके listboxes के सभी चयनित करने के लिए स्क्रॉल (जो मैं एक डिफ़ॉल्ट व्यवहार के रूप की तरह) में आता है।

5

मैं (मेरी राय में) इस का उपयोग कर रहा स्पष्ट और आसान समाधान

listView.SelectionChanged += (s, e) => 
    listView.ScrollIntoView(listView.SelectedItem); 

जहां listView है XAML में ListView नियंत्रण के नाम पर, SelectedItem मेरी MVVM और कोड से प्रभावित होता है में निर्माता में डाला जाता है xaml.cs फ़ाइल।

+0

अच्छा धन्यवाद :) सर – Peter

+0

@ आपका स्वागत है। – honzakuzel1989

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