source: trunk/eraser/Eraser/SchedulerPanel.cs @ 2051

Revision 2051, 20.9 KB checked in by lowjoel, 4 years ago (diff)

r2050 allows us to reference a plugin that is required for functionality, since the plugin will always be loaded.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Drawing;
26using System.Text;
27using System.Windows.Forms;
28
29using System.Globalization;
30using System.Runtime.InteropServices;
31using System.Diagnostics;
32using System.IO;
33using System.Runtime.Serialization;
34using System.ComponentModel;
35
36using Eraser.Manager;
37using Eraser.Util;
38using Eraser.DefaultPlugins;
39using Microsoft.Samples;
40using ProgressChangedEventArgs = Eraser.Util.ProgressChangedEventArgs;
41
42namespace Eraser
43{
44    internal partial class SchedulerPanel : Eraser.BasePanel
45    {
46        public SchedulerPanel()
47        {
48            InitializeComponent();
49            Theming.ApplyTheme(schedulerDefaultMenu);
50            if (!IsHandleCreated)
51                CreateHandle();
52
53            //Populate the scheduler list-view with the current task list
54            ExecutorTasksCollection tasks = Program.eraserClient.Tasks;
55            foreach (Task task in tasks)
56                DisplayTask(task);
57
58            //Hook the event machinery to our class. Handle the task Added and Removed
59            //events.
60            Program.eraserClient.TaskAdded += TaskAdded;
61            Program.eraserClient.TaskDeleted += TaskDeleted;
62        }
63
64        private void DisplayTask(Task task)
65        {
66            //Add the item to the list view
67            ListViewItem item = scheduler.Items.Add(task.UIText);
68            item.SubItems.Add(string.Empty);
69            item.SubItems.Add(string.Empty);
70
71            //Set the tag of the item so we know which task on the LV corresponds
72            //to the physical task object.
73            item.Tag = task;
74
75            //Add our event handlers to the task
76            task.TaskStarted += task_TaskStarted;
77            task.ProgressChanged += task_ProgressChanged;
78            task.TaskFinished += task_TaskFinished;
79
80            //Show the fields on the list view
81            UpdateTask(item);
82        }
83
84        private void UpdateTask(ListViewItem item)
85        {
86            //Get the task object
87            Task task = (Task)item.Tag;
88
89            //Set the task name
90            item.Text = task.UIText;
91
92            //Set the next run time of the task
93            if (task.Queued)
94            {
95                item.SubItems[1].Text = S._("Queued for execution");
96                item.SubItems[2].Text = string.Empty;
97            }
98            else if (task.Schedule is RecurringSchedule)
99                item.SubItems[1].Text = ((task.Schedule as RecurringSchedule).NextRun.
100                    ToString("F", CultureInfo.CurrentCulture));
101            else if (task.Schedule == Schedule.RunNow || task.Schedule == Schedule.RunManually)
102                item.SubItems[1].Text = S._("Not queued");
103            else
104                item.SubItems[1].Text = task.Schedule.UIText;
105
106            //Set the group of the task.
107            CategorizeTask(task, item);
108        }
109
110        private void CategorizeTask(Task task)
111        {
112            CategorizeTask(task, GetTaskItem(task));
113        }
114
115        private void CategorizeTask(Task task, ListViewItem item)
116        {
117            if (task.Schedule == Schedule.RunNow || task.Schedule == Schedule.RunManually)
118                item.Group = scheduler.Groups["manual"];
119            else if (task.Schedule == Schedule.RunOnRestart)
120                item.Group = scheduler.Groups["restart"];
121            else
122                item.Group = scheduler.Groups["recurring"];
123        }
124
125        /// <summary>
126        /// Handles the Task Added event.
127        /// </summary>
128        private void TaskAdded(object sender, TaskEventArgs e)
129        {
130            if (InvokeRequired)
131            {
132                Invoke((EventHandler<TaskEventArgs>)TaskAdded, sender, e);
133                return;
134            }
135
136            //Display a balloon notification if the parent frame has been minimised.
137            MainForm parent = (MainForm)FindForm();
138            if (parent != null && (parent.WindowState == FormWindowState.Minimized || !parent.Visible))
139            {
140                parent.ShowNotificationBalloon(S._("New task added"), S._("{0} " +
141                    "has just been added to the list of tasks.", e.Task.UIText),
142                    ToolTipIcon.Info);
143            }
144
145            DisplayTask(e.Task);
146        }
147
148        private void DeleteSelectedTasks()
149        {
150            if (MessageBox.Show(this, S._("Are you sure you want to delete the selected tasks?"),
151                    S._("Eraser"), MessageBoxButtons.YesNo, MessageBoxIcon.Question,
152                    MessageBoxDefaultButton.Button1, Localisation.IsRightToLeft(this) ?
153                        MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign : 0
154                ) != DialogResult.Yes)
155            {
156                return;
157            }
158
159            foreach (ListViewItem item in scheduler.SelectedItems)
160            {
161                Task task = (Task)item.Tag;
162                if (!task.Executing)
163                    Program.eraserClient.Tasks.Remove(task);
164            }
165        }
166
167        /// <summary>
168        /// Handles the task deleted event.
169        /// </summary>
170        private void TaskDeleted(object sender, TaskEventArgs e)
171        {
172            if (InvokeRequired)
173            {
174                Invoke((EventHandler<TaskEventArgs>)TaskDeleted, sender, e);
175                return;
176            }
177
178            foreach (ListViewItem item in scheduler.Items)
179                if (((Task)item.Tag) == e.Task)
180                {
181                    scheduler.Items.Remove(item);
182                    break;
183                }
184
185            PositionProgressBar();
186        }
187
188        /// <summary>
189        /// Handles the task start event.
190        /// </summary>
191        /// <param name="e">The task event object.</param>
192        void task_TaskStarted(object sender, EventArgs e)
193        {
194            if (InvokeRequired)
195            {
196                Invoke((EventHandler)task_TaskStarted, sender, e);
197                return;
198            }
199
200            //Get the list view item
201            Task task = (Task)sender;
202            ListViewItem item = GetTaskItem(task);
203
204            //Update the status.
205            item.SubItems[1].Text = S._("Running...");
206
207            //Show the progress bar
208            schedulerProgress.Tag = item;
209            schedulerProgress.Visible = true;
210            schedulerProgress.Value = 0;
211            PositionProgressBar();
212        }
213
214        /// <summary>
215        /// Handles the progress event by the task.
216        /// </summary>
217        void task_ProgressChanged(object sender, ProgressChangedEventArgs e)
218        {
219            //Make sure we handle the event in the main thread as this requires
220            //GUI calls.
221            if (InvokeRequired)
222            {
223                Invoke((EventHandler<ProgressChangedEventArgs>)task_ProgressChanged, sender, e);
224                return;
225            }
226
227            //Update the progress bar
228            ErasureTarget target = (ErasureTarget)sender;
229            schedulerProgress.Value = (int)(target.Task.Progress.Progress * 1000.0);
230        }
231
232        /// <summary>
233        /// Handles the task completion event.
234        /// </summary>
235        void task_TaskFinished(object sender, EventArgs e)
236        {
237            if (InvokeRequired)
238            {
239                Invoke((EventHandler)task_TaskFinished, sender, e);
240                return;
241            }
242
243            //Get the list view item
244            Task task = (Task)sender;
245            ListViewItem item = GetTaskItem(task);
246            if (item == null)
247                return;
248
249            //Hide the progress bar
250            if (schedulerProgress.Tag != null && schedulerProgress.Tag == item)
251            {
252                schedulerProgress.Tag = null;
253                schedulerProgress.Visible = false;
254            }
255
256            //Get the exit status of the task.
257            LogLevel highestLevel = task.Log.Last().Highest;
258
259            //Show a balloon to inform the user
260            MainForm parent = (MainForm)FindForm();
261            if (parent.WindowState == FormWindowState.Minimized || !parent.Visible)
262            {
263                string message = null;
264                ToolTipIcon icon = ToolTipIcon.None;
265
266                switch (highestLevel)
267                {
268                    case LogLevel.Warning:
269                        message = S._("The task {0} has completed with warnings.", task.UIText);
270                        icon = ToolTipIcon.Warning;
271                        break;
272                    case LogLevel.Error:
273                        message = S._("The task {0} has completed with errors.", task.UIText);
274                        icon = ToolTipIcon.Error;
275                        break;
276                    case LogLevel.Fatal:
277                        message = S._("The task {0} did not complete.", task.UIText);
278                        icon = ToolTipIcon.Error;
279                        break;
280                    default:
281                        message = S._("The task {0} has completed.", task.UIText);
282                        icon = ToolTipIcon.Info;
283                        break;
284                }
285
286                parent.ShowNotificationBalloon(S._("Task executed"), message,
287                    icon);
288            }
289
290            //If the user requested us to remove completed one-time tasks, do so.
291            if (EraserSettings.Get().ClearCompletedTasks &&
292                (task.Schedule == Schedule.RunNow) && highestLevel < LogLevel.Warning)
293            {
294                Program.eraserClient.Tasks.Remove(task);
295            }
296
297            //Otherwise update the UI
298            else
299            {
300                switch (highestLevel)
301                {
302                    case LogLevel.Warning:
303                        item.SubItems[2].Text = S._("Completed with warnings");
304                        break;
305                    case LogLevel.Error:
306                        item.SubItems[2].Text = S._("Completed with errors");
307                        break;
308                    case LogLevel.Fatal:
309                        item.SubItems[2].Text = S._("Not completed");
310                        break;
311                    default:
312                        item.SubItems[2].Text = S._("Completed");
313                        break;
314                }
315
316                //Recategorize the task. Do not assume the task has maintained the
317                //category since run-on-restart tasks will be changed to immediately
318                //run tasks.
319                CategorizeTask(task, item);
320
321                //Update the status of the task.
322                UpdateTask(item);
323            }
324        }
325
326        /// <summary>
327        /// Occurs when the user presses a key on the list view.
328        /// </summary>
329        /// <param name="sender">The list view which triggered the event.</param>
330        /// <param name="e">Event argument.</param>
331        private void scheduler_KeyDown(object sender, KeyEventArgs e)
332        {
333            if (e.KeyCode == Keys.Delete)
334                DeleteSelectedTasks();
335        }
336
337        /// <summary>
338        /// Occurs when the user double-clicks a scheduler item. This will result
339        /// in the log viewer being called, or the progress dialog to be displayed.
340        /// </summary>
341        /// <param name="sender">The list view which triggered the event.</param>
342        /// <param name="e">Event argument.</param>
343        private void scheduler_ItemActivate(object sender, EventArgs e)
344        {
345            if (scheduler.SelectedItems.Count == 0)
346                return;
347
348            ListViewItem item = scheduler.SelectedItems[0];
349            if (((Task)item.Tag).Executing)
350                using (ProgressForm form = new ProgressForm((Task)item.Tag))
351                    form.ShowDialog();
352            else
353                editTaskToolStripMenuItem_Click(sender, e);
354        }
355
356        /// <summary>
357        /// Occurs when the user drags a file over the scheduler
358        /// </summary>
359        private void scheduler_DragEnter(object sender, DragEventArgs e)
360        {
361            string descriptionMessage = string.Empty;
362            string descriptionInsert = string.Empty;
363            const string descrptionPlaceholder = "%1";
364            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
365                e.Effect = DragDropEffects.None;
366            else
367            {
368                string[] files = (string[])e.Data.GetData(DataFormats.FileDrop, false);
369                bool isTaskList = true;
370                foreach (string file in files)
371                {
372                    if (descriptionInsert.Length < 259 &&
373                        (descriptionInsert.Length < 3 || descriptionInsert.Substring(descriptionInsert.Length - 3) != "..."))
374                    {
375                        string append = string.Format(CultureInfo.InvariantCulture, "{0}, ",
376                            Path.GetFileNameWithoutExtension(file));
377                        if (descriptionInsert.Length + append.Length > 259)
378                        {
379                            descriptionInsert += ".....";
380                        }
381                        else
382                        {
383                            descriptionInsert += append;
384                        }
385                    }
386
387                    if (Path.GetExtension(file) != ".ersx")
388                        isTaskList = false;
389                }
390                descriptionInsert = descriptionInsert.Remove(descriptionInsert.Length - 2);
391
392                if (isTaskList)
393                {
394                    e.Effect = DragDropEffects.Copy;
395                    descriptionMessage = S._("Import tasks from {0}", descrptionPlaceholder);
396                }
397                else
398                {
399                    e.Effect = DragDropEffects.Move;
400                    descriptionMessage = S._("Erase {0}", descrptionPlaceholder);
401                }
402            }
403
404            if (e.Data.GetDataPresent("DragImageBits"))
405                DropTargetHelper.DragEnter(this, e.Data, new Point(e.X, e.Y), e.Effect,
406                    descriptionMessage, descriptionInsert);
407        }
408
409        private void scheduler_DragLeave(object sender, EventArgs e)
410        {
411            DropTargetHelper.DragLeave(this);
412        }
413
414        private void scheduler_DragOver(object sender, DragEventArgs e)
415        {
416            DropTargetHelper.DragOver(new Point(e.X, e.Y), e.Effect);
417        }
418
419        /// <summary>
420        /// Occurs when the user drops a file into the scheduler.
421        /// </summary>
422        private void scheduler_DragDrop(object sender, DragEventArgs e)
423        {
424            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
425            {
426                e.Effect = DragDropEffects.None;
427                return;
428            }
429
430            DropTargetHelper.Drop(e.Data, new Point(e.X, e.Y), e.Effect);
431
432            //Schedule the task dialog to be shown (to get to the event loop so that
433            //ComCtl32.dll v6 is used.)
434            BeginInvoke((Action<DragDropEffects, string[]>)scheduler_DragDropConfirm,
435                e.Effect, e.Data.GetData(DataFormats.FileDrop, false));
436        }
437
438        /// <summary>
439        /// Called after the files have been dropped into Eraser.
440        /// </summary>
441        /// <param name="effect">The Drag/drop effect of the operation.</param>
442        /// <param name="files">The files which were dropped into the program.</param>
443        private void scheduler_DragDropConfirm(DragDropEffects effect, string[] files)
444        {
445            //Determine whether we are importing a task list or dragging files for
446            //erasure.
447            if (effect == DragDropEffects.Copy)
448            {
449                foreach (string file in files)
450                    using (FileStream stream = new FileStream(file, FileMode.Open,
451                        FileAccess.Read, FileShare.Read))
452                    {
453                        try
454                        {
455                            Program.eraserClient.Tasks.LoadFromStream(stream);
456                        }
457                        catch (InvalidDataException ex)
458                        {
459                            MessageBox.Show(S._("Could not import task list from {0}. The " +
460                                "error returned was: {1}", file, ex.Message), S._("Eraser"),
461                                MessageBoxButtons.OK, MessageBoxIcon.Error,
462                                MessageBoxDefaultButton.Button1,
463                                Localisation.IsRightToLeft(this) ?
464                                    MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign : 0);
465                        }
466                    }
467            }
468            else if (effect == DragDropEffects.Move)
469            {
470                //Create a task with the default settings
471                Task task = new Task();
472                foreach (string file in files)
473                {   
474                    FileSystemObjectErasureTarget target;
475                    if ((File.GetAttributes(file) & FileAttributes.Directory) != 0)
476                        target = new FolderErasureTarget();
477                    else
478                        target = new FileErasureTarget();
479                    target.Path = file;
480
481                    task.Targets.Add(target);
482                }
483
484                //Add the task, asking the user for his intent.
485                DialogResult action = DialogResult.No;
486                if (TaskDialog.IsAvailableOnThisOS)
487                {
488                    TaskDialog dialog = new TaskDialog();
489                    dialog.WindowTitle = S._("Eraser");
490                    dialog.MainIcon = TaskDialogIcon.Information;
491                    dialog.MainInstruction = S._("You have dropped a set of files and folders into Eraser. What do you want to do with them?");
492                    dialog.AllowDialogCancellation = true;
493                    dialog.Buttons = new TaskDialogButton[] {
494                        new TaskDialogButton((int)DialogResult.Yes, S._("Erase the selected items\nSchedules the selected items for immediate erasure.")),
495                        new TaskDialogButton((int)DialogResult.OK, S._("Create a new Task\nA task will be created containing the selected items.")),
496                        new TaskDialogButton((int)DialogResult.No, S._("Cancel the drag-and-drop operation"))
497                    };
498                    dialog.RightToLeftLayout = Localisation.IsRightToLeft(this);
499                    dialog.UseCommandLinks = true;
500                    action = (DialogResult)dialog.Show(this);
501                }
502                else
503                {
504                    action = MessageBox.Show(S._("Are you sure you wish to erase the selected "
505                        + "items?"), S._("Eraser"), MessageBoxButtons.YesNo,
506                        MessageBoxIcon.Question, MessageBoxDefaultButton.Button2,
507                        Localisation.IsRightToLeft(this) ?
508                            MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign : 0);
509                }
510
511                switch (action)
512                {
513                    case DialogResult.OK:
514                        task.Schedule = Schedule.RunManually;
515                        goto case DialogResult.Yes;
516
517                    case DialogResult.Yes:
518                        Program.eraserClient.Tasks.Add(task);
519                        break;
520                }
521            }
522        }
523
524        /// <summary>
525        /// Occurs when the user right-clicks the list view.
526        /// </summary>
527        /// <param name="sender">The list view which generated this event.</param>
528        /// <param name="e">Event argument.</param>
529        private void schedulerMenu_Opening(object sender, CancelEventArgs e)
530        {
531            //If nothing's selected, show the Scheduler menu which just allows users to
532            //create new tasks (like from the toolbar)
533            if (scheduler.SelectedItems.Count == 0)
534            {
535                schedulerDefaultMenu.Show(schedulerMenu.Left, schedulerMenu.Top);
536                e.Cancel = true;
537                return;
538            }
539
540            bool aTaskNotQueued = false;
541            bool aTaskExecuting = false;
542            foreach (ListViewItem item in scheduler.SelectedItems)
543            {
544                Task task = (Task)item.Tag;
545                aTaskNotQueued = aTaskNotQueued || (!task.Queued && !task.Executing);
546                aTaskExecuting = aTaskExecuting || task.Executing;
547            }
548
549            runNowToolStripMenuItem.Enabled = aTaskNotQueued;
550            cancelTaskToolStripMenuItem.Enabled = aTaskExecuting;
551
552            editTaskToolStripMenuItem.Enabled = scheduler.SelectedItems.Count == 1 &&
553                !((Task)scheduler.SelectedItems[0].Tag).Executing &&
554                !((Task)scheduler.SelectedItems[0].Tag).Queued;
555            deleteTaskToolStripMenuItem.Enabled = !aTaskExecuting;
556        }
557
558        /// <summary>
559        /// Occurs when the user selects the New Task context menu item.
560        /// </summary>
561        /// <param name="sender">The menu which generated this event.</param>
562        /// <param name="e">Event argument.</param>
563        private void newTaskToolStripMenuItem_Click(object sender, EventArgs e)
564        {
565            using (TaskPropertiesForm form = new TaskPropertiesForm())
566            {
567                if (form.ShowDialog() == DialogResult.OK)
568                {
569                    Task task = form.Task;
570                    Program.eraserClient.Tasks.Add(task);
571                }
572            }
573        }
574
575        /// <summary>
576        /// Occurs whent the user selects the Run Now context menu item.
577        /// </summary>
578        /// <param name="sender">The menu which generated this event.</param>
579        /// <param name="e">Event argument.</param>
580        private void runNowToolStripMenuItem_Click(object sender, EventArgs e)
581        {
582            foreach (ListViewItem item in scheduler.SelectedItems)
583            {
584                //Queue the task
585                Task task = (Task)item.Tag;
586                if (!task.Executing && !task.Queued)
587                {
588                    Program.eraserClient.QueueTask(task);
589
590                    //Update the UI
591                    item.SubItems[1].Text = S._("Queued for execution");
592                }
593            }
594        }
595
596        /// <summary>
597        /// Occurs whent the user selects the Cancel Task context menu item.
598        /// </summary>
599        /// <param name="sender">The menu which generated this event.</param>
600        /// <param name="e">Event argument.</param>
601        private void cancelTaskToolStripMenuItem_Click(object sender, EventArgs e)
602        {
603            foreach (ListViewItem item in scheduler.SelectedItems)
604            {
605                //Queue the task
606                Task task = (Task)item.Tag;
607                if (task.Executing || task.Queued)
608                {
609                    task.Cancel();
610
611                    //Update the UI
612                    item.SubItems[1].Text = string.Empty;
613                }
614            }
615        }
616
617        /// <summary>
618        /// Occurs when the user selects the View Task Log context menu item.
619        /// </summary>
620        /// <param name="sender">The menu item which generated this event.</param>
621        /// <param name="e">Event argument.</param>
622        private void viewTaskLogToolStripMenuItem_Click(object sender, EventArgs e)
623        {
624            if (scheduler.SelectedItems.Count != 1)
625                return;
626
627            ListViewItem item = scheduler.SelectedItems[0];
628            using (LogForm form = new LogForm((Task)item.Tag))
629                form.ShowDialog();
630        }
631
632        /// <summary>
633        /// Occurs when the user selects the Edit Task context menu item.
634        /// </summary>
635        /// <param name="sender">The menu item which generated this event.</param>
636        /// <param name="e">Event argument.</param>
637        private void editTaskToolStripMenuItem_Click(object sender, EventArgs e)
638        {
639            if (scheduler.SelectedItems.Count != 1 ||
640                ((Task)scheduler.SelectedItems[0].Tag).Executing ||
641                ((Task)scheduler.SelectedItems[0].Tag).Queued)
642            {
643                return;
644            }
645
646            //Make sure that the task is not being executed, or else. This can
647            //be done in the Client library, but there will be no effect on the
648            //currently running task.
649            ListViewItem item = scheduler.SelectedItems[0];
650            Task task = (Task)item.Tag;
651            if (task.Executing)
652                return;
653
654            //Edit the task.
655            using (TaskPropertiesForm form = new TaskPropertiesForm())
656            {
657                form.Task = task;
658                if (form.ShowDialog() == DialogResult.OK)
659                {
660                    task = form.Task;
661
662                    //Update the list view
663                    UpdateTask(item);
664                }
665            }
666        }
667
668        /// <summary>
669        /// Occurs when the user selects the Delete Task context menu item.
670        /// </summary>
671        /// <param name="sender">The menu item which generated this event.</param>
672        /// <param name="e">Event argument.</param>
673        private void deleteTaskToolStripMenuItem_Click(object sender, EventArgs e)
674        {
675            DeleteSelectedTasks();
676        }
677
678        #region Item management
679        /// <summary>
680        /// Retrieves the ListViewItem for the given task.
681        /// </summary>
682        /// <param name="task">The task object whose list view entry is being sought.</param>
683        /// <returns>A ListViewItem for the given task object.</returns>
684        private ListViewItem GetTaskItem(Task task)
685        {
686            foreach (ListViewItem item in scheduler.Items)
687                if (item.Tag == task)
688                    return item;
689
690            return null;
691        }
692
693        /// <summary>
694        /// Maintains the position of the progress bar.
695        /// </summary>
696        private void PositionProgressBar()
697        {
698            if (schedulerProgress.Tag == null)
699                return;
700
701            Rectangle rect = ((ListViewItem)schedulerProgress.Tag).SubItems[2].Bounds;
702            rect.Offset(2, 2);
703            schedulerProgress.Location = rect.Location;
704            schedulerProgress.Size = rect.Size;
705        }
706
707        private void scheduler_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
708        {
709            e.DrawDefault = true;
710            if (schedulerProgress.Tag != null)
711                PositionProgressBar();
712        }
713
714        private void scheduler_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
715        {
716            e.DrawDefault = true;
717        }
718        #endregion
719    }
720}
Note: See TracBrowser for help on using the repository browser.