ガントチャート作成日記

久しぶりに更新。スマホのゲームにはまってしまって、なかなかこっちに時間が割けなかった。
とりあえず、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

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

PowerShellでEdgeを自動化(インストール不要。参考:郵便追跡サービス自動操作)

1.経緯について  RPAのソフトをインストールできないので、これまでVBSでCreateObjectでブラウザの自動操作をすることがたまにあった。 ※いざというときの手札として持っているだけで安心感が段違い  見た目上IEがインストールされていなくても、CreateObject...