選択情報を持った、一つだけ選択できるCollection

コントローラを使用すれば一つのみ選択するということも簡単にできますが、それをC#コード上でやりたいケースがありました。MVVMで言うModel内で排他選択を行う方法。

そもそも、こういう風にコレクションを扱っていいのかわからないのだけど、親と子のクラスがあってコレクションの追加を親クラスを通して行う。ただし、選択状態は子ノードから変更できるようにしたい。

子ノード Item

namespace TestApp.Models
{
    using System;
    using System.Collections.ObjectModel;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using Livet;

    /// <summary>
    /// 既存モデル単体の情報
    /// </summary>
    public class MovieFileItem : NotificationObject
    {
        public MovieFileItem(MovieFile parent)
        {
            unselectItem = parent.UnselectItem;
        }

        /// <summary>
        /// 兄弟アイテムの選択を削除
        /// </summary>
        public Action unselectItem = null;

        /// <summary>
        /// 選択されているかどうか
        /// </summary>
        private bool isSelected = true;

        /// <summary>
        /// 選択されているかどうか
        /// </summary>
        public bool IsSelected
        {
            get
            {
                return isSelected;
            }
            set
            {
                if(value && !(isSelected))
                {
                    // 新たに選択する場合は削除
                    if(unselectItem != null)
                        unselectItem();
                }
                isSelected = value;
                RaisePropertyChanged(() => IsSelected);
            }
        }

    }
}


親ノード

namespace TestApp.Models
{
    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Linq.Expressions;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using Livet;

    /// <summary>
    /// 既存モデル単体の情報
    /// </summary>
    public class MovieFile : ObservableCollection<MovieFileItem>, INotifyPropertyChanged
    {
        public MovieFile()
        {
        }


        #region 選択

        /// <summary>
        /// 全て選択解除する
        /// </summary>
        public void UnselectItem()
        {
            foreach(var item in this)
            {
                item.IsSelected = false;
            }
            RaisePropertyChanged(() => SelectedItem);
        }

        /// <summary>
        /// 指定されたアイテムを選択する
        /// </summary>
        /// <param name="selectItem">選択したいアイテム</param>
        public void SelectItem(MovieFileItem selectItem)
        {
            foreach(var item in this)
            {
                item.IsSelected = false;
            }

            selectItem.IsSelected = true;
            RaisePropertyChanged(() => SelectedItem);
        }

        /// <summary>
        /// 選択されたアイテム
        /// </summary>
        private MovieFileItem selectedItem = null;

        /// <summary>
        /// 選択されたアイテム
        /// </summary>
        public MovieFileItem SelectedItem
        {
            get
            {
                return selectedItem;
            }
            set
            {
                selectedItem = value;
                RaisePropertyChanged(() => SelectedItem);
            }
        }

        #endregion // 選択

        #region ObservableCollection override メンバー

        protected override void ClearItems()
        {
            SelectedItem = null;

            base.ClearItems();
        }

        protected override void RemoveItem(int index)
        {
            if(this[index].IsSelected)
            {
                SelectedItem = null;
            }

            base.RemoveItem(index);
        }

        protected override void SetItem(int index, MovieFileItem item)
        {
            if(this[index].IsSelected)
            {
                SelectedItem = null;
            }

            base.SetItem(index, item);
        }

        #endregion ObservableCollection override メンバー

        #region INotifyPropertyChanged メンバー

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
        
        protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
        {
            // Livetの実装
            if (propertyExpression == null) 
                throw new ArgumentNullException("propertyExpression");
            if (!(propertyExpression.Body is MemberExpression)) 
                throw new NotSupportedException("このメソッドでは ()=>プロパティ の形式のラムダ式以外許可されません");
            var memberExpression = (MemberExpression)propertyExpression.Body;
            RaisePropertyChanged(memberExpression.Member.Name);
        }
        
        #endregion
    }
}

更新通知のRaisePropertyChangedはLivetを使用。親ノードの更新通知はLivetコードをコピペ。

OpenFileDialogのCustomPlaceとDefaultExt

