2013-06-28 27 views
5

これは私がWPFで達成しようとしているものです。 wrappanelのタイトルとしてのテキストブロックとその下のボタン。問題はスクロールなどが必要だということです。 ItemsControlと各グループのバインディングを使用してこれを実現しました。 私は、Paneltemplateとしてスタックパネルを持ち、そのitemtemplateがtextblockとwrappanelであるItemsControlを持っています。WPFカスタムレイアウト/仮想化

これは動作しますが、項目が多い場合は低速のIntel gma +アトムマシンではインスタンス化が遅くなります。レンダリングは問題ではなく、ビジュアルツリーの作成と思われます。 私のここでの唯一の賭けは、仮想化を使ったカスタムパネルを作成することだと思いますか?

ここに私が行ったことがあります。 http://pastebin.com/u8C7ddP0
上記のソリューションは一部のマシンでは遅いです。

私は低速のマシンで最大100msの時間を要するというソリューションを探しています。 が速くWPFのレイアウトを作成するには、あなたに

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

あなたのコード内の添付プロパティの多くは、名前空間からあります。言及されていない。これは特に疑わしく見えます: '' Message.Attach = "[Event Checked] = [Action AddPreference($ dataContext、false)]; [Event Unchecked] = [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

をありがとう、あなたは仮想化を有効にする必要があります。あなたのコードで:

  1. あなたのすべてのコントロールをラップするScrollViewerを削除してください。
  2. ListBoxでトップレベルItemsControlを交換:

    <ListBox Name="items" HorizontalContentAlignment="Stretch" 
         ScrollViewer.HorizontalScrollBarVisibility="Disabled" ... > 
    
  3. がでStackPanelを交換ListBoxItemsPanelVirtualizingStackPanel有する:

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

これは、トップレベルのアイテムのための仮想化を可能にします。私のコンピュータでは、1秒以内に100,000のアイテムを表示することができます。

N.B:

  1. あなたはボトルネックがWPFのレイアウトだと思いますが、あなたのアプリケーションをプロファイリングしていないとして、あなたは、間違っている可能性があります。これはあなたの質問に答えている間、それは実際にゆっくりと動作するウィンドウで問題を解決することはできません。プロファイラはコードだけでなく、フレームワークコードも解析できます。あなたのソースではなく、コール、メモリなどを分析します。彼らはあなたのパフォーマンスを向上させる素晴らしいツールであり、パフォーマンスの問題の原因を見つける唯一の真の方法です。

  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; 
    } 
} 

そうでない場合は、アイテムモードで動作しますスクロール、ListBoxVirtualizingStackPanellocal:PixelBasedScrollingBehavior.IsEnabled="True"の両方を設定する必要があります。このコードは.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

okしかし、時にはボタンが画面に合わない(多くの設定を持つ1つのpreferencegroupのように)ときどき衝撃的なので、wrappanelを仮想化せずにジャンプします。その接続されたプロパティを削除してtweeksを実行し、発射していたタイマーを修正した後、私はより良いパフォーマンスを得ました。しかし、遅れはいくつかのプラットフォームにあります。私はATOMとGMA GPUに問題があります。しかし、ペンティアム4と古いMatrox g450カードでも遅れはありません。 – GorillaApe

+0

「実用的でない」と正確にはどういう意味ですか?あなたの例と私の間に行動に違いがある場合は、それを記述してください。覚えておいて、私はあなたの例を実行することができません(第2のN.B.参照)。 「それ」とは何ですか?何のタイマー?ああ、来て、私はあなたの心を読むことを本当に期待していますか?私はあなたに何か情報を提供しなければあなたを助けることができません。何件のアイテムを表示する必要があるのか​​、あなたは言わなかった。 – Athari

+0

あなたの例は非常に私に近いものでした。私はスタイリング以外は90%を意味します。私の例を実行するには、多くのファイルを含める必要があるので、実際には難しいでしょう。しかし、私はコンバータで更新します。添付のプロパティで私は私の質問であなたの最初のコメントで私に言ったことを意味します。いくつかのユーザーが5から100まであることを意味しています.Windows XPをサポートしていないので、私はWPF 4.5を使用していないと言いました。 //stackoverflow.com/questions/14349848/wpf-4-0-pixel-based-scrolling-in-virtualizingstackpanel – GorillaApe

関連する問題