Лучшее решение, таймер, секундомер, промежуток времени - PullRequest
0 голосов
/ 02 мая 2020

Я работаю над небольшим инструментом для отслеживания продолжительности различных действий.

В этом примере у нас есть 3 действия, Drive, Walk и Wait.

Каждая активность - это кнопка в Form1

Пример:

  • Нажать кнопку «Привод», запустить секундомер «SW» и таймер «Tmr» и считать время «Drive».
  • Через 5 секунд я нажимаю при нажатии кнопки «Ожидание» SW и Tmr останавливаются, SW1 и Tmr1 запускаются и отсчитывается время для действия «Ожидание».
  • Нажмите еще раз кнопку «Drive», SW1 и Tmr1 как остановленные, SW и Tmr запустились и время возобновилось. с 5-й секунды

И так далее, может быть включено одно или несколько действий. В конце измерения у меня есть общая продолжительность для каждого действия.

Этот код ниже на самом деле работает хорошо. Функция вызывается из формы Form1, измерение начинается, и позже у меня есть доступные значения в переменных publi c.

Модуль:

Dim SW, SW1, SW2 As New Stopwatch
Dim WithEvents Tmr, Tmr1, Tmr2 As New Timer
Dim stws() = {SW, SW1, SW2}
Dim tmrs() = {Tmr, Tmr1, Tmr2}
Public Drive, Walk, Wait As String

Public Function WhichButton(btn As Button)

WhichButton = btn.Text

        Select Case WhichButton
            Case "Drive"
                For Each s As Stopwatch In stws
                    s.Stop()
                Next
                For Each t As Timer In tmrs
                    t.Stop()
                Next
                SW.Start()
                Tmr.Start()
            Case "Wait"
                For Each s As Stopwatch In stws
                    s.Stop()
                Next
                For Each t As Timer In tmrs
                    t.Stop()
                Next
                SW.Start()
                Tmr1.Start()
            Case "Walk"
                For Each s As Stopwatch In stws
                    s.Stop()
                Next
                For Each t As Timer In tmrs
                    t.Stop()
                Next
                SW2.Start()
                Tmr2.Start()
        End Select
End Function

Private Sub Tmr_Tick(sender As Object, e As System.EventArgs) Handles Tmr.Tick
    Dim elapsed As TimeSpan = SW.Elapsed
    Drive = $"{elapsed.Hours:00}:{elapsed.Minutes:00}.{elapsed.Seconds:00}"
End Sub

Private Sub Tmr1_Tick(sender As Object, e As System.EventArgs) Handles Tmr1.Tick
    Dim elapsed As TimeSpan = SW1.Elapsed
    Walk = $"{elapsed.Hours:00}:{elapsed.Minutes:00}.{elapsed.Seconds:00}"
End Sub

Private Sub Tmr2_Tick(sender As Object, e As System.EventArgs) Handles Tmr2.Tick
    Dim elapsed As TimeSpan = SW2.Elapsed
    Wait = $"{elapsed.Hours:00}:{elapsed.Minutes:00}.{elapsed.Seconds:00}"
End Sub

Причина в том, что я не доволен с этим решением, и у меня нет знаний для продвинутого. Проблема в том, что у меня может быть Х количество кнопок, я могу добавлять новые или удалять несколько, это зависит от ситуации, и я не хочу писать блок кода для каждой. Также, если я изменю текстовое свойство кнопки, Select Case не будет работать. Поэтому я хочу динамически создавать таймеры и секундомеры для каждой кнопки.

Я хотел бы начать с этого:

 Dim timers As List(Of Timer) = New List(Of Timer)

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    For Each btn As Button In Panel1.Controls.OfType(Of Button)
        timers.Add(New Timer() With {.Tag = btn.Name})
        AddHandler btn.Click, AddressOf Something
    Next

End Sub

Public Sub Something(sender As Object, e As EventArgs)
    Dim btn = DirectCast(sender, Button)

    Dim tmr As Timer = timers.SingleOrDefault(Function(t) t.Tag IsNot Nothing AndAlso t.Tag.ToString = btn.Name)

End Sub

Здесь я могу сослаться на Timer через свойство Tag, но у меня нет Идея, как реализовать секундомер и время. Спасибо за чтение и любую помощь, предложения, псевдокод, примеры кода.

1 Ответ

3 голосов
/ 02 мая 2020

Во-первых, нет смысла использовать три Timers. Один Timer может обрабатывать все три раза. Во-вторых, исходя из того, что вы опубликовали, нет смысла использовать Timer. Единственная причина, по которой я мог видеть, что Timer был бы полезен, - это постоянное отображение текущего истекшего времени в пользовательском интерфейсе, но вы этого не делаете. Повторно устанавливать эти String переменные бессмысленно, если вы не собираетесь их отображать. Просто получите значение Elapsed из соответствующего Stopwatch, если и когда вам это нужно.

Что касается вашего обработчика событий Buttons' Click, это тоже ужасно. Весь смысл обычного обработчика событий в том, что вы хотите сделать одно и то же для каждого объекта, поэтому вы должны написать код только один раз. Если в итоге вы пишете отдельный код для каждого объекта в этом общем обработчике событий, то это лишает вас смысла и делает ваш код более сложным, а не меньшим. Вы должны использовать отдельные обработчики событий для каждого Button.

