2011-09-09 15 views
10

मैं है एक WPF ListView नियंत्रण, ItemsSource एक ICollectionView पर सेट है इस तरह से बनाया के बाद काम नहीं करते प्रकार। ListView को प्रत्येक आइटम के लिए, जटिल प्रकार पर केवल एक स्ट्रिंग प्रॉपर्टी प्रदर्शित करने के लिए कॉन्फ़िगर किया गया है।तीर कुंजी प्रोग्राम के रूप में स्थापित करने ListView.SelectedItem

उपयोगकर्ता सूची दृश्य को रीफ्रेश कर सकता है, जिस बिंदु पर मेरा तर्क वर्तमान में चयनित आइटम के लिए "कुंजी स्ट्रिंग" संग्रहीत करता है, अंतर्निहित पर्यवेक्षण चयन को फिर से पॉप्युलेट करता है। पिछला सॉर्ट और फ़िल्टर तब संग्रह दृश्य पर लागू होता है। इस बिंदु पर मैं उस आइटम को "फिर से चुनना" चाहता हूं जिसे रीफ्रेश करने के अनुरोध से पहले चुना गया था। अवलोकन योग्य में आइटम नए उदाहरण हैं, इसलिए मैं संबंधित स्ट्रिंग गुणों की तुलना करता हूं और फिर मिलान करने वाले किसी एक को चुनें। इस तरह:

private void SelectThisItem(string value) 
{ 
    foreach (var item in collectionView) // for the ListView in question 
    { 
     var thing = item as MyComplexType; 
     if (thing.StringProperty == value) 
     { 
      this.listView1.SelectedItem = thing; 
      return; 
     } 
    } 
} 

यह सब काम करता है। यदि चौथा आइटम चुना जाता है, और उपयोगकर्ता F5 दबाता है, तो सूची का पुनर्गठन किया जाता है और फिर उसी स्ट्रिंग प्रॉपर्टी वाला आइटम पिछले 4 वें आइटम के रूप में चुना जाता है। कभी-कभी यह नया चौथा आइटम होता है, कभी-कभी नहीं, लेकिन यह "least astonishment behavior" प्रदान करता है।

समस्या तब आती है जब उपयोगकर्ता बाद में सूची दृश्य के माध्यम से नेविगेट करने के लिए तीर कुंजियों का उपयोग करता है। ताज़ा करने के बाद पहला ऊपर या नीचे तीर (नए) सूचीदृश्य में पहला आइटम चुना जाता है, भले ही पिछले आइटम द्वारा कौन सा आइटम चुना गया हो। अपेक्षित के रूप में कोई और तीर कुंजी काम करते हैं।

ऐसा क्यों हो रहा है?

यह सुंदर स्पष्ट रूप से "कम से कम आश्चर्य" नियम का उल्लंघन करता है। मैं इससे कैसे बच सकता हूं?


संपादित
आगे खोज करने पर, यह एक ही विसंगति अनुत्तरित
WPF ListView arrow navigation and keystroke problem द्वारा वर्णित की तरह लगता है, सिवाय इसके कि मैं और अधिक विस्तार प्रदान करते हैं।

उत्तर

15

ऐसा लगता है कि यह a sort of known but not-well-described problematic behavior with ListView (और शायद कुछ अन्य WPF नियंत्रण) के कारण है। यह आवश्यक है कि चयनित सूची ViewItem पर प्रोग्राम्सेटिक रूप से चयनित इटैम सेट करने के बाद, एक ऐप Focus() पर कॉल करें।

