создание расширенного метронома в WPF (проблемы с созданной кодом анимацией и событием завершения)

Добрый день,

последние несколько недель я работал над проектом по созданию усовершенствованного метронома. метроном состоит из следующих вещей

  1. качающийся рычаг
  2. световая вспышка
  3. набор динамически создаваемых пользовательских элементов управления, представляющих доли (4 из них, которые либо включены, либо акцентированы, либо выключены ).
  4. пользовательский элемент управления, который отображает числовой ЖК-дисплей и вычисляет количество миллисекунд между ударами для выбранного BPM (60000/BPM=миллисекунды)

пользователь выбирает BPM и нажимает кнопку «Старт», и происходит следующее

  1. рука качается между двумя углами со скоростью n миллисекунд за разворот
  2. индикатор мигает в конце каждого взмаха руки
  3. индикаторы создаются и мигают последовательно (по одному в конце каждой развертки ).

теперь проблема Анимация руки и световой вспышки создаются в коде и добавляются в раскадровку с повтором навсегда и автореверсом.

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

Итак, что я сделал после долгих возни, так это создал таймер, который работает в том же темпе, что и раскадровка.

проблема в том, что через 30 секунд таймер и раскадровка рассинхронизировались, и поэтому индикаторы и взмах руки не синхронны (не очень хорошо для метронома!! ).

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

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

мой вопрос, когда я пытаюсь привязаться к завершенному событию анимации, она никогда не срабатывает. У меня сложилось впечатление, что завершены даже пожары независимо от автореверса (, то есть между каждой итерацией ). это не так?

кто-нибудь может придумать другой (более хитрый )способ синхронизировать эти две вещи.

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

если есть какие-либо предложения, я не ценен, я просто хочу закончить это!!

конечная достопримечательность, количество ударов в минуту можно регулировать во время работы метронома, это достигается путем расчета миллисекундной продолжительности «на лету» (нажатием кнопки мыши )и масштабированием раскадровки по разнице между текущей скоростью и новой скоростью. очевидно, что таймер, запускающий индикаторы, должен быть изменен одновременно (с использованием интервала ).

