ガントチャート作成日記

久しぶりにガントチャート関連の記事を作成。
以前の日記でも書いたが、簡単にガントチャートを作る方法として、
Canvasを使わずにGridを利用する方法を試している。

データ件数が多くなると、遅くなると思われるが500件程度であれば何とか使えるレベルのものを作れると信じて色々やってみているが、前回まで作ったものは件数が多いとちょっと遅くなるので復習と処理速度の改善ができないかをゆっくり考えながら一から作ってみようと思う。

イメージとしてはGridコントロールに独自で作ったGanntCellインスタンスをバインドしてデータを表示する。
GanntCellはGridコントロールにバインドするためのクラス。
行、列、タスクを表現するためには後でDataTemplateを切り替えるようにする。

まずは、明細部分の表示として、ユーザーコントロール(GanttGrid.xml)を作成する。
今回はとりあえず、ベースのコントロールを作るところまで。
次回があれば、データをバインドするところまでやりたい。



---- <GanttGrid.xaml> ----
<UserControl x:Class="GanntChart.GanntGrid.GanttGrid"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:GanntChart.GanntGrid"
             xmlns:helper="clr-namespace:GanntChart.GanntGrid.helper"
             xmlns:data="clr-namespace:GanntChart.GanntGrid.data"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
 
    <UserControl.Resources>
        <data:GanntGridViewModel x:Key="GanntGridVM" />
    </UserControl.Resources>


    <UserControl.DataContext>
        <Binding Mode="OneWay" Source="{StaticResource GanntGridVM}"/>
    </UserControl.DataContext>


    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="55"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
     
        <!-- 明細 -->
        <ScrollViewer Name="scrollDetail" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Grid.Row="1" >
            <ItemsControl Name="ic" ItemsSource="{Binding GanttGridCells}" VirtualizingPanel.IsVirtualizing="True" 
                              VirtualizingPanel.VirtualizationMode="Recycling" >
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Grid Name="gridCalender" helper:GridHelpers.RowCount="{Binding RowCount}"
                              helper:GridHelpers.ColumnCount="{Binding ColumnCount}"
                              helper:GridHelpers.ColumnWidth="15"
                              helper:GridHelpers.RowHeight="25"
                              Margin="0,0,0,25" ShowGridLines="True">
                        </Grid>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </ScrollViewer>
    </Grid>

</UserControl>

-------------------------

---- <GanttGrid.cs> ----

namespace GanntChart.GanntGrid
{
    /// <summary>
    /// GanttGrid.xaml の相互作用ロジック
    /// </summary>
    public partial class GanttGrid : UserControl
    {
        public GanttGrid()
        {
            InitializeComponent();
        }

    }
}
-------------------------

次にGanntGridコントロールにバインドするためのViewModelを作成する

---- <GanntGridViewModel.cs> ----

namespace GanntChart.GanntGrid.data
{
    public class GanntGridViewModel : INotifyPropertyChanged
    {

        public GanntGridViewModel()
        {
            this.GanttGridCells = new ObservableCollection<GanttCell>();

            for (int i = 0; i < 500; i++)
            {
                this.GanttGridCells.Add(new GanttCell() );
            }

        }

        /// <summary>
        /// GridHelpersクラスを利用してGantGridのGridコントロールにバインドし、
        /// 行数に対してバインディングを設定する
        /// </summary>
        public int RowCount
        {
            get { return this.GanttGridCells.Count(); }
        }

        public int ColumnCount
        {
            get { return 365; }
        }
        public int RowHeight
        {
            get { return 25; }
        }

        public int ColumnWidth
        {
            get { return 25; }
        }

        private ObservableCollection<GanttCell> _ganttGridCells;
        /// <summary>
        /// GanttGridに表示するResizeRectangleや各セルを設定するためのクラス
        /// 行や列の罫線に対するデータも配列内に設定するので、配列内の要素数とタスク数は一致しない
        /// </summary>
        public ObservableCollection<GanttCell> GanttGridCells
        {
            get { return _ganttGridCells; }
            set
            {
                _ganttGridCells = value;
                NotifyPropertyChanged();
            }
        }


        #region インターフェイス実装イベント
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}
---------------------

GanntGridに表示するCellクラスを作成する。
※見た目についてはあとでDataTemplateを作成する。
---- <GanntCell.cs> ----
namespace GanntChart.GanntGrid.data
{
    /// <summary>
    /// GanttGrid上にデータを表示するためのクラス
    /// Taskを表すResizeRectangleだけではなく、ThunderLineや交互色を表すためにも利用する
    /// </summary>
    public class GanttCell : INotifyPropertyChanged
    {

        #region インターフェイス実装イベント
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion

    }
}
---------------------

GanntGridでのタスクを管理するためのクラスを作成する。

---- <GanttGridTask.cs> ----
namespace GanntChart.GanntGrid.data
{
    /// <summary>
    /// GanntGridで利用するタスククラス
    /// バインドで利用するために、INotifyPropertyChangedインターフェイスとエラー通知のためのINotifyDataErrorInfoインターフェイスを継承している
    /// GantGrid上では、ResizeRectangleユーザーコントロールにバインドすることでタスクを表現しているが、
    /// その際にはタスクに実装しているCellプロパティで表現している
    /// タスクには子タスクを複数保持することができる
    /// </summary>
    public class GanttGridTask : INotifyPropertyChanged
    {
        /// <summary>
        /// タスクの所属するViewMode
        /// </summary>
        public GanntGridViewModel ViewModel;

        public GanttGridTask()
        {
        }

        #region インターフェイス実装イベント
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        #region プロパティ

        /// <summary>
        /// Gannt表示用
        /// </summary>
        public GanttCell Cell { get; set; }

        #endregion

    }

}
---------------------

共通関数クラス
---- <Common.cs> ----
namespace GanntChart
{
    public static class Common
    {

        public static string CommandLine_Path = "";

        /// <summary>
        /// 文字列の指定した位置から指定した長さを取得する
        /// </summary>
        /// <param name="str">文字列</param>
        /// <param name="start">開始位置</param>
        /// <param name="len">長さ</param>
        /// <returns>取得した文字列</returns>
        public static string Mid(string str, int start, int len)
        {
            if (start <= 0)
            {
                throw new ArgumentException("引数'start'は1以上でなければなりません。");
            }
            if (len < 0)
            {
                throw new ArgumentException("引数'len'は0以上でなければなりません。");
            }
            if (str == null || str.Length < start)
            {
                return "";
            }
            if (str.Length < (start + len))
            {
                return str.Substring(start - 1);
            }
            return str.Substring(start - 1, len);
        }

        /// <summary>
        /// 文字列の指定した位置から末尾までを取得する
        /// </summary>
        /// <param name="str">文字列</param>
        /// <param name="start">開始位置</param>
        /// <returns>取得した文字列</returns>
        public static string Mid(string str, int start)
        {
            return Mid(str, start, str.Length);
        }

        /// <summary>
        /// 文字列の先頭から指定した長さの文字列を取得する
        /// </summary>
        /// <param name="str">文字列</param>
        /// <param name="len">長さ</param>
        /// <returns>取得した文字列</returns>
        public static string Left(string str, int len)
        {
            if (len < 0)
            {
                throw new ArgumentException("引数'len'は0以上でなければなりません。");
            }
            if (str == null)
            {
                return "";
            }
            if (str.Length <= len)
            {
                return str;
            }
            return str.Substring(0, len);
        }

        /// <summary>
        /// 文字列の末尾から指定した長さの文字列を取得する
        /// </summary>
        /// <param name="str">文字列</param>
        /// <param name="len">長さ</param>
        /// <returns>取得した文字列</returns>
        public static string Right(string str, int len)
        {
            if (len < 0)
            {
                throw new ArgumentException("引数'len'は0以上でなければなりません。");
            }
            if (str == null)
            {
                return "";
            }
            if (str.Length <= len)
            {
                return str;
            }
            return str.Substring(str.Length - len, len);
        }

        #region エラー処理

        /// <summary>
        /// エラーメッセージ表示
        /// </summary>
        /// <param name="ex"></param>
        public static void ShowErrMsg(Exception ex)
        {
            System.Windows.MessageBox.Show("エラーが発生しました。" + "\n" + ex.Message + "\n" + ex.StackTrace);
        }

        #endregion

        #region 祝日判定

        // 春分の日を返すメソッド
        public static int Syunbun(int yy)
        {
            int dd;
            if (yy <= 1947)
            {
                dd = 99;
            }
            else if (yy <= 1979)
            {
                dd = (int)(20.8357 + (0.242194 * (yy - 1980)) - (int)((yy - 1983) / 4));
            }
            else if (yy <= 2099)
            {
                dd = (int)(20.8431 + (0.242194 * (yy - 1980)) - (int)((yy - 1980) / 4));
            }
            else if (yy <= 2150)
            {
                dd = (int)(21.851 + (0.242194 * (yy - 1980)) - (int)((yy - 1980) / 4));
            }
            else
            {
                dd = 99;
            }
            return dd;
        }

        // 秋分の日を返すメソッド
        public static int Syubun(int yy)
        {
            int dd;
            if (yy <= 1947)
            {
                dd = 99;
            }
            else if (yy <= 1979)
            {
                dd = (int)(23.2588 + (0.242194 * (yy - 1980)) - (int)((yy - 1983) / 4));
            }
            else if (yy <= 2099)
            {
                dd = (int)(23.2488 + (0.242194 * (yy - 1980)) - (int)((yy - 1980) / 4));
            }
            else if (yy <= 2150)
            {
                dd = (int)(24.2488 + (0.242194 * (yy - 1980)) - (int)((yy - 1980) / 4));
            }
            else
            {
                dd = 99;
            }
            return dd;
        }

        // 祝日情報(戻り値)
        public struct HolidayInfo
        {
            public enum HOLIDAY
            {
                /// <summary>
                /// 平日
                /// </summary>
                WEEKDAY = 0, // 平日
                /// <summary>
                /// 休日
                /// </summary>
                HOLIDAY = 1, // 休日
                /// <summary>
                /// 振替休日
                /// </summary>
                C_HOLIDAY = 2, // 振休
                /// <summary>
                /// 祝日
                /// </summary>
                SYUKUJITSU = 3, // 祝日
            };
            public HOLIDAY holiday; // その日の種類
            public DayOfWeek week; // その日の曜日
            public String name; // その日に名前が付いている場合はその名前。
        };
        private static readonly DateTime SYUKUJITSU = new DateTime(1948, 7, 20); // 祝日法施行日
        private static readonly DateTime FURIKAE = new DateTime(1973, 07, 12); // 振替休日制度の開始日

        // その日が何かを調べるメソッド
        public static HolidayInfo Holiday(DateTime t)
        {
            int yy = t.Year;
            int mm = t.Month;
            int dd = t.Day;
            DayOfWeek ww = t.DayOfWeek;

            HolidayInfo result = new HolidayInfo();
            result.week = ww;
            result.holiday = HolidayInfo.HOLIDAY.WEEKDAY;

            // 祝日法施行以前
            if (t < SYUKUJITSU) return result;

            switch (mm)
            {
                // 1月 //
                case 1:
                    if (dd == 1)
                    {
                        result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                        result.name = "元日";
                    }
                    else
                    {
                        if (yy >= 2000)
                        {
                            if (((int)((dd - 1) / 7) == 1) && (ww == DayOfWeek.Monday))
                            {
                                result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                                result.name = "成人の日";
                            }
                        }
                        else
                        {
                            if (dd == 15)
                            {
                                result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                                result.name = "成人の日";
                            }
                        }
                    }
                    break;
                // 2月 //
                case 2:
                    if (dd == 11)
                    {
                        if (yy >= 1967)
                        {
                            result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                            result.name = "建国記念の日";
                        }
                    }
                    else if ((yy == 1989) && (dd == 24))
                    {
                        result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                        result.name = "昭和天皇の大喪の礼";
                    }
                    break;
                // 3月 //
                case 3:
                    if (dd == Syunbun(yy))
                    {
                        result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                        result.name = "春分の日";
                    }
                    break;
                // 4月 //
                case 4:
                    if (dd == 29)
                    {
                        if (yy >= 2007)
                        {
                            result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                            result.name = "昭和の日";
                        }
                        else if (yy >= 1989)
                        {
                            result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                            result.name = "みどりの日";
                        }
                        else
                        {
                            result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                            result.name = "天皇誕生日";
                        }
                    }
                    else if ((yy == 1959) && (dd == 10))
                    {
                        result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                        result.name = "皇太子明仁親王の結婚の儀";
                    }
                    break;
                // 5月 //
                case 5:
                    if (dd == 3)
                    {
                        result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                        result.name = "憲法記念日";
                    }
                    else if (dd == 4)
                    {
                        if (yy >= 2007)
                        {
                            result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                            result.name = "みどりの日";
                        }
                        else if (yy >= 1986)
                        {
                            /* 5/4が日曜日は『只の日曜』、月曜日は『憲法記念日の振替休日』(~2006年)*/
                            if (ww > DayOfWeek.Monday)
                            {
                                result.holiday = HolidayInfo.HOLIDAY.HOLIDAY;
                                result.name = "国民の休日";
                            }
                        }
                    }
                    else if (dd == 5)
                    {
                        result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                        result.name = "こどもの日";
                    }
                    else if (dd == 6)
                    {
                        /* [5/3,5/4が日曜]ケースのみ、ここで判定 */
                        if ((yy >= 2007) && ((ww == DayOfWeek.Tuesday) || (ww == DayOfWeek.Wednesday)))
                        {
                            result.holiday = HolidayInfo.HOLIDAY.C_HOLIDAY;
                            result.name = "振替休日";
                        }
                    }
                    break;
                // 6月 //
                case 6:
                    if ((yy == 1993) && (dd == 9))
                    {
                        result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                        result.name = "皇太子徳仁親王の結婚の儀";
                    }
                    break;
                // 7月 //
                case 7:
                    if (yy >= 2003)
                    {
                        if (((int)((dd - 1) / 7) == 2) && (ww == DayOfWeek.Monday))
                        {
                            result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                            result.name = "海の日";
                        }
                    }
                    else if (yy >= 1996)
                    {
                        if (dd == 20)
                        {
                            result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                            result.name = "海の日";
                        }
                    }
                    break;
                // 8月 //
                case 8:
                    if (dd == 11)
                    {
                        if (yy >= 2016)
                        {
                            result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                            result.name = "山の日";
                        }
                    }
                    break;
                // 9月 //
                case 9:
                    if (dd == Syubun(yy))
                    {
                        result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                        result.name = "秋分の日";
                    }
                    else
                    {
                        if (yy >= 2003)
                        {
                            if (((int)((dd - 1) / 7) == 2) && (ww == DayOfWeek.Monday))
                            {
                                result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                                result.name = "敬老の日";
                            }
                            else if (ww == DayOfWeek.Tuesday)
                            {
                                if (dd == Syubun(yy) - 1)
                                {
                                    result.holiday = HolidayInfo.HOLIDAY.HOLIDAY;
                                    result.name = "国民の休日";
                                }
                            }
                        }
                        else if (yy >= 1966)
                        {
                            if (dd == 15)
                            {
                                result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                                result.name = "敬老の日";
                            }
                        }
                    }
                    break;
                // 10月 //
                case 10:
                    if (yy >= 2000)
                    {
                        if (((int)((dd - 1) / 7) == 1) && (ww == DayOfWeek.Monday))
                        {
                            result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                            result.name = "体育の日";
                        }
                    }
                    else if (yy >= 1966)
                    {
                        if (dd == 10)
                        {
                            result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                            result.name = "体育の日";
                        }
                    }
                    break;
                // 11月 //
                case 11:
                    if (dd == 3)
                    {
                        result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                        result.name = "文化の日";
                    }
                    else if (dd == 23)
                    {
                        result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                        result.name = "勤労感謝の日";
                    }
                    else if ((yy == 1990) && (dd == 12))
                    {
                        result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                        result.name = "即位礼正殿の儀";
                    }
                    break;
                // 12月 //
                case 12:
                    if (dd == 23)
                    {
                        if (yy >= 1989)
                        {
                            result.holiday = HolidayInfo.HOLIDAY.SYUKUJITSU;
                            result.name = "天皇誕生日";
                        }
                    }
                    break;
                default:
                    break;
            }

            if ((result.holiday == HolidayInfo.HOLIDAY.WEEKDAY
                 || result.holiday == HolidayInfo.HOLIDAY.HOLIDAY) &&
                (ww == DayOfWeek.Monday))
            {
                /*月曜以外は振替休日判定不要
                  5/6(火,水)の判定は上記ステップで処理済
                  5/6(月)はここで判定する  */
                if (t >= FURIKAE)
                {
                    if (Holiday(t.AddDays(-1)).holiday == HolidayInfo.HOLIDAY.SYUKUJITSU)
                    {    /* 再帰呼出 */
                        result.holiday = HolidayInfo.HOLIDAY.C_HOLIDAY;
                        result.name = "振替休日";
                    }
                }
            }
            return result;
        }
        #endregion

    }
}
---------------------

GanntGridではGridコントロールに対してデータをバインドして利用するためにヘルパークラスを作成する。
※https://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/のソースを利用
 一部修正
---- <GridHelpers.cs> ----
namespace GanntChart.GanntGrid.helper
{
    // https://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/
    public class GridHelpers
    {
        #region RowCount Property

        /// <summary>
        /// Adds the specified number of Rows to RowDefinitions.
        /// Default Height is Auto
        /// </summary>
        public static readonly DependencyProperty RowCountProperty =
            DependencyProperty.RegisterAttached(
                "RowCount", typeof(int), typeof(GridHelpers),
                new PropertyMetadata(-1, RowCountChanged));

