Advertisement
CherMi

test

Dec 19th, 2020
547
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 26.75 KB | None | 0 0
  1. using System;
  2. using System.Windows.Threading;
  3. using System.Windows;
  4. using System.Windows.Input;
  5. using System.Windows.Media;
  6. using System.Windows.Controls;
  7.  
  8. namespace Clock
  9. {
  10.     public partial class MainWindow : Window
  11.     {
  12.         private static MechanicalClock clock;
  13.         private static Pendulum pendulum;
  14.         private static Weight weight;
  15.         private double scaleKoefPendulum;
  16.         private double scaleKoefWeight;
  17.         private DateTime lastTimerTickTime;
  18.  
  19.         public MainWindow()
  20.         {
  21.             InitializeComponent();
  22.  
  23.             //В полях для параметров маятника разрешено вводить только цифры и десятичную запятую
  24.             weightTextBox.PreviewTextInput += ValidateNumbersAndDotOnly;
  25.             pendulumWeightTextBox.PreviewTextInput += ValidateNumbersAndDotOnly;
  26.             weightHeightTextBox.PreviewTextInput += ValidateNumbersAndDotOnly;
  27.             frictionKTextBox.PreviewTextInput += ValidateNumbersAndDotOnly;
  28.  
  29.             //В полях для начального времени часов разрешено вводить только цифры
  30.             secondsTextBox.PreviewTextInput += ValidateNumbersOnly;
  31.             minutesTextBox.PreviewTextInput += ValidateNumbersOnly;
  32.             hoursTextBox.PreviewTextInput += ValidateNumbersOnly;
  33.  
  34.             startClockButton.Click += StartClock;  //При нажатии на кнопку "Завести часы" происходит запуск часов
  35.         }
  36.  
  37.         private void ValidateNumbersOnly(object sender, TextCompositionEventArgs e) //Допускает лишь цифры в поле ввода.
  38.         {
  39.             for (int i = 0; i < e.Text.Length; i++)
  40.             {
  41.                 if (!Char.IsDigit(e.Text[i])) //Если не цифра, то выдаём сообщение об ошибке и не вводим этот символ
  42.                 {
  43.                     MessageBox.Show("Неверный формат ввода. В данном поле разрешено вводить только цифры.", "Ошибка формата ввода");
  44.                     e.Handled = true;
  45.                 }
  46.             }
  47.         }
  48.  
  49.         private void ValidateNumbersAndDotOnly(object sender, TextCompositionEventArgs e) //Допускает лишь цифры и запятую в поле ввода. Точку заменяте на запятую
  50.         {
  51.             for (int i = 0; i < e.Text.Length; i++)
  52.             {
  53.                 if (e.Text[i] == '.') //Точку заменяем на запятую
  54.                 {
  55.                     (sender as TextBox).Text += ",";
  56.                     (sender as TextBox).Select((sender as TextBox).Text.Length, 0);
  57.                     e.Handled = true;
  58.                     return;
  59.                 }
  60.                 if (!(Char.IsDigit(e.Text[i]) || e.Text[i] == ',')) //Если не точка и не цифра, то выдаём сообщение об ошибке и не вводим этот символ
  61.                 {
  62.                     MessageBox.Show("Неверный формат ввода. В данном поле разрешено вводить только цифры и десятичную запятую.", "Ошибка формата ввода");
  63.                     e.Handled = true;
  64.                 }
  65.             }
  66.         }
  67.  
  68.         private void StartClock(object sender, RoutedEventArgs e) //Запуск часов по нажатию кнопки
  69.         {
  70.             double L = 0.1d, MassWeight = 1d, massPendulum = 1d, height = 1d, beta = 0.1d;
  71.             try
  72.             {
  73.                 L = Double.Parse(pendulumLengthTextBox.Text); //Длина маятника [м]
  74.                 MassWeight = Double.Parse(weightTextBox.Text); //Вес гири [кг]
  75.                 massPendulum = Double.Parse(pendulumWeightTextBox.Text); //Вес маятника [кг]
  76.                 height = Double.Parse(weightHeightTextBox.Text); //Высота подъёма гири [м]
  77.                 beta = Double.Parse(frictionKTextBox.Text); //Коэффициент трения
  78.                 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)
  79.                 {
  80.                     throw new OverflowException(); //Если хотя бы одно из значений вне допустимых пределов
  81.                 }
  82.             }
  83.             catch (Exception ex) //Если хотя бы одно из значений вне допустимых пределов или не является десятичным числом, представимыым в формате double
  84.             {
  85.                 MessageBox.Show("Неверный формат ввода.\nКоэффициент трения должен лежать в диапазоне [0; 1].\nДлина маятника должна лежать в диапазоне [0,1; 5] м.\nМасса гири должна лежать в диапазоне [0,1; 10] кг.\nМасса маятника должна лежать в диапазоне [0,1; 10] кг.\nВысота подъёма гири должна лежать в диапазоне [0,1; 5] м.", "Ошибка формата ввода");
  86.                 return; //Вернуться к вводу параметров маятника
  87.             }
  88.  
  89.             int s = 0, m = 0, hour = 0;
  90.             try
  91.             {
  92.                 s = Int32.Parse(secondsTextBox.Text); //Секунды, целое число в пределах [0; 59]
  93.                 m = Int32.Parse(minutesTextBox.Text); //Минуты, целое число в пределах [0; 59]
  94.                 hour = Int32.Parse(hoursTextBox.Text); //Часы, целое число в пределах [0; 11]
  95.                 if (s < 0 || m < 0 || hour < 0 || s >= 60 || m >= 60 || hour >= 12)
  96.                 {
  97.                     throw new OverflowException(); //Если хотя бы одно из значений вне допустимых пределов
  98.                 }
  99.             }
  100.             catch (Exception ex) //Если хотя бы одно из значений вне допустимых пределов или не является wtksv числом, представимыым в формате int
  101.             {
  102.                 MessageBox.Show("Неверный формат ввода. Секунды и минуты должны быть в диапазоне от 0 до 59 включительно, часы - от 0 до 11 включительно.", "Ошибка формата ввода");
  103.                 return; //Вернуться к вводу начального времени
  104.             }
  105.  
  106.             //Блокировака полей ввода параметров маятника
  107.             pendulumLengthTextBox.IsEnabled = false;
  108.             weightTextBox.IsEnabled = false;
  109.             pendulumWeightTextBox.IsEnabled = false;
  110.             weightHeightTextBox.IsEnabled = false;
  111.             frictionKTextBox.IsEnabled = false;
  112.  
  113.             //Скрытие полей ввода начального времени
  114.             startClockButton.IsEnabled = false;
  115.             startClockButton.Visibility = Visibility.Hidden;
  116.             setInitialTimeText.Visibility = Visibility.Hidden;
  117.             secondsTextBlock.Visibility = Visibility.Hidden;
  118.             secondsTextBox.IsEnabled = false;
  119.             secondsTextBox.Visibility = Visibility.Hidden;
  120.             minutesTextBlock.Visibility = Visibility.Hidden;
  121.             minutesTextBox.IsEnabled = false;
  122.             minutesTextBox.Visibility = Visibility.Hidden;
  123.             hoursTextBlock.Visibility = Visibility.Hidden;
  124.             hoursTextBox.IsEnabled = false;
  125.             hoursTextBox.Visibility = Visibility.Hidden;
  126.  
  127.             //Коэффициенты для перевода длины маятника и высоты подъёма гири из метров в экранные единицы измерения
  128.             scaleKoefPendulum = (oscillatorRope.Y2 - oscillatorRope.Y1) / L;
  129.             scaleKoefWeight = (weightRope.Y2 - weightRope.Y1 - 50) / height;
  130.  
  131.             //Создание обхектов модели: часов, маятника и гири
  132.             clock = new MechanicalClock(s, m, hour);
  133.             pendulum = new Pendulum(L, massPendulum, beta);
  134.             weight = new Weight(MassWeight);
  135.  
  136.             //Интервал времени, после которого двигаются стрелки часов. Для упрощения этот же интервал используется в анимации маятника и гири. Этот интервал зависит от периода маятника.
  137.             double millisecondInterval = ((pendulum.T * 1000) / 50);
  138.  
  139.             DispatcherTimer dispatcherTimer = new DispatcherTimer();
  140.             pendulum.prevX = pendulum.CalcX(); //Запоминаем начальные координаты маятника
  141.             pendulum.prevY = pendulum.CalcY();
  142.             weight.LiftWeight(height); //Поднимаем гирю, чтобы придать ей потенциальную энергию и завести часы
  143.             weight.StartPendulumWithPotentialenergy(m, L, pendulum.angle); //Гиря отдала часть энергии для отклонения маятника из положения равновесие на некоторый угол, чтобы запустить колебания
  144.  
  145.             dispatcherTimer.Interval = TimeSpan.FromMilliseconds(millisecondInterval);
  146.             dispatcherTimer.Tick += ExecuteEveryTick; //Через заданный интервал, зависящий от периода, будет вызываться ExecuteEveryTick
  147.             dispatcherTimer.Start();
  148.             lastTimerTickTime = DateTime.Now; //Время запуска таймера
  149.         }
  150.  
  151.         private void ExecuteEveryTick(object sender, EventArgs e)
  152.         {
  153.             double elapsedTime = (DateTime.Now - lastTimerTickTime).TotalMilliseconds; //Количество миллисекунд с прошлого тика таймера
  154.             lastTimerTickTime = DateTime.Now;
  155.  
  156.             pendulum.x = pendulum.CalcX() * scaleKoefPendulum; //Вычисляем новые координаты маятника
  157.             pendulum.y = pendulum.CalcY() * scaleKoefPendulum;
  158.             double deltaX = Math.Abs(pendulum.prevX - pendulum.x); //Вычисляем расстояния по осям, которые маятник прошёл за время с прошлого тика
  159.             double deltaY = Math.Abs(pendulum.prevY - pendulum.y);
  160.             //Вычисляем работу, которую сила трения совершила с момента последнего тика
  161.             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))));
  162.             if (weight.h >= 0.00001) //Если у гири остаётся потенциальная энергия, с её помощью компенсируется работа силы трения, а гиря опускается
  163.             {
  164.                 weight.CompensateFrictionWork(FrictionWork);
  165.                 (weightBlock.RenderTransform as TranslateTransform).Y = -(weight.h * scaleKoefWeight);
  166.                 weightRope.Y2 = 250 - (weight.h * scaleKoefWeight);
  167.             }
  168.             //Если у гири нет потенциальной энергии, то работа силы трения компенсируется из энергии маятника. Колебания начинают замедляться с некоторым коэффициентом затухания
  169.             if (weight.h < 0.00001)
  170.             {
  171.                 pendulum.StartDeceleration(); //Вычислить и установить коэффициент затухания
  172.                 if (deltaX < 1 && deltaY < 1) //Если маятник практически не сдвинулся с места
  173.                 {
  174.                     (sender as DispatcherTimer).Stop(); //Остановить часы
  175.                     RestartClock(); //Привести часы в режим работы с пользователем
  176.                 }
  177.             }
  178.            
  179.             //Сдвинуть маятник в его новую позицию
  180.             (oscillatorWeight.RenderTransform as TranslateTransform).X = pendulum.x;
  181.             (oscillatorWeight.RenderTransform as TranslateTransform).Y = pendulum.y;
  182.             oscillatorRope.X2 = pendulum.x + 380;
  183.             oscillatorRope.Y2 = pendulum.y + 250;
  184.  
  185.             //Добавить прошедшее время к часам (в секундах). Это вызовет пересчёт углов для всех стрелок
  186.             clock.Seconds += (elapsedTime / 1000d);
  187.  
  188.             //Повернуть все стрелки на их новые углы
  189.             (secondArrow.RenderTransform as RotateTransform).Angle = clock.secAngle;
  190.             (minuteArrow.RenderTransform as RotateTransform).Angle = clock.minAngle;
  191.             (hourArrow.RenderTransform as RotateTransform).Angle = clock.hourAngle;
  192.  
  193.             //Записать текущие координаты маятника как прошедшие
  194.             pendulum.prevX = pendulum.x / scaleKoefPendulum;
  195.             pendulum.prevY = pendulum.y / scaleKoefPendulum;
  196.             pendulum.time += elapsedTime / 1000d; //Добавить прошедшее время к времени маятника (в секундах)
  197.         }
  198.         public static double GradToRadian(double angle) //Вспомогательная функция. Принимает угол в градусах, возвращает этот же угол в радианах
  199.         {
  200.             return angle * Math.PI / 180;
  201.         }
  202.  
  203.         private void RestartClock() //Перевести часы в режим работы с пользователем после их остановки
  204.         {
  205.             //Активировать для ввода текстовые поля с параметрами маятника
  206.             pendulumLengthTextBox.IsEnabled = true;
  207.             weightTextBox.IsEnabled = true;
  208.             pendulumWeightTextBox.IsEnabled = true;
  209.             weightHeightTextBox.IsEnabled = true;
  210.             frictionKTextBox.IsEnabled = true;
  211.  
  212.             //Показать и активировать для ввода текстовые поля для установления начального времени на часах. В них записать время, на котором стоят стрелки после остановки.
  213.             startClockButton.IsEnabled = true;
  214.             startClockButton.Visibility = Visibility.Visible;
  215.             setInitialTimeText.Visibility = Visibility.Visible;
  216.             secondsTextBlock.Visibility = Visibility.Visible;
  217.             secondsTextBox.Text = (Math.Round(clock.Seconds)).ToString();
  218.             secondsTextBox.IsEnabled = true;
  219.             secondsTextBox.Visibility = Visibility.Visible;
  220.             minutesTextBlock.Visibility = Visibility.Visible;
  221.             minutesTextBox.Text = (Math.Round(clock.Minutes)).ToString();
  222.             minutesTextBox.IsEnabled = true;
  223.             minutesTextBox.Visibility = Visibility.Visible;
  224.             hoursTextBox.Text = (Math.Round(clock.Hours)).ToString();
  225.             hoursTextBlock.Visibility = Visibility.Visible;
  226.             hoursTextBox.IsEnabled = true;
  227.             hoursTextBox.Visibility = Visibility.Visible;
  228.  
  229.             //Переместить маятник в состояние равновесия. Гирю в начальное состояние перемещать не надо, т.к. её высота подъёма всегда 0 при остановке часов.
  230.             (oscillatorWeight.RenderTransform as TranslateTransform).X = 0;
  231.             (oscillatorWeight.RenderTransform as TranslateTransform).Y = 0;
  232.             oscillatorRope.X2 = 380;
  233.             oscillatorRope.Y2 = 250;
  234.         }
  235.     }
  236.  
  237.     public class MechanicalClock //Класс часов со стрелками
  238.     {
  239.         //Поля, доступ к которым осуществляется через свойства
  240.         private double sec; //Секунды
  241.         private double min; //Минуты
  242.         private double h; //Часы
  243.         private double sAngle; //Угол поворота секундной стрелки в градусах
  244.         private double mAngle; //Угол поворота минутной стрелки в градусах
  245.         private double hAngle; //Угол поворота часовой стрелки в градусах
  246.  
  247.         public MechanicalClock(double sec, double min, double h)
  248.         {
  249.             Seconds = sec;
  250.             Minutes = min;
  251.             Hours = h;
  252.         }
  253.  
  254.         public double Seconds //Свойство доступа к секундам
  255.         {
  256.             get { return sec; }
  257.             set //Переводит переданное число в минуты и секунды, секунды записывает, а минуты передаёт в свойство доступа к минутам. Запускает пересчёт угла секундной стрелки.
  258.             {
  259.                 sec = value % 60;
  260.                 Minutes += (int)value / 60;
  261.                 secAngle = sec; //Неважно, какое значение присваивается, свойство персчитывает угол с учётом значений полей
  262.             }
  263.         }
  264.  
  265.         public double Minutes //Свойство доступа к минутам
  266.         {
  267.             get { return min; }
  268.             private set //Менять значение минут может только свойство доступа к секундам
  269.             { //Переводит переданное число в часы и минуты, минуты записывает, а часы передаёт в свойство доступа к часам. Запускает пересчёт угла минутной стрелки.
  270.                 min = value % 60;
  271.                 Hours += (int)value / 60;
  272.                 minAngle = min; //Неважно, какое значение присваивается, свойство персчитывает угол с учётом значений полей
  273.             }
  274.         }
  275.  
  276.         public double Hours //Свойство доступа к часам
  277.         {
  278.             get { return h; }
  279.             private set //Менять значение минут может только свойство доступа к минутам
  280.             { //Переводит переданное число в часы в диапазоне [0; 11], записывает их. Запускает пересчёт угла часовой стрелки.
  281.                 h = value % 12;
  282.                 hourAngle = h; //Неважно, какое значение присваивается, свойство персчитывает угол с учётом значений полей
  283.             }
  284.         }
  285.  
  286.         public double secAngle //Свойство доступа к углу секундной стрелки
  287.         {
  288.             get { return sAngle; }
  289.             private set { sAngle = sec * 6; } //Пересчитывает угол секундной стрелки, исходя из значения поля данных секунд.
  290.             //Передаваемое в свойство значение никак не влияет. Секундная стрелка проходит 6 градусов за секунду.
  291.         }
  292.         public double minAngle //Свойство доступа к углу минутной стрелки
  293.         {
  294.             get { return mAngle; }
  295.             private set { mAngle = min * 6 + sec * 0.1; } //Пересчитывает угол минутной стрелки, исходя из значений полей данных секунд и минут.
  296.             //Передаваемое в свойство значение никак не влияет. Минутная стрелка проходит 6 градусов за минуту и 0,1 градус за секунду.
  297.         }
  298.         public double hourAngle //Свойство доступа к углу часовой стрелки
  299.         {
  300.             get { return hAngle; }
  301.             private set { hAngle = h * 30 + (30 / 60) * min + (30 / 3600) * sec; } //Пересчитывает угол часовой стрелки, исходя из значений полей данных секунд, минут и часов.
  302.             //Передаваемое в свойство значение никак не влияет. Часовая стрелка проходит 30 градусов за час, 30/60 градусов за минуту и 30/3600 градусов за секунду.
  303.         }
  304.     }
  305.  
  306.     public class Pendulum //Класс маятника
  307.     {
  308.         public double angle = 5; //Угол отклонения маятника от вертикальной оси в градусах
  309.         public double L; //Длина нити [м]
  310.         public double T; //Период колебаний [с]
  311.         public double frictionK; //Коэффициент трения
  312.         public double decelerationK; //Коэффициент затухания
  313.         public double Amplitude; //Амплитуда [м]
  314.         public double m; //Масса маятника [кг]
  315.         public double time; //Время [с]
  316.         public double x, y; //Координаты маятника
  317.         public double prevX, prevY; //Предыдущие координаты маятника
  318.         //Фаза колебаний не используется в работе и считается равной 0
  319.  
  320.         public Pendulum(double L, double m, double beta)
  321.         {
  322.             this.decelerationK = 0; //Коэффициент затухания равен 0, пока работа силы трения компенсируется потенциальной энергией гири
  323.             this.time = 0; //[с]
  324.             this.L = L; //[м]
  325.             this.m = m; //[кг]
  326.             this.frictionK = beta;
  327.             this.x = 0; //[м]
  328.             this.y = 0; //[м]
  329.  
  330.             this.Amplitude = Math.Sin(MainWindow.GradToRadian(angle)) * L; //[м]
  331.             const double g = 9.81; //[м/с^2]
  332.             this.T = 2 * Math.PI * Math.Sqrt(L / g); //[с]
  333.         }
  334.  
  335.         public double CalcX()  //Вычисляем текущее значение X в метрах
  336.         {
  337.             double angularFrequency = Math.Sqrt(Math.Pow(((2 * Math.PI) / this.T), 2) - Math.Pow(this.decelerationK, 2)); //Циклическая частота [рад/c]
  338.             if (Double.IsNaN(angularFrequency)) //Если под корнем в формуле циклической частоты отрицательное число, колебания не совершаются. Маятник остаётся на месте.
  339.                 return this.prevX;
  340.             double deceleration = Math.Pow(Math.E, -this.decelerationK * this.time);
  341.             return this.Amplitude * deceleration * Math.Cos(angularFrequency * (this.time));
  342.         }
  343.         public double CalcY() //Вычисляем текущее значение Y в метрах
  344.         {
  345.             double result = Math.Sqrt(L * L - Math.Pow(CalcX(), 2)) - L; //Если под корнем в формуле отрицательное число, колебания не совершаются. Маятник остаётся на месте.
  346.             if (Double.IsNaN(result)) return this.prevY;
  347.             else return result;
  348.         }
  349.  
  350.         public void StartDeceleration() //Вычисляет коэффициент затухания, когда автоколебания прекратились и начались затухающие колебания
  351.         {
  352.             decelerationK = frictionK / (2 * m);
  353.         }
  354.     }
  355.  
  356.     public class Weight //Класс гири
  357.     {
  358.         public double M; //Масса гири [кг]
  359.         public double PotentialEnergy; //Потенциальная энергия гири [Дж]
  360.         public double h; //Высота подъёма гири [м]
  361.         public const double g = 9.81; //[м/с^2]
  362.  
  363.         public Weight(double M)
  364.         {
  365.             this.M = M;
  366.             this.h = 0;
  367.             PotentialEnergy = 0;
  368.         }
  369.  
  370.         public void LiftWeight(double h) //Поднимает гирю до высоты h. Пересчитывает потенциальную энергию гири.
  371.         {
  372.             this.h = h;
  373.             PotentialEnergy = M * g * h;
  374.         }
  375.  
  376.         public void StartPendulumWithPotentialenergy(double m, double L, double angle) //Отклоняет маятник из положения равновесия на угол angle.
  377.         { //Вычитает затраченную энергию из потенциальной энергии гири, пересчитывает высоту подъёма гири.
  378.             double E = m * g * L * (1 - Math.Cos(MainWindow.GradToRadian(angle)));
  379.             double heightDecrease = E / (M * g);
  380.             PotentialEnergy -= E;
  381.             h -= heightDecrease;
  382.             if (h < 0.00001) //Если высота подъёма равна нулю или отрицательна
  383.             {
  384.                 h = 0;
  385.                 PotentialEnergy = 0;
  386.             }
  387.         }
  388.  
  389.         public void CompensateFrictionWork(double FrictionWork) //Компенсирует работу силы трения за счёт потенциальной энергии гири.
  390.         { //Вычитает затраченную энергию из потенциальной энергии гири, пересчитывает высоту подъёма гири.
  391.             double heightDecrease = FrictionWork / (M * g);
  392.             PotentialEnergy -= FrictionWork;
  393.             h -= heightDecrease;
  394.             if (h < 0.00001) //Если высота подъёма равна нулю или отрицательна
  395.             {
  396.                 h = 0;
  397.                 PotentialEnergy = 0;
  398.             }
  399.         }
  400.     }
  401. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement