ガントチャートを作成するときはプロジェクトを完成させるために必要なタスクを洗い出します。大まかなタスクに分けることができたら、これをさらに細かく分けて「子タスク」をつくります。今回は「子タスク」をカテゴリ分けできるようにします。またこれまではタスクは一番下に追加することしかできませんでしたが、任意の場所に追加できるようにします。
そのためにフォームの左側にカテゴリ表示用の領域をつくります。各タスクはカテゴリの右側につくられます。
それからデータ構造をはっきりとつくります。これまではタスクを作って設定することができてもこれをデータファイルとして保存することはできませんでした。将来、ファイルとして保存し読み出しができるものとして完成させるためにデータ構造をつくります。
1 2 3 4 5 6 |
カテゴリA タスク1 タスク2 カテゴリB タスク3 タスク4 |
カテゴリオブジェクトをつくってそのなかにタスクをリストとして持たせます。そしてカテゴリオブジェクトもリストに格納します。
パネルを右クリックしたらあたらしいカテゴリを追加します。そしてカテゴリのなかにタスクを自動的に追加します。
またカテゴリを右クリックしたらその上側や下側に新しいカテゴリを追加できるようにします。また一番下にタスクを追加できるようにします。タスクを右クリックしたらこれまでのようにタスクの詳細を設定できるようにするほかにこのタスクの上側や下側に新しいタスクを追加できるようにします。
まずカテゴリの表すコントロールとクラスを作成します。
まずコンテキストメニューを表示できるように、コンストラクタ内で自作メソッドInitContextMenuを実行します。CategoryUserControl上で右クリックしたら、[このカテゴリ前に新しいカテゴリを追加]、[このカテゴリ次に新しいカテゴリを追加]、[このカテゴリの一番下にタスクを追加]のメニューを表示します。
またCategoryUserControlはリストで管理するので、これを格納するリストをコンストラクタで渡します。これで新しいCategoryUserControlがどの部分に挿入されるかもわかります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public partial class CategoryUserControl : UserControl { public CategoryUserControl(List<CategoryUserControl> categoryUserControls) { InitializeComponent(); InitContextMenu(); BackColor = Color.White; BorderStyle = BorderStyle.FixedSingle; CategoryUserControls = categoryUserControls; } // コンストラクタで渡されたリストを保管 List<CategoryUserControl> CategoryUserControls; // CategoryUserControlのなかにつくられるTaskUserControlをリストで管理する public List<TaskUserControl> TaskUserControls = new List<TaskUserControl>(); // コンテキストメニューを表示するために必要 ContextMenuStrip ContextMenuStrip1 = new ContextMenuStrip(); ToolStripMenuItem AddTaskMenuItem = new ToolStripMenuItem(); ToolStripMenuItem AddCategoryMenuItem = new ToolStripMenuItem(); ToolStripMenuItem AddCategoryPrevMenuItem = new ToolStripMenuItem(); // コンテキストメニューを表示させるためのメソッド void InitContextMenu() { this.ContextMenuStrip = ContextMenuStrip1; AddCategoryPrevMenuItem.Text = "このカテゴリ前に新しいカテゴリを追加"; AddCategoryPrevMenuItem.Click += AddCategoryPrevMenuItem_Click; ; AddCategoryMenuItem.Text = "このカテゴリ次に新しいカテゴリを追加"; AddCategoryMenuItem.Click += AddCategoryMenuItem_Click; ; AddTaskMenuItem.Text = "このカテゴリの一番下にタスクを追加"; AddTaskMenuItem.Click += AddTaskMenuItem_Click; ; this.ContextMenuStrip1.Items.AddRange(new ToolStripItem[] { AddCategoryPrevMenuItem, AddCategoryMenuItem, new ToolStripSeparator(), AddTaskMenuItem, }); } } |
[このカテゴリ前に新しいカテゴリを追加]、または[このカテゴリ次に新しいカテゴリを追加]が選択されたら新しいCategoryAddedイベントを発生させてCategoryUserControlが挿入される場所を知らせます。実際に新しいCategoryUserControlを挿入する処理はForm1でおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class CategoryUserControl : UserControl { public delegate void CategoryAddedHandler(CategoryUserControl sender, CategoryAddedArgs e); public event CategoryAddedHandler CategoryAdded; private void AddCategoryMenuItem_Click(object sender, EventArgs e) { int index = CategoryUserControls.IndexOf(this); CategoryAdded?.Invoke(this, new CategoryAddedArgs(index + 1)); } private void AddCategoryPrevMenuItem_Click(object sender, EventArgs e) { int index = CategoryUserControls.IndexOf(this); CategoryAdded?.Invoke(this, new CategoryAddedArgs(index)); } } |
以下はイベントハンドラの引数です。新しいCategoryUserControlはIndexの位置に入ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class CategoryAddedArgs : EventArgs { public CategoryAddedArgs(int index) { Index = index; } public int Index { protected set; get; } } |
コンテキストメニューの[このカテゴリの一番下にタスクを追加]が選択されたときの処理を示します。新しいTaskUserControlはCategoryUserControlのなかにあるTaskUserControlsの一番最後に入ります。これもどの位置にTaskUserControlが追加されるかをイベントハンドラの引数でForm1クラスに知らせます。
1 2 3 4 5 6 7 8 9 10 |
public partial class CategoryUserControl : UserControl { public delegate void TaskAddedHandler(CategoryUserControl sender, TaskAddedArgs e); public event TaskAddedHandler TaskAdded; private void AddTaskMenuItem_Click(object sender, EventArgs e) { TaskAdded?.Invoke(this, new TaskAddedArgs(TaskUserControls.Count)); } } |
以下はイベントハンドラの引数です。新しいTaskUserControlはIndexの位置に入ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class TaskAddedArgs : EventArgs { public TaskAddedArgs(int index) { Index = index; } public int Index { protected set; get; } } |
次にTaskUserControlクラスを変更します。
まずコンストラクタの引数を変更します。TaskUserControlの親コントロールになるCategoryUserControlを引数として渡します。またCategoryUserControlが持っているTaskUserControlのリストもいっしょに渡します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class TaskUserControl : UserControl { CategoryUserControl CategoryUserControl; List<TaskUserControl> TaskUserControls = new List<TaskUserControl>(); public TaskUserControl(CategoryUserControl categoryUserControl, List<TaskUserControl> taskUserControls) { InitializeComponent(); InitContextMenu(); BorderStyle = BorderStyle.None; CategoryUserControl = categoryUserControl; TaskUserControls = taskUserControls; } } |
次に右クリックされたときに表示するコンテキストメニューをつくる処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public partial class TaskUserControl : UserControl { ContextMenuStrip ContextMenuStrip1 = new ContextMenuStrip(); ToolStripMenuItem SetTaskDateMenuItem = new ToolStripMenuItem(); ToolStripMenuItem AddTaskPrevMenuItem = new ToolStripMenuItem(); ToolStripMenuItem AddTaskNextMenuItem = new ToolStripMenuItem(); void InitContextMenu() { this.ContextMenuStrip = this.ContextMenuStrip1; SetTaskDateMenuItem.Text = "日時設定"; SetTaskDateMenuItem.Click += SetTaskDateMenuItem_Click; AddTaskPrevMenuItem.Text = "この上にタスク追加"; AddTaskPrevMenuItem.Click += AddTaskPrevMenuItem_Click; ; AddTaskNextMenuItem.Text = "この下にタスク追加"; AddTaskNextMenuItem.Click += AddTaskNextMenuItem_Click; ; this.ContextMenuStrip.Items.AddRange(new ToolStripItem[] { SetTaskDateMenuItem, new ToolStripSeparator(), AddTaskPrevMenuItem, AddTaskNextMenuItem, }); } } |
コンテキストメニュー[この下にタスク追加]が選択されたらこの下に新しいタスクをつくる処理を示します。イベントTaskInsertedを定義します。右クリックされたTaskUserControlがどれかわかればその下が上から何番目かがわかります。作成したTaskUserControlをリストに追加し、イベントハンドラで追加されたTaskUserControlをForm1クラスに伝えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class TaskUserControl : UserControl { public event TaskInsertedHandler TaskInserted; public delegate void TaskInsertedHandler(object sender, TaskInsertedArgs e); private void AddTaskNextMenuItem_Click(object sender, EventArgs e) { int index = TaskUserControls.IndexOf(this); TaskUserControl taskUserControl = new TaskUserControl(CategoryUserControl, TaskUserControls); TaskUserControls.Insert(index + 1, taskUserControl); CategoryUserControl.Controls.Add(taskUserControl); TaskInserted?.Invoke(this, new TaskInsertedArgs(taskUserControl)); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class TaskInsertedArgs : EventArgs { public TaskInsertedArgs(TaskUserControl newTaskUserControl) { NewTaskUserControl = newTaskUserControl; } public TaskUserControl NewTaskUserControl { protected set; get; } } |
[この上にタスク追加]が選択されたときも同じように処理をします。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class TaskUserControl : UserControl { private void AddTaskPrevMenuItem_Click(object sender, EventArgs e) { int index = TaskUserControls.IndexOf(this); TaskUserControl taskUserControl = new TaskUserControl(CategoryUserControl, TaskUserControls); TaskUserControls.Insert(index, taskUserControl); CategoryUserControl.Controls.Add(taskUserControl); TaskInserted?.Invoke(this, new TaskInsertedArgs(taskUserControl)); } } |
Form1クラスも変更が必要です。
まずコンストラクタですが、PanelMainの幅にあわせてCategoryUserControlの幅が変化するようにしてしまうとPanelMainの高さでは収まりきらないCategoryUserControlが生成されると縦スクロールバーが現れます。するとスクロールバーが現れる前と後ではCategoryUserControlの幅が違ってくるという問題がおきるので、最初から縦スクロールバーを表示させることにします。
このとき最初からAutoScrollがtrueだとVerticalScroll.Visible = trueとやっても意味がありません。そこで最初はfalseにして、VerticalScroll.Visible = trueにしてからAutoScroll = trueにするという変な処理をしています。これでPanelMainに常に縦スクロールバーを表示されるようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); PanelMain.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom; ButtonConfigChart.Anchor = AnchorStyles.Left | AnchorStyles.Bottom; InitPanelMainContextMenu(); DoubleBuffered = true; // 常に縦スクロールバーを表示させる PanelMain.AutoScroll = false; PanelMain.VerticalScroll.Visible = true; PanelMain.AutoScroll = true; } } |
PanelMainを右クリックされたときに表示されるコンテキストメニューの初期化の処理ですが、[カテゴリを追加]だけにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { ContextMenuStrip panelMainContextMenuStrip = new ContextMenuStrip(); ToolStripMenuItem AddCategoryMenuItem = new ToolStripMenuItem(); void InitPanelMainContextMenu() { this.PanelMain.ContextMenuStrip = this.panelMainContextMenuStrip; this.AddCategoryMenuItem.Text = "カテゴリを追加"; this.AddCategoryMenuItem.Click += AddCategoryMenuItem_Click; this.panelMainContextMenuStrip.Items.AddRange(new ToolStripItem[] { this.AddCategoryMenuItem, }); } } |
PanelMainに表示されるCategoryUserControlをリストで管理するためにCategoryUserControlのリストをつくります。そして右クリックで[カテゴリを追加]が選択されたらカテゴリを一番下に追加してそのなかにタスクをつくります。このとき追加されるCategoryUserControlとTaskUserControlはイベントに対応できるようにイベントハンドラを追加しておきます。
コントロールが追加されたらじっさいに表示させるために自作メソッドShowChartを呼び出します。そして追加されたコントロールが見えるようにPanelMainをスクロールさせます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public partial class Form1 : Form { List<CategoryUserControl> CategoryUserControls = new List<CategoryUserControl>(); private async void AddCategoryMenuItem_Click(object sender, EventArgs e) { CategoryUserControl categoryUserControl = new CategoryUserControl(CategoryUserControls); CategoryUserControls.Add(categoryUserControl); TaskUserControl taskUserControl = new TaskUserControl(categoryUserControl, categoryUserControl.TaskUserControls); categoryUserControl.TaskUserControls.Add(taskUserControl); PanelMain.Controls.Add(categoryUserControl); categoryUserControl.Controls.Add(taskUserControl); categoryUserControl.CategoryAdded += CategoryUserControl_CategoryAdded; ; categoryUserControl.TaskAdded += CategoryUserControl_TaskAdded; taskUserControl.TaskInserted += TaskUserControl_TaskInserted; ShowChart(); PanelMain.ScrollControlIntoView(categoryUserControl); // 最初は日付とその境界線が表示されていないので表示させる if (CategoryUserControls.Count == 1) { await System.Threading.Tasks.Task.Delay(100); this.Invalidate(); } } } |
CategoryUserControl上で[カテゴリを(上または下に)追加する]が選択されたときの処理を示します。CategoryUserControlを生成してCategoryUserControlsとPanelMain.Controlsに追加(後者を忘れると表示されない)します。そして追加されたCategoryUserControlが表示されるようにPanelMainをスクロールさせます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public partial class Form1 : Form { private void CategoryUserControl_CategoryAdded(CategoryUserControl sender, CategoryAddedArgs e) { CategoryUserControl categoryUserControl = new CategoryUserControl(CategoryUserControls); CategoryUserControls.Insert(e.Index, categoryUserControl); TaskUserControl taskUserControl = new TaskUserControl(sender, categoryUserControl.TaskUserControls); categoryUserControl.TaskUserControls.Add(taskUserControl); PanelMain.Controls.Add(categoryUserControl); categoryUserControl.Controls.Add(taskUserControl); categoryUserControl.CategoryAdded += CategoryUserControl_CategoryAdded; ; categoryUserControl.TaskAdded += CategoryUserControl_TaskAdded; taskUserControl.TaskInserted += TaskUserControl_TaskInserted; ShowChart(); PanelMain.ScrollControlIntoView(categoryUserControl); } } |
CategoryUserControl上で[タスクを追加する]が選択されたときの処理を示します。TaskUserControlを生成してCategoryUserControl.TaskUserControlsとCategoryUserControl.Controlsに追加します。これも後者を忘れると追加したコントロールが表示されません。追加したら追加されたコントロールが表示されるようにPanelMainをスクロールさせます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public partial class Form1 : Form { private void CategoryUserControl_TaskAdded(CategoryUserControl sender, TaskAddedArgs e) { TaskUserControl taskUserControl = new TaskUserControl(sender, sender.TaskUserControls); sender.TaskUserControls.Add(taskUserControl); sender.Controls.Add(taskUserControl); taskUserControl.TaskInserted += TaskUserControl_TaskInserted; ShowChart(); PanelMain.ScrollControlIntoView(taskUserControl); } } |
TaskUserControl上で右クリックされてTaskUserControlが追加されたときの処理を示します。この場合はすでに処理はTaskUserControlクラスで行なわれているので、ここでやることは追加されたコントロールが表示されるようにPanelMainをスクロールさせるだけです。
1 2 3 4 5 6 7 8 |
public partial class Form1 : Form { private void TaskUserControl_TaskInserted(object sender, TaskInsertedArgs e) { ShowChart(); PanelMain.ScrollControlIntoView(e.NewTaskUserControl); } } |
コントロールが追加されたときは表示順が変わっているかもしれないので一番上に表示されるものから配置しなおす必要があります。そのための処理を示します。
CategoryUserControlのPanelMain上におけるX座標0でよいのですが、幅はスクロールバーが表示されるぶん、短くしています。またCategoryUserControl上に表示されるTaskUserControlのX座標はleftMarginTaskUserControl(=200)だけ右寄りとし、幅もそれだけ短くなっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public partial class Form1 : Form { void ShowChart() { int leftMarginTaskUserControl = 200; int heightTaskUserControl = 50; int rightMarginCategoryUserControl = 20; PanelMain.VerticalScroll.Value = 0; int curCategoryUserControlPosY = 0; foreach (CategoryUserControl categoryUserControl in CategoryUserControls) { categoryUserControl.Location = new Point(0, curCategoryUserControlPosY); categoryUserControl.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; int curTaskUserControlPosY = 0; foreach (TaskUserControl taskUserControl in categoryUserControl.TaskUserControls) { taskUserControl.Location = new Point(leftMarginTaskUserControl, curTaskUserControlPosY); taskUserControl.Size = new Size(categoryUserControl.Size.Width - leftMarginTaskUserControl, heightTaskUserControl); taskUserControl.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; curTaskUserControlPosY += heightTaskUserControl; } categoryUserControl.Size = new Size(PanelMain.Width - rightMarginCategoryUserControl, curTaskUserControlPosY); curCategoryUserControlPosY += categoryUserControl.Size.Height; } } } |
[チャートの設定]ボタンがクリックされたときの処理もCategoryUserControlが加わったので書き直しとなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public partial class Form1 : Form { private async void ButtonConfigChart_Click(object sender, EventArgs e) { int prevDays = TaskUserControl.GetPrevDays(); int afterDays = TaskUserControl.GetAfterDays(); SetBandDateForm setBandDateForm = new SetBandDateForm(prevDays, afterDays); if (setBandDateForm.ShowDialog() == DialogResult.OK) { DateTime startDate = DateTime.Today - new TimeSpan(setBandDateForm.PrevDays, 0, 0, 0); TaskUserControl.SetBandStartDay(startDate.Year, startDate.Month, startDate.Day); DateTime endDate = DateTime.Today + new TimeSpan(setBandDateForm.AfterDays, 0, 0, 0); TaskUserControl.SetBandEndDay(endDate.Year, endDate.Month, endDate.Day); foreach (CategoryUserControl categoryUserControl in CategoryUserControls) { foreach (TaskUserControl taskUserControl in categoryUserControl.TaskUserControls) { taskUserControl.UpdateBand(); } } await Task.Delay(100); this.Invalidate(); } setBandDateForm.Dispose(); } } |
またこの処理をおこなうにあたって必要なGetFirstTaskUserControlメソッドですが、スクロールされていてY座標が0未満のものをつかってもなにもおきないのでTaskUserControlであり、Y座標が正で最小のものを取得しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class Form1 : Form { TaskUserControl GetFirstTaskUserControl() { foreach (CategoryUserControl categoryUserControl in CategoryUserControls) { foreach (TaskUserControl taskUserControl in categoryUserControl.TaskUserControls) { Point screen = taskUserControl.PointToScreen(taskUserControl.Location); if (PanelMain.PointToClient(screen).Y >= 0) return taskUserControl; } } return null; } } |
それ以外のメソッドにとくに変更はありません。