        // Get
        public static int GetRowCount(DependencyObject obj)
        {
            return (int)obj.GetValue(RowCountProperty);
        }

        // Set
        public static void SetRowCount(DependencyObject obj, int value)
        {
            obj.SetValue(RowCountProperty, value);
        }

        // Change Event - Adds the Rows
        public static void RowCountChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || (int)e.NewValue < 0)
                return;

            Grid grid = (Grid)obj;
            grid.RowDefinitions.Clear();

            for (int i = 0; i < (int)e.NewValue; i++)
                grid.RowDefinitions.Add(
                    new RowDefinition() { Height = GridLength.Auto });

            SetStarRows(grid);

            // 行幅再設定
            SetRowHeight(grid);

        }

        #endregion

        #region ColumnCount Property

        /// <summary>
        /// Adds the specified number of Columns to ColumnDefinitions.
        /// Default Width is Auto
        /// </summary>
        public static readonly DependencyProperty ColumnCountProperty =
            DependencyProperty.RegisterAttached(
                "ColumnCount", typeof(int), typeof(GridHelpers),
                new PropertyMetadata(-1, ColumnCountChanged));

        // Get
        public static int GetColumnCount(DependencyObject obj)
        {
            return (int)obj.GetValue(ColumnCountProperty);
        }

        // Set
        public static void SetColumnCount(DependencyObject obj, int value)
        {
            obj.SetValue(ColumnCountProperty, value);
        }

        // Change Event - Add the Columns
        public static void ColumnCountChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || (int)e.NewValue < 0)
                return;

            Grid grid = (Grid)obj;
            grid.ColumnDefinitions.Clear();

            for (int i = 0; i < (int)e.NewValue; i++)
                grid.ColumnDefinitions.Add(
                    new ColumnDefinition() { Width = GridLength.Auto });

            SetStarColumns(grid);

            // カラム幅再設定
            SetColumnWidth(grid);

        }

        #endregion

        #region StarRows Property

        /// <summary>
        /// Makes the specified Row's Height equal to Star.
        /// Can set on multiple Rows
        /// </summary>
        public static readonly DependencyProperty StarRowsProperty =
            DependencyProperty.RegisterAttached(
                "StarRows", typeof(string), typeof(GridHelpers),
                new PropertyMetadata(string.Empty, StarRowsChanged));

        // Get
        public static string GetStarRows(DependencyObject obj)
        {
            return (string)obj.GetValue(StarRowsProperty);
        }

        // Set
        public static void SetStarRows(DependencyObject obj, string value)
        {
            obj.SetValue(StarRowsProperty, value);
        }

        // Change Event - Makes specified Row's Height equal to Star
        public static void StarRowsChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
                return;

            SetStarRows((Grid)obj);
        }

        #endregion

        #region StarColumns Property

        /// <summary>
        /// Makes the specified Column's Width equal to Star.
        /// Can set on multiple Columns
        /// </summary>
        public static readonly DependencyProperty StarColumnsProperty =
            DependencyProperty.RegisterAttached(
                "StarColumns", typeof(string), typeof(GridHelpers),
                new PropertyMetadata(string.Empty, StarColumnsChanged));

        // Get
        public static string GetStarColumns(DependencyObject obj)
        {
            return (string)obj.GetValue(StarColumnsProperty);
        }

        // Set
        public static void SetStarColumns(DependencyObject obj, string value)
        {
            obj.SetValue(StarColumnsProperty, value);
        }

        // Change Event - Makes specified Column's Width equal to Star
        public static void StarColumnsChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
                return;

            SetStarColumns((Grid)obj);
        }

        #endregion

        #region ColumnWidth Property

        /// <summary>
        /// カラム幅設定用独自プロパティ
        /// </summary>
        public static readonly DependencyProperty ColumnWidthProperty =
            DependencyProperty.RegisterAttached(
                "ColumnWidth", typeof(int), typeof(GridHelpers),
                new PropertyMetadata(0, ColumnWidthChanged));

        // 取得処理
        public static int GetColumnWidth(DependencyObject obj)
        {
            return (int)obj.GetValue(ColumnWidthProperty);
        }

        // 設定処理
        public static void SetColumnWidth(DependencyObject obj, string value)
        {
            obj.SetValue(ColumnWidthProperty, value);
        }

        // 値変更時処理
        public static void ColumnWidthChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
                return;

            SetColumnWidth((Grid)obj);
        }

        #endregion

        #region RowHeight Property
        /// <summary>
        /// カラム幅設定用独自プロパティ
        /// </summary>
        public static readonly DependencyProperty RowHeightProperty =
            DependencyProperty.RegisterAttached(
                "RowHeight", typeof(int), typeof(GridHelpers),
                new PropertyMetadata(0, RowHeightChanged));

        // 取得処理
        public static int GetRowHeight(DependencyObject obj)
        {
            return (int)obj.GetValue(RowHeightProperty);
        }

        // 設定処理
        public static void SetRowHeight(DependencyObject obj, string value)
        {
            obj.SetValue(RowHeightProperty, value);
        }

        // 値変更時処理
        public static void RowHeightChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
                return;
            SetRowHeight((Grid)obj);
        }

        #endregion


        private static void SetStarColumns(Grid grid)
        {
            string[] starColumns =
                GetStarColumns(grid).Split(',');

            for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
            {
                if (starColumns.Contains(i.ToString()))
                    grid.ColumnDefinitions[i].Width =
                        new GridLength(1, GridUnitType.Star);
            }
        }

        private static void SetStarRows(Grid grid)
        {
            string[] starRows =
                GetStarRows(grid).Split(',');

            for (int i = 0; i < grid.RowDefinitions.Count; i++)
            {
                if (starRows.Contains(i.ToString()))
                    grid.RowDefinitions[i].Height =
                        new GridLength(1, GridUnitType.Star);
            }
        }

        // カラム幅設定の独自プロパティ設定用
        private static void SetColumnWidth(Grid grid)
        {
            for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
            {
                grid.ColumnDefinitions[i].Width = new GridLength(GetColumnWidth(grid));
            }
        }

        // 行幅設定の独自プロパティ設定用
        private static void SetRowHeight(Grid grid)
        {
            for (int i = 0; i < grid.RowDefinitions.Count; i++)
            {
                grid.RowDefinitions[i].Height = new GridLength(GetRowHeight(grid));
            }
        }
    }
}
---------------------


