ラベル ガントチャート の投稿を表示しています。 すべての投稿を表示
ラベル ガントチャート の投稿を表示しています。 すべての投稿を表示

Excelで簡易ガントチャートツール

仕事でプロジェクトツールが全く使われていなく、導入する予定もない部署への応援が決定した。

取り急ぎ、ガントチャートを作れるソフトが欲しかったのでExcelで作れるか試してみた。まぁちゃんとしたソフトがインストールできればそれでよかったんだけど、難しそうなので自作でしのげればと思う。

作成途中だけど、日付を入れるとその日の色が変わる程度なら作れたので一旦、ここまでとしておこうと思う。正直どんな管理項目が必要かもわからないので、そのあたりは融通が利くように列名の定義をできるようにしておいた。さて、必要そうな機能(まぁ色変えるだけなんだけど)の実装は終わったのであとはコードのコピペして完成品を作ろう。


<概要>
 ・処理開始時にメインシートに定義しておいた、色を変えたい日付範囲の項目名を探す
 ・列があとからどんどん追加されていくので「ガントチャート」部分の開始列が分かるように「【ガントチャート開始】」という文字列を入れておく。(消さないでね・・・)
 ・日付セルの場合は加算、減算ができるので基準日から開始日、終了日からの差を見ればどの列範囲かもわかるのでそこの色を変える。
 ・キーワードの設定(〆切日、その他色々)もできればいいな。(願望)
 ・範囲を複数持たせていろんな意味の色を塗れるようにする。発注期間の色、使用期限の色、利用期間の色、などなど(できればいいな)

<プログラム とりあえず4行目の色を変えるだけ>

Private fintBetween1Start As Integer
Private fintBetween1End As Integer
Private fintBetween2Start As Integer
Private fintBetween2End As Integer
Private fintBetween3Start As Integer
Private fintBetween3End As Integer
Private fintBetween4Start As Integer
Private fintBetween4End As Integer
Private fintBetween5Start As Integer
Private fintBetween5End As Integer
Private fdatePrjStartDate As Date

Private Const SHEETNAME_DATA As String = "データ"
Private Const SHEETNAME_MAIN As String = "メイン"
Private Const HeaderRow As Integer = 3
Private Const DataStartRow As Integer = 4


Private Const FINT_MAX_COLUMN_IN_DATE As Integer = 1500

'メインシート
Private Const FINT_KEYWORD_START_ROW As Integer = 6
Private Const FINT_KEYWORD_ITEMNAME_COLUMN As Integer = 12
Private Const FINT_KEYWORD_ITEMVALUE_COLUMN As Integer = 13

'データシート
Private fintGanttChartStartColumn As Integer

Private Const KEY_WORD_MAX_COUNT As Integer = 100 '何件でもいいがとりあえずの総件数

Private Sub Main()

    Call InitializeSetting
    
    Call InitializeRowStatus(4)
    
    Call SetRow(4)

End Sub


Private Sub SetRow(ByVal lngRow As Long)
    Dim lngDiff As Long
    Dim lngEndDiff As Long
    Dim rng As Range
    Dim rngColor As Range
    
    '範囲1の内容を色塗り
    lngDiff = DateDiff("d", fdatePrjStartDate, Sheets(SHEETNAME_DATA).Cells(lngRow, fintBetween1Start))
    lngEndDiff = DateDiff("d", fdatePrjStartDate, Sheets(SHEETNAME_DATA).Cells(lngRow, fintBetween1End))
    Set rngColor = Sheets(SHEETNAME_MAIN).Range("F7")
    Set rng = Sheets(SHEETNAME_DATA).Range(Sheets(SHEETNAME_DATA).Cells(lngRow, fintGanttChartStartColumn + lngDiff), Sheets(SHEETNAME_DATA).Cells(lngRow, fintGanttChartStartColumn + lngEndDiff))
    With rng.Interior
        .Color = rngColor.Interior.Color
        .TintAndShade = rngColor.Interior.TintAndShade
        .PatternTintAndShade = rngColor.Interior.PatternTintAndShade
    End With

    'キーワードを設定(作成中)

'    Dim i, j As Long
'    Dim strKeyWord As String
'    Dim strCellValue As String
'    Dim strColumns As String
'    Dim strColumnArr() As String
'
'    For i = 0 To 100
'        strKeyWord = Sheets(SHEETNAME_MAIN).Cells(i + FINT_KEYWORD_START_ROW, FINT_KEYWORD_ITEMVALUE_COLUMN).Value
'        If strKeyWord = "" Then Exit Sub
'        strColumns = GetSearchStringCell(strKeyWord, True, CStr(lngRow) & ":" & CStr(lngRow), SHEETNAME_DATA)
'
'        strColumnArr = Split(strColumns, ",")
'        For j = 0 To UBound(strColumnArr)
'            If Sheets(SHEETNAME_DATA).Cells(lngRow, strColumnArr(j)).Value = strKeyWord Then
'                Sheets(SHEETNAME_DATA).Cells(lngRow, strColumnArr(j)).Value = ""
'            End If
'        Next
'    Next


