I am trying to write a small VB.NET application to conduct a cross team time in motion study in work. I need the ability to have multipul timers running for single tasks, and when a user has finished their tasks they can end it and it will be stored in a CSV file. I can do it with single tasks at once, and this has been running for a while within the business. What is the best method to have multipul tasks running at once. I had the idea of adding the tasks to a listview and having double click event as the end task function. However I can't seem to get the timers to run independantly. Can anyone help? Does this make sense?
This is the main bulk of the workload in this class. If this doesnt make sense I appologise in advance. I'm not a programmer by trade, it's just a side hobby which I am practising in work.
Another thing would be to have the elapsed seconds counting live, I can do this with a label but I would need an unlitmited amount of task in theory...
Public Class runningTask
Dim timenow As DateTime = Now
Dim elapsed
Dim localTask = GlobalVars.task
Public Sub AddItem()
CreateTimer()
Dim str(3) As String
Dim itm As ListViewItem
str(0) = localTask
str(1) = timenow
str(2) = elapsed
itm = New ListViewItem(str)
frmTime.lvTimers.Items.Add(itm)
End Sub
Private Sub CreateTimer()
Dim tmr As New Timer
tmr.Interval = 1000
tmr.Enabled = True
AddHandler tmr.Tick, AddressOf GlobalTimerTick
End Sub
Private Sub GlobalTimerTick(ByVal sender As Object, ByVal e As EventArgs)
Dim t As TimeSpan = Now - timenow
elapsed = String.Format("{0:00}:{1:00}:{2:00}:{3:00}", Math.Floor(t.TotalHours), t.Minutes, t.Seconds, t.Milliseconds)
End Sub
End Class
I took a more OOP approach which may be more economical as far as Timers, but has a very simple interface. The core is a TaskItem which tracks the job and time, then a collection class to expose them, start them and stop them. By burying as much functionality at as low of level as possible things get simpler.
I only used one timer and cant see why you'd need multiple timers for a single task. If a TaskItem records the DateTime
when it starts (this happens automatically when it is created in mine), then you don't need any Timers at all. Elapsed is always calculated using StartedTime
and either DateTime.Now
(for a running task) or CompletedTime
for completed tasks. I cant see where a Timer adds anything.
You'll have to make many mods for your purposes. This does not save completed items, nor remove them from the list, but may give you a place to start. Its also a first-pass thing; there are surely issues here and there.
Imports System.Collections.ObjectModel
Public Class Tasks
' slight overkill
Private Enum LVGroups
Running
Completed
End Enum
Public Enum TaskJobs
SweepFloor
BuildRobot
CleanOven
RepairRobot
EmptyTrash
TeachRobotToTalk
MakeCoffee
TeachRobotToWalk
WashDishes
TeachRobotVisualBasic
End Enum
' class for a single task
Public Class TaskItem
' these ought to be readonly; so no cheating
Public Property ID As String
Public Property EmpName As String
Public Property StartedTime As DateTime
Public Property CompletedTime As DateTime
' or job code...
Public Property Job As TaskJobs
' put in a ClassLib or Namespace
Friend Sub New(sName As String, tj As TaskJobs)
EmpName = sName
Job = tj
StartedTime = DateTime.Now
CompletedTime = DateTime.MaxValue
ID = System.Guid.NewGuid.ToString
End Sub
Public Function IsActive() As Boolean
Return (CompletedTime = DateTime.MaxValue)
End Function
Public Sub EndTask()
CompletedTime = DateTime.Now
End Sub
Public Function GetElapsed() As String
Dim t As TimeSpan
If IsActive() Then
t = DateTime.Now - StartedTime
Else
t = CompletedTime - StartedTime
End If
Return String.Format("{0:00}:{1:00}:{2:00}", Math.Floor(t.TotalHours),
t.Minutes, t.Seconds)
End Function
Public Overrides Function ToString() As String
Return String.Format("{0} {1} {2}", EmpName, Job.ToString, GetElapsed)
End Function
End Class
Private col As Collection(Of TaskItem)
Private myLV As ListView
' assign the LV when tasks are created
Public Sub New(lvTask As ListView)
myLV = lvTask
col = New Collection(Of TaskItem)
End Sub
' add new task with EmpName and JobCode
Public Sub NewTask(sName As String, tj As TaskJobs)
Dim ti As New TaskItem(sName, tj)
col.Add(ti)
' LV layout: empname, job, elapsed, ID
' but only 3 columns so that ID is "hidden"
Dim lvi As New ListViewItem(ti.EmpName)
lvi.SubItems.Add(ti.Job.ToString)
lvi.SubItems.Add(ti.GetElapsed)
lvi.SubItems.Add(ti.ID)
lvi.Group = myLV.Groups(LVGroups.Running.ToString)
myLV.Items.Add(lvi)
End Sub
' called from dbl click on a running task item
Public Sub StopTask(lvi As ListViewItem)
Dim ti As TaskItem = (From t In col Where t.ID = lvi.SubItems(3).Text).First
' the form doesnt check the status, so we must
If ti.IsActive Then
ti.EndTask()
lvi.Group = myLV.Groups(LVGroups.Completed.ToString)
End If
End Sub
' called from the form timer tick event
Public Sub UpdateDisplay()
Dim ti As TaskItem
' iterate items to get ID of runners
For Each lvi As ListViewItem In myLV.Groups(LVGroups.Running.ToString).Items
' ToDo add error checking
ti = (From t In col Where t.ID = lvi.SubItems(3).Text).First
lvi.SubItems(2).Text = ti.GetElapsed
Next
End Sub
Public Function RunnersCount() As Integer
Return GetActiveList.Count
End Function
Public Function GetCompletedList() As List(Of TaskItem)
' long form
Dim tList As New List(Of TaskItem)
For Each ti As TaskItem In col
If ti.IsActive = False Then
tList.Add(ti)
End If
Next
Return tList
' short form
'Dim list = (From t In col Where t.IsActive = False).ToList
End Function
Public Function GetActiveList() As List(Of TaskItem)
' long form
'Dim tList As New List(Of TaskItem)
'For Each ti As TaskItem In col
' If ti.IsActive Then
' tList.Add(ti)
' End If
'Next
'Return tList
' short form
Dim list = (From t In col Where t.IsActive).ToList
Return list
End Function
End Class
Usage:
Public Class Form1
Private myTasks As Tasks
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTasks = New Tasks(Me.lvTask)
cboTask.Items.AddRange([Enum].GetNames(GetType(Tasks.TaskJobs)))
End Sub
Add a task:
myTasks.NewTask(cboEmp.Text, CType(cboTask.SelectedIndex, Tasks.TaskJobs))
Stop a task:
' called from LV mousedoubleclick, so determine the item
Dim lvi As ListViewItem = lvTask.GetItemAt(e.X, e.Y)
myTasks.StopTask(lvi)
Obviously, stopping a task might automatically append the data to the export file. That functionality would be in the Task Collection Class, where it would be done just before the task is removed from the collection (optional - they arent hurting any thing there as long as you can tell active from completed ones).
Timer Tick event:
myTasks.UpdateDisplay()
Finally, turn on Option Strict
especially since people will be making decisions based on the project's output.