メイン画面
とりあえず配置するだけ。

---- <MainWindow.xaml> ----

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:GanntChart"
        xmlns:GanntGrid="clr-namespace:GanntChart.GanntGrid" x:Class="GanntChart.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="720" Width="1024">
    <Grid>

        <GanntGrid:GanttGrid x:Name="grid" HorizontalAlignment="Left" Height="571" Margin="47,75,0,0" VerticalAlignment="Top" Width="933"/>

    </Grid>
</Window>
-------------------------


---- <MainWindow.cs> ----
namespace GanntChart
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            SetEvents();
        }

        private void SetEvents()
        {
            this.Loaded += MainWindow_Loaded;
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            GanntChart.GanntGrid.data.GanntGridViewModel ViewModel = new GanntGrid.data.GanntGridViewModel();
            grid.DataContext = ViewModel;

        }
    }
}
-------------------------

タスク管理ツールについて

ガントチャートのソフトを放置して、ずっと作っていたタスクを管理するソフトがいい感じにできてきた。
インストール不要で共有フォルダにファイルを配置すれば複数人でも同時に触ることができる。また、自分の関係するタスクにコメントや変更が加わったら都度確認を行うことができるウィンドウや、一覧検索画面、分析用のグルーピング機能なども詰め込んでみた。

もう少しバグ取りをしたらVectorにでも登録してみようかと思う

・メイン画面



・検索画面


・検索画面(グルーピング)



SQLの速度改善について(小ネタ)

開発中のプログラムがあまり進んでないのでSQLの小ネタ。
昔、よくあった遅いSQLの改善方法について、記載。
インデックスを張ったりするのもいいけど、SQLを作るときにデータをたくさん持ってきてまとめて条件分岐をCASE式でするだけで
圧倒的に速度が改善できる。

例えば、業務アプリで帳票などを作成する際に、列に月の集計結果を、持たせようとするケースがよくある。

 2018年 

       1月|2月|3月|・・・・
得意先A    100|  60|  40|・・・・
得意先B      0|  20|  40|・・・・



こういう集計をSQL側でGroupByを何個も書いて集計していたりするのをよく見る。

(例)

      SELECT *
      FROM 得意先マスター AS 得意先
      LEFT JOIN (
         SELECT SUM(売上金額) AS 1月売上
                      ,得意先CD
          FROM 売上テーブル
            WHERE 年月 = '201801'
           GROUP BY 年月,得意先CD
        ) AS 1月売上金額

  ON 得意先.得意先CD = 1月売上金額.得意先CD
  以下、LEFT JOIN が11ヶ月分続く


しかし、やむを得ない場合なら仕方ないけど、特に考えもせずにこんなソースを書いている人がいると殴りたくなってくる。
実際に昔、こんな感じのSQLを発行して、メモリエラーが発生するんでどうしたらいいでしょう。みたいなことを言われたことがある。
その時には3万行のSQLを発行して使い物にならなかった。

SUM関数の中にCASE式を書くことができるので、下記のSQLで一気に12か月分のデータを取得できる。

(例)
      SELECT SUM(CASE WHEN RIGHT(年月,2) = '01' THEN 売上金額 ELSE 0 END) AS 1月売上
                   ,SUM(CASE WHEN RIGHT(年月,2) = '02' THEN 売上金額 ELSE 0 END) AS 2月売上
                   ,SUM(CASE WHEN RIGHT(年月,2) = '03' THEN 売上金額 ELSE 0 END) AS 3月売上
                   ,SUM(CASE WHEN RIGHT(年月,2) = '04' THEN 売上金額 ELSE 0 END) AS 4月売上
                   ,SUM(CASE WHEN RIGHT(年月,2) = '05' THEN 売上金額 ELSE 0 END) AS 5月売上
                   ,得意先CD
          FROM 売上テーブル
            WHERE LEFT(年月,4) = '2018'
           GROUP BY 得意先CD

件数のカウントとかも、「SUM(CASE WHEN RIGHT(年月,2) = '02' THEN 1 ELSE 0 END) AS 2月売上件数」とすることで
一つのSQL文で対応ができる。

Window関数を利用すればもっといろんなことができる。

意外と集計関数の中にCASE式が書けないことを知らない人がいたのでメモをしておく。
処理速度もSQLの文量も修正の手間も圧倒的に違うので、普段からある程度慣れ親しんでいてほしい。

SQLは慣れてくるとパズルみたいに思えてくるので、頭の体操としてはすごくいい感じ。

sqlite3を.NETで利用する


いろんなソフトのデータの保存方法についてシリアライズ化して保存したXmlファイルをまとめて
Zipファイルにすることで対応しようとしていたが、複数人で触ることを考えてsqliteのほうがトラブルが少なそうだったので
データの保存の関連を修正した。

データの取得については汎用性を持たせたかったので、DataSet、DataTableで取得するようにしておいた。
データを取得するときにインテリセンスが効かないので微妙な気もするが、
SQL側で集計したり、分析した結果を表示したりして列名を色々作ることが多そうなのでDataTableを利用したほうが融通が利く気がする。
融通を聞かせすぎると統一感がなくなるけど、どうせそこまで統一したシステムなんて組めないからいいんじゃないかな・・・。

インジェクション対策としてパラメータも利用できるようにしてみた。

sqlite以外でもコネクション周りを修正すれば利用できるDataBaseAccessクラスを作成した。
SQL Serverの場合であればSQLiteConnectionStringBuilderなどを軒並みSQLConnectionに変更すれば対応できる、気がする。
※SQL文の書き方が違うのでそのあたりは頑張っていただくしかないが・・・。
 どこかの機会に遅いSQLを早くするコツも書いてみたいと思う。いつになるかわからないけど。

