Reference Sorceを読む。 ItemBlock - 1Page 

ItemContainerGeneratorは文字どおりItemContainerを生成する役割のクラスでしたが、
生成するにあたりコンテナーを入れ子として管理しているItemBlock及び、ItemBlockを継承した
RealizedItemBlockとUnrealizedItemBlockについて書いていきます。
(この辺りの進め方がゴチャゴチャして申し訳ありませんが、ある程度まとまったらまとめ記事を書きますのでご了承ください)

今回はItemBlock、RealizedItemBlock、UnrealizedItemBlockの3つセットで進めていきます。
Reference Source

概要

        //------------------------------------------------------
        //
        //  Private Nested Classes
        //
        //------------------------------------------------------
 
        // The ItemContainerGenerator uses the following data structure to maintain
        // the correspondence between items and their containers.  It's a doubly-linked
        // list of ItemBlocks, with a sentinel node serving as the header.
        // Each node maintains two counts:  the number of items it holds, and
        // the number of containers.
        //
        // There are two kinds of blocks - one holding only "realized" items (i.e.
        // items that have been generated into containers) and one holding only
        // unrealized items.  The container count of a realized block is the same
        // as its item count (one container per item);  the container count of an
        // unrealized block is zero.
        //
        // Unrealized blocks can hold any number of items.  We only need to know
        // the count.  Realized blocks have a fixed-sized array (BlockSize) so
        // they hold up to that many items and their corresponding containers.  When
        // a realized block fills up, it inserts a new (empty) realized block into
        // the list and carries on.
        //
        // This data structure was chosen with virtualization in mind.  The typical
        // state is a long block of unrealized items (the ones that have scrolled
        // off the top), followed by a moderate number (<50?) of realized items
        // (the ones in view), followed by another long block of unrealized items
        // (the ones that have not yet scrolled into view).  So the list will contain
        // an unrealized block, followed by 3 or 4 realized blocks, followed by
        // another unrealized block.  Fewer than 10 blocks altogether, so linear
        // searching won't cost that much.  Thus we don't need a more sophisticated
        // data structure.  (If profiling reveals that we do, we can always replace
        // this one.  It's totally private to the ItemContainerGenerator and its
        // Generators.)
 
        // represents a block of items
        private class ItemBlock

        // represents a block of unrealized (ungenerated) items
        private class UnrealizedItemBlock : ItemBlock

        // represents a block of realized (generated) items
        private class RealizedItemBlock : ItemBlock

3つともprivateなのはItemContainerGeneratorのネストクラスだからですね。 概要コメントが長いですが、要約すると
アイテムとコンテナ間の関係性を管理する機能を提供します。またブロックは2種類(生成済ブロック / 未生成なブロック)存在しており、 目的としては仮想化を実現するために2種類のブロックが存在しています。 ちなみに生成済ブロックがRealizedItemBlock、未生成ブロックがUnrealizedItemBlockですね。