End Sub
Private Sub InitializeSetting()

    Dim strGanttChartStartDay As String
    strGanttChartStartDay = GetSearchStringCell("【ガントチャート開始】", True, "1:1", SHEETNAME_DATA)

    fintGanttChartStartColumn = CInt(strGanttChartStartDay)
    fdatePrjStartDate = Sheets(SHEETNAME_MAIN).Range("F4").Value
    fintBetween1Start = GetColumnNo(Sheets(SHEETNAME_MAIN).Range("F5").Value)
    fintBetween1End = GetColumnNo(Sheets(SHEETNAME_MAIN).Range("F6").Value)
    
End Sub

Private Function GetColumnNo(ByVal strColName As String)
    Dim i As Long
    GetColumnNo = 0
    For i = 1 To 100
        If Sheets(SHEETNAME_DATA).Cells(HeaderRow, i).Value = strColName Then
            GetColumnNo = i
            Exit Function
        End If
    Next
End Function
Private Function GetStartGanntChartColumn(ByVal strColName As String)
    Dim i As Long
    GetColumnNo = 0
    For i = 1 To 300
        If Sheets(SHEETNAME_DATA).Cells(1, i).Value = strColName Then
            GetColumnNo = i
            Exit Function
        End If
    Next
End Function


'---------------------------------------------------------
' 指定行の初期化処理を行う
'---------------------------------------------------------
Private Sub InitializeRowStatus(ByVal lngRow As Long)
    
    'Rowの背景色を全て削除
    Call ClearBackgroundInRow(lngRow)

    'キーワードを削除
    Call ClearKeyWordInRow(lngRow)
    
End Sub


Private Sub ClearBackgroundInRow(ByVal lngRow As Long)
    Dim rng As Range
    
    Dim strRange As String
    
    Set rng = Sheets(SHEETNAME_DATA).Range(Cells(lngRow, fintGanttChartStartColumn), Sheets(SHEETNAME_DATA).Cells(lngRow, FINT_MAX_COLUMN_IN_DATE))
    
    '背景色削除
    With rng.Interior
        .Pattern = xlNone
        .TintAndShade = 0
        .PatternTintAndShade = 0
    End With
    
End Sub

'---------------------------------------------------------
' キーワードとして設定されている内容をすべて削除する
'---------------------------------------------------------
Private Sub ClearKeyWordInRow(ByVal lngRow As Long)

    Dim i, j As Long
    Dim strKeyWord As String
    Dim strCellValue As String
    Dim strColumns As String
    Dim strColumnArr() As String
    
    For i = 0 To 100
        strKeyWord = Sheets(SHEETNAME_MAIN).Cells(i + FINT_KEYWORD_START_ROW, FINT_KEYWORD_ITEMVALUE_COLUMN).Value
        If strKeyWord = "" Then Exit Sub
        strColumns = GetSearchStringCell(strKeyWord, True, CStr(lngRow) & ":" & CStr(lngRow), SHEETNAME_DATA)
        
        strColumnArr = Split(strColumns, ",")
        For j = 0 To UBound(strColumnArr)
            If Sheets(SHEETNAME_DATA).Cells(lngRow, strColumnArr(j)).Value = strKeyWord Then
                Sheets(SHEETNAME_DATA).Cells(lngRow, strColumnArr(j)).Value = ""
            End If
        Next
    Next

End Sub


'---------------------------------------------------------
' 日付セルを検索
' シート名と行か列位置を指定し検索する。該当した行、列位置をカンマ区切りで返す。
'  1件ずつループしながらIf関数で1行ずつ判定するより圧倒的に高速
'  複数条件は対応外なので、この関数で該当行を絞ったうえで、If関数で詳細なチェックをかけたほうが高速になる
' 利用例)GetSearchDateCell("2020/08/01",False,"A:A","データ")
' 戻り値例)4,15,20
'---------------------------------------------------------
Public Function GetSearchDateCell(ByVal datValue As Date, ByVal blnSearchInRow As Boolean, ByVal strRange As String, ByVal strSheetName As String) As String
    Dim rng As Range
    Dim adr As String
    Dim strResult As String
    GetSearchDateCell = ""
  
    If blnSearchInRow = True Then
        Set rng = Sheets(strSheetName).Rows(strRange).Find(datValue)
    Else
        Set rng = Sheets(strSheetName).Columns(strRange).Find(datValue)
    End If
  
    If rng Is Nothing Then
        Exit Function
    Else
        adr = rng.Address
        If blnSearchInRow = True Then
            strResult = rng.Column
        Else
            strResult = rng.Row
        End If
    End If

    Do
      
    If blnSearchInRow = True Then
        Set rng = Sheets(strSheetName).Rows(strRange).FindNext(After:=rng)
    Else
        Set rng = Sheets(strSheetName).Columns(strRange).FindNext(After:=rng)
    End If
    
    If rng.Address = adr Then
            Exit Do
        Else
            If blnSearchInRow = True Then
                strResult = strResult & "," & rng.Column
            Else
                strResult = strResult & "," & rng.Row
            End If
        End If
    Loop

    GetSearchDateCell = strResult

