2016-02-01 9 views
6

एमवीवीएम का उपयोग करते समय हम दृश्य का निपटान कर रहे हैं (जबकि व्यूमोडेल बनी रहती है)।सूची पुनर्स्थापित करें स्थिति देखें एमवीवीएम

मेरा प्रश्न यह है कि ListView स्थिति को पुनर्स्थापित कैसे करें जब दृश्य को डिस्प्ले किए जाने पर एक से अधिक नए दृश्य को बनाते समय बंद करें?

ScrollIntoView केवल आंशिक रूप से काम करता है। मैं केवल एक ही आइटम पर स्क्रॉल कर सकता हूं और यह ऊपर या नीचे हो सकता है, इस पर कोई नियंत्रण नहीं है कि दृश्य में आइटम कहां दिखाई देगा।

मेरे पास multi-selection (और क्षैतिज स्क्रॉल-बार है, लेकिन यह अपेक्षाकृत महत्वहीन है) और कोई भी कई आइटम चुन सकता है और शायद आगे स्क्रॉल कर सकता है (चयन को बदलने के बिना)।

आदर्श रूप में ListView की ScrollViewer गुण बंधन करना होगा ViewModel करने के लिए है, लेकिन मुझे लगता है कि सीधे के लिए पूछ XY समस्या के अंतर्गत आते हैं के लिए डर लग रहा है (यकीन नहीं करता है, तो this भी लागू होता है)। इसके अलावा यह मुझे wpf के लिए एक बहुत ही आम बात प्रतीत होता है, लेकिन शायद मैं Google क्वेरी को ठीक तरह से तैयार करने में विफल रहता हूं क्योंकि मुझे संबंधित ListView + ScrollViewer + MVVM कॉम्बो नहीं मिल रहा है।

क्या यह संभव है?


मैं नहीं बल्कि बदसूरत समाधान के साथ ScrollIntoView और डेटा टेम्पलेट्स (MVVM) के साथ समस्या है। ListViewScrollIntoView के साथ राज्य को पुनर्स्थापित करना गलत लगता है। एक और तरीका होना चाहिए। आज Google मुझे अपने स्वयं के अनुत्तरित प्रश्न पर ले जाता है।


मैं ListView स्थिति को पुनर्स्थापित करने के लिए एक समाधान की तलाश में हूं। mcve के रूप में निम्नलिखित पर विचार करें:

public class ViewModel 
{ 
    public class Item 
    { 
     public string Text { get; set; } 
     public bool IsSelected { get; set; } 

     public static implicit operator Item(string text) => new Item() { Text = text }; 
    } 

    public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item> 
    { 
     "Item 1", 
     "Item 2", 
     "Item 3 long enough to use horizontal scroll", 
     "Item 4", 
     "Item 5", 
     new Item {Text = "Item 6", IsSelected = true }, // select something 
     "Item 7", 
     "Item 8", 
     "Item 9", 
    }; 
} 

public partial class MainWindow : Window 
{ 
    ViewModel _vm = new ViewModel(); 

    public MainWindow() 
    { 
     InitializeComponent(); 
    } 

    void Button_Click(object sender, RoutedEventArgs e) => DataContext = DataContext == null ? _vm : null; 
} 

XAML:

<StackPanel> 
    <ContentControl Content="{Binding}"> 
     <ContentControl.Resources> 
      <DataTemplate DataType="{x:Type local:ViewModel}"> 
       <ListView Width="100" Height="100" ItemsSource="{Binding Items}"> 
        <ListView.ItemTemplate> 
         <DataTemplate> 
          <TextBlock Text="{Binding Text}" /> 
         </DataTemplate> 
        </ListView.ItemTemplate> 
        <ListView.ItemContainerStyle> 
         <Style TargetType="ListViewItem"> 
          <Setter Property="IsSelected" Value="{Binding IsSelected}" /> 
         </Style> 
        </ListView.ItemContainerStyle> 
       </ListView> 
      </DataTemplate> 
     </ContentControl.Resources> 
    </ContentControl> 
    <Button Content="Click" 
      Click="Button_Click" /> 
</StackPanel> 

यह कौन-सी सामग्री DataContext के लिए बाध्य है ContentControl के साथ एक खिड़की (बटन द्वारा चालू किए जाने या तो null या ViewModel उदाहरण होने के लिए) है।

मैंने IsSelected समर्थन जोड़ा है (कुछ आइटम चुनने का प्रयास करें, छुपाएं/दिखाएं ListView इसे पुनर्स्थापित करेगा)।