ItemBlockが提供している役割としてはItemBlock同士の繋がりを表現しています。 ソースを交えながら見ていきます。

       private class ItemBlock
        {
            public const int BlockSize = 16;
 
            public int ItemCount { get { return _count; } set { _count = value; } }
            public ItemBlock Prev { get { return _prev; } set { _prev = value; } }
            public ItemBlock Next { get { return _next; } set { _next = value; } }
 
            public virtual int ContainerCount { get { return Int32.MaxValue; } }
            public virtual DependencyObject ContainerAt(int index) { return null; }
            public virtual object ItemAt(int index) { return null; }

コレクション等ではなく自身で次の要素(ItemBlock)、前の要素(ItemBlock)の要素を持ち管理しています。 この管理のメリットとしては、イテレーションのように再帰処理などで柔軟に要素に対する処理が可能になることですね。

a - b - c - d
のうちcをRemoveをすると勿論、下記のようになります。 a - b - d

ちなみに実際のRemoveメソッドは

            public void Remove()
            {
                Prev.Next = Next;
                Next.Prev = Prev;
            }

となってます。前後の要素を自身で持っているので参照先を変えれば参照されなくなり、GCの対象となり解放されるという流れでしょうか。
非常にシンプルなロジックではありますが、普段はコレクションを使っているため.NETでも実際にどういう風に管理しているのかを改めて確認すると勉強になりますね。

ちなみに先ほどのabcdの流れで、生成済、非生成のItemBlockが並んでいるので呼び出し元の観点だと。

a(RealizedItemBlock) - b(RealizedItemBlock) - c(RealizedItemBlock) - d(UnrealizedItemBlock) となり、cまで生成済(スクロールバーなどの表示領域)、dは非表示領域にいるため生成されていないという状態がみえたりします。 この辺りはItemContainerGeneretarにて流れを書ければと思います。

というわけでは次の記事ではまたItemContainerGeneretarに戻ります。

Reference Sorceを読む。 ItemContainerGenerator - 1Page 

昨日のPanelの記事でちらほら出てきたItemContainerGeneratorについて今日は読んでいきます。

この記事の共通項目

共通的に出てくる列挙型を先に記載しておきます。
https://msdn.microsoft.com/ja-jp/library/system.windows.controls.primitives.generatorposition(v=vs.110).aspx
https://technet.microsoft.com/ja-jp/library/system.windows.controls.primitives.generatordirection.aspx

ItemContainerGenerator

Reference Source

概要

    /// <summary>
    /// An ItemContainerGenerator is responsible for generating the UI on behalf of
    /// its host (e.g. ItemsControl).  It maintains the association between the items in
    /// the control's data view and the corresponding
    /// UIElements.  The control's item-host can ask the ItemContainerGenerator for
    /// a Generator, which does the actual generation of UI.
    /// </summary>
    public sealed class ItemContainerGenerator : IRecyclingItemContainerGenerator, IWeakEventListener
    {

Panelの場合、実際のUI要素はこのジェネレータが生成しています。
規模が大きいので、大体の役割を理解するためにまずはインターフェース(IRecyclingItemContainerGenerator, IWeakEventListener)を確認しましょう。

IRecyclingItemContainerGenerator

Reference Source
概要

    /// <summary>
    ///     Interface through which a layout element (such as a panel) marked
    ///     as an ItemsHost communicates with the ItemContainerGenerator of its
    ///     items owner.
    ///     
    ///     This interface adds the notion of recycling a container to the 
    ///     IItemContainerGenerator interface.  This is used for virtualizing
    ///     panels.
    /// </summary>
    public interface IRecyclingItemContainerGenerator : IItemContainerGenerator

仮想化としてリサイクルを行うコンテナのインターフェースです。
継承元のIItemContainerGeneratorについては後ほど見ていきましょう。

インターフェース

  • void Recycle(GeneratorPosition position, int count);
    リサイクルする範囲を指定します。

IItemContainerGenerator

Reference Source

概要

    /// <summary>
    ///     Interface through which a layout element (such as a panel) marked
    ///     as an ItemsHost communicates with the ItemContainerGenerator of its
    ///     items owner.
    /// </summary>
    public interface IItemContainerGenerator

インターフェース

  • IRecyclingItemContainerGenerator ItemContainerGenerator GetItemContainerGeneratorForPanel(Panel panel);
    パネルを指定してジェネレータを返します。

  • IDisposable StartAt(GeneratorPosition position, GeneratorDirection direction);

  • IDisposable StartAt(GeneratorPosition position, GeneratorDirection direction, bool allowStartAtRealizedItem);
    指定された位置と方向からジェネレータの作成を開始します。 注意としてはGenerateNextを呼びだすまえにStartAtを呼び出す必要があり。 IDisposableを返却しているため呼び出し元でDisposeの管理を可能としています。

  • DependencyObject GenerateNext();
    次の項目を表示するための要素を返却します。初期位置や方向性はStartAtで指定された内容がベースとなります。
    そのため事前にStartAtメソッドをコールする必要あり。

  • DependencyObject GenerateNext(out bool isNewlyRealized);
    GenerateNext()のオーバーロード。違いとしてIsNewlyRealizedは次の要素があればTrue.なければFalseが設定されます。

  • void PrepareItemContainer(DependencyObject container);
    指定された要素がUIとして機能するように準備するメソッドです。 (スタイルや親要素からの情報伝達など)
    このメソッド単独での説明は分かりにくいので、大まかなライフサイクルとともに後ほど記載します。

  • void RemoveAll();
    全てのジェネレータ要素を削除します。

  • void Remove(GeneratorPosition position, int count);
    指定されたポジションから指定された要素分を削除します。

  • GeneratorPosition GeneratorPositionFromIndex(int itemIndex);
    itemのIndexを元にGeneratorPositionを返却します。

  • int IndexFromGeneratorPosition(GeneratorPosition position);
    GeneratorPositionからindexを返却します。

IWeakEventListener

Reference Source

概要

    /// <summary>
    /// A class should implement this interface if it needs to listen to
    /// events via the centralized event dispatcher of WeakEventManager.
    /// The event dispatcher forwards an event by calling the ReceiveWeakEvent
    /// method.
    /// </summary>
    /// <remarks>
    /// The principal reason for doing this is that the event source has a
    /// lifetime independent of the receiver.  Using the central event
    /// dispatching allows the receiver to be GC'd even if the source lives on.
    /// Whereas the normal event hookup causes the source to hold a reference
    /// to the receiver, thus keeping the receiver alive too long.
    /// </remarks>
    public interface IWeakEventListener
    {
        /// <summary>
        /// Handle events from the centralized event table.
        /// </summary>
        /// <returns>
        /// True if the listener handled the event.  It is an error to register
        /// a listener for an event that it does not handle.
        /// </returns>
        bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e);
    }

イベントを弱参照で管理するリスナーのインターフェースです。
超ざっくりいうと通常イベント(+=で登録するやつ)だと依存関係が強くなりガベージコレクションが出来ず、
メモリが解放されない可能性があるので弱参照でイベントを管理するリスナーを提供する機能のインターフェースです。

インターフェース

  • bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e);
    リスナーがイベントを処理した場合にtrueを返却します。