WPFでファイルを開くダイアログを出す場合は、Microsoft.Win32名前空間にあるOpenFIleDialogクラスを使用します。

OpenFileDialog クラス (Microsoft.Win32)

これはWindows FormsのSystem.Windows.Forms.OpenFileDialogとよく似ているのですが、CustomPlaceというプロパティが追加されており、左メニューの中に一時的なショートカットを置くことができます。Visual Studioでも使われています。

そして、DefaultExtですが、Windows.Formsの時と使い方が違うようで、FilterIndexにFilterプロパティで指定したリストのインデックスを代入しなくてはいけないようです。後に示す例ではUseDefaultExtAsFilterIndexメソッドにてDefaultExtとFilterからFilterIndexを指定しています。
DefaultExtには代入ができるんだけど、ShowDialogすると効いてないようなので困りました。

以下、こんな感じで使っています。

public static class ImageFileDialog
{
    /// <summary>
    /// 画像ファイルを開くダイアログを表示
    /// </summary>
    /// <param name="filePath">得られたファイルパス</param>
    /// <param name="defaultPath">ダイアログを開いた時のパス</param>
    /// <returns>処理の可否</returns>
    public static bool OpenImageDialog(out string filePath, string defaultPath = "")
    {
        // デフォルト値
        filePath = "";

        // ファイルパスを取得
        OpenFileDialog dialog = new OpenFileDialog();
        string[] filters = new string[]
            {
                "Image Files(*.png;*.jpg;*.jpeg;*.tif;*.tiff;*.bmp;*.gif)|*.png;*.jpg;*.jpeg;*.tif;*.tiff;*.bmp;*.gif",
                "All files(*.*)|*.*"
            };
        dialog.Filter = String.Join("|", filters);
        dialog.Title = "Open Image File";
        dialog.DefaultExt = ".png";
        UseDefaultExtAsFilterIndex(dialog, filters);

        // カスタムプレース
        dialog.CustomPlaces = CustomPlace;

        // デフォルトパスを設定
        if(defaultPath != "")
        {
            string getDirectoryName = Path.GetDirectoryName(defaultPath);
            if(Directory.Exists(defaultPath))
                dialog.InitialDirectory = defaultPath;
            else if(Directory.Exists(getDirectoryName))
                dialog.InitialDirectory = getDirectoryName;
        }

        bool? resultDialog = dialog.ShowDialog();
        if(!(resultDialog ?? false))
            return false;

        // ファイルパスを設定
        filePath = dialog.FileName;

        return true;
    }

    private static IList<FileDialogCustomPlace> customPlace = null;

    public static IList<FileDialogCustomPlace> CustomPlace
    {
        get
        {
            if (customPlace == null)
            {
                customPlace = new FileDialogCustomPlace[1]
                {
                    new FileDialogCustomPlace(@"C:\Users\Public\Documents")
                };
            }
            return customPlace;
        }
    }
    
    /// <summary>
    /// ダイアログでデフォルト拡張子を指定する
    /// </summary>
    /// <param name="dialog">ダイアログ</param>
    /// <param name="filters">フィルタ文字列配列</param>
    private static void UseDefaultExtAsFilterIndex(FileDialog dialog, string[] filters)
    {
        string ext = "." + dialog.DefaultExt;
        if (ext == string.Empty)
            return;

        for(int i = 0; i < filters.Length; i++)
        {
            string flt = filters[i];

            string[] lets = flt.Split('|');
            if (lets.Length != 2)
                continue;
            string extsemi = lets[1];

            string[] extarrays = extsemi.Split(';');

            bool existExt = false;
            for (int j = 0; j < extarrays.Length; j++)
            {
                if (extarrays[j].EndsWith(ext))
                {
                    existExt = true;
                    break;
                }
            }

            if (existExt)
            {
                dialog.FilterIndex = i + 1;
                break;
            }
        }
    }
}

文字化け対処(未解決)