код ниже взят из моего проекта до сих пор (не XAML, а только C#)

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Threading;    

namespace MetronomeLibrary
{    
    public partial class MetronomeLarge
    {
        private bool Running;

        //Speed and time signature
        private int _bpm = 60;
        private int _beats = 4;
        private int _beatUnit = 4;
        private int _currentBeat = 1;
        private readonly int _baseSpeed = 60000 / 60;
        private readonly DispatcherTimer BeatTimer = new DispatcherTimer();

        private Storyboard storyboard = new Storyboard();

        public MetronomeLarge()
        {
            InitializeComponent();

            NumericDisplay.Value = BPM;

            BeatTimer.Tick += new EventHandler(TimerTick);

            SetUpAnimation();    
            SetUpIndicators(); 
        }

        public int Beats
        {
            get
            {
                return _beats;
            }
            set
            {
                _beats = value;
                SetUpIndicators();
            }
        }

        public int BPM
        {
            get
            {
                return _bpm;
            }
            set
            {
                _bpm = value;
                //Scale the story board here
                SetSpeedRatio();
            }
        }

        public int BeatUnit
        {
            get
            {
                return _beatUnit;
            }
            set
            {
                _beatUnit = value;
            }
        }

        private void SetSpeedRatio()
        {
            //divide the new speed (bpm by the old speed to get the new ratio)
            float newMilliseconds = (60000 / BPM);
            float newRatio = _baseSpeed / newMilliseconds;
            storyboard.SetSpeedRatio(newRatio);

            //Set the beat timer according to the beattype (standard is quarter beats for one sweep of the metronome
            BeatTimer.Interval = TimeSpan.FromMilliseconds(newMilliseconds);
        }

        private void TimerTick(object sender, EventArgs e)
        {
            MetronomeBeat(_currentBeat);

            _currentBeat++;

            if (_currentBeat > Beats)
            {
                _currentBeat = 1;
            }
        }

        private void MetronomeBeat(int Beat)
        {
                //turnoff all indicators
                TurnOffAllIndicators();

                //Find a control by name
                MetronomeLargeIndicator theIndicator = (MetronomeLargeIndicator)gridContainer.Children[Beat-1];

                //illuminate the control
                theIndicator.TurnOn();
                theIndicator.PlaySound();    
        }

        private void TurnOffAllIndicators()
        {

            for (int i = 0; i <= gridContainer.Children.Count-1; i++)
            {
                MetronomeLargeIndicator theIndicator = (MetronomeLargeIndicator)gridContainer.Children[i];
                theIndicator.TurnOff();
            }
        }

        private void SetUpIndicators()
        {
            gridContainer.Children.Clear();
            gridContainer.ColumnDefinitions.Clear();

            for (int i = 1; i <= _beats; i++)
            {
                MetronomeLargeIndicator theNewIndicator = new MetronomeLargeIndicator();

                ColumnDefinition newCol = new ColumnDefinition() { Width = GridLength.Auto };
                gridContainer.ColumnDefinitions.Add(newCol);
                gridContainer.Children.Add(theNewIndicator);
                theNewIndicator.Name = "Indicator" + i.ToString();
                Grid.SetColumn(theNewIndicator, i - 1);
            }
        }   

        private void DisplayOverlay_MouseDown(object sender, MouseButtonEventArgs e)
        {
            ToggleAnimation();
        }

        private void ToggleAnimation()
        {
            if (Running)
            {
                //stop the animation
                ((Storyboard)Resources["Storyboard"]).Stop() ;
                BeatTimer.Stop();
            }
            else
            {
                //start the animation
                BeatTimer.Start();
                ((Storyboard)Resources["Storyboard"]).Begin();
                SetSpeedRatio();                 
            }

            Running = !Running;
        }


        private void ButtonIncrement_Click(object sender, RoutedEventArgs e)
        {
            NumericDisplay.Value++;
            BPM = NumericDisplay.Value;
        }

        private void ButtonDecrement_Click(object sender, RoutedEventArgs e)
        {
            NumericDisplay.Value--;
            BPM = NumericDisplay.Value;
        }

        private void ButtonIncrement_MouseEnter(object sender, MouseEventArgs e)
        {
            ImageBrush theBrush = new ImageBrush() 
            { 
                ImageSource = new BitmapImage(new 
                    Uri(@"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-increment-button-over.png")) 
            };
            ButtonIncrement.Background = theBrush;
        }

        private void ButtonIncrement_MouseLeave(object sender, MouseEventArgs e)
        {
            ImageBrush theBrush = new ImageBrush() 
            { 
                ImageSource = new BitmapImage(new 
                    Uri(@"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-increment-button.png")) 
            };
            ButtonIncrement.Background = theBrush;
        }

        private void ButtonDecrement_MouseEnter(object sender, MouseEventArgs e)
        {
            ImageBrush theBrush = new ImageBrush() 
            { 
                ImageSource = new BitmapImage(new 
                    Uri(@"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-decrement-button-over.png")) 
            };
            ButtonDecrement.Background = theBrush;
        }

        private void ButtonDecrement_MouseLeave(object sender, MouseEventArgs e)
        {
            ImageBrush theBrush = new ImageBrush() 
            { 
                ImageSource = new BitmapImage(new 
                    Uri(@"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-decrement-button.png")) 
            };
            ButtonDecrement.Background = theBrush;
        }

        private void SweepComplete(object sender, EventArgs e)
        {
            BeatTimer.Stop();
            BeatTimer.Start();
        }

        private void SetUpAnimation()
        {
            NameScope.SetNameScope(this, new NameScope());
            RegisterName(Arm.Name, Arm);

            DoubleAnimation animationRotation = new DoubleAnimation()
            {
                From = -17,
                To = 17,
                Duration = new Duration(TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds)),
                RepeatBehavior = RepeatBehavior.Forever,
                AccelerationRatio = 0.3,
                DecelerationRatio = 0.3,
                AutoReverse = true,                 
            };

            Timeline.SetDesiredFrameRate(animationRotation, 90);

            MetronomeFlash.Opacity = 0;

            DoubleAnimation opacityAnimation = new DoubleAnimation()
            {
                From = 1.0,
                To = 0.0,
                AccelerationRatio = 1,
                BeginTime = TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds - 0.5),
                Duration = new Duration(TimeSpan.FromMilliseconds(100)),
            };

            Timeline.SetDesiredFrameRate(opacityAnimation, 10);

            storyboard.Duration = new Duration(TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds * 2));
            storyboard.RepeatBehavior = RepeatBehavior.Forever;
            Storyboard.SetTarget(animationRotation, Arm);
            Storyboard.SetTargetProperty(animationRotation, new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
            Storyboard.SetTarget(opacityAnimation, MetronomeFlash);
            Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("Opacity"));    
            storyboard.Children.Add(animationRotation);
            storyboard.Children.Add(opacityAnimation);

            Resources.Add("Storyboard", storyboard);    
        }
    }
}
7
задан Lain 6 March 2014 в 06:50
поделиться