End Function


'---------------------------------------------------------
' 文字列セルを検索
' シート名と行か列位置を指定し検索する。該当した行、列位置をカンマ区切りで返す。
'  1件ずつループしながらIf関数で1行ずつ判定するより圧倒的に高速
'  複数条件は対応外なので、この関数で該当行を絞ったうえで、If関数で詳細なチェックをかけたほうが高速になる
' 利用例)GetSearchDateCell("2020/08/01",False,"A:A","データ")
' 戻り値例)4,15,20
'---------------------------------------------------------
Public Function GetSearchStringCell(ByVal strValue As String, ByVal blnSearchInRow As Boolean, ByVal strRange As String, ByVal strSheetName As String) As String
    Dim rng As Range
    Dim adr As String
    Dim strResult As String
    GetSearchStringCell = ""
  
    If blnSearchInRow = True Then
        Set rng = Sheets(strSheetName).Rows(strRange).Find(strValue)
    Else
        Set rng = Sheets(strSheetName).Columns(strRange).Find(strValue)
    End If
  
    If rng Is Nothing Then
        Exit Function
    Else
        adr = rng.Address
        If blnSearchInRow = True Then
            strResult = rng.Column
        Else
            strResult = rng.Row
        End If
    End If

    Do
      
    If blnSearchInRow = True Then
        Set rng = Sheets(strSheetName).Rows(strRange).FindNext(After:=rng)
    Else
        Set rng = Sheets(strSheetName).Columns(strRange).FindNext(After:=rng)
    End If
    
    If rng.Address = adr Then
            Exit Do
        Else
            If blnSearchInRow = True Then
                strResult = strResult & "," & rng.Column
            Else
                strResult = strResult & "," & rng.Row
            End If
        End If
    Loop

    GetSearchStringCell = strResult

End Function









WPFのコンボボックスの背景色について

TreeListView上でコンボボックスを利用するときにフォーカスのあるコンボボックスの色をイベントで変えようとしたけど上手くいかなかった。

色々と調べているとXaml側のテンプレートをいじる必要があるみたいだったので、いろいろとやってみた。

とりあえず、コンボボックスをデザイナに配置し、コンボボックスを選択した状態で「右クリック」->「テンプレートの編集」ー>「コピーして編集」を選択する。


リソースの名前は後でも変更できるのと、自動で作成されるXAMLの場所も移動できるので一旦は初期のままにして「OK」を押す。


ものすごく長いリソースがWindow.Resourceの中に作成される


このリソースの中でBackgroundを管理している箇所のStyleを変更すれば変えることができる。具体的には下記の画像の部分。
※編集可能なコンボボックスを使っているときは「ComboBox.Static.Editable.Background」を変更すればよいと思われる。




今回の場合は、フォーカスの移動によって背景色を変更したかったので、「ComboBox.Static.Background」、「ComboBox.MouseOver.Background」をデザイナ上は「Transparent」に設定しておいて、プログラムからGotFocusイベントとLostFocusイベントを利用して変更している。

=== ソース抜粋1 ===
            // 選択中の背景色を設定する
            this.GotFocus += SetSelectionBackgroundColor;
            // 選択中の背景色を初期値にもどす
            this.LostFocus += ClearSelectionBackgroundColor;
==============

=== ソース抜粋2 ===

        /// <summary>
        /// フォーカス取得時の背景色
        /// </summary>
        public Brush SelectionBackgroundColor = new SolidColorBrush(Color.FromArgb(255, 250, 250, 0));

        /// <summary>
        /// 選択中のコンボボックスの背景色を設定
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SetSelectionBackgroundColor(object sender, RoutedEventArgs e)
        {
            var grid = this.Template.FindName("templateRoot", this) as Grid;
            if (grid != null)
                grid.Background = SelectionBackgroundColor;
        }

        /// <summary>
        /// 選択中のコンボボックスの背景色を初期化
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ClearSelectionBackgroundColor(object sender, RoutedEventArgs e)
        {
            var grid = this.Template.FindName("templateRoot", this) as Grid;
            if (grid != null)
                grid.Background = null;
        }

=============
※templateRootは先ほどデザイナから自動生成されたXAMLの中で「ComboBoxTemplate」というControlTemplateがあるが、その中のGridのこと。
 なのでコンボボックスを構成する各コントロールを配置している元のGridの背景色を変更しているだけ。逆に言うとコンボボックスをどうやって作っているのかがControlTemplateを見ればわかる。


後はこの作成したリソースをコンボボックスのStyleに適用すればよい。

=== ソース抜粋3 ===
<ComboBox  ItemsSource="{Binding PriorityList, Mode=TwoWay}"
DisplayMemberPath="Name" SelectedValuePath="PriorityNo"
SelectedValue="{Binding SelectedPriorityNo}" Style="{StaticResource ComboBoxStyle1}">

