2013-06-28 16 views
5

Questo è ciò che sto cercando di ottenere con WPF. Un blocco di testo come titolo e sotto i pulsanti in un wrappanel. Il problema è che questo ha bisogno di scorrimento ecc. Ho raggiunto questo utilizzando ItemsControl e il binding per ogni gruppo. Ho un ItemsControl che ha uno stackpanel come paneltemplate e il suo itemtemplate è un textblock e un wrappanel.Layout/virtualizzazione WPF personalizzato

Funziona ma è lento all'istanza alle macchine lente intel gma + atom quando gli elementi sono molti. Sembra che il rendering non sia il problema ma la creazione del Visual Tree. Quindi la mia unica scommessa qui è creare un pannello personalizzato con la virtualizzazione, immagino?

Ecco cosa ho fatto. http://pastebin.com/u8C7ddP0
La soluzione sopra indicata è lenta in alcune macchine.

Sto cercando una soluzione che richiederebbe un massimo di 100 ms alle macchine lente per creare. Grazie

UPDATE

public class PreferenceCheckedConvertor : IMultiValueConverter 
    { 


    public object Convert(object[] values, Type targetType, 
      object parameter, System.Globalization.CultureInfo culture) 
    { 

     var preference = values[0] as OrderItemPreference; 
     var items = values[1] as ObservableCollection<OrderItemPreference>; 

     var found = items.FirstOrDefault(item => item.Preference.Id == preference.Preference.Id); 
     if (found == null) 
     { 
      return false; 
     } 
     return true; 

    } 
    public object[] ConvertBack(object value, Type[] targetTypes, 
      object parameter, System.Globalization.CultureInfo culture) 
    { 
     try 
     { 
      return null; 
     } 
     catch (Exception e) 
     { 
      return null; 
     } 
    } 


} 

ff

public class PreferenceConvertor : IMultiValueConverter 
    { 
     public object Convert(object[] values, Type targetType, 
       object parameter, System.Globalization.CultureInfo culture) 
     { 
      var preferences=values[0] as IEnumerable<Preference>; 
      var items=values[1] as ObservableCollection<OrderItemPreference>; 

      var newList = new List<OrderItemPreference>(preferences.Count()); 



      foreach (var preference in preferences) 
      { 
       var curItem = items.FirstOrDefault(item => item.Preference.Id == preference.Id); 

       if (curItem == null) 
       { 
        newList.Add(new OrderItemPreference() 
        { 
         Preference = preference 
        }); 
       } 
       else 
       { 
        newList.Add(curItem); 
       } 

      } 

      return newList; 







     } 
     public object[] ConvertBack(object value, Type[] targetTypes, 
       object parameter, System.Globalization.CultureInfo culture) 
     { 
      try 
      { 
       return null; 
      } 
      catch (Exception e) 
      { 
       return null; 
      } 
     }} 

enter image description here

+0

Ci sono un sacco di proprietà associate nel codice da spazi dei nomi, che sono mai menzionato. Questo in particolare sembra sospetto: 'cal: Message.Attach =" [Event Checked] = [Azione AddPreference ($ dataContext, false)]; [Event Unchecked] = [Azione RemovePreference ($ datacontext, false)] "'. Non parli mai della natura e del numero esatto degli oggetti a cui ti leghi. Tutto sommato, c'è troppo codice. Vedi http://sscce.org/ Perché pensi che la virtualizzazione sia il collo di bottiglia? Hai profilato la tua domanda? – Athari

+0

questo è un evento per caliburn micro. In realtà non esiste un codice che si lega a una lista. con le proprietà che vedi. Gli articoli non sono sempre molti. – GorillaApe

+0