そのときにサンプルプログラムを作成し、調査した内容をメモしておく。

sqliteをNuGetパッケージの管理からインストールし、参照の追加を行う




下記の using を追加する。

  using System.Data;
  using System.Data.SQLite;


↓↓↓↓↓ サンプルソース ↓↓↓↓↓

private void btnSample_Click(object sender, RoutedEventArgs e)
{
    string path = @"C:\test\SampleDataBase.db";
    var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = path };

    // ▽▽▽ データベース作成 ▽▽▽
    DataBaseAccess dataBaseAccess = new DataBaseAccess();
    dataBaseAccess.CreateDataBase_NewFile(path);
    // △△△ データベース作成 △△△


    // ▽▽▽ データ登録処理 ▽▽▽

    List<DataBaseAccess.ExecutionSQL> lstInsUpd = new List<DataBaseAccess.ExecutionSQL>();

    //   データ登録(ファイル実体)
    DataBaseAccess.ExecutionSQL InsSqlFile = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.INSERT);
    InsSqlFile.ExecuteSQL = " INSERT INTO T_File(intProjectNo,intCardNo,txtCommentNo,intFileNo,bloFile)  "
                              + "             VALUES(@intProjectNo , @intCardNo , @txtCommentNo , @intFileNo , @bloFile) ";
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@intProjectNo", "1"));
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@intCardNo", "1"));
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@txtCommentNo", "0"));
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@intFileNo", "1"));
    //      ファイルをByte型配列で読み込み
    var data = System.IO.File.ReadAllBytes(@"C:\test\sample.txt");
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@bloFile", data));
    lstInsUpd.Add(InsSqlFile);

    //   データ登録(ファイル情報)
    DataBaseAccess.ExecutionSQL InsSqlFileInfo = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.INSERT);
    InsSqlFileInfo.ExecuteSQL = " INSERT INTO T_FileInfo(intProjectNo,intCardNo,txtCommentNo,intFileNo,txtFileName)  "
                              + "             VALUES(@intProjectNo , @intCardNo , @txtCommentNo , @intFileNo , @txtFileName) ";
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intProjectNo", "1"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intCardNo", "1"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@txtCommentNo", "0"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intFileNo", "1"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@txtFileName", "sample.txt"));
    lstInsUpd.Add(InsSqlFileInfo);

    //   データ登録実行
    dataBaseAccess.ExecuteSQL_InsUpdDel(path, lstInsUpd);

    // △△△ データ登録処理 △△△

    // ▽▽▽ データ取得処理 ▽▽▽

    List<DataBaseAccess.ExecutionSQL> lstSelect = new List<DataBaseAccess.ExecutionSQL>();

    //   データを取得するためのSQL(ファイル実体)
    DataBaseAccess.ExecutionSQL SelSqlFile = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.SELECT);
    SelSqlFile.TableName = "T_File";
    SelSqlFile.ExecuteSQL = " SELECT * FROM  T_File ";
    lstSelect.Add(SelSqlFile);

    //   データを取得するためのSQL(ファイル情報)
    DataBaseAccess.ExecutionSQL SelSqlFileInfo = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.SELECT);
    SelSqlFileInfo.TableName = "T_FileInfo";
    SelSqlFileInfo.ExecuteSQL = " SELECT * FROM  T_FileInfo WHERE intProjectNo = @intProjectNo ";
    SelSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intProjectNo", "1"));
    lstSelect.Add(SelSqlFileInfo);

    //   SQLを実行し、結果をDataSetで取得する
    DataSet ds = dataBaseAccess.GetDatas(path, lstSelect);

    //   取得したファイルを別名で出力する
    System.IO.File.WriteAllBytes(@"C:\test\output.txt", (byte[])ds.Tables["T_File"].Rows[0]["bloFile"]);

    // △△△ データ登録処理 △△△

}


/// <summary>
/// データベースアクセス関連
/// </summary>
public class DataBaseAccess
{

    #region Public Method

    /// <summary>
    /// 新規ファイル作成時のSQL
    /// </summary>
    /// <param name="strFilePath"></param>
    public void CreateDataBase_NewFile(string strFilePath)
    {
        ExecuteSQL_DDL(strFilePath, _GetCreateTableSQL_V0100());
    }

    /// <summary>
    /// SQL実行処理
    /// </summary>
    /// <param name="strFilePath"></param>
    /// <param name="ExecutionSQL"></param>
    /// <returns></returns>
    public DataSet GetDatas(string strFilePath, ExecutionSQL ExecutionSQL)
    {
        List<ExecutionSQL> lst = new List<ExecutionSQL>();
        lst.Add(ExecutionSQL);
        return GetDatas(strFilePath, lst);
    }

    /// <summary>
    /// SQL実行処理
    /// </summary>
    public DataSet GetDatas(string strFilePath, List<ExecutionSQL> ExecutionSQLs)
    {
        var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = strFilePath };
        DataSet dataSet = new DataSet();

        try
        {
            using (var cn = new SQLiteConnection(sqlConnectionSb.ToString()))
            {
                cn.Open();

                using (var cmd = new SQLiteCommand(cn))
                {
                    foreach (var param in ExecutionSQLs)
                    {
                        // ↓↓↓ チェック ↓↓↓
                        if (param.Type != ExecutionSQL.SqlType.SELECT) { throw new Exception("GetDatasメソッドはSELECT構文のみが実行可能です。SqlTypeを確認してください。"); }
                        // ↑↑↑ チェック ↑↑↑

                        cmd.CommandText = param.ExecuteSQL;

                        // SQLパラメータを設定
                        cmd.Parameters.Clear();
                        foreach (var item in param.Parameters) { cmd.Parameters.Add(new SQLiteParameter(item.Name, item.Value)); }

                        // SQLを実行し、データを取得する
                        using (var reader = cmd.ExecuteReader())
                        {
                            DataTable dt = new DataTable(param.TableName);
                            DataRow dr;

                            for (int i = 0; i < reader.FieldCount; i++)
                            {
                                dt.Columns.Add(new DataColumn(reader.GetName(i), reader.GetFieldType(i)));
                            }

                            while (reader.Read() == true)
                            {
                                dr = dt.NewRow();
                                for (int i = 0; i < reader.FieldCount; i++)
                                {
                                    dr[i] = reader[i];
                                }
                                dt.Rows.Add(dr);
                            }

                            dataSet.Tables.Add(dt);

                            reader.Close();
                        }

                    }

                }
            }

        }
        catch (Exception)
        {

            throw;
        }