लेकिन चयनित इटिम स्वयं UIElement नहीं है। यह ListView में जो कुछ भी आप प्रदर्शित कर रहे हैं उसका एक आइटम है, अक्सर एक कस्टम प्रकार। इसलिए आप this.listView1.SelectedItem.Focus() पर कॉल नहीं कर सकते हैं। वह काम नहीं करेगा। आपको UIElement (या नियंत्रण) प्राप्त करने की आवश्यकता है जो उस विशेष आइटम को प्रदर्शित करता है। ItemContainerGenerator नामक WPF इंटरफ़ेस का एक गहरा कोना है, जो माना जाता है कि आपको उस सूची को प्राप्त करने देता है जो ListView में किसी विशेष आइटम को प्रदर्शित करता है।

कुछ इस तरह:

this.listView1.SelectedItem = thing; 
// *** WILL NOT WORK! 
((UIElement)this.listView1.ItemContainerGenerator.ContainerFromItem(thing)).Focus(); 

लेकिन वहाँ भी है कि के साथ एक दूसरी समस्या - यह सही SelectedItem सेट करने के बाद काम नहीं करता। ItemContainerGenerator.ContainerFromItem() हमेशा शून्य लौटता प्रतीत होता है। गुगलस्पेस में कहीं और लोगों ने ग्रुप स्टाइल सेट के साथ वापस लौटने के रूप में इसकी सूचना दी है। लेकिन यह समूह के बिना, मेरे साथ इस व्यवहार का प्रदर्शन किया।

ItemContainerGenerator.ContainerFromItem() सूची में प्रदर्शित होने वाली सभी वस्तुओं के लिए शून्य लौट रहा है। ItemContainerGenerator.ContainerFromIndex() सभी इंडस्ट्रीज के लिए शून्य वापस आती है। ListView प्रस्तुत किए जाने के बाद ही कुछ चीजों को कॉल करना आवश्यक है (या कुछ)।

मैंने इसे सीधे Dispatcher.BeginInvoke() के माध्यम से करने की कोशिश की लेकिन यह या तो काम नहीं करता है।

कुछ अन्य धागे के सुझाव पर, मैंने StatusChanged घटना के भीतर ItemContainerGenerator पर Dispatcher.BeginInvoke() का उपयोग किया। हाँ, सरल हुह? (नहीं)

यहां कोड कैसा दिखता है।

MyComplexType current; 

private void SelectThisItem(string value) 
{ 
    foreach (var item in collectionView) // for the ListView in question 
    { 
     var thing = item as MyComplexType; 
     if (thing.StringProperty == value) 
     { 
      this.listView1.ItemContainerGenerator.StatusChanged += icg_StatusChanged; 
      this.listView1.SelectedItem = thing; 
      current = thing; 
      return; 
     } 
    } 
} 


void icg_StatusChanged(object sender, EventArgs e) 
{ 
    if (this.listView1.ItemContainerGenerator.Status 
     == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated) 
    { 
     this.listView1.ItemContainerGenerator.StatusChanged 
      -= icg_StatusChanged; 
     Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input, 
           new Action(()=> { 
             var uielt = (UIElement)this.listView1.ItemContainerGenerator.ContainerFromItem(current); 
             uielt.Focus();})); 

    } 
} 

यह कुछ बदसूरत कोड है। लेकिन, प्रोग्रामेटिक रूप से चयनित इटैम को सेट करने से इस प्रकार सूची दृश्य में काम करने के लिए बाद में तीर नेविगेशन की अनुमति मिलती है।

+0

मेरे पास समान, requring dispatch.begininvokes है जो बाद में "refocus" होगा। जवाब के रूप में खुद को चिह्नित करने के लिए मत भूलना! –

+0

मैं इसे उत्तर के रूप में स्वीकार करना चाहता हूं, लेकिन मुझे लगता है कि मैं अभी तक नहीं कर सकता - एक टाइमर है। – Cheeso

+0

बदसूरत, और यह भी विश्वसनीय नहीं है। एक संक्षिप्त पल के लिए, आइटम को फिर से केंद्रित करने से पहले, सूची बॉक्स केंद्रित है (आप फोकस आयताकार देख सकते हैं)। उस पल में तीर कुंजी दबाते समय, आपका चयन समाप्त हो गया है। – ygoe

0

किसी आइटम को प्रोग्रामेटिक रूप से चुनना इसे कीबोर्ड फोकस नहीं देता है। आपको यह स्पष्ट रूप से करना है ... ((Control)listView1.SelectedItem).Focus()

+1

धन्यवाद। वह फेंकता है। ListView में प्रदर्शित आइटम एक नियंत्रण नहीं है इसलिए कलाकार फेंकता है। मैंने 'this.listView1.Focus() 'भी कोशिश की, जो फेंक नहीं देता है, लेकिन इससे कोई फर्क नहीं पड़ता कि मैं देख सकता हूं। – Cheeso

0

Cheeso, में अपने previous answer आप ने कहा:

लेकिन वहाँ भी है कि के साथ एक दूसरी समस्या - यह SelectedItem सेट करने के बाद सही काम नहीं करता। ItemContainerGenerator.ContainerFromItem() हमेशा शून्य वापस लौटता प्रतीत होता है।

इसका एक आसान समाधान चयनित इटैम को बिल्कुल सेट नहीं करना है। जब आप तत्व पर ध्यान केंद्रित करेंगे तो यह स्वचालित रूप से तब होगा। तो बस निम्नलिखित लाइन बुला करना होगा:

((UIElement)this.listView1.ItemContainerGenerator.ContainerFromItem(thing)).Focus(); 
+0

मुझे लगता है कि मैंने कोशिश की, और यह काम नहीं किया। किसी भी मामले में अब यह एक बहुत समय है, और मैं इसे पिछले कर रहा हूँ। हालांकि किसी भी समस्या का सामना करने वाले किसी के लिए उपयोगी हो सकता है। – Cheeso

0

यह सब एक छोटा सा घुसपैठ लगता है ... मैं तर्क अपने आप को फिर से लिखने के साथ चला गया:

public class CustomListView : ListView 
{ 
      protected override void OnPreviewKeyDown(KeyEventArgs e) 
      { 
       // Override the default, sloppy behavior of key up and down events that are broken in WPF's ListView control. 
       if (e.Key == Key.Up) 
       { 
        e.Handled = true; 
        if (SelectedItems.Count > 0) 
        { 
         int indexToSelect = Items.IndexOf(SelectedItems[0]) - 1; 
         if (indexToSelect >= 0) 
         { 
          SelectedItem = Items[indexToSelect]; 
          ScrollIntoView(SelectedItem); 
         } 
        } 
       } 
       else if (e.Key == Key.Down) 
       { 
        e.Handled = true; 
        if (SelectedItems.Count > 0) 
        { 
         int indexToSelect = Items.IndexOf(SelectedItems[SelectedItems.Count - 1]) + 1; 
         if (indexToSelect < Items.Count) 
         { 
          SelectedItem = Items[indexToSelect]; 
          ScrollIntoView(SelectedItem); 
         } 
        } 
       } 
       else 
       { 
        base.OnPreviewKeyDown(e); 
       } 
      } 
} 
0

एक बहुत एक लगभग नगण्य मैं नहीं कर सके 'के बाद इसे एमवीवीएम में काम करने के लिए नहीं मिलता है। मैंने इसे स्वयं जाने दिया और निर्भरता प्रॉपर्टी का उपयोग किया। यह मेरे लिए बहुत अच्छा काम किया। XAML

में
<ListBox IsSynchronizedWithCurrentItem="True" extenders:ListBoxExtenders.AutoScrollToCurrentItem="True" ..../> 
3

मैं एक ListBox नियंत्रण के साथ इस समस्या हो रही थी (जो कि कैसे मैं इस तो सवाल खोजने समाप्त हो गया)

