XAML без WPF - анимация - PullRequest
8 голосов
/ 05 марта 2010

Я пытаюсь использовать XAML полностью вне WPF, в частности, внутри приложения XNA. До сих пор мне удалось (довольно легко, я с удивлением признаю) загрузить некоторые данные внутри моего приложения XNA из файла XAML. Проблемы начались, когда я решил, что хочу анимировать одно из свойств моего класса ... Ничего не происходит: (

Вот основной класс, который я загружаю из файла XAML:

[ContentPropertyAttribute("Animation")]
public class Test : FrameworkContentElement
{
  public string Text { get; set; }
  public Vector2 Position { get; set; }
  public Color Color { get; set; }
  public Storyboard Animation { get; set; }

  public static DependencyProperty RotationProperty = 
    DependencyProperty.Register("Rotation", typeof(double), typeof(Test), new PropertyMetadata(0.0));
  public double Rotation { get { return (double)GetValue(RotationProperty); } set { SetValue(RotationProperty, value); } }
}

Вот файл XAML:

<l:Test xmlns:l="clr-namespace:XAMLAndXNA;assembly=XAMLAndXNA"
      xmlns:a1="clr-namespace:System.Windows.Media.Animation;assembly=PresentationFramework"
      xmlns:a2="clr-namespace:System.Windows.Media.Animation;assembly=PresentationCore"
  Text="Testo" Position="55,60" Color="0,255,255,255">
  <a1:Storyboard>
    <a2:DoubleAnimation a1:Storyboard.TargetProperty="Rotation"
                              From="0"
                              To="360"
                              Duration="00:00:10.0"/>
  </a1:Storyboard>
</l:Test>

А вот загрузка и запуск анимации (попытка):

Test test = XamlReader.Load(new XmlTextReader("SpriteBatchStuff.xaml")) as Test;
test.Animation.Begin(test);

Я умираю от любопытства :)

Ответы [ 4 ]

7 голосов
/ 06 марта 2010

Хотя XAML не зависит от WPF, визуальные элементы - нет. В частности, анимация и макет являются частью WPF и зависят от присутствия в нем WPF - через объект приложения, PresentationSource, такой как HwndSource, XBAP PresentationHost.exe и т. Д.

Таким образом, вы можете читать в своем XAML и получать граф объектов объекта Test с дочерним объектом Storyboard, но этот объект Test не подключается к механизмам анимации или компоновки, пока не будет помещен в контекст WPF. Все, что XAML получает от вас, - это тупой граф объектов в памяти: это WPF, а не XAML, который делает объекты «живыми».

Так что, как говорит Бен, вам, вероятно, в конечном итоге понадобится «толкать или подталкивать» анимацию самостоятельно. Я не знаю какой-либо документации о том, как это сделать, но из возни в Reflector похоже, что ключевым API является Storyboard.SeekAlignedToLastTick , о котором в документах говорится:

Значения немедленно обновляются до отражать изменения, вызванные SeekAlignedToLastTick, даже если экран не отражает эти изменения пока экран не обновится.

Обратите внимание на это второе предложение. Обычно WPF обрабатывает обновление экрана при изменении значений визуального объекта. Если вы не используете WPF, то вам нужно прочитать измененные значения и соответствующим образом перерисовать экран: у вас нет менеджера макета WPF, который бы его обрабатывал.

Наконец, обратите внимание, что я не проверял, будет ли SeekAlignedToLastTick работать в среде без загруженной сантехники WPF. Это звучит , как и должно быть, потому что это не заботится о том, управляет ли часами WPF или пользовательский код, но я не могу давать никаких обещаний ... хотя я признаю, что вы заинтересовали меня !


ОБНОВЛЕНИЕ: Я сделал это быстро, и, похоже, это работает. Вот демонстрация размещения анимации в Windows Forms (в данном случае с использованием обычного таймера Windows Forms, но в XNA я предполагаю, что фреймворк предоставит вам игровой таймер - не пробовал, потому что я не знаю XNA). Предположим, у вас есть ванильная форма Windows с таймером (timer1) и меткой (label1), и что проект ссылается на сборки WPF.

Во-первых, моя упрощенная версия вашего класса:

[ContentProperty("Animation")]
public class Fie : DependencyObject
{
  public double Test
  {
    get { return (double)GetValue(TestProperty); }
    set { SetValue(TestProperty, value); }
  }

  public static readonly DependencyProperty TestProperty =
    DependencyProperty.Register("Test", typeof(double), typeof(Fie),
    new FrameworkPropertyMetadata(0.0));

  public Storyboard Animation { get; set; }
}

Теперь код WPF для загрузки одного из этих детей из XAML и запуска анимации:

private Fie _f;
private DateTime _startTime;

