プログラムの事とか

お約束ですが「掲載内容は私個人の見解です」

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の場合

f:id:puni-o:20170406164315p:plain

MaxHeightをHeightで処理してくれているような動きですね

.Net Framework 4.7の場合

f:id:puni-o:20170406164400p:plain

どのように解釈したのかわからない結果になりました(どこかにドキュメントでもあるのかな?)

おまけ

<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で完全に名前負けしてます

期待した人ごめんなさい

名前はどうしたらよかったんですかねぇ

www.nuget.org

RxSocketって名前は既に使われていたみたい(検索結果に出てこないけど)なので、Punio.RxSocketです

ほんと名前・・

ソースはこちら

github.com

使い方

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にはテスト用アプリも入っているのでそちらもみてみてみてください。