TracLightningを利用してJenkinsで.Netアプリケーションの自動ビルドを試しています。その過程でGitのChange Logで文字化けが起こってしまい、どうしても直らない。

以下のサイトを参考に修正してみました。

Windows を Jenkins の Slave にして Git管理してる Rspec のテストを実行するためのセットアップ手順 - Qiita

[1] 以下の環境変数をシステムに追加する。


JAVA_TOOL_OPTIONS
-Dfile.encoding=UTF8

[2] Jenkinsの起動Argumentsにエンコード指定を追加する。

C:\TracLight\jenkins\jenkins.xml
の行の後ろにスペースを空けて以下を追加

 -Dfile.encoding=UTF-8

[3] サービスから再起動

TracLightning(Jenkins)を再起動する。


「Jenkinsの管理」−「システム情報」からエンコード設定がUTF8になっているかどうか確認。

なぜか、どちらかではダメでした。[1]だけではServer Errorになり、[2]だけでは何も変化なし。

一応、Gitのログは直った。

しかし、今度は「コンソール出力」が文字化けする。ただし、過去のコンソール出力は文字化けしていない。

上のやり方では、JAVAをUTF-8に対応するようにしているようだが、コンソール出力の文字列はMSBuildのログそのままなのでShift-JISか何かになっている様子。過去のものはShift-JIS同士(Jenkins - MSBuild)で受け取った後にDBに保存しているので問題ないということか。MSBuildの出力をUTF-8に変えるか、GitのログをShift-JISに変換するかどちらかになるかとは思うが、やり方は調査中。

Colorsクラス( System.Windows.Media.Colors ) 規定の色の色見本

Colorsクラス( System.Windows.Media.Colors ) 規定の色の色見本

http://msdn.microsoft.com/ja-jp/library/system.windows.media.colors%28v=vs.110%29.aspx

作って後に気づいたけど、System.Drawing.Colorと同じらしい。

Windows Presentation Foundation (WPF) の色名は、Microsoft .NET Framework Version 1.0、Windows フォーム、および Microsoft Internet Explorer の色名と一致します。 この表現は、UNIX X11 で使用される名前付きの色の値に基づいています。