=============

Window.Resourceに配置すると他のWindowでは利用できなくなるので、App.Xamlに配置すれば他のWindowから利用できる。
App.Xamlが長くなりすぎるのであれば、別のリソースディクショナリに作成して、App.Xamlは「ResourceDictionary.MergedDictionaries」でリソースの指定をすればよい。

<適用した例>


TreeListViewでのコンボボックスのデータバインドについて

TreeListViewにコンボボックスを表示するのに手間取ったのでメモ。

相変わらずゆっくりとガントチャートのアプリをいじっている。
前作っていたのはZipファイルにデータを保存する形式だったので、今度はSqliteにデータを保存する形に作り直している。

別のアプリのTaskManagerと共通のデータベースを利用すれば面白いのではないかと思って作っていたら、コンボボックスの表示がうまくできなかったので試行錯誤をしていた。



クラスとしてはこんな構造を持っていた。

メイン画面のDataContextにMainWindowViewModelをバインドして、TreeListViewには下記のような形でTreeViewへのバインドと同じような形で処理をしていた。

==== ソース抜粋 ====

               <TreeListView:TreeListView x:Name="treeView" Background="White" AllowsColumnReorder="True" ItemsSource="{Binding TreeListViewModel.TreeListItems ,Mode=TwoWay}">
                    <TreeListView:TreeListView.ItemTemplate>
                        <HierarchicalDataTemplate ItemsSource="{Binding Children}" />
                    </TreeListView:TreeListView.ItemTemplate>

===============

コンボボックスの中身をバインドでやろうとして、プロパティをMainViewModelやTreeListViewModelに実装し、「ItemsSource="{Binding MainWindowViewModel.ComboboxItems,Mode=TwoWay}">」や「ItemsSource="{Binding TreeListViewModel.ComboboxItems,Mode=TwoWay}">」と指定していたが、うまく表示されない。コンボボックスを開いてみても項目の生成すらされていなかったので、バインディング自体が上手くいっていなかったので、よくよく考えるとChildにはTreeListItemをバインドしているのだから、TreeListItemクラスにコンボボックスの中身を持つ必要があるんド絵はないかと思って、TreeListItemクラスにコンボボックスの内容を作成し、バインドしてみたら上手くいった・・・。

とはいえ、TreeListItemの中身にコンボボックスの要素を実体として持つなどあほらしすぎる。
というわけで、コンボボックスの実体はTreeListViewModelに作成しておいて、参照のみTreeListItemに持つようにした。