उद्देश्य है: शो ListView, स्क्रॉल (यह 100x100 आकार है, तो उस सामग्री को बड़ा है) खड़ी और/या क्षैतिज रूप से, क्लिक करें छिपाने के लिए बटन पर क्लिक करें बटन को दिखाने के लिए और इस समय ListView में अपने राज्य को बहाल करना चाहिए (अर्थात् स्थिति ScrollViewer)।

उत्तर

3

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

आप व्यावहारिक एमवीवीएम दृष्टिकोण ले सकते हैं और इसे व्यूमोडेल पर संग्रहीत कर सकते हैं जैसा कि यहां दिखाया गया है: WPF & MVVM: Save ScrollViewer Postion And Set When Reloading। यदि आवश्यक हो तो इसे संभवतः पुन: प्रयोज्यता के लिए संलग्न संपत्ति/व्यवहार से सजाया जा सकता है।

वैकल्पिक रूप से आप पूरी तरह से MVVM ध्यान न दें और दृश्य पक्ष पर यह पूरी तरह से रख सकता है:

दृश्य:

<Window x:Class="RestorableView.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:RestorableView" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="350" Width="525"> 
    <Grid> 
     <Grid> 
      <Grid.RowDefinitions> 
       <RowDefinition/> 
       <RowDefinition Height="Auto"/> 
      </Grid.RowDefinitions> 
      <ListView x:Name="list" ItemsSource="{Binding Items}" ScrollViewer.HorizontalScrollBarVisibility="Auto"> 
       <ListView.ItemTemplate> 
        <DataTemplate> 
         <TextBlock Text="{Binding Text}" /> 
        </DataTemplate> 
       </ListView.ItemTemplate> 
       <ListView.ItemContainerStyle> 
        <Style TargetType="ListViewItem"> 
         <Setter Property="IsSelected" Value="{Binding IsSelected}" /> 
        </Style> 
       </ListView.ItemContainerStyle> 
      </ListView> 
      <StackPanel Orientation="Horizontal" Grid.Row="1"> 
       <Button Content="MVVM Based" x:Name="MvvmBased" Click="MvvmBased_OnClick"/> 
       <Button Content="View Based" x:Name="ViewBased" Click="ViewBased_OnClick" /> 
      </StackPanel> 
     </Grid> 
    </Grid> 
</Window> 

code- अपने कोड के आधार पर अपडेट किया गया नमूना:

संपादित करें पीछे एमवीवीएम और केवल-देखने के दृष्टिकोण को चित्रित करने के लिए दो बटन हैं

public partial class MainWindow : Window 
{ 
    ViewModel _vm = new ViewModel(); 

    public MainWindow() 
    { 
     InitializeComponent(); 
    } 

    private void MvvmBased_OnClick(object sender, RoutedEventArgs e) 
    { 
     var scrollViewer = list.GetChildOfType<ScrollViewer>(); 
     if (DataContext != null) 
     { 
      _vm.VerticalOffset = scrollViewer.VerticalOffset; 
      _vm.HorizontalOffset = scrollViewer.HorizontalOffset; 
      DataContext = null; 
     } 
     else 
     { 
      scrollViewer.ScrollToVerticalOffset(_vm.VerticalOffset); 
      scrollViewer.ScrollToHorizontalOffset(_vm.HorizontalOffset); 
      DataContext = _vm; 
     } 
    } 

    private void ViewBased_OnClick(object sender, RoutedEventArgs e) 
    { 
     var scrollViewer = list.GetChildOfType<ScrollViewer>(); 
     if (DataContext != null) 
     { 
      View.State[typeof(MainWindow)] = new Dictionary<string, object>() 
      { 
       { "ScrollViewer_VerticalOffset", scrollViewer.VerticalOffset }, 
       { "ScrollViewer_HorizontalOffset", scrollViewer.HorizontalOffset }, 
       // Additional fields here 
      }; 
      DataContext = null; 
     } 
     else 
     { 
      var persisted = View.State[typeof(MainWindow)]; 
      if (persisted != null) 
      { 
       scrollViewer.ScrollToVerticalOffset((double)persisted["ScrollViewer_VerticalOffset"]); 
       scrollViewer.ScrollToHorizontalOffset((double)persisted["ScrollViewer_HorizontalOffset"]); 
       // Additional fields here 
      } 
      DataContext = _vm; 
     } 
    } 
} 

में मानों धारण करने के लिए दृश्य वर्ग केवल देखने

public class View 
{ 
    private readonly Dictionary<string, Dictionary<string, object>> _views = new Dictionary<string, Dictionary<string, object>>(); 