        return dataSet;

    }

    /// <summary>
    /// SQL実行時の設定内容
    /// </summary>
    public class ExecutionSQL
    {
        public ExecutionSQL(SqlType sqlType)
        {
            this.Parameters = new List<SQLPara>();
            this.Type = sqlType;
        }

        public enum SqlType
        {
            INSERT, UPDATE, DELETE, SELECT
        }

        /// <summary>
        /// SELECT 構文の時には必須
        /// INSERT,UPDATE,DELETE 構文の時には不要
        /// </summary>
        public string TableName;

        /// <summary>
        /// 実行するSQL文
        /// </summary>
        public string ExecuteSQL;

        /// <summary>
        /// SQLのパラメータ
        /// </summary>
        public List<SQLPara> Parameters;

        public SqlType Type;
    }

    /// <summary>
    /// SQLパラメータ
    /// </summary>
    public class SQLPara
    {
        public SQLPara(string name, object value)
        {
            this.Name = name;
            this.Value = value;
        }
        public string Name;
        public object Value;
    }

    /// <summary>
    /// 登録・更新・削除のSQLを実行するためのメソッド
    /// </summary>
    public void ExecuteSQL_InsUpdDel(string strFilePath, List<ExecutionSQL> ExecutionSQLs)
    {
        var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = strFilePath };

        try
        {
            using (var cn = new SQLiteConnection(sqlConnectionSb.ToString()))
            {
                cn.Open();
                using (var cmd = new SQLiteCommand(cn))
                {
                    using (SQLiteTransaction trans = cn.BeginTransaction())
                    {

                        foreach (var param in ExecutionSQLs)
                        {
                            // ↓↓↓ チェック ↓↓↓
                            if (param.Type == ExecutionSQL.SqlType.SELECT) { throw new Exception("ExecuteSQL_InsUpdDelメソッドはSELECT構文以外のみが実行可能です。SqlTypeを確認してください。"); }
                            // ↑↑↑ チェック ↑↑↑

                            cmd.CommandText = param.ExecuteSQL;

                            // SQLパラメータを設定
                            cmd.Parameters.Clear();
                            foreach (var item in param.Parameters) { cmd.Parameters.Add(new SQLiteParameter(item.Name, item.Value)); }

                            cmd.ExecuteNonQuery();

                        }

                        trans.Commit();
                    }

                }

            }
        }
        catch (Exception)
        {

            throw;
        }
    }

    #endregion

    #region Private Method 1st.

    /// <summary>
    /// DDL構文(Create、Drop)系列のSQLを実行する
    /// </summary>
    /// <param name="strFilePath">ファイルパス</param>
    /// <param name="strSQL">実行SQL</param>
    private void ExecuteSQL_DDL(string strFilePath, string strSQL)
    {
        var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = strFilePath };

        using (var cn = new SQLiteConnection(sqlConnectionSb.ToString()))
        {
            cn.Open();
            using (var cmd = new SQLiteCommand(cn))
            {
                cmd.CommandText = strSQL;
                cmd.ExecuteNonQuery();
            }
        }
    }

    #region Private Method 2st.

    /// <summary>
    /// テーブル作成処理(バージョン01.00)
    /// </summary>
    /// <returns></returns>
    private string _GetCreateTableSQL_V0100()
    {
        string strSQL = "";
        strSQL = " create table T_File ( "
                + "   intProjectNo integer "
                + "   , intCardNo integer"
                + "   , txtCommentNo TEXT "
                + "   , intFileNo integer"
                + "   , bloFile BLOB "
                + "   , constraint T_File_PKC primary key (intProjectNo,intCardNo,txtCommentNo,intFileNo) "
                + " ); "
                + "  "
                + " create table T_FileInfo ( "
                + "   intProjectNo integer"
                + "   , intCardNo integer"
                + "   , txtCommentNo TEXT "
                + "   , intFileNo integer"
                + "   , txtFileName TEXT "
                + "   , constraint T_FileInfo_PKC primary key (intProjectNo,intCardNo,txtCommentNo,intFileNo) "
                + " ); ";
        return strSQL;
    }

    #endregion

    #endregion

}

↑↑↑↑↑ サンプルソース ↑↑↑↑↑

AvalonDockについて

作成中のアプリのサイドバーにExtenderコントロールでサブ情報を表示するようにしていたが、どうにも見た目が気に入らないのでIDEみたいなドッキングウィンドウ(?)にしてみたくて色々調べていると有名どころを見つけたので利用してみた。

1.インストール。


