Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Windows.Threading;
- using System.Windows;
- using System.Windows.Input;
- using System.Windows.Media;
- using System.Windows.Controls;
- namespace Clock
- {
- public partial class MainWindow : Window
- {
- private static MechanicalClock clock;
- private static Pendulum pendulum;
- private static Weight weight;
- private double scaleKoefPendulum;
- private double scaleKoefWeight;
- private DateTime lastTimerTickTime;
- public MainWindow()
- {
- InitializeComponent();
- //В полях для параметров маятника разрешено вводить только цифры и десятичную запятую
- weightTextBox.PreviewTextInput += ValidateNumbersAndDotOnly;
- pendulumWeightTextBox.PreviewTextInput += ValidateNumbersAndDotOnly;
- weightHeightTextBox.PreviewTextInput += ValidateNumbersAndDotOnly;
- frictionKTextBox.PreviewTextInput += ValidateNumbersAndDotOnly;
- //В полях для начального времени часов разрешено вводить только цифры
- secondsTextBox.PreviewTextInput += ValidateNumbersOnly;
- minutesTextBox.PreviewTextInput += ValidateNumbersOnly;
- hoursTextBox.PreviewTextInput += ValidateNumbersOnly;
- startClockButton.Click += StartClock; //При нажатии на кнопку "Завести часы" происходит запуск часов
- }
- private void ValidateNumbersOnly(object sender, TextCompositionEventArgs e) //Допускает лишь цифры в поле ввода.
- {
- for (int i = 0; i < e.Text.Length; i++)
- {
- if (!Char.IsDigit(e.Text[i])) //Если не цифра, то выдаём сообщение об ошибке и не вводим этот символ
- {
- MessageBox.Show("Неверный формат ввода. В данном поле разрешено вводить только цифры.", "Ошибка формата ввода");
- e.Handled = true;
- }
- }
- }
- private void ValidateNumbersAndDotOnly(object sender, TextCompositionEventArgs e) //Допускает лишь цифры и запятую в поле ввода. Точку заменяте на запятую
- {
- for (int i = 0; i < e.Text.Length; i++)
- {
- if (e.Text[i] == '.') //Точку заменяем на запятую
- {
- (sender as TextBox).Text += ",";
- (sender as TextBox).Select((sender as TextBox).Text.Length, 0);
- e.Handled = true;
- return;
- }
- if (!(Char.IsDigit(e.Text[i]) || e.Text[i] == ',')) //Если не точка и не цифра, то выдаём сообщение об ошибке и не вводим этот символ
- {
- MessageBox.Show("Неверный формат ввода. В данном поле разрешено вводить только цифры и десятичную запятую.", "Ошибка формата ввода");
- e.Handled = true;
- }
- }
- }
- private void StartClock(object sender, RoutedEventArgs e) //Запуск часов по нажатию кнопки
- {
- double L = 0.1d, MassWeight = 1d, massPendulum = 1d, height = 1d, beta = 0.1d;
- try
- {
- L = Double.Parse(pendulumLengthTextBox.Text); //Длина маятника [м]
- MassWeight = Double.Parse(weightTextBox.Text); //Вес гири [кг]
- massPendulum = Double.Parse(pendulumWeightTextBox.Text); //Вес маятника [кг]
- height = Double.Parse(weightHeightTextBox.Text); //Высота подъёма гири [м]
- beta = Double.Parse(frictionKTextBox.Text); //Коэффициент трения
- if (L < 0.1 || L > 5 || beta < 0 || beta > 1 || MassWeight < 0.1 || MassWeight > 10 || massPendulum < 0.1 || massPendulum > 10 || height < 0.1 || height > 5)
- {
- throw new OverflowException(); //Если хотя бы одно из значений вне допустимых пределов
- }
- }
- catch (Exception ex) //Если хотя бы одно из значений вне допустимых пределов или не является десятичным числом, представимыым в формате double
- {
- MessageBox.Show("Неверный формат ввода.\nКоэффициент трения должен лежать в диапазоне [0; 1].\nДлина маятника должна лежать в диапазоне [0,1; 5] м.\nМасса гири должна лежать в диапазоне [0,1; 10] кг.\nМасса маятника должна лежать в диапазоне [0,1; 10] кг.\nВысота подъёма гири должна лежать в диапазоне [0,1; 5] м.", "Ошибка формата ввода");
- return; //Вернуться к вводу параметров маятника
- }
- int s = 0, m = 0, hour = 0;
- try
- {
- s = Int32.Parse(secondsTextBox.Text); //Секунды, целое число в пределах [0; 59]
- m = Int32.Parse(minutesTextBox.Text); //Минуты, целое число в пределах [0; 59]
- hour = Int32.Parse(hoursTextBox.Text); //Часы, целое число в пределах [0; 11]
- if (s < 0 || m < 0 || hour < 0 || s >= 60 || m >= 60 || hour >= 12)
- {
- throw new OverflowException(); //Если хотя бы одно из значений вне допустимых пределов
- }
- }
- catch (Exception ex) //Если хотя бы одно из значений вне допустимых пределов или не является wtksv числом, представимыым в формате int
- {
- MessageBox.Show("Неверный формат ввода. Секунды и минуты должны быть в диапазоне от 0 до 59 включительно, часы - от 0 до 11 включительно.", "Ошибка формата ввода");
- return; //Вернуться к вводу начального времени
- }
- //Блокировака полей ввода параметров маятника
- pendulumLengthTextBox.IsEnabled = false;
- weightTextBox.IsEnabled = false;
- pendulumWeightTextBox.IsEnabled = false;
- weightHeightTextBox.IsEnabled = false;
- frictionKTextBox.IsEnabled = false;
- //Скрытие полей ввода начального времени
- startClockButton.IsEnabled = false;
- startClockButton.Visibility = Visibility.Hidden;
- setInitialTimeText.Visibility = Visibility.Hidden;
- secondsTextBlock.Visibility = Visibility.Hidden;
- secondsTextBox.IsEnabled = false;
- secondsTextBox.Visibility = Visibility.Hidden;
- minutesTextBlock.Visibility = Visibility.Hidden;
- minutesTextBox.IsEnabled = false;
- minutesTextBox.Visibility = Visibility.Hidden;
- hoursTextBlock.Visibility = Visibility.Hidden;
- hoursTextBox.IsEnabled = false;
- hoursTextBox.Visibility = Visibility.Hidden;
- //Коэффициенты для перевода длины маятника и высоты подъёма гири из метров в экранные единицы измерения
- scaleKoefPendulum = (oscillatorRope.Y2 - oscillatorRope.Y1) / L;
- scaleKoefWeight = (weightRope.Y2 - weightRope.Y1 - 50) / height;
- //Создание обхектов модели: часов, маятника и гири
- clock = new MechanicalClock(s, m, hour);
- pendulum = new Pendulum(L, massPendulum, beta);
- weight = new Weight(MassWeight);
- //Интервал времени, после которого двигаются стрелки часов. Для упрощения этот же интервал используется в анимации маятника и гири. Этот интервал зависит от периода маятника.
- double millisecondInterval = ((pendulum.T * 1000) / 50);
- DispatcherTimer dispatcherTimer = new DispatcherTimer();
- pendulum.prevX = pendulum.CalcX(); //Запоминаем начальные координаты маятника
- pendulum.prevY = pendulum.CalcY();
- weight.LiftWeight(height); //Поднимаем гирю, чтобы придать ей потенциальную энергию и завести часы
- weight.StartPendulumWithPotentialenergy(m, L, pendulum.angle); //Гиря отдала часть энергии для отклонения маятника из положения равновесие на некоторый угол, чтобы запустить колебания
- dispatcherTimer.Interval = TimeSpan.FromMilliseconds(millisecondInterval);
- dispatcherTimer.Tick += ExecuteEveryTick; //Через заданный интервал, зависящий от периода, будет вызываться ExecuteEveryTick
- dispatcherTimer.Start();
- lastTimerTickTime = DateTime.Now; //Время запуска таймера
- }
- private void ExecuteEveryTick(object sender, EventArgs e)
- {
- double elapsedTime = (DateTime.Now - lastTimerTickTime).TotalMilliseconds; //Количество миллисекунд с прошлого тика таймера
- lastTimerTickTime = DateTime.Now;
- pendulum.x = pendulum.CalcX() * scaleKoefPendulum; //Вычисляем новые координаты маятника
- pendulum.y = pendulum.CalcY() * scaleKoefPendulum;
- double deltaX = Math.Abs(pendulum.prevX - pendulum.x); //Вычисляем расстояния по осям, которые маятник прошёл за время с прошлого тика
- double deltaY = Math.Abs(pendulum.prevY - pendulum.y);
- //Вычисляем работу, которую сила трения совершила с момента последнего тика
- double FrictionWork = pendulum.frictionK * Math.Sqrt(Math.Pow(deltaX / scaleKoefPendulum, 2) + Math.Pow(deltaY / scaleKoefPendulum, 2)) * Math.Sqrt(2 * Weight.g * pendulum.L * (1 - Math.Cos(GradToRadian(pendulum.angle))));
- if (weight.h >= 0.00001) //Если у гири остаётся потенциальная энергия, с её помощью компенсируется работа силы трения, а гиря опускается
- {
- weight.CompensateFrictionWork(FrictionWork);
- (weightBlock.RenderTransform as TranslateTransform).Y = -(weight.h * scaleKoefWeight);
- weightRope.Y2 = 250 - (weight.h * scaleKoefWeight);
- }
- //Если у гири нет потенциальной энергии, то работа силы трения компенсируется из энергии маятника. Колебания начинают замедляться с некоторым коэффициентом затухания
- if (weight.h < 0.00001)
- {
- pendulum.StartDeceleration(); //Вычислить и установить коэффициент затухания
- if (deltaX < 1 && deltaY < 1) //Если маятник практически не сдвинулся с места
- {
- (sender as DispatcherTimer).Stop(); //Остановить часы
- RestartClock(); //Привести часы в режим работы с пользователем
- }
- }
- //Сдвинуть маятник в его новую позицию
- (oscillatorWeight.RenderTransform as TranslateTransform).X = pendulum.x;
- (oscillatorWeight.RenderTransform as TranslateTransform).Y = pendulum.y;
- oscillatorRope.X2 = pendulum.x + 380;
- oscillatorRope.Y2 = pendulum.y + 250;
- //Добавить прошедшее время к часам (в секундах). Это вызовет пересчёт углов для всех стрелок
- clock.Seconds += (elapsedTime / 1000d);
- //Повернуть все стрелки на их новые углы
- (secondArrow.RenderTransform as RotateTransform).Angle = clock.secAngle;
- (minuteArrow.RenderTransform as RotateTransform).Angle = clock.minAngle;
- (hourArrow.RenderTransform as RotateTransform).Angle = clock.hourAngle;
- //Записать текущие координаты маятника как прошедшие
- pendulum.prevX = pendulum.x / scaleKoefPendulum;
- pendulum.prevY = pendulum.y / scaleKoefPendulum;
- pendulum.time += elapsedTime / 1000d; //Добавить прошедшее время к времени маятника (в секундах)
- }
- public static double GradToRadian(double angle) //Вспомогательная функция. Принимает угол в градусах, возвращает этот же угол в радианах
- {
- return angle * Math.PI / 180;
- }
- private void RestartClock() //Перевести часы в режим работы с пользователем после их остановки
- {
- //Активировать для ввода текстовые поля с параметрами маятника
- pendulumLengthTextBox.IsEnabled = true;
- weightTextBox.IsEnabled = true;
- pendulumWeightTextBox.IsEnabled = true;
- weightHeightTextBox.IsEnabled = true;
- frictionKTextBox.IsEnabled = true;
- //Показать и активировать для ввода текстовые поля для установления начального времени на часах. В них записать время, на котором стоят стрелки после остановки.
- startClockButton.IsEnabled = true;
- startClockButton.Visibility = Visibility.Visible;
- setInitialTimeText.Visibility = Visibility.Visible;
- secondsTextBlock.Visibility = Visibility.Visible;
- secondsTextBox.Text = (Math.Round(clock.Seconds)).ToString();
- secondsTextBox.IsEnabled = true;
- secondsTextBox.Visibility = Visibility.Visible;
- minutesTextBlock.Visibility = Visibility.Visible;
- minutesTextBox.Text = (Math.Round(clock.Minutes)).ToString();
- minutesTextBox.IsEnabled = true;
- minutesTextBox.Visibility = Visibility.Visible;
- hoursTextBox.Text = (Math.Round(clock.Hours)).ToString();
- hoursTextBlock.Visibility = Visibility.Visible;
- hoursTextBox.IsEnabled = true;
- hoursTextBox.Visibility = Visibility.Visible;
- //Переместить маятник в состояние равновесия. Гирю в начальное состояние перемещать не надо, т.к. её высота подъёма всегда 0 при остановке часов.
- (oscillatorWeight.RenderTransform as TranslateTransform).X = 0;
- (oscillatorWeight.RenderTransform as TranslateTransform).Y = 0;
- oscillatorRope.X2 = 380;
- oscillatorRope.Y2 = 250;
- }
- }
- public class MechanicalClock //Класс часов со стрелками
- {
- //Поля, доступ к которым осуществляется через свойства
- private double sec; //Секунды
- private double min; //Минуты
- private double h; //Часы
- private double sAngle; //Угол поворота секундной стрелки в градусах
- private double mAngle; //Угол поворота минутной стрелки в градусах
- private double hAngle; //Угол поворота часовой стрелки в градусах
- public MechanicalClock(double sec, double min, double h)
- {
- Seconds = sec;
- Minutes = min;
- Hours = h;
- }
- public double Seconds //Свойство доступа к секундам
- {
- get { return sec; }
- set //Переводит переданное число в минуты и секунды, секунды записывает, а минуты передаёт в свойство доступа к минутам. Запускает пересчёт угла секундной стрелки.
- {
- sec = value % 60;
- Minutes += (int)value / 60;
- secAngle = sec; //Неважно, какое значение присваивается, свойство персчитывает угол с учётом значений полей
- }
- }
- public double Minutes //Свойство доступа к минутам
- {
- get { return min; }
- private set //Менять значение минут может только свойство доступа к секундам
- { //Переводит переданное число в часы и минуты, минуты записывает, а часы передаёт в свойство доступа к часам. Запускает пересчёт угла минутной стрелки.
- min = value % 60;
- Hours += (int)value / 60;
- minAngle = min; //Неважно, какое значение присваивается, свойство персчитывает угол с учётом значений полей
- }
- }
- public double Hours //Свойство доступа к часам
- {
- get { return h; }
- private set //Менять значение минут может только свойство доступа к минутам
- { //Переводит переданное число в часы в диапазоне [0; 11], записывает их. Запускает пересчёт угла часовой стрелки.
- h = value % 12;
- hourAngle = h; //Неважно, какое значение присваивается, свойство персчитывает угол с учётом значений полей
- }
- }
- public double secAngle //Свойство доступа к углу секундной стрелки
- {
- get { return sAngle; }
- private set { sAngle = sec * 6; } //Пересчитывает угол секундной стрелки, исходя из значения поля данных секунд.
- //Передаваемое в свойство значение никак не влияет. Секундная стрелка проходит 6 градусов за секунду.
- }
- public double minAngle //Свойство доступа к углу минутной стрелки
- {
- get { return mAngle; }
- private set { mAngle = min * 6 + sec * 0.1; } //Пересчитывает угол минутной стрелки, исходя из значений полей данных секунд и минут.
- //Передаваемое в свойство значение никак не влияет. Минутная стрелка проходит 6 градусов за минуту и 0,1 градус за секунду.
- }
- public double hourAngle //Свойство доступа к углу часовой стрелки
- {
- get { return hAngle; }
- private set { hAngle = h * 30 + (30 / 60) * min + (30 / 3600) * sec; } //Пересчитывает угол часовой стрелки, исходя из значений полей данных секунд, минут и часов.
- //Передаваемое в свойство значение никак не влияет. Часовая стрелка проходит 30 градусов за час, 30/60 градусов за минуту и 30/3600 градусов за секунду.
- }
- }
- public class Pendulum //Класс маятника
- {
- public double angle = 5; //Угол отклонения маятника от вертикальной оси в градусах
- public double L; //Длина нити [м]
- public double T; //Период колебаний [с]
- public double frictionK; //Коэффициент трения
- public double decelerationK; //Коэффициент затухания
- public double Amplitude; //Амплитуда [м]
- public double m; //Масса маятника [кг]
- public double time; //Время [с]
- public double x, y; //Координаты маятника
- public double prevX, prevY; //Предыдущие координаты маятника
- //Фаза колебаний не используется в работе и считается равной 0
- public Pendulum(double L, double m, double beta)
- {
- this.decelerationK = 0; //Коэффициент затухания равен 0, пока работа силы трения компенсируется потенциальной энергией гири
- this.time = 0; //[с]
- this.L = L; //[м]
- this.m = m; //[кг]
- this.frictionK = beta;
- this.x = 0; //[м]
- this.y = 0; //[м]
- this.Amplitude = Math.Sin(MainWindow.GradToRadian(angle)) * L; //[м]
- const double g = 9.81; //[м/с^2]
- this.T = 2 * Math.PI * Math.Sqrt(L / g); //[с]
- }
- public double CalcX() //Вычисляем текущее значение X в метрах
- {
- double angularFrequency = Math.Sqrt(Math.Pow(((2 * Math.PI) / this.T), 2) - Math.Pow(this.decelerationK, 2)); //Циклическая частота [рад/c]
- if (Double.IsNaN(angularFrequency)) //Если под корнем в формуле циклической частоты отрицательное число, колебания не совершаются. Маятник остаётся на месте.
- return this.prevX;
- double deceleration = Math.Pow(Math.E, -this.decelerationK * this.time);
- return this.Amplitude * deceleration * Math.Cos(angularFrequency * (this.time));
- }
- public double CalcY() //Вычисляем текущее значение Y в метрах
- {
- double result = Math.Sqrt(L * L - Math.Pow(CalcX(), 2)) - L; //Если под корнем в формуле отрицательное число, колебания не совершаются. Маятник остаётся на месте.
- if (Double.IsNaN(result)) return this.prevY;
- else return result;
- }
- public void StartDeceleration() //Вычисляет коэффициент затухания, когда автоколебания прекратились и начались затухающие колебания
- {
- decelerationK = frictionK / (2 * m);
- }
- }
- public class Weight //Класс гири
- {
- public double M; //Масса гири [кг]
- public double PotentialEnergy; //Потенциальная энергия гири [Дж]
- public double h; //Высота подъёма гири [м]
- public const double g = 9.81; //[м/с^2]
- public Weight(double M)
- {
- this.M = M;
- this.h = 0;
- PotentialEnergy = 0;
- }
- public void LiftWeight(double h) //Поднимает гирю до высоты h. Пересчитывает потенциальную энергию гири.
- {
- this.h = h;
- PotentialEnergy = M * g * h;
- }
- public void StartPendulumWithPotentialenergy(double m, double L, double angle) //Отклоняет маятник из положения равновесия на угол angle.
- { //Вычитает затраченную энергию из потенциальной энергии гири, пересчитывает высоту подъёма гири.
- double E = m * g * L * (1 - Math.Cos(MainWindow.GradToRadian(angle)));
- double heightDecrease = E / (M * g);
- PotentialEnergy -= E;
- h -= heightDecrease;
- if (h < 0.00001) //Если высота подъёма равна нулю или отрицательна
- {
- h = 0;
- PotentialEnergy = 0;
- }
- }
- public void CompensateFrictionWork(double FrictionWork) //Компенсирует работу силы трения за счёт потенциальной энергии гири.
- { //Вычитает затраченную энергию из потенциальной энергии гири, пересчитывает высоту подъёма гири.
- double heightDecrease = FrictionWork / (M * g);
- PotentialEnergy -= FrictionWork;
- h -= heightDecrease;
- if (h < 0.00001) //Если высота подъёма равна нулю или отрицательна
- {
- h = 0;
- PotentialEnergy = 0;
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement