トップ回答者
WrapControlで子要素が初めて画面に表示されたとき

質問
-
こんにちは。
<ScrollViewer HorizontalScrollBarVisibility="Disabled" Grid.Row="1" > <WrapPanel Name="Wrp" Orientation="Horizontal" /> </ScrollViewer>
例えばこんなXAMLを用意してコードビハインドで
For i As Integer = 0 To 50 Dim a As New Rectangle a.Width = 50 a.Height = 50 a.Margin = New Thickness(10, 10, 0, 0) a.Fill = New System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Blue) Wrp.Children.Add(a) Next
適当にRectangleを並べたとします。
Windowサイズが小さいと下の方のRectangleは、画面に表示されません。
ここからが、質問なのですがこのRectangleが「画面内に表示されたとき」というイベントを
発生させることは可能でしょうか?
実際に考えているアプリとしてはRectangleではなく画像を数千枚表示させようと
考えているので、初期は画面に表示された分だけ画像を読込み、Scrollされるたびに
徐々に画像を読み込むことでUIの反応速度を上げたいと考えています。
ご存知方がいらっしゃいましたら、ご教示ください。
回答
-
直接の回答ではありませんが、目的に合うものとしては以下が参考になるのではないかと思います。
ListView で表示用データを仮想化する
http://d.hatena.ne.jp/tt_clown/20130327/listview_virtualizingWPFでコレクションの表示を高速化するオプションとか
http://yuuxxxx.hatenablog.com/entry/2014/02/01/232320
★良い回答には回答済みマークを付けよう! MVP - .NET http://d.hatena.ne.jp/trapemiya/
- 編集済み trapemiyaModerator 2016年12月6日 2:13 追記
- 回答の候補に設定 栗下 望Microsoft employee, Moderator 2016年12月6日 2:46
- 回答としてマーク 栗下 望Microsoft employee, Moderator 2016年12月26日 1:09
-
要素の大きさが固定であるなら、ScrollViewerの表示領域に表示されている要素は計算から調べられます。
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:app="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525"> <DockPanel> <Button Content="TEST" DockPanel.Dock="Right" Click="Button_Click" /> <UniformGrid Columns="2"> <ScrollViewer HorizontalScrollBarVisibility="Disabled" Grid.Row="1" > <WrapPanel x:Name="Wrp" Orientation="Horizontal" Loaded="Wrp_Loaded"/> </ScrollViewer> <ListBox x:Name="listBox1" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <ListBox.ItemsPanel> <ItemsPanelTemplate> <app:WrapPanelEx Orientation="Horizontal" /> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate > <Border Width="50" Height="50" Margin="5" > <Image /> </Border> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </UniformGrid> </DockPanel> </Window>
Imports System.Collections.ObjectModel Class MainWindow Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded For i As Integer = 0 To 50 Dim b As New Border b.Width = 50 b.Height = 50 b.Margin = New Thickness(10, 10, 0, 0) Dim img As New Image b.Child = img '読み込ませたいURIを覚えさせておく b.DataContext = New Uri("https://i1.social.s-msft.com/profile/u/avatar.jpg?displayname=quarterdeck") Wrp.Children.Add(b) Next Me.listBox1.ItemsSource = collection End Sub Private Sub Wrp_Loaded(sender As Object, e As RoutedEventArgs) WrapPanelEx.OnLoaded(sender, e) End Sub 'コレクションで増減に反応させる Dim collection As New ObservableCollection(Of Uri) Private Sub Button_Click(sender As Object, e As RoutedEventArgs) For i As Integer = 0 To 10 collection.Add(New Uri("https://i1.social.s-msft.com/profile/u/avatar.jpg?displayname=quarterdeck")) Next End Sub End Class Class WrapPanelEx Inherits WrapPanel Public Shared Sub OnLoaded(sender As Object, e As RoutedEventArgs) Dim wrap As WrapPanel = CType(sender, WrapPanel) Dim d As DependencyObject = wrap While (d IsNot Nothing) Dim sv As ScrollViewer = TryCast(d, ScrollViewer) If (sv IsNot Nothing) Then sv.Tag = wrap wrap.Tag = sv AddHandler sv.ScrollChanged, AddressOf ScrollViewer_ScrollChanged Return End If d = VisualTreeHelper.GetParent(d) End While End Sub Private Shared Sub ScrollViewer_ScrollChanged(sender As Object, e As ScrollChangedEventArgs) Changed(CType(sender, ScrollViewer)) End Sub Private Shared Sub Changed(ByVal sv As ScrollViewer) Dim wrap As WrapPanel = CType(sv.Tag, WrapPanel) If (wrap.Children.Count = 0) Then Return End If 'ScrollViewerの表示領域に表示される要素を調べる Dim w As Double = sv.ExtentWidth 'ScrollViewerの最大スクロール幅 Dim h As Double = sv.ExtentHeight 'ScrollViewerの最大スクロール高さ Dim child As FrameworkElement = wrap.Children(0) Dim childWidth As Double = (child.ActualWidth + child.Margin.Left + child.Margin.Right) '固定サイズの要素の幅(マージン含む) Dim childHeight As Double = (child.ActualHeight + child.Margin.Top + child.Margin.Bottom) '固定サイズの要素の高さ(マージン含む) Dim columnCount = sv.ViewportWidth \ childWidth '1行に表示される列数 'ScrollViewerの左上の要素のインデックス Dim visibleStartIndex As Integer = CInt(Math.Floor(sv.VerticalOffset / childHeight)) * columnCount 'ScrollViewerの最後に表示されている要素のインデックス(見切れ含む) Dim visibleEndIndex As Integer = CInt(Math.Floor((sv.VerticalOffset + sv.ViewportHeight) / childHeight)) * columnCount + (columnCount - 1) visibleStartIndex = Math.Min(wrap.Children.Count - 1, visibleStartIndex) visibleEndIndex = Math.Min(wrap.Children.Count - 1, visibleEndIndex) OnScroll(wrap, visibleStartIndex, visibleEndIndex) End Sub Private Shared Sub OnScroll(ByVal wrp As WrapPanel, ByVal startIndex As Integer, ByVal endIndex As Integer) If (startIndex >= 0) Then For index As Integer = startIndex To endIndex Dim child As FrameworkElement = wrp.Children(index) If (child.Tag Is Nothing AndAlso TypeOf child.DataContext Is Uri) Then child.Tag = True Dim img As Image = FindChild(Of Image)(child) If (img IsNot Nothing AndAlso img.Source Is Nothing) Then 'まだ読み込んでないなら読み込むImageをつくる Dim bitmap As New BitmapImage() Dim bnd As New Binding("DataContext") bnd.Source = child img.SetBinding(Image.SourceProperty, bnd) End If End If Next End If End Sub Private Shared Function FindChild(Of T As {DependencyObject})(ByVal p As DependencyObject) As T Dim count = VisualTreeHelper.GetChildrenCount(p) For i As Integer = 0 To count - 1 Dim child = VisualTreeHelper.GetChild(p, i) If (TypeOf child Is T) Then Return CType(child, T) End If child = FindChild(Of T)(child) If child IsNot Nothing Then Return CType(child, T) End If Next Return Nothing End Function #Region "要素の追加削除に反応するように" Sub New() AddHandler Me.Loaded, AddressOf OnLoaded End Sub Private flag As Boolean Private Sub OnVisualChildrenChangedDelay() flag = False Dim sv As ScrollViewer = TryCast(Me.Tag, ScrollViewer) If (sv IsNot Nothing) Then Changed(sv) End If End Sub Protected Overrides Sub OnVisualChildrenChanged(visualAdded As DependencyObject, visualRemoved As DependencyObject) MyBase.OnVisualChildrenChanged(visualAdded, visualRemoved) If (Not flag) Then flag = True Dispatcher.BeginInvoke(New Action(AddressOf OnVisualChildrenChangedDelay), System.Windows.Threading.DispatcherPriority.Loaded) End If End Sub #End Region End Class
#Wrappanelの仮想化は大変なのです…個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答としてマーク 栗下 望Microsoft employee, Moderator 2016年12月26日 1:09
-
WrapPanel と似たような挙動をし、仮想化に対応している高速なコントロールでは、Infragistics 社が開発したxamDataCards というコントロールがあります。
サードパーティー製で有償になりますが、多彩な表現力を有した優秀なコンポーネント群を提供してます。カスタマイズすれは、お望みのUIが得られるかもしれません。Infragisitics Ultimate の無料トライアルをダウンロード・インストールして、WPF サンプル ブラウザ―を起動すれば、挙動を確認できます。
本フォーラムは、ユーザー(開発者)同士で情報交換を行うためのコミュニティです。初めて利用される方は、以下のアナウンスをご覧ください。 https://social.msdn.microsoft.com/Forums/ja-JP/ca9ecfb7-4407-4fcb-b8bd-207d68257e68?
- 編集済み ひらぽんModerator 2016年12月12日 7:47 リンクの追加
- 回答としてマーク 栗下 望Microsoft employee, Moderator 2016年12月26日 1:09
すべての返信
-
直接の回答ではありませんが、目的に合うものとしては以下が参考になるのではないかと思います。
ListView で表示用データを仮想化する
http://d.hatena.ne.jp/tt_clown/20130327/listview_virtualizingWPFでコレクションの表示を高速化するオプションとか
http://yuuxxxx.hatenablog.com/entry/2014/02/01/232320
★良い回答には回答済みマークを付けよう! MVP - .NET http://d.hatena.ne.jp/trapemiya/
- 編集済み trapemiyaModerator 2016年12月6日 2:13 追記
- 回答の候補に設定 栗下 望Microsoft employee, Moderator 2016年12月6日 2:46
- 回答としてマーク 栗下 望Microsoft employee, Moderator 2016年12月26日 1:09
-
要素の大きさが固定であるなら、ScrollViewerの表示領域に表示されている要素は計算から調べられます。
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:app="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525"> <DockPanel> <Button Content="TEST" DockPanel.Dock="Right" Click="Button_Click" /> <UniformGrid Columns="2"> <ScrollViewer HorizontalScrollBarVisibility="Disabled" Grid.Row="1" > <WrapPanel x:Name="Wrp" Orientation="Horizontal" Loaded="Wrp_Loaded"/> </ScrollViewer> <ListBox x:Name="listBox1" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <ListBox.ItemsPanel> <ItemsPanelTemplate> <app:WrapPanelEx Orientation="Horizontal" /> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate > <Border Width="50" Height="50" Margin="5" > <Image /> </Border> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </UniformGrid> </DockPanel> </Window>
Imports System.Collections.ObjectModel Class MainWindow Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded For i As Integer = 0 To 50 Dim b As New Border b.Width = 50 b.Height = 50 b.Margin = New Thickness(10, 10, 0, 0) Dim img As New Image b.Child = img '読み込ませたいURIを覚えさせておく b.DataContext = New Uri("https://i1.social.s-msft.com/profile/u/avatar.jpg?displayname=quarterdeck") Wrp.Children.Add(b) Next Me.listBox1.ItemsSource = collection End Sub Private Sub Wrp_Loaded(sender As Object, e As RoutedEventArgs) WrapPanelEx.OnLoaded(sender, e) End Sub 'コレクションで増減に反応させる Dim collection As New ObservableCollection(Of Uri) Private Sub Button_Click(sender As Object, e As RoutedEventArgs) For i As Integer = 0 To 10 collection.Add(New Uri("https://i1.social.s-msft.com/profile/u/avatar.jpg?displayname=quarterdeck")) Next End Sub End Class Class WrapPanelEx Inherits WrapPanel Public Shared Sub OnLoaded(sender As Object, e As RoutedEventArgs) Dim wrap As WrapPanel = CType(sender, WrapPanel) Dim d As DependencyObject = wrap While (d IsNot Nothing) Dim sv As ScrollViewer = TryCast(d, ScrollViewer) If (sv IsNot Nothing) Then sv.Tag = wrap wrap.Tag = sv AddHandler sv.ScrollChanged, AddressOf ScrollViewer_ScrollChanged Return End If d = VisualTreeHelper.GetParent(d) End While End Sub Private Shared Sub ScrollViewer_ScrollChanged(sender As Object, e As ScrollChangedEventArgs) Changed(CType(sender, ScrollViewer)) End Sub Private Shared Sub Changed(ByVal sv As ScrollViewer) Dim wrap As WrapPanel = CType(sv.Tag, WrapPanel) If (wrap.Children.Count = 0) Then Return End If 'ScrollViewerの表示領域に表示される要素を調べる Dim w As Double = sv.ExtentWidth 'ScrollViewerの最大スクロール幅 Dim h As Double = sv.ExtentHeight 'ScrollViewerの最大スクロール高さ Dim child As FrameworkElement = wrap.Children(0) Dim childWidth As Double = (child.ActualWidth + child.Margin.Left + child.Margin.Right) '固定サイズの要素の幅(マージン含む) Dim childHeight As Double = (child.ActualHeight + child.Margin.Top + child.Margin.Bottom) '固定サイズの要素の高さ(マージン含む) Dim columnCount = sv.ViewportWidth \ childWidth '1行に表示される列数 'ScrollViewerの左上の要素のインデックス Dim visibleStartIndex As Integer = CInt(Math.Floor(sv.VerticalOffset / childHeight)) * columnCount 'ScrollViewerの最後に表示されている要素のインデックス(見切れ含む) Dim visibleEndIndex As Integer = CInt(Math.Floor((sv.VerticalOffset + sv.ViewportHeight) / childHeight)) * columnCount + (columnCount - 1) visibleStartIndex = Math.Min(wrap.Children.Count - 1, visibleStartIndex) visibleEndIndex = Math.Min(wrap.Children.Count - 1, visibleEndIndex) OnScroll(wrap, visibleStartIndex, visibleEndIndex) End Sub Private Shared Sub OnScroll(ByVal wrp As WrapPanel, ByVal startIndex As Integer, ByVal endIndex As Integer) If (startIndex >= 0) Then For index As Integer = startIndex To endIndex Dim child As FrameworkElement = wrp.Children(index) If (child.Tag Is Nothing AndAlso TypeOf child.DataContext Is Uri) Then child.Tag = True Dim img As Image = FindChild(Of Image)(child) If (img IsNot Nothing AndAlso img.Source Is Nothing) Then 'まだ読み込んでないなら読み込むImageをつくる Dim bitmap As New BitmapImage() Dim bnd As New Binding("DataContext") bnd.Source = child img.SetBinding(Image.SourceProperty, bnd) End If End If Next End If End Sub Private Shared Function FindChild(Of T As {DependencyObject})(ByVal p As DependencyObject) As T Dim count = VisualTreeHelper.GetChildrenCount(p) For i As Integer = 0 To count - 1 Dim child = VisualTreeHelper.GetChild(p, i) If (TypeOf child Is T) Then Return CType(child, T) End If child = FindChild(Of T)(child) If child IsNot Nothing Then Return CType(child, T) End If Next Return Nothing End Function #Region "要素の追加削除に反応するように" Sub New() AddHandler Me.Loaded, AddressOf OnLoaded End Sub Private flag As Boolean Private Sub OnVisualChildrenChangedDelay() flag = False Dim sv As ScrollViewer = TryCast(Me.Tag, ScrollViewer) If (sv IsNot Nothing) Then Changed(sv) End If End Sub Protected Overrides Sub OnVisualChildrenChanged(visualAdded As DependencyObject, visualRemoved As DependencyObject) MyBase.OnVisualChildrenChanged(visualAdded, visualRemoved) If (Not flag) Then flag = True Dispatcher.BeginInvoke(New Action(AddressOf OnVisualChildrenChangedDelay), System.Windows.Threading.DispatcherPriority.Loaded) End If End Sub #End Region End Class
#Wrappanelの仮想化は大変なのです…個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答としてマーク 栗下 望Microsoft employee, Moderator 2016年12月26日 1:09
-
WrapPanel と似たような挙動をし、仮想化に対応している高速なコントロールでは、Infragistics 社が開発したxamDataCards というコントロールがあります。
サードパーティー製で有償になりますが、多彩な表現力を有した優秀なコンポーネント群を提供してます。カスタマイズすれは、お望みのUIが得られるかもしれません。Infragisitics Ultimate の無料トライアルをダウンロード・インストールして、WPF サンプル ブラウザ―を起動すれば、挙動を確認できます。
本フォーラムは、ユーザー(開発者)同士で情報交換を行うためのコミュニティです。初めて利用される方は、以下のアナウンスをご覧ください。 https://social.msdn.microsoft.com/Forums/ja-JP/ca9ecfb7-4407-4fcb-b8bd-207d68257e68?
- 編集済み ひらぽんModerator 2016年12月12日 7:47 リンクの追加
- 回答としてマーク 栗下 望Microsoft employee, Moderator 2016年12月26日 1:09