â– â– â– â– â–  AliceBlue
â– â– â– â– â–  AntiqueWhite
â– â– â– â– â–  Aqua
â– â– â– â– â–  Aquamarine
â– â– â– â– â–  Azure
â– â– â– â– â–  Beige
â– â– â– â– â–  Bisque
â– â– â– â– â–  Black
â– â– â– â– â–  BlanchedAlmond
â– â– â– â– â–  Blue
â– â– â– â– â–  BlueViolet
â– â– â– â– â–  Brown
â– â– â– â– â–  BurlyWood
â– â– â– â– â–  CadetBlue
â– â– â– â– â–  Chartreuse
â– â– â– â– â–  Chocolate
â– â– â– â– â–  Coral
â– â– â– â– â–  CornflowerBlue
â– â– â– â– â–  Cornsilk
â– â– â– â– â–  Crimson
â– â– â– â– â–  Cyan
â– â– â– â– â–  DarkBlue
â– â– â– â– â–  DarkCyan
â– â– â– â– â–  DarkGoldenrod
â– â– â– â– â–  DarkGray
â– â– â– â– â–  DarkGreen
â– â– â– â– â–  DarkKhaki
â– â– â– â– â–  DarkMagenta
â– â– â– â– â–  DarkOliveGreen
â– â– â– â– â–  DarkOrange
â– â– â– â– â–  DarkOrchid
â– â– â– â– â–  DarkRed
â– â– â– â– â–  DarkSalmon
â– â– â– â– â–  DarkSeaGreen
â– â– â– â– â–  DarkSlateBlue
â– â– â– â– â–  DarkSlateGray
â– â– â– â– â–  DarkTurquoise
â– â– â– â– â–  DarkViolet
â– â– â– â– â–  DeepPink
â– â– â– â– â–  DeepSkyBlue
â– â– â– â– â–  DimGray
â– â– â– â– â–  DodgerBlue
â– â– â– â– â–  Firebrick
â– â– â– â– â–  FloralWhite
â– â– â– â– â–  ForestGreen
â– â– â– â– â–  Fuchsia
â– â– â– â– â–  Gainsboro
â– â– â– â– â–  GhostWhite
â– â– â– â– â–  Gold
â– â– â– â– â–  Goldenrod
â– â– â– â– â–  Gray
â– â– â– â– â–  Green
â– â– â– â– â–  GreenYellow
â– â– â– â– â–  Honeydew
â– â– â– â– â–  HotPink
â– â– â– â– â–  IndianRed
â– â– â– â– â–  Indigo
â– â– â– â– â–  Ivory
â– â– â– â– â–  Khaki
â– â– â– â– â–  Lavender
â– â– â– â– â–  LavenderBlush
â– â– â– â– â–  LawnGreen
â– â– â– â– â–  LemonChiffon
â– â– â– â– â–  LightBlue
â– â– â– â– â–  LightCoral
â– â– â– â– â–  LightCyan
â– â– â– â– â–  LightGoldenrodYellow
â– â– â– â– â–  LightGray
â– â– â– â– â–  LightGreen
â– â– â– â– â–  LightPink
â– â– â– â– â–  LightSalmon
â– â– â– â– â–  LightSeaGreen
â– â– â– â– â–  LightSkyBlue
â– â– â– â– â–  LightSlateGray
â– â– â– â– â–  LightSteelBlue
â– â– â– â– â–  LightYellow
â– â– â– â– â–  Lime
â– â– â– â– â–  LimeGreen
â– â– â– â– â–  Linen
â– â– â– â– â–  Magenta
â– â– â– â– â–  Maroon
â– â– â– â– â–  MediumAquamarine
â– â– â– â– â–  MediumBlue
â– â– â– â– â–  MediumOrchid
â– â– â– â– â–  MediumPurple
â– â– â– â– â–  MediumSeaGreen
â– â– â– â– â–  MediumSlateBlue
â– â– â– â– â–  MediumSpringGreen
â– â– â– â– â–  MediumTurquoise
â– â– â– â– â–  MediumVioletRed
â– â– â– â– â–  MidnightBlue
â– â– â– â– â–  MintCream
â– â– â– â– â–  MistyRose
â– â– â– â– â–  Moccasin
â– â– â– â– â–  NavajoWhite
â– â– â– â– â–  Navy
â– â– â– â– â–  OldLace
â– â– â– â– â–  Olive
â– â– â– â– â–  OliveDrab
â– â– â– â– â–  Orange
â– â– â– â– â–  OrangeRed
â– â– â– â– â–  Orchid
â– â– â– â– â–  PaleGoldenrod
â– â– â– â– â–  PaleGreen
â– â– â– â– â–  PaleTurquoise
â– â– â– â– â–  PaleVioletRed
â– â– â– â– â–  PapayaWhip
â– â– â– â– â–  PeachPuff
â– â– â– â– â–  Peru
â– â– â– â– â–  Pink
â– â– â– â– â–  Plum
â– â– â– â– â–  PowderBlue
â– â– â– â– â–  Purple
â– â– â– â– â–  Red
â– â– â– â– â–  RosyBrown
â– â– â– â– â–  RoyalBlue
â– â– â– â– â–  SaddleBrown
â– â– â– â– â–  Salmon
â– â– â– â– â–  SandyBrown
â– â– â– â– â–  SeaGreen
â– â– â– â– â–  SeaShell
â– â– â– â– â–  Sienna
â– â– â– â– â–  Silver
â– â– â– â– â–  SkyBlue
â– â– â– â– â–  SlateBlue
â– â– â– â– â–  SlateGray
â– â– â– â– â–  Snow
â– â– â– â– â–  SpringGreen
â– â– â– â– â–  SteelBlue
â– â– â– â– â–  Tan
â– â– â– â– â–  Teal
â– â– â– â– â–  Thistle
â– â– â– â– â–  Tomato
â– â– â– â– â–  Transparent
â– â– â– â– â–  Turquoise
â– â– â– â– â–  Violet
â– â– â– â– â–  Wheat
â– â– â– â– â–  White
â– â– â– â– â–  WhiteSmoke
â– â– â– â– â–  Yellow
â– â– â– â– â–  YellowGreen