==== ソース抜粋1 ====

   /// <summary>
    /// TreeListViewにバインディングするクラス
    /// </summary>
    public class TreeListItem : Model.ModelBase
    {
        public TreeListViewViewModel TreeListVM;
        private ObservableCollection<Model.Status> _statusList;
        /// <summary>
        /// 状況コンボボックスに表示するリストを定義
        /// データのもとはTreeListViewModelに設定をし、TreeListItemは参照(Class)のみとする。
        /// </summary>
        public ObservableCollection<Model.Status> StatusList { get { return this._statusList; } set { this._statusList = value; NotifyPropertyChanged(); } }

===============

==== ソース抜粋1 ====

    /// <summary>
        /// TreeListItemを生成したときの初期設定
        /// </summary>
        /// <param name="task"></param>
        /// <param name="treeListViewViewModel"></param>
        public void Init(Model.Task task, TreeListViewViewModel treeListViewViewModel,TreeListItem parentTree)
        {
            this.Data = task;
            this.TreeListVM = treeListViewViewModel;

            // コンボボックス用の参照をセットする
            this.StatusList = treeListViewViewModel.StatusList;
            this.DifficultyList= treeListViewViewModel.DifficultyList;
            this.PriorityList = treeListViewViewModel.PriorityList;
            this.SelectedStatsuNo = Data.CardStatusNo;
            this.SelectedDifficultyNo = Data.DifficultyNo;
            this.SelectedPriorityNo= Data.PriorityNo;


            this.Data.TreeItem = this;

            if (parentTree != null)
            {
                SetParent(parentTree, null);
            }
        }

===============


==== ソース抜粋3 ====
                        <!-- *** 状態 *** -->
                        <GridViewColumn Header="状態" Width="80"  HeaderContainerStyle="{StaticResource GridViewColumnHeaderStyle}">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <!-- Marginで左右から-6pxとしないとHearder部のタイトルとラインの位置がずれる.GridViewの既知の不具合 -->
                                    <Border BorderBrush="Black"  BorderThickness="0,0,0.5,0" Margin="-6,0,-6,0">
                                        <DockPanel   >
                                            <ComboBox  VerticalAlignment="Stretch" VerticalContentAlignment="Center" ItemsSource="{Binding StatusList,Mode=TwoWay}"
                                                                                           HorizontalAlignment="Stretch" HorizontalContentAlignment="Left"
                                                                                           DisplayMemberPath="Name" SelectedValuePath="StatusNo"
                                                                                           SelectedValue="{Binding SelectedStatsuNo}">
                                                <ComboBox.ItemTemplate>
                                                    <DataTemplate>
                                                        <TextBlock><Run Text="{Binding StatusNo, Mode=TwoWay}" /><Run Text=":" /><Run Text="{Binding Name, Mode=TwoWay}" /></TextBlock>
                                                    </DataTemplate>
                                                </ComboBox.ItemTemplate>
                                            </ComboBox>
                                        </DockPanel>
                                    </Border>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
===============

クラスは参照型なので、TreeListViewModel側でインスタンス化し、各選択肢をリストに追加して、TreeListItemでは参照の設定のみとしておく。

昔はClassが参照型、Structureが値型だとかは知識としては知っていたけど、あまり意識して利用するところはなかった。(それはそれでどうなのかと思うけど)
こういう時にちゃんと知っていると便利だと思う。

ComboboxのBindingについては、他のいろんなサイト見れば参考になると思う。
そもそもTreeListViewでComboboxを使うような状況になっている人がどれだけいるのか謎だけど。









ガントチャート作成日記

あいかわらずのんびりと作成中。
タスクの削除を付けたが、チャートの削除をまだつけていないことに気づいた。
っていうかそれ以前にデータの保存処理を付けてないし、初期の日付範囲とかも一切つけていない。



プロジェクトの初期設定として、プロジェクトの開始日・終了日を変更できるようにすることから始めるべきかな・・・。

頑張ったところとしては、左側のタスクの一番左側のタスクの縦幅を広げて、大きいグループの範囲を広げた時に、濃い線をタスクの一覧とガントチャートの一覧に表示して、どこまでがグループに属しているかをわかりやすくしたことと、マウスで複数のチャートを選択・移動できるようにした。

グループを表現する線についてはGanttGridTemplateSelecter.csとGanttGridTaskViewTemplateSelecter.csにパターンを追加して、DataTemplateを新しく定義してこちゃこちゃすればできるけど、マウスでの移動については一苦労した。
CanvasコントロールをGridコントロールの下に張り付けて、Rectを新たに描画している。

Canvasコントロールを追加したことでスクロールバーがうまく出なくなってしまったので、SizeChangeイベントで変更するようにした。

マウスの範囲選択でGridコントロール上のチャートを選択するのもネットで調べてみるといろいろとサンプルがある。
世の中にはすごい人がいるなぁ。

<ソース>
        #region 右側のCanvasのサイズを合わせる

        // マウスで複数選択をさせるためにCanvasコントロールを利用しているがCanvasコントロールはScrollBarが出しずらい
        // ガントチャートの上にCanvasをTransportで重ねることも考えたが、チャートをクリックできなくなってしまう。
        // Canvasコントロールをクリックしたときの通知などをチャート側に伝えるようにすればよいが、さすがに手間なので
        // Canvasコントロールのサイズを変更しScrollBarが出るようにした。

        /// <summary>
        /// 幅を合わせる
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void gridCalender_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Grid grid = (Grid)sender;
            CalenderBaseCanvas.Width = e.NewSize.Width;
        }

        /// <summary>
        /// 高さはタスク一覧側と同じになるので合わせる
        /// gridCalender側と合わせるとタスクが少ないときにスクロールバーが上に出てしまう
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void icTaskGrid_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            CalenderBaseCanvas.Height = icTaskGrid.ActualHeight;
        }
        #endregion

<DownLoad>

ガントチャート作成日記

久しぶりの更新。
ガントチャートを自分で自由に作成できるツールを作ってみているが、いい感じになってきた。(保存はできないけど・・・。)

左側に一覧を作って、自分で縦幅を自由に変えて、グループを表現しつつ、右側にチャートやメモを配置していく。

スマホのゲームをしながらだからいつになるかわからないけど・・・。


試行錯誤をしながらだが、根本は変わらない。Gridコントロールにコントロールを配置していくだけ。DataTemplateSelecterでDataTemplateを切り替えてコントロールを切り替えていく。慣れてくると簡単に表現できるようになってくるので楽しい。
Paintイベントで頑張る必要もないので作りやすい。
ただし、データ数が多くなると速度が気になりそう・・・。

まぁ趣味だしいいよね。

<DownLoad>

ガントチャート作成日記

久しぶりに更新。スマホのゲームにはまってしまって、なかなかこっちに時間が割けなかった。
とりあえず、DataGridではなく、独自で一覧を作成する方向で作り続けてみようと思う。

チャートの部分と同様にGridコントロールに独自クラスをバインドする形式にして、DataTemplateSelecterでDataTemplateを切り替えることでテキストボックスや行部分を表示している。
Thumbコントロールを使ってリサイズも可能なようにしておいて、セルのマージができないのを補ってみた。

工夫したところとしては処理速度を補うために、バインド用のリストとテキストボックス用のリストと行を表現するためのリストを分けた。