Prima di chiedere come migliorare le prestazioni, è necessario profilare il proprio codice. Vedi [C# profiler] (https://www.google.com/search?q=c%23+profiler). – Athari

risposta

10

Per rendere il layout WPF più velocemente, è necessario abilitare la virtualizzazione. Nel tuo codice:

  1. Rimuovi ScrollViewer che include tutti i controlli.
  2. Sostituire alto livello ItemsControl con ListBox:

    <VirtualizingStackPanel Orientation="Vertical" ScrollUnit="Pixel" 
             VirtualizationMode="Recycling"/> 
    

Ciò consentirà di virtualizzazione per gli oggetti di livello superiore:

<ListBox Name="items" HorizontalContentAlignment="Stretch" 
     ScrollViewer.HorizontalScrollBarVisibility="Disabled" ... > 
  • Sostituire StackPanel in s' ItemsPanel con VirtualizingStackPanel la ListBox . Sul mio computer, questo consente di visualizzare 100.000 articoli entro 1 secondo.

    NB .:

    1. Mentre si pensa che il collo di bottiglia è di layout WPF, si può essere sbagliato, come non si è profilata l'applicazione. Quindi mentre questo risponde alla tua domanda, potrebbe non risolvere il problema con il rallentamento della finestra. I profiler possono analizzare non solo il tuo codice, ma anche il codice della struttura. Analizzano chiamate, memoria ecc., Non le tue fonti. Sono un ottimo strumento per migliorare le prestazioni e l'unico vero modo per trovare l'origine dei problemi di prestazioni.

    2. Per l'amore di tutto ciò che è sacro, per favore, leggi http://sscce.org! Non avrai abbastanza reputazione da dare per risolvere tutti i tuoi problemi di codice se non provi a rendere i tuoi esempi brevi, autonomi e compilabili.Solo per dare il tuo esempio, ho dovuto creare i miei modelli di visualizzazione, sbarazzarmi di tutto il codice irrilevante, semplificare i collegamenti, per non parlare di tutti i tipi di convertitori, controlli e collegamenti che non sono descritti in nessun punto.

    aggiornato per supportare .NET 4,0

    public static class PixelBasedScrollingBehavior 
    { 
        public static bool GetIsEnabled (DependencyObject obj) 
        { 
         return (bool)obj.GetValue(IsEnabledProperty); 
        } 
    
        public static void SetIsEnabled (DependencyObject obj, bool value) 
        { 
         obj.SetValue(IsEnabledProperty, value); 
        } 
    
        public static readonly DependencyProperty IsEnabledProperty = 
         DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(PixelBasedScrollingBehavior), 
          new UIPropertyMetadata(false, IsEnabledChanged)); 
    
        private static void IsEnabledChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) 
        { 
         var isEnabled = (bool)e.NewValue; 
    
         if (d is VirtualizingPanel) { 
          if (TrySetScrollUnit(d, isEnabled)) 
           return; 
          if (!TrySetIsPixelBased(d, isEnabled)) 
           throw new InvalidOperationException("Failed to set IsPixelBased or ScrollUnit property."); 
         } 
         if (d is ItemsControl) { 
          TrySetScrollUnit(d, isEnabled); 
         } 
        } 
    
        private static bool TrySetScrollUnit (DependencyObject ctl, bool isEnabled) 
        { 
         // .NET 4.5: ctl.SetValue(VirtualizingPanel.ScrollUnitProperty, isEnabled ? ScrollUnit.Pixel : ScrollUnit.Item); 
    
         var propScrollUnit = typeof(VirtualizingPanel).GetField("ScrollUnitProperty", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); 
         if (propScrollUnit == null) 
          return false; 
         var dpScrollUnit = (DependencyProperty)propScrollUnit.GetValue(null); 
    
         var assemblyPresentationFramework = typeof(Window).Assembly; 
         var typeScrollUnit = assemblyPresentationFramework.GetType("System.Windows.Controls.ScrollUnit"); 
         if (typeScrollUnit == null) 
          return false; 
         var valueScrollUnit = Enum.Parse(typeScrollUnit, isEnabled ? "Pixel" : "Item"); 
    
         ctl.SetValue(dpScrollUnit, valueScrollUnit); 
         return true; 
        } 
    
        private static bool TrySetIsPixelBased (DependencyObject ctl, bool isEnabled) 
        { 
         // .NET 4.0: ctl.IsPixelBased = isEnabled; 
    
         var propIsPixelBased = ctl.GetType().GetProperty("IsPixelBased", BindingFlags.NonPublic | BindingFlags.Instance); 
         if (propIsPixelBased == null) 
          return false; 
    
         propIsPixelBased.SetValue(ctl, isEnabled, null); 
         return true; 
        } 
    } 
    

    È necessario impostare local:PixelBasedScrollingBehavior.IsEnabled="True" sia sul ListBox e VirtualizingStackPanel, altrimenti lo scorrimento funzionerà in modalità voce. Il codice viene compilato in .NET 4.0. Se .NET 4.5 è installato, utilizzerà nuove proprietà.

    esempio funzionante:

    MainWindow.xaml

    <Window x:Class="So17371439ItemsLayoutBounty.MainWindow" x:Name="root" 
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
         xmlns:local="clr-namespace:So17371439ItemsLayoutBounty" 
         Title="MainWindow"> 
    
        <Window.Resources> 
         <Style x:Key="OrderRadioButton" TargetType="{x:Type RadioButton}"></Style> 
         <Style x:Key="OrderCheckboxButton" TargetType="{x:Type ToggleButton}"></Style> 
         <Style x:Key="OrderProductButton" TargetType="{x:Type Button}"></Style> 
        </Window.Resources> 
    
        <ListBox Name="items" ItemsSource="{Binding PreferenceGroups, ElementName=root}" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled" local:PixelBasedScrollingBehavior.IsEnabled="True"> 
         <ItemsControl.Resources> 
          <ItemsPanelTemplate x:Key="wrapPanel"> 
           <WrapPanel/> 
          </ItemsPanelTemplate> 
    
          <DataTemplate x:Key="SoloSelection" DataType="local:PreferenceGroup"> 
           <ItemsControl ItemsSource="{Binding Preferences}" ItemsPanel="{StaticResource wrapPanel}"> 
            <ItemsControl.ItemTemplate> 
             <DataTemplate> 
              <RadioButton Width="146" Height="58" Margin="0,0,4,4" GroupName="{Binding GroupId}" Style="{StaticResource OrderRadioButton}"> 
               <TextBlock Margin="4,0,3,0" VerticalAlignment="Center" TextWrapping="Wrap" Text="{Binding Name}"/> 
              </RadioButton> 
             </DataTemplate> 
            </ItemsControl.ItemTemplate> 
           </ItemsControl> 
          </DataTemplate> 
    
          <DataTemplate x:Key="MultiSelection" DataType="local:PreferenceGroup"> 
           <ItemsControl ItemsSource="{Binding Preferences}" ItemsPanel="{StaticResource wrapPanel}"> 
            <ItemsControl.ItemTemplate> 
             <DataTemplate> 
              <ToggleButton Width="146" Height="58" Margin="0,0,4,4" Style="{StaticResource OrderCheckboxButton}"> 
               <TextBlock Margin="4,0,3,0" VerticalAlignment="Center" TextWrapping="Wrap" Text="{Binding Name}"/> 
              </ToggleButton> 
             </DataTemplate> 
            </ItemsControl.ItemTemplate> 
           </ItemsControl> 
          </DataTemplate> 
    
          <DataTemplate x:Key="MultiQuantitySelection" DataType="local:PreferenceGroup"> 
           <ItemsControl ItemsSource="{Binding Preferences}" ItemsPanel="{StaticResource wrapPanel}"> 
            <ItemsControl.ItemTemplate> 
             <DataTemplate> 
              <Grid Width="146" Height="58" Margin="0,0,4,4"> 
               <Grid.ColumnDefinitions> 
                <ColumnDefinition Width="Auto"/> 
                <ColumnDefinition Width="*"/> 
               </Grid.ColumnDefinitions> 
               <Button Name="quantity" Background="White" Width="45" Style="{StaticResource OrderProductButton}"> 
                <TextBlock Text="{Binding Quantity}"/> 
               </Button> 
               <Button Margin="-1,0,0,0" Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Style="{StaticResource OrderProductButton}"> 
                <TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" TextTrimming="CharacterEllipsis" Text="{Binding Name}"/> 
               </Button> 
              </Grid> 
             </DataTemplate> 
            </ItemsControl.ItemTemplate> 
           </ItemsControl> 
          </DataTemplate> 
    
         </ItemsControl.Resources> 
    
         <ItemsControl.ItemTemplate> 
          <DataTemplate> 
           <StackPanel> 
            <TextBlock FontSize="25" FontWeight="Light" Margin="0,8,0,5" Text="{Binding Name}"/> 
            <ContentControl Content="{Binding}" Name="items"/> 
           </StackPanel> 
    
           <DataTemplate.Triggers> 
            <DataTrigger Binding="{Binding SelectionMode}" Value="1"> 
             <Setter TargetName="items" Property="ContentTemplate" Value="{StaticResource SoloSelection}"/> 
            </DataTrigger> 
            <DataTrigger Binding="{Binding SelectionMode}" Value="2"> 
             <Setter TargetName="items" Property="ContentTemplate" Value="{StaticResource MultiSelection}"/> 
            </DataTrigger> 
            <DataTrigger Binding="{Binding SelectionMode}" Value="3"> 
             <Setter TargetName="items" Property="ContentTemplate" Value="{StaticResource MultiQuantitySelection}"/> 
            </DataTrigger> 
           </DataTemplate.Triggers> 
    
          </DataTemplate> 
         </ItemsControl.ItemTemplate> 
         <ItemsControl.ItemsPanel> 
          <ItemsPanelTemplate> 
           <VirtualizingStackPanel x:Name="panel" Orientation="Vertical" VirtualizationMode="Recycling" local:PixelBasedScrollingBehavior.IsEnabled="True"/> 
          </ItemsPanelTemplate> 
         </ItemsControl.ItemsPanel> 
    
        </ListBox> 
    
    </Window> 
    

    MainWindow.xaml.cs

    using System; 
    using System.Collections.ObjectModel; 
    using System.Reflection; 
    using System.Windows; 
    using System.Windows.Controls; 
    
    namespace So17371439ItemsLayoutBounty 
    { 
        public partial class MainWindow 
        { 
         public ObservableCollection<PreferenceGroup> PreferenceGroups { get; private set; } 
    
         public MainWindow() 
         { 
          var rnd = new Random(); 
          PreferenceGroups = new ObservableCollection<PreferenceGroup>(); 
          for (int i = 0; i < 100000; i++) { 
           var group = new PreferenceGroup { Name = string.Format("Group {0}", i), SelectionMode = rnd.Next(1, 4) }; 
           int nprefs = rnd.Next(5, 40); 
           for (int j = 0; j < nprefs; j++) 
            group.Preferences.Add(new Preference { Name = string.Format("Pref {0}", j), Quantity = rnd.Next(100) }); 
           PreferenceGroups.Add(group); 
          } 
          InitializeComponent(); 
         } 
        } 
    
        public class PreferenceGroup 
        { 
         public string Name { get; set; } 
         public int SelectionMode { get; set; } 
         public ObservableCollection<Preference> Preferences { get; private set; } 
    
         public PreferenceGroup() 
         { 
          Preferences = new ObservableCollection<Preference>(); 
         } 
        } 
    
        public class Preference 
        { 
         public string Name { get; set; } 
         public string GroupId { get; set; } 
         public int Quantity { get; set; } 
        } 
    
        public static class PixelBasedScrollingBehavior 
        { 
         public static bool GetIsEnabled (DependencyObject obj) 
         { 
          return (bool)obj.GetValue(IsEnabledProperty); 
         } 
    
         public static void SetIsEnabled (DependencyObject obj, bool value) 
         { 
          obj.SetValue(IsEnabledProperty, value); 
         } 
    
         public static readonly DependencyProperty IsEnabledProperty = 
          DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(PixelBasedScrollingBehavior), 
           new UIPropertyMetadata(false, IsEnabledChanged)); 
    
         private static void IsEnabledChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) 
         { 
          var isEnabled = (bool)e.NewValue; 
    
          if (d is VirtualizingPanel) { 
           if (TrySetScrollUnit(d, isEnabled)) 
            return; 
           if (!TrySetIsPixelBased(d, isEnabled)) 
            throw new InvalidOperationException("Failed to set IsPixelBased or ScrollUnit property."); 
          } 
          if (d is ItemsControl) { 
           TrySetScrollUnit(d, isEnabled); 
          } 
         } 
    
         private static bool TrySetScrollUnit (DependencyObject ctl, bool isEnabled) 
         { 
          // .NET 4.5: ctl.SetValue(VirtualizingPanel.ScrollUnitProperty, isEnabled ? ScrollUnit.Pixel : ScrollUnit.Item); 
    
          var propScrollUnit = typeof(VirtualizingPanel).GetField("ScrollUnitProperty", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); 
          if (propScrollUnit == null) 
           return false; 
          var dpScrollUnit = (DependencyProperty)propScrollUnit.GetValue(null); 
    
          var assemblyPresentationFramework = typeof(Window).Assembly; 
          var typeScrollUnit = assemblyPresentationFramework.GetType("System.Windows.Controls.ScrollUnit"); 
          if (typeScrollUnit == null) 
           return false; 
          var valueScrollUnit = Enum.Parse(typeScrollUnit, isEnabled ? "Pixel" : "Item"); 
    
          ctl.SetValue(dpScrollUnit, valueScrollUnit); 
          return true; 
         } 
    
         private static bool TrySetIsPixelBased (DependencyObject ctl, bool isEnabled) 
         { 
          // .NET 4.0: ctl.IsPixelBased = isEnabled; 
    
          var propIsPixelBased = ctl.GetType().GetProperty("IsPixelBased", BindingFlags.NonPublic | BindingFlags.Instance); 
          if (propIsPixelBased == null) 
           return false; 
    
          propIsPixelBased.SetValue(ctl, isEnabled, null); 
          return true; 
         } 
        } 
    } 
    
  • +0

    ok ma questo non è pratico in quanto a volte i pulsanti non si adattano allo schermo (come un gruppo preferenziale con molte preferenze). Quindi senza virtualizzare wrappanel si saltava. Dopo aver rimosso quella proprietà allegata e fatto alcuni tweek e aggiustando un timer che stava sparando ottenevo prestazioni migliori. Tuttavia il ritardo è su alcune piattaforme. Ho problemi con ATOM e GMA GPU. Ma anche con un Pentium 4 e una vecchia scheda g450 matrox non si è verificato alcun ritardo. – GorillaApe

    +0

    Cosa intendi esattamente per "poco pratico"? Se c'è qualche differenza nel comportamento tra il tuo esempio e il mio, descriverlo. Ricorda, non posso eseguire il tuo esempio (vedi 2 ° B). Che "quella" proprietà allegata? Quale timer? Oh, andiamo, ti aspetti davvero che legga la tua mente? Non posso aiutarti se non fornisci alcuna informazione. Non hai detto nemmeno quale numero di elementi devi mostrare. – Athari

    +0

    Il tuo esempio era molto vicino al mio. Intendo il 90% tranne lo stile. Per eseguire il mio esempio sarebbe davvero difficile in quanto dovrei includere molti file. Tuttavia, aggiornerò con il convertitore. Per proprietà allegata intendo quello che mi hai detto al tuo primo commento alla mia domanda. Il numero di elementi varia. Voglio dire che alcuni utenti hanno da 5 a 100. Anche perché non ho menzionato non sto usando WPF 4.5 perché non posso supportare WIN XP quindi non c'è scrolling del pixel :(. Ho provato ma non ha funzionato come menzionato qui http: //stackoverflow.com/questions/14349848/wpf-4-0-pixel-based-scrolling-in-virtualizingstackpanel – GorillaApe

    Problemi correlati