Если вы собираетесь использовать go с общим обработчиком событий, хотя бы выньте общий код. У вас есть одинаковые два цикла For Each во всех трех блоках Case. Это должно быть сделано до Select Case, а затем только запускать соответствующие Stopwatch в каждом Case.

Я не думаю, что вы должны использовать Buttons. На самом деле вы должны использовать RadioButtons. Вы можете установить для их свойства Appearance значение Button, и тогда они будут выглядеть как обычные Buttons, но при этом вести себя как RadioButtons. Когда вы нажимаете один, он сохраняет подавленное отображение, чтобы указать, что он отмечен, и если щелкнуть другой, будет выпущен ранее подавленный. В этом случае ваш код может выглядеть следующим образом:

Private ReadOnly driveStopwatch As New Stopwatch
Private ReadOnly waitStopwatch As New Stopwatch
Private ReadOnly walkStopwatch As New Stopwatch

Private Sub driveRadioButton_CheckedChanged(sender As Object, e As EventArgs) Handles driveRadioButton.CheckedChanged
    If driveRadioButton.Checked Then
        driveStopwatch.Start()
    Else
        driveStopwatch.Stop()
    End If
End Sub

Private Sub waitRadioButton_CheckedChanged(sender As Object, e As EventArgs) Handles waitRadioButton.CheckedChanged
    If waitRadioButton.Checked Then
        waitStopwatch.Start()
    Else
        waitStopwatch.Stop()
    End If
End Sub

Private Sub walkRadioButton_CheckedChanged(sender As Object, e As EventArgs) Handles walkRadioButton.CheckedChanged
    If walkRadioButton.Checked Then
        walkStopwatch.Start()
    Else
        walkStopwatch.Stop()
    End If
End Sub

Поскольку при проверке RadioButton автоматически снимается любая другая, каждый обработчик CheckedChanged должен беспокоиться только о своих Stopwatch. * 1034. *

Если вы хотите отобразить прошедшее время для определенного Stopwatch, когда оно останавливается, вы делаете это, когда оно останавливается, например,

Private Sub driveRadioButton_CheckedChanged(sender As Object, e As EventArgs) Handles driveRadioButton.CheckedChanged
    If driveRadioButton.Checked Then
        driveStopwatch.Start()
    Else
        driveStopwatch.Stop()
        driveLabel.Text = driveStopwatch.Elapsed.ToString("hh\:mm\:ss")
    End If
End Sub

. Эта перегрузка TimeSpan.ToString была впервые доступна в. NET 4.5 Я думаю, поэтому вам следует использовать его, если вы не нацеливаетесь. NET 4.0 или ранее.

Если вы хотите постоянно отображать текущее прошедшее время, то, как я уже сказал, вы только нужен один Timer. Вы бы просто позволили ему работать все время и обновлялись соответствующим образом на основе Stopwatch, который в данный момент запущен, например,

Private Sub displayTimer_Tick(sender As Object, e As EventArgs) Handles displayTimer.Tick
    If driveStopwatch.IsRunning Then
        driveLabel.Text = driveStopwatch.Elapsed.ToString("hh\:mm\:ss")
    ElseIf waitStopwatch.IsRunning Then
        waitLabel.Text = waitStopwatch.Elapsed.ToString("hh\:mm\:ss")
    ElseIf walkStopwatch.IsRunning Then
        walkLabel.Text = walkStopwatch.Elapsed.ToString("hh\:mm\:ss")
    End If
End Sub

Вы не показали нам, как отображаете прошедшее время, так что это немного догадки В этом сценарии вы определенно должны обновлять Label, когда Stopwatch останавливается, потому что Timer не будет обновлять Label в следующие Tick.

Вы, вероятно, захотите Button где-нибудь, что может остановить и / или сбросить все три Stopwatches. Это означало бы установить Checked на False на всех трех RadioButtons и затем вызвать Reset на всех трех Stopwatches. Вы, вероятно, захотите очистить / сбросить Labels тоже.

Существует также потенциальная ошибка, использующая RadioButtons, подобную этой. Если один из ваших RadioButtons является первым в порядке табуляции, он получит фокус по умолчанию при загрузке формы. Фокусировка на RadioButton проверит его, так что это будет означать, что вы начнете Stopwatch по умолчанию. Если это не то, что вам нужно, убедитесь, что какой-то другой элемент управления находится сначала в порядке табуляции. Если по какой-то причине вы не можете этого сделать, обработайте событие Shown формы, установите ActiveControl на Nothing, снимите флажок с этого RadioButton и сбросьте соответствующие Stopwatch и Label.