Reference Sorceを読む。 Panel - 2Page

Reference Sorceを勉強ついでに読み込んでいきます。
個人的な備忘録を兼ねた記事となります。

引き続きPanelの読み込みです。
まずは主要素であるChildrenを構成を確認します。

public UIElementCollection Children

        /// <summary>
        /// Returns a UIElementCollection of children for user to add/remove children manually
        /// Returns read-only collection if Panel is data-bound (no manual control of children is possible,
        /// the associated ItemsControl completely overrides children)
        /// Note: the derived Panel classes should never use this collection for
        /// internal purposes like in their MeasureOverride or ArrangeOverride.
        /// They should use InternalChildren instead, because InternalChildren
        /// is always present and either is a mirror of public Children collection (in case of Direct Panel)
        /// or is generated from data binding.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public UIElementCollection Children
        {
            get
            {
                //When we will change from UIElementCollection to IList<UIElement>, we might
                //consider returning a wrapper IList here which coudl be read-only for mutating methods
                //while INternalChildren could be R/W even in case of Generator attached.
                return InternalChildren;
            }
        }

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

この属性は列挙でVisible,Content,HIddenがあります。
MSDNを参照すると下記が表で記載されています。

https://msdn.microsoft.com/ja-jp/library/system.componentmodel.designerserializationvisibility(v=vs.110).aspx

メンバー名 説明
Content
コード ジェネレーターは、オブジェクト自体ではなく、オブジェクトの内容のコードを生成します。
Hidden
コード ジェネレーターは、オブジェクトのコードを生成しません。
Visible
コード ジェネレーターは、オブジェクトのコードを生成します。

Content とVisibleがわかりにくいですがシンプルな値であればVisibleを指定します(デフォルトはこれ)
ただし、TextBoxのFontなど複雑なクラスの情報を保持するものはContentを指定する必要があります。

protected internal UIElementCollection InternalChildren

Childrenプロパティの戻り値で指定しているInternalChildrenです。 呼び出しているメソッドも含めて確認します。

        /// <summary>
        /// Returns a UIElementCollection of children - added by user or generated from data binding.
        /// Panel-derived classes should use this collection for all internal purposes, including
        /// MeasureOverride/ArrangeOverride overrides.
        /// </summary>
        protected internal UIElementCollection InternalChildren
        {
            get
            {
                VerifyBoundState();
 
                if (IsItemsHost)
                {
                    EnsureGenerator();
                }
                else
                {
                    if (_uiElementCollection == null)
                    {
                        // First access on a regular panel
                        EnsureEmptyChildren(/* logicalParent = */ this);
                    }
                }
 
                return _uiElementCollection;
            }
        }

private bool VerifyBoundState()