Color構造体( System.Drawing.Color ) 規定の色の色見本

Color構造体( System.Drawing.Color ) 規定の色の色見本

http://msdn.microsoft.com/ja-jp/library/system.drawing.color%28v=vs.110%29.aspx

■■■■■ AliceBlue
■■■■■ AntiqueWhite
■■■■■ Aqua
■■■■■ Aquamarine
■■■■■ Azure
■■■■■ Beige
■■■■■ Bisque
■■■■■ Black
■■■■■ BlanchedAlmond
■■■■■ Blue
■■■■■ BlueViolet
■■■■■ Brown
■■■■■ BurlyWood
■■■■■ CadetBlue
■■■■■ Chartreuse
■■■■■ Chocolate
■■■■■ Coral
■■■■■ CornflowerBlue
■■■■■ Cornsilk
■■■■■ Crimson
■■■■■ Cyan
■■■■■ DarkBlue
■■■■■ DarkCyan
■■■■■ DarkGoldenrod
■■■■■ DarkGray
■■■■■ DarkGreen
■■■■■ DarkKhaki
■■■■■ DarkMagenta
■■■■■ DarkOliveGreen
■■■■■ DarkOrange
■■■■■ DarkOrchid
■■■■■ DarkRed
■■■■■ DarkSalmon
■■■■■ DarkSeaGreen
■■■■■ DarkSlateBlue
■■■■■ DarkSlateGray
■■■■■ DarkTurquoise
■■■■■ DarkViolet
■■■■■ DeepPink
■■■■■ DeepSkyBlue
■■■■■ DimGray
■■■■■ DodgerBlue
■■■■■ Firebrick
■■■■■ FloralWhite
■■■■■ ForestGreen
■■■■■ Fuchsia
■■■■■ Gainsboro
■■■■■ GhostWhite
■■■■■ Gold
■■■■■ Goldenrod
■■■■■ Gray
■■■■■ Green
■■■■■ GreenYellow
■■■■■ Honeydew
■■■■■ HotPink
■■■■■ IndianRed
■■■■■ Indigo
■■■■■ Ivory
■■■■■ Khaki
■■■■■ Lavender
■■■■■ LavenderBlush
■■■■■ LawnGreen
■■■■■ LemonChiffon
■■■■■ LightBlue
■■■■■ LightCoral
■■■■■ LightCyan
■■■■■ LightGoldenrodYellow
■■■■■ LightGray
■■■■■ LightGreen
■■■■■ LightPink
■■■■■ LightSalmon
■■■■■ LightSeaGreen
■■■■■ LightSkyBlue
■■■■■ LightSlateGray
■■■■■ LightSteelBlue
■■■■■ LightYellow
■■■■■ Lime
■■■■■ LimeGreen
■■■■■ Linen
■■■■■ Magenta
■■■■■ Maroon
■■■■■ MediumAquamarine
■■■■■ MediumBlue
■■■■■ MediumOrchid
■■■■■ MediumPurple
■■■■■ MediumSeaGreen
■■■■■ MediumSlateBlue
■■■■■ MediumSpringGreen
■■■■■ MediumTurquoise
■■■■■ MediumVioletRed
■■■■■ MidnightBlue
■■■■■ MintCream
■■■■■ MistyRose
■■■■■ Moccasin
■■■■■ NavajoWhite
■■■■■ Navy
■■■■■ OldLace
■■■■■ Olive
■■■■■ OliveDrab
■■■■■ Orange
■■■■■ OrangeRed
■■■■■ Orchid
■■■■■ PaleGoldenrod
■■■■■ PaleGreen
■■■■■ PaleTurquoise
■■■■■ PaleVioletRed
■■■■■ PapayaWhip
■■■■■ PeachPuff
■■■■■ Peru
■■■■■ Pink
■■■■■ Plum
■■■■■ PowderBlue
■■■■■ Purple
■■■■■ Red
■■■■■ RosyBrown
■■■■■ RoyalBlue
■■■■■ SaddleBrown
■■■■■ Salmon
■■■■■ SandyBrown
■■■■■ SeaGreen
■■■■■ SeaShell
■■■■■ Sienna
■■■■■ Silver
■■■■■ SkyBlue
■■■■■ SlateBlue
■■■■■ SlateGray
■■■■■ Snow
■■■■■ SpringGreen
■■■■■ SteelBlue
■■■■■ Tan
■■■■■ Teal
■■■■■ Thistle
■■■■■ Tomato
■■■■■ Turquoise
■■■■■ Violet
■■■■■ Wheat
■■■■■ White
■■■■■ WhiteSmoke
■■■■■ Yellow
■■■■■ YellowGreen