<ソース抜粋(GanntGridViewModel.cs)>
   // 左部のタスク一覧を表現するためのセルを格納する各リスト
   this.GanttGridTaskViewPartsRowCells = new ObservableCollection<GanttCell>();
   this.GanttGridTaskViewPartsTaskCells = new ObservableCollection<GanttCell>();
   this.GanttGridTaskViewCells = new ObservableCollection<GanttCell>();

GanttGridTaskViewPartsRowCellsとGanttGridTaskViewPartsTaskCellsにGanntCellクラスをインスタンス化してそれぞれに追加したのちに、this.GanttGridTaskViewCellsに両方のリストの内容をAddしてバインディングに利用する。
そうすることでタスクだけを変更したい時にはGanttGridTaskViewCellsをループするのではなく、GanttGridTaskViewPartsTaskCellsをループすることで対応できる。

問題としては、タスクを追加したときにリストへの追加ミスがないようにしないといけないところと、その際に他の問題が起きないか、というところ・・・。

これは試してみないとわからないので、次回やってみようと思う。

<DownLoad>

ガントチャート作成日記

行や列を見やすくするために別のDataTemplateを表現するようにした。



バインドする全体のGanttCellを格納するGanttGridCellsと列や行を分けて格納しておく
GanttGridRowCells やGanttGridColumnCells を新たに追加した。

public GanttGridViewModel()
{
    this.GanttGridCells = new ObservableCollection<GanttCell>();
    this.GanttGridRowCells = new ObservableCollection<GanttCell>();
    this.GanttGridColumnCells = new ObservableCollection<GanttCell>();
    this.GanttGridTaskCells = new ObservableCollection<GanttCell>();

    GanttHeader h = new GanttHeader();
 
    h.ColumnCount = this.ColumnCount;
    h.ColumnWidth = this.ColumnWidth;

    ObservableCollection<GanttHeaderCell> topCells = new ObservableCollection<GanttHeaderCell>();
    ObservableCollection<GanttHeaderCell> bottomCells = new ObservableCollection<GanttHeaderCell>();

    int colSpanCnt = 0;
    for (int i = 0; i <= this.ColumnCount; i++)
    {
        DateTime d = DateTime.Now.AddDays(i);
        if (d.ToString("dd") == "01" || i == 0)
        {
            int iDaysInMonth = DateTime.DaysInMonth(d.Year, d.Month);
            colSpanCnt = iDaysInMonth - int.Parse(d.ToString("dd")) + 1;
            topCells.Add(new GanttHeaderCell() { ColumnIndex = i, RowIndex = 0, ColumnSpan = colSpanCnt, Date = d });
        }
        bottomCells.Add(new GanttHeaderCell() { ColumnIndex = i, RowIndex = 0, Date = d });
    }

    h.BottomHeaderCells = bottomCells;
    h.TopHeaderCells = topCells;

    TimeSpan ts = DateTime.Now.AddYears(1) - DateTime.Now;
    h.ColumnCount = ts.Days;

    this.Header = h;


    // タスクを表現するセルを追加
    for (int i = 0; i < 500; i++)
    {
        this.GanttGridTaskCells.Add(new GanttCell() { RowIndex = i, RowSpan = 1, ColumnIndex = i, ColumnSpan = 2, DataTemplateType = GanttCell.TemplateType.Task });
    }

    // タスクの数だけ行を表現するセルを追加
    for (int i = 0; i < this.GanttGridTaskCells.Count - 1; i++)
    {
        if (i % 2 == 0)
        {
            this.GanttGridRowCells.Add(new GanttCell() { RowIndex = i, RowSpan = 1, ColumnIndex = 0, ColumnSpan = 365, DataTemplateType = GanttCell.TemplateType.RowLine });
        }
        else
        {
            this.GanttGridRowCells.Add(new GanttCell() { RowIndex = i, RowSpan = 1, ColumnIndex = 0, ColumnSpan = 365, DataTemplateType = GanttCell.TemplateType.AlternateRowLine });
        }
    }

    // 列の数だけ列を表現するセルを追加
    foreach (var item in this.Header.BottomHeaderCells)
    {
        string week = item.Date.ToString("ddd");
        GanttCell.TemplateType type = GanttCell.TemplateType.ColumnLine;
        if (week == "日")
        {
            type = GanttCell.TemplateType.ColumnLineOrange;
        }
        else if (week == "土")
        {
            type = GanttCell.TemplateType.ColumnLineBlue;
        }
        else if (Common.Holiday(item.Date).holiday != Common.HolidayInfo.HOLIDAY.WEEKDAY)
        {
            // 平日以外(祝日・振替休日)の場合は日と同じ色を設定
            type = GanttCell.TemplateType.ColumnLineOrange;
        }

        this.GanttGridColumnCells.Add(new GanttCell() { RowIndex = 0, ColumnIndex = item.ColumnIndex, ColumnSpan = 1, RowSpan = this.GanttGridTaskCells.Count, DataTemplateType = type });
    }

    //バインディングするためのセルを作成する
    foreach (var item in this.GanttGridColumnCells) { this.GanttGridCells.Add(item); }
    foreach (var item in this.GanttGridRowCells) { this.GanttGridCells.Add(item); }
    foreach (var item in this.GanttGridTaskCells) { this.GanttGridCells.Add(item); }

}