アンバウンドになっていないかを確認し必要に応じて処理を行っています。 スタイルの切り替えやテーマの変更などによって不適切な状態になっていれば最適化するための処理を行っています。 まずはItemsHostの状態を維持できているかを確認し ItemsHostではないのにItemsのジェネレータがあるとクリアしています。 ItemsHostである場合にジェネレータがNullの場合、ClearChildrenメソッドにて状態をリセットしています。

       private bool VerifyBoundState()
        {
            // If the panel becomes "unbound" while attached to a generator, this
            // method detaches it and makes it really behave like "unbound."  This
            // can happen because of a style change, a theme change, etc. It returns
            // the correct "bound" state, after the dust has settled.
            //
            // This is really a workaround for a more general problem that the panel
            // needs to release resources (an event handler) when it is "out of the tree."
            // Currently, there is no good notification for when this happens.
 
            bool isItemsHost = (ItemsControl.GetItemsOwnerInternal(this) != null);
 
            if (isItemsHost)
            {
                if (_itemContainerGenerator == null)
                {
                    // Transitioning from being unbound to bound
                    ClearChildren();
                }
 
                return (_itemContainerGenerator != null);
            }
            else
            {
                if (_itemContainerGenerator != null)
                {
                    // Transitioning from being bound to unbound
                    DisconnectFromGenerator();
                    ClearChildren();
                }
 
                return false;
            }
        }

EnsureGenerator

InternalChildren取得時にIsItemsHost=trueだとコールされます。 直訳すると"確実に生成する” アイテムコンテナーのジェネレータがNullの場合は再生成します。

        internal void EnsureGenerator()
        {
            Debug.Assert(IsItemsHost, "Should be invoked only on an ItemsHost panel");
 
            if (_itemContainerGenerator == null)
            {
                // First access on an items presenter panel
                ConnectToGenerator();
 
                // Children of this panel should not have their logical parent reset
                EnsureEmptyChildren(/* logicalParent = */ null);
 
                GenerateChildren();
            }
        }

EnsureEmptyChildren

IsItemsHost取得時にIsItemsHost=falseだとコールされます。 uiElementCollectionが空、もしくは論理的な親が自分はない場合にuiElementCollectionを再生成しています。

        private void EnsureEmptyChildren(FrameworkElement logicalParent)
        {
            if ((_uiElementCollection == null) || (_uiElementCollection.LogicalParent != logicalParent))
            {
                _uiElementCollection = CreateUIElementCollection(logicalParent);
            }
            else
            {
                ClearChildren();
            }
        }

今日はここまで。

Reference Sorceを読む。 Panel - 1Page

Reference Sorceを勉強ついでに読み込んでいきます。
個人的な備忘録を兼ねた記事となります。

Panel

よく使うGridやStackPanelのベースとなっているPanelクラス。

Reference Source

概要

まずはクラスの概要から。

    /// <summary>
    ///     Base class for all layout panels.
    /// </summary>
    [Localizability(LocalizationCategory.Ignore)]
    [ContentProperty("Children")]
    public abstract class Panel : FrameworkElement, IAddChild

abstractの定義なのであくまでベースクラス。
コメントでも書いてますがパネルのベースクラスとなります。
パネル(枠)なので提供する機能としてはやはり子要素の操作・保持が主目的です。
LocalizationCategory.Ignoreなのでローカライゼーションは不要。
ContentPropertyにChildrenを指定しているため、Xamlで指定した内容はChildrenに格納されています。
IAddChildのインターフェースの役割も指定していますね。

<StackPanel>
  <Label>AAAA</Label>
  <Label>BBBB</Label>
</StackPanel>

主要素

Panelの子要素の操作・保持がメインですので主な要素をピックアップ

public UIElementCollection Children
public bool ShouldSerializeChildren()
public static readonly DependencyProperty IsItemsHostProperty
public bool IsItemsHost
protected internal UIElementCollection InternalChildren
protected override int VisualChildrenCount
protected override Visual GetVisualChild(int index)
void IAddChild.AddChild (Object value)
private void ClearChildren()
protected override void OnRender(DrawingContext dc)
protected internal override IEnumerator LogicalChildren

次回はさらに主要素の掘り下げの作業にしたいと思います。

"JXUGC #24 春の App Center 祭り"に参加しました。

"JXUGC #24 春の App Center 祭り"に参加しました!

JXUGC #24 春の App Center 祭り - connpassに参加してきました!

AppCenterがメインテーマでした。
勉強会に参加するまえは、もやっとVisual Studio Mobile Center と同程度の機能かな?という知識しかありませんでしたが、
予想以上にインパクトのあるサービスでした。

イベントの内容については、加藤さんのイベントレポートが綺麗にまとめられていますので是非ご参考ください!
[イベントレポート] 「JXUGC #24 春のApp Center祭り」に参加してきました! #JXUG #VSAppCenter | Developers.IO

試してみました!

ということで、さっそくですが勉強会後に自作のアプリに適応してみました。
ハンズオンでも実感できましたがすごく簡単に設定できます。