public class ListBoxExtenders : DependencyObject 
{ 
    public static readonly DependencyProperty AutoScrollToCurrentItemProperty = DependencyProperty.RegisterAttached("AutoScrollToCurrentItem", typeof(bool), typeof(ListBoxExtenders), new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged)); 

    public static bool GetAutoScrollToCurrentItem(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(AutoScrollToSelectedItemProperty); 
    } 

    public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value) 
    { 
     obj.SetValue(AutoScrollToSelectedItemProperty, value); 
    } 

    public static void OnAutoScrollToCurrentItemChanged(DependencyObject s, DependencyPropertyChangedEventArgs e) 
    { 
     var listBox = s as ListBox; 
     if (listBox != null) 
     { 
      var listBoxItems = listBox.Items; 
      if (listBoxItems != null) 
      { 
       var newValue = (bool)e.NewValue; 

       var autoScrollToCurrentItemWorker = new EventHandler((s1, e2) => OnAutoScrollToCurrentItem(listBox, listBox.Items.CurrentPosition)); 

       if (newValue) 
        listBoxItems.CurrentChanged += autoScrollToCurrentItemWorker; 
       else 
        listBoxItems.CurrentChanged -= autoScrollToCurrentItemWorker; 
      } 
     } 
    } 

    public static void OnAutoScrollToCurrentItem(ListBox listBox, int index) 
    { 
     if (listBox != null && listBox.Items != null && listBox.Items.Count > index && index >= 0) 
      listBox.ScrollIntoView(listBox.Items[index]); 
    } 

} 

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

स्वीकार किए जाते हैं जवाब में दी गई जानकारी के आधार पर, मैं ListBox के निम्नलिखित उपवर्ग के साथ उसके चारों ओर काम करने में सक्षम था:

internal class KeyboardNavigableListBox : ListBox 
{ 
    protected override void OnSelectionChanged(SelectionChangedEventArgs e) 
    { 
     base.OnSelectionChanged(e); 

     var container = (UIElement) ItemContainerGenerator.ContainerFromItem(SelectedItem); 

     if(container != null) 
     { 
      container.Focus(); 
     } 
    } 
} 

आशा इस मदद करता है किसी को कुछ समय बचाने के।

1

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

public MainWindow() 
    { 
     ... 
     this.ListView.PreviewKeyDown += this.ListView_PreviewKeyDown; 
    } 

    private void ListView_PreviewKeyDown(object sender, KeyEventArgs e) 
    { 
     UIElement selectedElement = (UIElement)this.ListView.ItemContainerGenerator.ContainerFromItem(this.ListView.SelectedItem); 
     if (selectedElement != null) 
     { 
      selectedElement.Focus(); 
     } 

     e.Handled = false; 
    } 

यह केवल यह सुनिश्चित करें कि सही फोकस दे WPF कुंजी दबाने संभाल से पहले सेट कर दिया जाता बनाता

0

यह निर्दिष्ट करने प्राथमिकता के आधार पर यह पाने के बाद BeginInvoke साथ एक आइटम ध्यान केंद्रित करने के लिए संभव है:

Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => 
{ 
    var lbi = AssociatedObject.ItemContainerGenerator.ContainerFromIndex(existing) as ListBoxItem; 
    lbi.Focus(); 
})); 
0

Cheeso के समाधान मेरे लिए काम करता है। ऐसा करने के लिए timer.tick सेट करके null अपवाद को रोकें, इसलिए आपने अपना मूल दिनचर्या छोड़ा है।

var uiel = (UIElement)this.lv1.ItemContainerGenerator       
      .ContainerFromItem(lv1.Items[ix]); 
if (uiel != null) uiel.Focus(); 

समस्या हल जब एक RemoveAt/Insert के बाद टाइमर बुला, और भी Window.Loaded पर ध्यान केंद्रित करने की स्थापना की और पहले आइटम के लिए चयन करने के लिए।

एसई में मिली बहुत प्रेरणा और समाधान के लिए यह पहला पोस्ट वापस देना चाहता था। हैप्पी कोडिंग!

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