public Form1()
{
  InitializeComponent();

  string xaml =
@"<local:Fie xmlns:local=""clr-namespace:AnimationsOutsideWpf;assembly=AnimationsOutsideWpf""
             xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
             xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
             >
  <Storyboard>
    <DoubleAnimation Storyboard.TargetProperty=""Test""
                     From=""0""
                     To=""360""
                     Duration=""00:00:10.0""/>
  </Storyboard>
</local:Fie>";

  _f = (Fie)XamlReader.Load(XmlReader.Create(new StringReader(xaml)));
  Storyboard.SetTarget(_f.Animation, _f);
  _f.Animation.Begin();

  _startTime = DateTime.Now;
  timer1.Enabled = true;
}

Обратите внимание, что я должен был установить цель раскадровки как объект XAML, который только что загрузил. Это не происходит автоматически. Я пытался сделать это с Storyboard.TargetName в XAML, но это не сработало - возможно, вам повезет больше.

Последние строки просто настроены для обратного вызова таймера:

private void timer1_Tick(object sender, EventArgs e)
{
  TimeSpan sinceStart = DateTime.Now - _startTime;
  _f.Animation.SeekAlignedToLastTick(sinceStart);

  label1.Text = _f.Test.ToString();
}

Я сохранил время начала анимации и использовал его, чтобы вычислить, насколько далеко мы дошли до анимации. Таймеры WinForms немного грубоваты, но этого достаточно для доказательства концепции; без сомнения, у XNA будет что-то лучше. Затем я вызываю Storyboard.SeekAlignedToLastTick, которая обновляет анимированные значения. Ничего не отображается автоматически, потому что мой объект XAML не подключен для отображения, но я могу проверить его свойство Test и убедиться, что он действительно анимируется. В действительности, я бы использовал это для обновления положения или ориентации любого визуального элемента XNA, представленного объектом XAML.

4 голосов
/ 06 марта 2010

Просто для справки, я сейчас документирую, как мне удалось заставить эту работу работать с XNA. Спасибо itowlson за предоставленную недостающую ссылку: в противном случае мне пришлось создать пустое приложение с невидимым окном ...

Мы определяем класс с его анимацией в XAML (обратите внимание на директивы xmlns):

<l:Test 
      xmlns:l="clr-namespace:XAMLAndXNA;assembly=XAMLAndXNA"
      xmlns:a1="clr-namespace:System.Windows.Media.Animation;assembly=PresentationFramework"
      xmlns:a2="clr-namespace:System.Windows.Media.Animation;assembly=PresentationCore"
  Text="Testo" Position="55,60" Color="0,255,255,255">
  <a1:Storyboard>
    <a2:DoubleAnimation a1:Storyboard.TargetProperty="Rotation"
                              From="0"
                              To="6.28"
                              Duration="00:00:2.0"
                              RepeatBehavior="Forever"/>
  </a1:Storyboard>
</l:Test>

Тест класса code-behind является следующим:

[ContentPropertyAttribute("Animation")]
public class Test : DependencyObject
{
  public string Text { get; set; }
  public Vector2 Position { get; set; }
  public Color Color { get; set; }
  public Storyboard Animation { get; set; }

  public static DependencyProperty RotationProperty =
    DependencyProperty.Register("Rotation", typeof(double), typeof(Test), new PropertyMetadata(0.0));
  public double Rotation { get { return (double)GetValue(RotationProperty); } set { SetValue(RotationProperty, value); } }
}

В функции Initialize класса XNA Game мы десериализовываем наш файл xaml и запускаем анимацию:

test = XamlReader.Load(new XmlTextReader("SpriteBatchStuff.xaml")) as Test;
Storyboard.SetTarget(test.Animation, test);
test.Animation.Begin();

Функция Update принимает в качестве входных данных GameTime, которая предлагает поле TotalGameTime, в котором хранится TimeSpan количества времени, прошедшего с момента запуска приложения: это именно то, что нужно пометить раскадровке:

protected override void Update(GameTime gameTime)
{
  // Allows the game to exit
  if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
    this.Exit();

  test.Animation.SeekAlignedToLastTick(gameTime.TotalGameTime);

  base.Update(gameTime);
}

В методе draw мы можем просто нарисовать некоторый текст, используя свойство Rotation, которое теперь будет правильно анимировано:

protected override void Draw(GameTime gameTime)
{
  GraphicsDevice.Clear(Color.CornflowerBlue);

  spriteBatch.Begin();
  spriteBatch.DrawString(Content.Load<SpriteFont>("font"), test.Text, test.Position, test.Color, (float)test.Rotation, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);
  spriteBatch.End();

  base.Draw(gameTime);
}
1 голос
/ 05 марта 2010

Ух ты, это круто! К сожалению, это, скорее всего, сводится к какому-либо вызову типа «обновление», который делается в каком-то внутреннем API. И если вы не вызовете его, анимация не будет анимирована ... так же, как если бы игра XNA не вызывала метод Update.

Мне бы очень хотелось больше информации о том, как вы это делаете и какой уровень успеха вы обнаруживаете. Вы должны написать сообщение в блоге / статью где-нибудь: -)

1 голос
/ 05 марта 2010

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

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

Эта статья MSDN может предоставить некоторую полезную информацию в этом начинании

Это интересный проект ... Мне было бы интересно услышать, если у вас получится!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...