バインドされているのはGanttGridCellsプロパティだが見た目を変更するためにDataTemplateSelectorを継承したGanttGridTemplateSelecterを作成している。

GanttGridTemplateSelecterはGanttGrid.xamlで利用している
ItemsControlのItemTemplateSelectorに設定している。
各DataTemplateはUserControlのResourcesに定義しておいて、GanttGridCellsがバインドされたらバインドされているGanttCellのDataTemplateTypeプロパティに応じて利用するDateTemplateを切り替えている。

詳細はソースを参照。
前回アップしたものとWinMergeなどで比較するとわかりやすい。

ついでにDataGridにGanttGridTaskCellsをバインドすることで、ガントチャートっぽくなった。DataGridをスクロールしたときに同期するようにしてみた。
ガントチャート側をスクロールしたときにもスクロールするようにしたりする必要はある。

横に出すのをDataGridのような一覧にするか、移動、リサイズ可能なTextBoxを作成して、それをバインドするか悩むところ。
せっかくなんでいろんなソフトを作ってみようかと思う。

DataGridで階層を表現できると一番いいけど・・・。
セルのマージは難しそうだし、悩ましい。

<DownLoad>

ガントチャート作成日記

タスクを表現してみた。
タスクの移動とサイズをドラッグで変更できるようにプログラムを修正してみた。

リサイズと移動のためにThumbコントロールを利用している。
Gridの幅を細かくしたり、Canvasコントロールに配置すれば、ユーザーが自由にコントロールを配置できる。

<GanttGrid.xaml>に修正を行う。
明細にタスクを表現するためにItemsControlのスタイルを追加する。

                <!-- タスクを表現 -->
                <ItemsControl.ItemContainerStyle>
                    <Style>
                        <Setter Property="Grid.Column" Value="{Binding Path=ColumnIndex}"/>
                        <Setter Property="Grid.Row" Value="{Binding Path=RowIndex}"/>
                        <Setter Property="Grid.ColumnSpan" Value="{Binding Path=ColumnSpan}"/>
                    </Style>
                </ItemsControl.ItemContainerStyle>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <!--ドラッグ対象のオブジェクトを定義する-->
                        <Border BorderBrush="Black" BorderThickness="0.25"  Height="18">
                            <Grid Height="18">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="3"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="3"/>
                                </Grid.ColumnDefinitions>
                                <Thumb Name="LeftThumb" DragDelta="LeftThumb_DragDelta" QueryCursor="LeftThumb_QueryCursor" Grid.Column="0" >
                                    <Thumb.Template>
                                        <ControlTemplate>
                                            <Grid>
                                                <TextBlock Background="#FFF9CC95" Text="" />
                                            </Grid>
                                        </ControlTemplate>
                                    </Thumb.Template>
                                </Thumb>
                                <Thumb Name="CenterThumb" Grid.Column="1" DragDelta="Thumb_DragDelta" QueryCursor="CenterThumb_QueryCursor">
                                    <Thumb.Template>
                                        <ControlTemplate>
                                            <Grid>
                                                <TextBlock Background="#FFF9CC95" Text="サンプル"  FontFamily="MS Gothic"/>
                                            </Grid>
                                        </ControlTemplate>
                                    </Thumb.Template>
                                </Thumb>
                                <Thumb Name="RightThumb" Grid.Column="2" Width="5" Background="red" DragDelta="RightThumb_DragDelta" QueryCursor="RightThumb_QueryCursor">
                                    <Thumb.Template>
                                        <ControlTemplate>
                                            <Grid>
                                                <TextBlock Background="#FFF9CC95" Text="" />
                                            </Grid>
                                        </ControlTemplate>
                                    </Thumb.Template>
                                </Thumb>

                            </Grid>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                