В качестве заключительного общего замечания обратите внимание, что я назвал все так, чтобы даже тот, кто не имел предварительных знаний о проекте, не сомневался, что все это было и для чего оно было. Такие имена, как SW, SW1 и SW2 плохие. Даже если вы поняли, что SW означает Stopwatch, вы не представляете, для чего на самом деле каждый из них. В этот день Intellisense просто лениво использовать такие имена. Каждый опытный разработчик может рассказать вам историю о том, как вернуться к чтению собственного кода через некоторое время и не имея представления о том, что они подразумевают под различными вещами. Не попадайтесь в эту ловушку и убедитесь, что у вас хорошие привычки рано.

РЕДАКТИРОВАТЬ:

В качестве бонуса, вот способ, которым вы можете правильно использовать общий обработчик событий. Во-первых, определите пользовательский класс Stopwatch, который имеет связанный Label:

Public Class StopwatchEx
    Inherits Stopwatch

    Public Property Label As Label

End Class

После того, как вы создадите эту связь, вы автоматически узнаете, какой Label использовать для отображения истекшего времени для Stopwatch. Затем определите пользовательский класс RadioButton, с которым связан Stopwatch:

Public Class RadioButtonEx
    Inherits RadioButton

    Public Property Stopwatch As StopwatchEx

End Class

Далее, используйте этот пользовательский класс в своей форме вместо стандартного RadioButtons. Вы можете добавить их прямо из панели инструментов (ваш пользовательский элемент управления будет добавлен автоматически после создания проекта) или вы можете отредактировать файл кода дизайнера и изменить тип элементов управления в коде. В последнем варианте есть определенный риск, поэтому обязательно создайте резервную копию заранее. Как только это будет сделано, измените тип вашего Stopwatches и обработайте событие Load формы, чтобы создать ассоциации:

Private ReadOnly driveStopwatch As New StopwatchEx
Private ReadOnly waitStopwatch As New StopwatchEx
Private ReadOnly walkStopwatch As New StopwatchEx

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    'Associate Stopwatches with RadioButtons
    driveRadioButton.Stopwatch = driveStopwatch
    waitRadioButton.Stopwatch = waitStopwatch
    walkRadioButton.Stopwatch = walkStopwatch

    'Associate Labels with Stopwatches
    driveStopwatch.Label = driveLabel
    waitStopwatch.Label = waitLabel
    walkStopwatch.Label = walkLabel
End Sub

Теперь вы можете использовать один метод для обработки CheckedChanged событие для всех трех RadioButtons, потому что теперь вы можете сделать то же самое для всех трех из них:

Private Sub RadioButtons_CheckedChanged(sender As Object, e As EventArgs) Handles driveRadioButton.CheckedChanged,
                                                                                  waitRadioButton.CheckedChanged,
                                                                                  walkRadioButton.CheckedChanged
    Dim rb = DirectCast(sender, RadioButtonEx)
    Dim sw = rb.Stopwatch

    If rb.Checked Then
        sw.Start()
    Else
        sw.Stop()
        sw.Label.Text = sw.Elapsed.ToString("hh\:mm\:ss")
    End If
End Sub

RadioButton, вызвавший событие, говорит вам, какой Stopwatch использовать, и это говорит Вы должны Label использовать, поэтому нет необходимости писать разные коды для каждого из них.

Обработчик событий Tick Timer также может обрабатывать каждый Stopwatch с помощью общего кода:

Private Sub displayTimer_Tick(sender As Object, e As EventArgs) Handles displayTimer.Tick
    For Each sw In {driveStopwatch, waitStopwatch, walkStopwatch}
        If sw.IsRunning Then
            sw.Label.Text = sw.Elapsed.ToString("hh\:mm\:ss")
            Exit For
        End If
    Next
End Sub

Вы можете создать массив на уровне класса, но, поскольку он используется только в этом одном месте, имеет смысл создать его здесь. Падение производительности незначительно и делает код более читабельным, создавая вещи, где они используются.

Обратите внимание, что в этом коде я использовал сокращения для имен переменных. Это по двум причинам. Во-первых, это переменные, которые будут ссылаться на разные объекты в разное время. Это означает, что использование имени, указывающего c для цели объекта, невозможно. Вы можете использовать контекстное имя, например, currentRadioButton, но я не делаю этого здесь по второй причине.

Вторая причина заключается в том, что они являются локальными переменными, используемыми в очень ограниченной области. Переменные rb и sw используются не более чем на несколько строк от того места, где они были объявлены, поэтому трудно не понять, что они из себя представляют. Если вы называете поле таким образом, тогда, когда вы видите его в коде, вы должны искать в другом месте, чтобы узнать, что это такое. В этом коде, если вы смотрите на использование одной из этих переменных, то объявление тоже бросается в глаза, так что вы должны быть слепы, чтобы не видеть, с каким типом вы имеете дело. По сути, если переменная используется далеко от ее объявления, я предлагаю содержательное, описательное имя. Если он используется только в нескольких строках своего объявления, краткое имя в порядке. Я обычно склонен использовать инициалы типа, как я сделал здесь. Если вам нужно несколько локальных переменных этого типа, я обычно предпочитаю использовать описательные имена для устранения их неоднозначности, а не числа. Иногда, однако, на самом деле не существует конкретного * c способа сделать это, в этом случае числа в порядке, например, сравнение двух Strings без контекста может использовать s1 и s2 в качестве имен переменных.

...