WPFのItemsControlで仮想化が効かない時に確認すること
なんで仮想化しないんだろー、と悩んでいたら当たり前だったこと
準備
<DataTemplate> <TextBlock Text="{Binding Converter={StaticResource TextConverter}}"/> </DataTemplate>
DataTemplateをこんな感じで書いて
public class TextConverter : IValueConverter { private int _counter; public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { System.Diagnostics.Debug.WriteLine($"Convert {_counter++}"); return value; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
こんなコンバーターを作っておきました
TextBlockが作られるたびにTextConverter.Convertが呼ばれて、何回呼ばれたかが_converterで分かります
あとはコードビハインドで
private void MainWindow_Loaded(object sender, RoutedEventArgs e) { Items.ItemsSource = Enumerable.Range(0, 100); }
って書いておけばいいですね (ItemsはItemsControlの名前)
_converterの数で何個TextBlockが作られたかわかるはずです
仮想化が効くとき
<ItemsControl x:Name="Items" VirtualizingStackPanel.IsVirtualizing="True" ScrollViewer.CanContentScroll="True"> <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Converter={StaticResource TextConverter}}"/> </DataTemplate> </ItemsControl.ItemTemplate> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.Template> <ControlTemplate> <ScrollViewer Padding="{TemplateBinding Control.Padding}" Focusable="False"> <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> </ScrollViewer> </ControlTemplate> </ItemsControl.Template> </ItemsControl>
TextBlockは画面に表示している分+1個くらいしか作られません
仮想化が効かないとき
<ScrollViewer> <ItemsControl x:Name="Items" VirtualizingStackPanel.IsVirtualizing="True" ScrollViewer.CanContentScroll="True"> <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Converter={StaticResource TextConverter}}"/> </DataTemplate> </ItemsControl.ItemTemplate> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl> </ScrollViewer>
TextBlockは100個作られます
外のScrollViewerで隠れてはいますがItemsControlの高さが全項目分表示できる高さになっているので当たり前ですよね
こぴぺヨクナイ
なんでこんな書き方をしていたかというと、ItemsControlをスクロール可能にする方法をStackOverflowなどで調べると下の書き方が結構出てくるからなんですね
コピペはしませんが「こー書けばいいのねー」と何も考えずに書いたせいです
スミマセンデシタ
.Net Framework 4.7で動きが変わったところ(WPF)
たまたま見つけたの一つだけです
<Grid> <Grid.RowDefinitions> <RowDefinition MaxHeight="0" /> <RowDefinition Height="*" /> <RowDefinition MaxHeight="4" /> </Grid.RowDefinitions> <Rectangle Fill="Green"/> <Rectangle Grid.Row="1" Fill="Blue"/> <Rectangle Grid.Row="2" Fill="Orange"/> </Grid>
こういうXAMLを用意します
RowDefinitionにMaxHeightしか書いていないとかなかなかの気持ち悪さを含んでいます
.Net Framework 4.6.2の場合
MaxHeightをHeightで処理してくれているような動きですね
.Net Framework 4.7の場合
どのように解釈したのかわからない結果になりました(どこかにドキュメントでもあるのかな?)
おまけ
<ControlTemplate x:Key="DefaultVerticalScrollBar" TargetType="{x:Type ScrollBar}"> <Grid> <Grid.RowDefinitions> <RowDefinition MaxHeight="0" /> <RowDefinition Height="0.00001*" /> <RowDefinition MaxHeight="4" /> </Grid.RowDefinitions> <Rectangle Grid.Row="1" Fill="Blue" Width="4" VerticalAlignment="Stretch" Margin="0,4,0,0"/> <Track x:Name="PART_Track" Grid.Row="1" IsDirectionReversed="true"> <Track.DecreaseRepeatButton> <RepeatButton Command="ScrollBar.PageUpCommand" Visibility="Collapsed"/> </Track.DecreaseRepeatButton> <Track.Thumb> <Thumb Margin="0,1,0,1" Background="Green"> </Thumb> </Track.Thumb> <Track.IncreaseRepeatButton> <RepeatButton Command="ScrollBar.PageDownCommand" Visibility="Collapsed"/> </Track.IncreaseRepeatButton> </Track> </Grid> </ControlTemplate>
こんなの使っていたらエラーも何も出ずにアプリが止まりました(止まるというかいつまでたってもウィンドウが表示されない状態)
結論
ちゃんとしたXAML書け
イベントをObservableにしたソケットラッパーをNugetで公開しました
ソケットのイベントをObservableにしただけ的な俺得ライブラリをNugetに上げることができました
名前はRxSocketで完全に名前負けしてます
期待した人ごめんなさい
名前はどうしたらよかったんですかねぇ
RxSocketって名前は既に使われていたみたい(検索結果に出てこないけど)なので、Punio.RxSocketです
ほんと名前・・
ソースはこちら
使い方
RxTcpClient
var client = new RxTcpClient(); // Subscribe client.Error.Subscribe(); client.Closed.Subscribe(); client.Received.Subscribe(); // Connect try{ client.Connect("127.0.0.1",10000); }catch(Excepction){}
TCPで接続するクライアントです
購読可能なイベントは、Error,Close,Receiveです
いつ来るのかはまぁ見た通りですね
接続は同期メソッドで失敗時は例外吐きます
今までの経験でいうとソケット通信のエラーの90%以上は接続で起きている気がします。 んで、エラー起きたときはまぁ少し待って再接続ですよね。 そういう感じの処理を書くときは同期呼び出しで失敗したらWinSockの例外拾うってのが私は使いやすいんで、こんな感じです。 (こういうところをちゃんと作れると少しは名前に追いつけそうですけどね)
IPv6やDSN名でもつながるはずです(DNSは試してないです)
RxTcpServer
var server = new RxTcpServer(); // Subscribe server.Error.Subscribe(); server.Accepted.Subscribe(); // client connected server.Closed.Subscribe(); // client closed server.Received.Subscribe(); // Listen try{ server.Listen(IPAddress.Any.ToString(),10000); }catch(Exception){}
TCPのサーバーです
クライアントの接続、切断イベントが購読できるところがサーバーソケットですね
こちらもListenは同期で失敗時に例外吐きます。「そのポート使われてるよ!」ってのが一番多い例外じゃないでしょうか。
こちらもListenに渡すアドレスでIPv4/6を判断するはずです
RxUdpListener
var listener = new RxUdpListener(); // Subscribe listener.Received.Subscribe(); // Listen try{ listener.Listen(10000); // UNICAST // or // listener.Listen("224.0.0.1",10000); // MULTICAST }catch(Exception){}
UDPのデータを受信する人です
Listenにアドレスを指定するとマルチキャストと判断してJOINします(マルチキャストのテストできる環境がないので実際に受信できるのか未確認)
こんな感じ
ソケット通信処理はつながったらあとは受信イベントを待つだけ、って感じなのが多いのでそれ用ですね。githubにはテスト用アプリも入っているのでそちらもみてみてみてください。