    private static readonly View _instance = new View(); 
    public static View State => _instance; 

    public Dictionary<string, object> this[string viewKey] 
    { 
     get 
     { 
      if (_views.ContainsKey(viewKey)) 
      { 
       return _views[viewKey]; 
      } 
      return null; 
     } 
     set 
     { 
      _views[viewKey] = value; 
     } 
    } 

    public Dictionary<string, object> this[Type viewType] 
    { 
     get 
     { 
      return this[viewType.FullName]; 
     } 
     set 
     { 
      this[viewType.FullName] = value; 
     } 
    } 
} 

public static class Extensions 
{ 
    public static T GetChildOfType<T>(this DependencyObject depObj) 
where T : DependencyObject 
    { 
     if (depObj == null) return null; 

     for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) 
     { 
      var child = VisualTreeHelper.GetChild(depObj, i); 

      var result = (child as T) ?? GetChildOfType<T>(child); 
      if (result != null) return result; 
     } 
     return null; 
    } 
} 

दृष्टिकोण MVVM आधारित दृष्टिकोण वीएम एक क्षैतिज/VerticalOffset संपत्ति को

public class ViewModel 
{ 
    public class Item 
    { 
     public string Text { get; set; } 
     public bool IsSelected { get; set; } 

     public static implicit operator Item(string text) => new Item() { Text = text }; 
    } 

    public ViewModel() 
    { 
     for (int i = 0; i < 50; i++) 
     { 
      var text = ""; 
      for (int j = 0; j < i; j++) 
      { 
       text += "Item " + i; 
      } 
      Items.Add(new Item() { Text = text }); 
     } 
    } 

    public double HorizontalOffset { get; set; } 

    public double VerticalOffset { get; set; } 

    public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>(); 
} 

तो मुश्किल काम वास्तव में हो रही है पहुँच गया है के लिए स्क्रॉलव्यूअर के ऑफसेट गुण, जिन्हें दृश्य वृक्ष को चलने वाली एक विस्तार विधि शुरू करने की आवश्यकता होती है। मूल जवाब लिखते समय मुझे इसका एहसास नहीं हुआ।

+0

मैं डॉन इस जवाब से कुछ भी उपयोग करने के लिए नहीं देखते हैं। संपादन देखें, मैंने एमसीवीई जोड़ा है, क्या आप इसे काम कर सकते हैं (राज्य बहाल कर सकते हैं)? – Sinatr

+0

मैं आपके नमूने के अनुसार उत्तर अद्यतन करता हूं। मुझे यह भी एहसास हुआ कि मेरे मूल उत्तर में संकलन त्रुटियों का एक गुच्छा था - मैं इसके लिए क्षमा चाहता हूं। यह नोटपैड में लिखा गया था, क्योंकि मेरे पास विजुअल स्टूडियो तक पहुंच नहीं थी। यह वीएस में लिखा और परीक्षण किया गया है :) – sondergard

+0

