2013-06-28 91 views
5

這是我想用WPF實現的。一個文本塊作爲標題和wrappanel按鈕下方。問題是,這需要滾動等 我已經實現了這個使用ItemsControl和綁定爲每個組。 我有一個ItemsControl有一個stackpanel作爲paneltemplate和它的itemtemplate是一個文本塊和一個wrappanel。WPF自定義佈局/虛擬化

它可以工作,但在物品很多時,它在速度慢的intel gma + atom機器上實例化時速度很慢。似乎渲染不是問題,而是創建可視化樹。 所以我唯一的選擇就是創建一個虛擬化的自定義面板,我猜?

這是我所做的。 http://pastebin.com/u8C7ddP0
以上解決方案在某些機器上很慢。

我正在尋找一個解決方案,它將花費最長100ms的時間在慢速機器上創建。 感謝

更新

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

有很多在你的代碼附加屬性的命名空間,從它們從未提及。這特別看起來很可疑:'cal:Message.Attach =「[Event Checked] = [Action AddPreference($ dataContext,false)]; [未事件] = [Action RemovePreference($ datacontext,false)]」'。你永遠不會提到你綁定的對象的性質和確切數量。總而言之,代碼太多了。請參閱http://sscce.org/ 您爲什麼認爲虛擬化是瓶頸?你有沒有分析你的應用程序? – Athari

+0

這是caliburn micro的事件。實際上,它沒有綁定到列表的代碼。與你看到的屬性。項目並不總是很多。 – GorillaApe

+0

在詢問如何提高性能之前,您需要分析您的代碼。請參閱[c#profiler](https://www.google.com/search?q=c%23+profiler)。 – Athari

回答

10

爲了讓WPF佈局速度快,就需要啓用虛擬化。在您的代碼中:

  1. 刪除ScrollViewer包裝所有控件。
  2. 更換頂級ItemsControlListBox

    <ListBox Name="items" HorizontalContentAlignment="Stretch" 
         ScrollViewer.HorizontalScrollBarVisibility="Disabled" ... > 
    
  3. 在更換StackPanelListBoxItemsPanelVirtualizingStackPanel

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

這將啓用頂級項目的虛擬化。在我的電腦上,這允許在1秒內顯示100,000個項目。

N.B:

  1. 當你認爲瓶頸是WPF佈局,你可能是錯誤的,因爲你還沒有成型的應用程序。所以,雖然這回答你的問題,但實際上可能無法解決窗口工作緩慢的問題。 Profiler不僅可以分析您的代碼,還可以分析框架代碼。他們分析電話,記憶等,而不是你的來源。它們是改善性能的絕佳工具,也是找到性能問題根源的唯一正確途徑。

  2. 對於所有神聖的愛,請閱讀http://sscce.org!如果您不想讓自己的示例簡短,自包含且可編譯,那麼您將無法提供足夠的信譽來解決所有代碼問題。爲了運行你的例子,我必須創建自己的視圖模型,擺脫所有不相關的代碼,簡化綁定,更不用說所有你自己的轉換器,控件和綁定,這些都是沒有描述的。

更新,以支持.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; 
    } 
} 

設置local:PixelBasedScrollingBehavior.IsEnabled="True"上都ListBoxVirtualizingStackPanel這是必要的,否則會滾動項模式下工作。代碼在.NET 4.0中編譯。如果安裝了.NET 4.5,它將使用新的屬性。

工作例如:

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

好的,但這是有影響的,因爲有時按鈕不適合屏幕(就像一個偏好組有很多偏好)。所以沒有虛擬化wrappanel它會跳。刪除附加的屬性,並做一些tweeks和固定一個計時器是射擊我有更好的表現。然而,這種滯後在某些平臺上。我有ATOM和GMA GPU的問題。但即使是奔騰4和舊的Matrox G450卡也沒有滯後。 – GorillaApe

+0

你是什麼意思「不切實際」?如果你的例子和我的行爲有任何不同,請描述它。請記住,我無法運行你的例子(見第2 N.B.)。什麼「那個」附屬物?什麼時間?哦,來吧,你真的期望我能讀懂你的想法嗎?如果您不提供任何信息,我無法幫助您。你甚至沒有說過你需要展示多少項目。 – Athari

+0

你的例子非常接近我的。我的意思是90%,除了造型。爲了運行我的例子,這將是非常困難的,因爲我將不得不包括許多文件。但是,我將更新與轉換器。通過附屬財產,我的意思是你在我的問題中對你的第一條評論告訴我的。數量的項目不同。我的意思是一些用戶有5至100.也因爲我沒有提到我不使用WPF 4.5,因爲我不能支持WIN XP,所以沒有像素滾動:(。嘗試,但沒有像這裏提到的工作http: //sackoverflow.com/questions/14349848/wpf-4-0-pixel-based-scrolling-in-virtualizingstackpanel – GorillaApe