2種類のColor構造体のシリアライズとstring変換

C#には二種類のColor構造体があり、情報にたどり着くのが手間だったのでまとめてみました。
以下、usingを使用していないので少し冗長なサンプルコードになっていますが、実際はusing System.Windows.Mediaを使ってます。

System.Drawing.Color 構造体
http://msdn.microsoft.com/query/dev12.query?appId=Dev12IDEF1&l=JA-JP&k=k%28System.Drawing.Color%29;k%28TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5%29;k%28DevLang-csharp%29&rd=true

System.Windows.Media.Color 構造体
http://msdn.microsoft.com/query/dev12.query?appId=Dev12IDEF1&l=JA-JP&k=k%28System.Windows.Media.Color%29;k%28TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5%29;k%28DevLang-csharp%29&rd=true

従来のWindows Formsアプリケーション等で使用されているColor構造体であるSystem.Drawing.Colorとstring型との相互変換

// 従来の Color <-> string 変換
System.Drawing.Color winColor = System.Drawing.Color.FromArgb(255, 255, 0, 0);
// to String
string winColorString = winColor.ToString();
// from String
System.Drawing.Color winColorSave = System.Drawing.ColorTranslator.FromHtml(winColorString);

WPFで使用されるColor構造体であるSystem.Windows.Media.Colorとstring型との相互変換