2.ソースコード
Windowのところに
「xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"」を追加しておいて、下記のようなソースを記載

            <xcad:DockingManager
                           AllowMixedOrientation="True"
                           BorderBrush="Black"
                           BorderThickness="1" >
                <xcad:DockingManager.DocumentHeaderTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Source="{Binding IconSource}" Margin="0,0,4,0" Height="5" Width="5" MaxHeight="5" MaxWidth="5" Stretch="None" />
                            <TextBlock Text="{Binding Title}" />
                        </StackPanel>
                    </DataTemplate>
                </xcad:DockingManager.DocumentHeaderTemplate>
                <xcad:LayoutRoot x:Name="_layoutRoot">

                    <xcad:LayoutRoot.LeftSide>
                        <xcad:LayoutAnchorSide>
                            <xcad:LayoutAnchorGroup>
                                <xcad:LayoutAnchorable Title="プロジェクト" ContentId="Project" IconSource="images/document.png" CanClose="False" CanHide="False" >
                                    <StackPanel>
                                        <ItemsControl x:Name="tSxtack" ItemsSource="{Binding Projects}">
                                            <ItemsControl.ItemsPanel>
                                                <ItemsPanelTemplate>
                                                    <StackPanel Orientation="Vertical">
                                                    </StackPanel>
                                                </ItemsPanelTemplate>
                                            </ItemsControl.ItemsPanel>
                                            <ItemsControl.ItemTemplate>
                                                <DataTemplate>
                                                    <DockPanel>
                                                        <Expander Style="{DynamicResource ExpanderCustomStyle}" >
                                                            <Expander.Header>
                                                                <StackPanel Orientation="Horizontal">
                                                                    <CheckBox Content="{Binding Name}" IsChecked="{Binding Showing}" ></CheckBox>
                                                                    <Button Name="btnProjectDetail" Content="..." Background="Transparent"  Width="25" BorderBrush="Silver"  BorderThickness="0.5" FontFamily="MS Gothic" Click="btnProjectDetail_Click"/>
                                                                </StackPanel>
                                                            </Expander.Header>
                                                            <StackPanel Margin="25,0,0,0" >
                                                                <TextBlock Text="{Binding StartDay,StringFormat=開始日:{0:yy/MM/dd}}"></TextBlock>
                                                                <TextBlock Text="{Binding EndDay,StringFormat=終了日:{0:yy/MM/dd}}"></TextBlock>
                                                                <TextBlock Text="{Binding Info}"></TextBlock>
                                                            </StackPanel>
                                                        </Expander>
                                                    </DockPanel>
                                                </DataTemplate>
                                            </ItemsControl.ItemTemplate>
                                        </ItemsControl>
                                    </StackPanel>
                                </xcad:LayoutAnchorable>
                                <xcad:LayoutAnchorable x:Name="AvalondockDelayCard" Title="遅延カード" ContentId="DelayCardList" IconSource="images/document.png" CanClose="False" CanHide="False" >
                                    <ListView Name="lvLimitCardList" ItemsSource="{Binding DelayCardList}">
                                        <ListView.View>
                                            <GridView>
                                                <GridViewColumn Header="タイトル" Width="120" DisplayMemberBinding="{Binding CardTitle}" />
                                                <GridViewColumn Header="メンバー" Width="50" DisplayMemberBinding="{Binding Members}" />
                                            </GridView>
                                        </ListView.View>
                                        <ListView.GroupStyle>
                                            <GroupStyle>
                                                <GroupStyle.ContainerStyle>
                                                    <Style TargetType="{x:Type GroupItem}">
                                                        <Setter Property="Template">
                                                            <Setter.Value>
                                                                <ControlTemplate>
                                                                    <Expander IsExpanded="True" >
                                                                        <Expander.Header>
                                                                            <StackPanel Orientation="Horizontal">
                                                                                <!-- GroupItemのNameプロパティをバインド -->
                                                                                <TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
                                                                                <TextBlock Text="{Binding ItemCount}" FontSize="22" Foreground="Green" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
                                                                                <TextBlock Text=" item(s)" FontSize="22" Foreground="Silver" FontStyle="Italic" VerticalAlignment="Bottom" />
                                                                            </StackPanel>
                                                                        </Expander.Header>
                                                                        <ItemsPresenter />
                                                                    </Expander>
                                                                </ControlTemplate>
                                                            </Setter.Value>
                                                        </Setter>
                                                    </Style>
                                                </GroupStyle.ContainerStyle>
                                            </GroupStyle>
                                        </ListView.GroupStyle>
                                    </ListView>
                                </xcad:LayoutAnchorable>
                            </xcad:LayoutAnchorGroup>
                        </xcad:LayoutAnchorSide>
                    </xcad:LayoutRoot.LeftSide>


                    <xcad:LayoutPanel Orientation="Horizontal">
                        <xcad:LayoutAnchorablePane DockWidth="200">
                            <xcad:LayoutAnchorable ContentId="MainWindow" Title="メイン画面" CanHide="False" CanClose="False" CanFloat="True" AutoHideWidth="240" CanAutoHide="False" IconSource="images/dokking.png">
                                <DockPanel>
                                    <ListView ItemsSource="{Binding CardLists,Mode=TwoWay}" ScrollViewer.VerticalScrollBarVisibility="Hidden" Style="{StaticResource NormalListView}">
                                        <ListView.ItemsPanel>
                                            <ItemsPanelTemplate>
                                                <StackPanel Orientation="Horizontal"/>
                                            </ItemsPanelTemplate>
                                        </ListView.ItemsPanel>

                                        <ListView.ItemTemplate>
                                            <DataTemplate>
                                                <DockPanel Height="{Binding ActualHeight,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}">
                                                    <control:CardList Margin="10" CardChanged="CardList_CardChanged"
                                          PreviewMouseLeftButtonDown="CardList_PreviewMouseLeftButtonDown"
                                          PreviewMouseMove="CardList_PreviewMouseMove"
                                          PreviewMouseUp="CardList_PreviewMouseUp"
                                          PreviewDrop="CardList_PreviewDrop"
                                          AllowDrop="True"
                                          PreviewDragEnter="CardList_PreviewDragEnter"
                                          PreviewDragLeave="CardList_PreviewDragLeave"
                                          />
                                                </DockPanel>
                                            </DataTemplate>
                                        </ListView.ItemTemplate>
                                    </ListView>
                                </DockPanel>
                            </xcad:LayoutAnchorable>
                        </xcad:LayoutAnchorablePane>
                    </xcad:LayoutPanel>

                </xcad:LayoutRoot>
            </xcad:DockingManager>

3.結果


本当はDataGridとかもWPF Extended Toolkitにあるものを使おうかと思ったけど各機能の資料とかサンプルが見つけられなかったので早々に諦めた。

早く公開できるレベルまで仕上げたい・・・・!!



PowerShellでWPFアプリケーションをBindingするときの注意点

 1.PowerShellでWPFアプリケーションのBindingについて マニアックだけどPowerShellでWPFアプリケーションを作っている。INotifyChangedを実装したいけど、PowerShellにはgetterやsetterがないので通知が発行できない。 そ...