登録

1. AppCenterにログインするとダッシュボードが表示されます。
[AddNewApp]を選択します。
f:id:furugen098:20180122234452p:plain

2. アプリ追加画面が開きますので入力します。
XamarinですがiOS側のみ作っているのでiOSを選択しAddNewAppボタンをクリック。
f:id:furugen098:20180122234834p:plain

3. アプリが登録されたら下記の画面に遷移します!
f:id:furugen098:20180122235416p:plain

ビルド設定

ビルドの設定も非常に簡単にできます。

1. ビルド画面(サイドバーの再生マークをクリック)を開きます。
TSTS、GIthub、Bitbuketが選択できます。今回はBitbucketを選択します。
f:id:furugen098:20180122235624p:plain


2. 認証画面が表示されますのでアカウント連携の許可を選択します。
f:id:furugen098:20180123000024p:plain

3.管理しているリポジトリの一覧が表示されますので連携したいリポジトリを選択します。
f:id:furugen098:20180122235324p:plain

4.リポジトリが追加されました。
f:id:furugen098:20180123000315p:plain

5. 設定ボタン(歯車)をクリックしてビルド環境を設定します。
対象ソリューションやプロジェクトの選択、いくつかのオプションを設定します。
さくっと試したい方はデフォルト設定で問題ないかと。
f:id:furugen098:20180123000422p:plain

6.設定を保存するとビルドキュー画面に遷移します。
この手順だけでBitbucketにプッシュされたら自動ビルドの連携までされます。超便利!
f:id:furugen098:20180123000929p:plain

ビルドのログをリアルタイムでの確認や
f:id:furugen098:20180123000957p:plain

今までのビルドジョブの状況が参照できます。
f:id:furugen098:20180123001011p:plain

アナライズ設定

1. アナライズ用のコードを下記から取得します。
f:id:furugen098:20180123002258p:plain

f:id:furugen098:20180123004207p:plain

2. AppCenterのパッケージを追加
f:id:furugen098:20180123002338p:plain

f:id:furugen098:20180123004459p:plain

3. 1の解析用コードをコピペすればOK
f:id:furugen098:20180123004539p:plain

4. アナライズの画面でデータが解析できることを確認します。
他、各イベントや情報など様々な情報が解析できます。
詳細はAtsushi Nakamuraさんの資料App center analyticsを使い倒そうに綺麗にまとめられてます!
f:id:furugen098:20180123005328p:plain

Slack連携

マネージドされているSlack連携も試して見ました。

1. 設定画面からWebhooksを選択しSlackをクリックします。
f:id:furugen098:20180123001531p:plain
2. 認証画面が表示されるのでAdd Configurationをクリックします。
f:id:furugen098:20180123001647p:plain
3. Slackの通知チャネルを選択します。
f:id:furugen098:20180123001703p:plain
4. Webhook URLをコピーします。
f:id:furugen098:20180123001724p:plain
5. AppCenterの設定画面に戻りWebhooksのNew Webhookをクリックします。
設定画面で4.でコピーしたWebhookURLをペーストします。
f:id:furugen098:20180123001743p:plain
6.テスト送信でSlackに連携されることを確認すればOK
f:id:furugen098:20180123001805p:plain

まとめ

AppCenterはJenkinsやGoogle Anticsのような既存の実現手段もありますが、とにかく手軽に実践できるといった印象です。
マネージドでここまで簡単に設定できるのはかなり嬉しいですね。使いこなすにはカスタマイズが必要ですが後々試していきたいと思います。
値段もテストの部分のみが有料なのでビルドやアナライズ機能は無料ですので気軽に試せます。
またMicrosoftテクノロジーとの相性は抜群ですし是非気軽にためしてみてください!

WPFパフォーマンスチューニングまとめ 2018年メモ

パフォーマンスチューニングする機会があったのでメモ。

この記事がまとまってますね。
WPFパフォーマンス関連の記事まとめ - Qiita

チューニングツール

高性能なVisualStudioのパフォーマンス解析ツール

New UI Performance Analysis Tool for WPF Applications | WPF Team Blog

Snoop

定番ツール、かなり使い易いです。
Snoop, the WPF Spy Utility - Home


メモリリークを調査

WinDbg

Windows デバッガー (WinDbg) ツールのダウンロード - Windows ハードウェア デベロッパー センター
正直、とっつきにくいですが使ってみるとかなり強力なツールです。
あとはVisualStudioの標準の診断ツールを利用する方でも良いかと思います。