// WPFの Color <-> string 変換
System.Windows.Media.Color wpfColor = System.Windows.Media.Colors.Red;
// to String
string wpfColorString = wpfColor.ToString();
// from String
System.Windows.Media.Color wpfColorSave = 
    (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(wpfColorString);

Colorクラスはダイレクトにシリアライズすることが出来ませんが、これを使用すると、アクセサ内でstring型に変換するプロパティを作成してシリアライズすることが出来ます。シリアライズの時のみ使用して、普段はColor構造体を直接操作すれば処理オーバーヘッド無し。

以下のサイトをそのまま参考にしました。K.Takaokaさんのコードです。
https://social.msdn.microsoft.com/Forums/ja-JP/de34aa14-ea7b-4190-88a9-6cba0c7680ef/colorxmlserializer?forum=csharpexpressja
どうでもいいけど、K.Takaokaさんの回答がベストアンサーになっていないのがかわいそう。

[field:NonSerialized]
private System.Windows.Media.Color winColor = System.Windows.Media.Colors.Red;

//[DataMember] // エラーが出る(実際にやってみると出来たりするのが謎)
[XmlIgnore]
public System.Windows.Media.Color WinColor
{
    get
    {
        return winColor;
    }
    set
    {
        winColor = value;
    }
}

[DataMember]
[XmlElement]
public string WinColorString
{
    get
    {
        return WinColor.ToString();
    }
    set
    {
        WinColor = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(value); 
    }
}

ツリー構造クラスの自作

C#ではList, ObservableCollectionなど便利なクラスが標準で提供されていますが、ツリー構造はありません。

親切な方が以下のツリーコレクションを用意していたので、ダウンロードして使ってみたところ、かなり高機能なのですが継承して使用するにはasでキャストしたりして面倒で、やっぱり自分で実装した方が良いということで、必要最小限のツリー構造クラスを作成してみました。コメントはVisual Studioの記法です。

http://www.codeproject.com/Articles/12476/A-Generic-Tree-Collection

まずはインターフェースITreeNode.cs(ITreeNode)

/// <summary>
/// ツリー構造のインターフェース
/// </summary>
public interface ITreeNode<T>
{
    T Parent { get; set; }
    IList<T> Children { get; set; }

    T AddChild(T child);
    T RemoveChild(T child);
    bool TryRemoveChild(T child);
    T ClearChildren();
    bool TryRemoveOwn();
    T RemoveOwn();
}

その実装クラスTreeNodeBase.cs(TreeNodeBase)

/// <summary>
/// 簡易ツリー構造のジェネリッククラス
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class TreeNodeBase<T> : ITreeNode<TreeNodeBase<T>>  where T : class
{
    /// <summary>
    /// 親への参照フィールド
    /// </summary>
    protected TreeNodeBase<T> parent = null;

    /// <summary>
    /// 親への参照プロパティ
    /// </summary>
    public virtual TreeNodeBase<T> Parent
    {
        get
        {
            return parent;
        }
        set
        {
            parent = value;
        }
    }

    /// <summary>
    /// 子ノードのリストフィールド
    /// </summary>
    protected IList<TreeNodeBase<T>> children = null;

    /// <summary>
    /// 子ノードのリストプロパティ
    /// </summary>
    public virtual IList<TreeNodeBase<T>> Children
    {
        get
        {
            if (children == null)
                children = new List<TreeNodeBase<T>>();
            return children;
        }
        set
        {
            children = value;
        }
    }

    /// <summary>
    /// 子ノードを追加する。
    /// </summary>
    /// <param name="child">追加したいノード</param>
    /// <returns>追加後のオブジェクト</returns>
    public virtual TreeNodeBase<T> AddChild(TreeNodeBase<T> child)
    {
        if (child == null)
            throw new ArgumentNullException("Adding tree child is null.");

        this.Children.Add(child);
        child.Parent = this;

        return this;
    }

    /// <summary>
    /// 子ノードを削除する。
    /// </summary>
    /// <param name="child">削除したいノード</param>
    /// <returns>削除後のオブジェクト</returns>
    public virtual TreeNodeBase<T> RemoveChild(TreeNodeBase<T> child)
    {
        this.Children.Remove(child);
        return this;
    }

    /// <summary>
    /// 子ノードを削除する。
    /// </summary>
    /// <param name="child">削除したいノード</param>
    /// <returns>削除の可否</returns>
    public virtual bool TryRemoveChild(TreeNodeBase<T> child)
    {
        return this.Children.Remove(child);
    }

    /// <summary>
    /// 子ノードを全て削除する。
    /// </summary>
    /// <returns>子ノードを全削除後のオブジェクト</returns>
    public virtual TreeNodeBase<T> ClearChildren()
    {
        this.Children.Clear();
        return this;
    }

    /// <summary>
    /// 自身のノードを親ツリーから削除する。
    /// </summary>
    /// <returns>親のオブジェクト</returns>
    public virtual TreeNodeBase<T> RemoveOwn()
    {
        TreeNodeBase<T> parent = this.Parent;
        parent.RemoveChild(this);
        return parent;
    }

    /// <summary>
    /// 自身のノードを親ツリーから削除する。
    /// </summary>
    /// <returns>削除の可否</returns>
    public virtual bool TryRemoveOwn()
    {
        TreeNodeBase<T> parent = this.Parent;
        return parent.TryRemoveChild(this);
    }

}

通知型のツリー構造を作成する場合は

Observableなクラスを作成したい場合は、このクラスを継承してプロパティをoverrideするか、直接ITreeNodeを継承して作ってもいいです。

注意はコレクションプロパティの継承で、子ノードを格納するChildrenはObservableCollectionで宣言することが出来ないので、定義する際に自分でListかどちらかをnewして代入して使用することになります。