[GetChildOfType] (http://stackoverflow.com/a/10279201/1997232)? – Sinatr

0

आप ListView में चयनित वैल्यू जोड़ने और ऑटोस्कोल पर व्यवहार का उपयोग करने का प्रयास कर सकते हैं।

ViewModel के लिए:: यहाँ कोड है

public class ViewModel 
{ 
    public ViewModel() 
    { 
     // select something 
     SelectedValue = Items[5]; 
    } 

    public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item> 
    { 
     "Item 1", 
     "Item 2", 
     "Item 3 long enough to use horizontal scroll", 
     "Item 4", 
     "Item 5", 
     "Item 6", 
     "Item 7", 
     "Item 8", 
     "Item 9" 
    }; 

    // To save which item is selected 
    public Item SelectedValue { get; set; } 

    public class Item 
    { 
     public string Text { get; set; } 
     public bool IsSelected { get; set; } 

     public static implicit operator Item(string text) => new Item {Text = text}; 
    } 
} 

XAML के लिए:

<ListView Width="100" Height="100" ItemsSource="{Binding Items}" SelectedValue="{Binding SelectedValue}" local:ListBoxAutoscrollBehavior.Autoscroll="True"> 

व्यवहार के लिए:

public static class ListBoxAutoscrollBehavior 
{ 
    public static readonly DependencyProperty AutoscrollProperty = DependencyProperty.RegisterAttached(
     "Autoscroll", typeof (bool), typeof (ListBoxAutoscrollBehavior), 
     new PropertyMetadata(default(bool), AutoscrollChangedCallback)); 

    private static readonly Dictionary<ListBox, SelectionChangedEventHandler> handlersDict = 
     new Dictionary<ListBox, SelectionChangedEventHandler>(); 

    private static void AutoscrollChangedCallback(DependencyObject dependencyObject, 
     DependencyPropertyChangedEventArgs args) 
    { 
     var listBox = dependencyObject as ListBox; 
     if (listBox == null) 
     { 
      throw new InvalidOperationException("Dependency object is not ListBox."); 
     } 

     if ((bool) args.NewValue) 
     { 
      Subscribe(listBox); 
      listBox.Unloaded += ListBoxOnUnloaded; 
      listBox.Loaded += ListBoxOnLoaded; 
     } 
     else 
     { 
      Unsubscribe(listBox); 
      listBox.Unloaded -= ListBoxOnUnloaded; 
      listBox.Loaded -= ListBoxOnLoaded; 
     } 
    } 

    private static void Subscribe(ListBox listBox) 
    { 
     if (handlersDict.ContainsKey(listBox)) 
     { 
      return; 
     } 

     var handler = new SelectionChangedEventHandler((sender, eventArgs) => ScrollToSelect(listBox)); 
     handlersDict.Add(listBox, handler); 
     listBox.SelectionChanged += handler; 
     ScrollToSelect(listBox); 
    } 

    private static void Unsubscribe(ListBox listBox) 
    { 
     SelectionChangedEventHandler handler; 
     handlersDict.TryGetValue(listBox, out handler); 
     if (handler == null) 
     { 
      return; 
     } 
     listBox.SelectionChanged -= handler; 
     handlersDict.Remove(listBox); 
    } 

    private static void ListBoxOnLoaded(object sender, RoutedEventArgs routedEventArgs) 
    { 
     var listBox = (ListBox) sender; 
     if (GetAutoscroll(listBox)) 
     { 
      Subscribe(listBox); 
     } 
    } 

    private static void ListBoxOnUnloaded(object sender, RoutedEventArgs routedEventArgs) 
    { 
     var listBox = (ListBox) sender; 
     if (GetAutoscroll(listBox)) 
     { 
      Unsubscribe(listBox); 
     } 
    } 

    private static void ScrollToSelect(ListBox datagrid) 
    { 
     if (datagrid.Items.Count == 0) 
     { 
      return; 
     } 

     if (datagrid.SelectedItem == null) 
     { 
      return; 
     } 

     datagrid.ScrollIntoView(datagrid.SelectedItem); 
    } 

    public static void SetAutoscroll(DependencyObject element, bool value) 
    { 
     element.SetValue(AutoscrollProperty, value); 
    } 

    public static bool GetAutoscroll(DependencyObject element) 
    { 
     return (bool) element.GetValue(AutoscrollProperty); 
    } 
} 
+0

मैंने इसका परीक्षण नहीं किया (क्षमा करें), लेकिन मेरे पास अतीत में इसी तरह का विचार था। और समस्या निम्न के साथ थी: कुछ चुनें, फिर स्क्रॉल करें (ऊपर या नीचे) और अधिक आइटम का चयन करें। जैसे ही आप अपने बाहर के पहले आइटम का चयन करेंगे, आपके स्क्रॉल तर्क को ट्रिगर किया जाएगा और रणनीति के आधार पर (क्या आप पहले या आखिरी आइटम पर स्क्रॉल करेंगे?) कुछ ऐसा होगा। यह उपयोगकर्ता के लिए बहुत परेशान है। यहां एक और बात आप शब्दकोश का उपयोग कर रहे हैं, क्यों? बस 'लोड किए गए' के ​​साथ ऐसा करने के तरीके के साथ ही 'चयनित चेंज' की सदस्यता लें। और स्थानीय चर 'डेटाग्रिड' कहता है कि यह कहां से फट गया था। – Sinatr

+0

मैंने वास्तव में अधिक वस्तुओं का चयन करने पर विचार नहीं किया। मैं इसे फिर से जांचता हूं और यह ** पहली आइटम ** तक स्क्रॉल करेगा। शब्दकोश का चयन 'चयन चेंजएड हैंडलर'' की सदस्यता रद्द करने के लिए किया जाता है क्योंकि लैम्ब्डा फ़ंक्शन [लिंक] (http://stackoverflow.com/questions/183367/unsubscribe-anonymous-method-in-c-sharp)। 'डाटाग्रिड' एक गलती है क्योंकि मैंने शुरुआत में डाटाग्रिड में इस व्यवहार का उपयोग किया और मैंने इसे सूची बॉक्स में बदल दिया। – zzczzc004

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