<GanntGrid.cs>
下記のソースコードを追加
26で割っているのはColumnWidthの長さ。
移動量をColumnWidthで割ってColumnIndexを変更量を計算している

        //Thumbコントロールのドラッグイベント処理
        private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            var thumb = sender as Thumb;
            if (thumb == null) return;

            var cell = thumb.DataContext as GanttCell;

            int move = (int)e.HorizontalChange / 26;
            Console.WriteLine((e.HorizontalChange / 26).ToString() + ";" + move.ToString());
            cell.ColumnIndex = cell.ColumnIndex + (int)move;

        }

        private void LeftThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            Console.WriteLine((e.HorizontalChange / 26).ToString() + ";" + "LeftThumb_DragDelta");

            var thumb = sender as Thumb;
            if (thumb == null) return;

            var cell = thumb.DataContext as GanttCell;

            int move = (int)e.HorizontalChange / 26;
            cell.ColumnIndex = cell.ColumnIndex + (int)move;
            cell.ColumnSpan = cell.ColumnSpan + (int)move * -1;

            Console.WriteLine((move).ToString());

        }

        private void RightThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            Console.WriteLine((e.HorizontalChange / 26).ToString() + ";" + "RightThumb_DragDelta");

            var thumb = sender as Thumb;
            if (thumb == null) return;

            var cell = thumb.DataContext as GanttCell;

            int move = (int)e.HorizontalChange / 26;
            cell.ColumnSpan = cell.ColumnSpan + (int)move;
        }

        private void LeftThumb_QueryCursor(object sender, QueryCursorEventArgs e)
        {
            e.Cursor = Cursors.SizeWE;
            e.Handled = true;
        }

        private void CenterThumb_QueryCursor(object sender, QueryCursorEventArgs e)
        {
            e.Cursor = Cursors.Cross;
            e.Handled = true;
        }

        private void RightThumb_QueryCursor(object sender, QueryCursorEventArgs e)
        {
            e.Cursor = Cursors.SizeWE;
            e.Handled = true;
        }





<DownLoad>

ガントチャート作成日記

ヘッダー部を作成するために大きく下記の内容を対応していく。

 ・大きく分けて年月部分と日付部分をセルを表現する
 ・ヘッダーを表示するためのGanttHeaderクラスを作成し、ViewModelにHeaderプロパティを追加
 ・年月部分を表現するためにユーザーコントロールを作成する
 ・日付部分を表現するためにDataTemplateを利用する
  ※ユーザーコントロールを大量に作成すると遅くなるらしいので。

①の部分はユーザーコントロールで作成した。
②の部分はDataTemplateを利用して作成した。




ちょっと困ったのが日曜日と土曜日の背景色について、ValueConverterでバインディングしている日付を渡して色を変えようと思ったが色を事前にXamlに定義したものを利用しようと思うとFindResouceが利用できないので、MultiValueConverterを利用して、自分自身を渡すことで対応した。(DayOfTheWeekBackgroundConverter.cs)

細かいのはサンプルソースを参照。

ーーーーー DayOfTheWeekBackgroundConverter.cs
       public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime b = (DateTime)values[0];
            FrameworkElement callingElement = (FrameworkElement)values[1];
            LinearGradientBrush brush = null;
            string week = b.ToString("ddd");
            if (week == "日")
            {
                brush = (LinearGradientBrush)callingElement.FindResource("OrangeGradientBrush");
            }
            else if (week == "土")
            {
                brush = (LinearGradientBrush)callingElement.FindResource("BlueGradientBrush");
            }
            else if (Common.Holiday(b).holiday != Common.HolidayInfo.HOLIDAY.WEEKDAY)
            {
                // 平日以外(祝日・振替休日)の場合は日と同じ色を設定
                brush = (LinearGradientBrush)callingElement.FindResource("OrangeGradientBrush");
            }
            else
            {
                brush = (LinearGradientBrush)callingElement.FindResource("NormalGradientBrush");
            }
            return brush;
        }
ーーーーー 

ーーーーー GanttGrid.xaml 
                            <Border x:Name="DayLabelRowBorder" BorderBrush="Silver"  BorderThickness="0.25">
                                <Border.Background>
                                    <MultiBinding Converter="{StaticResource ConverterDayOfTheWeekBackground}">
                                        <Binding Path="Date"/>
                                        <Binding RelativeSource="{RelativeSource Self}"/>
                                    </MultiBinding>
                                </Border.Background>
                                
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="*"/>
                                        <RowDefinition Height="*"/>
                                    </Grid.RowDefinitions>
                                    <TextBlock x:Name="Day" Text="{Binding Date,Mode=TwoWay,StringFormat=dd}" TextAlignment="Center"  VerticalAlignment="Center" FontFamily="MS Gothic" Grid.Row="0" Padding="0" TextOptions.TextFormattingMode="Display" TextOptions.TextRenderingMode="ClearType" />
                                    <TextBlock x:Name="DayOfWeek" Text="{Binding Date,Converter={StaticResource ConverterDayOfTheWeek}}" TextAlignment="Center"  VerticalAlignment="Center" FontFamily="MS Gothic" Grid.Row="1" Padding="0" TextOptions.TextFormattingMode="Display" TextOptions.TextRenderingMode="ClearType" />
                                </Grid>

                            </Border>
ーーーーー 


<DownLoad>
https://sites.google.com/site/mmbloguserfile/wai-bufairu/20181204_GanntChart.zip

次は、タスクを表現する部分をやろうと思う。
タスクを大量に表示してもそんなに遅くならないようにするようにしたいと思うがどこまでできるか・・・。

ガントチャート作成日記

久しぶりにガントチャート関連の記事を作成。
以前の日記でも書いたが、簡単にガントチャートを作る方法として、
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;

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

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

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