Классически, на форумах MSDN, в одном из топиков, задали фопрос, как в WPF приложении заставить gif-изображение показывать не только первый кадр, а всю анимацию. Ответ под катом.
Итак, создаем пустой WPF проект, кидаем в него картинку (у меня это Source.gif), говорим, что ее надо копировать в папку с приложение, и что делать с ней ничего не надо:
Делаем разметку формы:
public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri(@"/source.gif", UriKind.RelativeOrAbsolute);
bi.EndInit();
imBackGround.Source = bi;
}
}
Запускаем. Картинка есть, но как и ожидалось, без анимации. Начинаем лечение.
Сделаем загрузку нашего изображения через Bitmap. Для этого, нам придеться подключить библиотеку System.Drawing.
if (_bitmap == null)
{
_bitmap = new Bitmap("source.gif");
}
IntPtr handle = IntPtr.Zero;
handle = _bitmap.GetHbitmap();
return Imaging.CreateBitmapSourceFromHBitmap(
handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
Зачем такие сложности? Я, если честно, не знаю, но как сделать анимацию gif-ок без Bitmap, я не нашел.
Правим загрузку изображения и включаем на нем анимацию:
BitmapSource _source;
_source = GetSource();
imBackGround.Source = _source;
ImageAnimator.Animate(_bitmap, OnFrameChanged);
}
Метод ImageAnimator.Animate переключает в _bitmap текущее изображение на следующий кадр. По окончании переключения, вызывается метод передаваемый в Animate вторым параметром. Именно в нем и будет изменяться кадр в нашем Image-е. Делаем это так:
ImageAnimator.UpdateFrames();
if (_source != null)
_source.Freeze();
_source = GetSource();
imBackGround.Source = _source;
InvalidateVisual();
}
Если внимательно читали или пытались повторить, то увидели, что имя этого метода не совпадает с именем метода передаваемого в ImageAnimator.Animate. Действительно, ведь OnFrameChanged будет вызываться в потоке отличном от потока создавшего визуальные элементы. Вот нам в нем и придеться сделать перевызов метода FrameUpdatedCallback в правильном потоке:
Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(FrameUpdatedCallback));
}
Все. Запускаем и смотрим анимацию.
Итак, создаем пустой WPF проект, кидаем в него картинку (у меня это Source.gif), говорим, что ее надо копировать в папку с приложение, и что делать с ней ничего не надо:
Делаем разметку формы:
<Grid>
<Image x:Name="imBackGround"
/>
</Grid>
Код:
public
partial class MainWindow : Window
{public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void
MainWindow_Loaded(object sender, RoutedEventArgs e)
{BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri(@"/source.gif", UriKind.RelativeOrAbsolute);
bi.EndInit();
imBackGround.Source = bi;
}
}
Запускаем. Картинка есть, но как и ожидалось, без анимации. Начинаем лечение.
Сделаем загрузку нашего изображения через Bitmap. Для этого, нам придеться подключить библиотеку System.Drawing.
Bitmap _bitmap;
private
BitmapSource GetSource()
{if (_bitmap == null)
{
_bitmap = new Bitmap("source.gif");
}
IntPtr handle = IntPtr.Zero;
handle = _bitmap.GetHbitmap();
return Imaging.CreateBitmapSourceFromHBitmap(
handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
Зачем такие сложности? Я, если честно, не знаю, но как сделать анимацию gif-ок без Bitmap, я не нашел.
Правим загрузку изображения и включаем на нем анимацию:
BitmapSource _source;
void
MainWindow_Loaded(object sender, RoutedEventArgs e)
{_source = GetSource();
imBackGround.Source = _source;
ImageAnimator.Animate(_bitmap, OnFrameChanged);
}
Метод ImageAnimator.Animate переключает в _bitmap текущее изображение на следующий кадр. По окончании переключения, вызывается метод передаваемый в Animate вторым параметром. Именно в нем и будет изменяться кадр в нашем Image-е. Делаем это так:
private
void FrameUpdatedCallback()
{ImageAnimator.UpdateFrames();
if (_source != null)
_source.Freeze();
_source = GetSource();
imBackGround.Source = _source;
InvalidateVisual();
}
Если внимательно читали или пытались повторить, то увидели, что имя этого метода не совпадает с именем метода передаваемого в ImageAnimator.Animate. Действительно, ведь OnFrameChanged будет вызываться в потоке отличном от потока создавшего визуальные элементы. Вот нам в нем и придеться сделать перевызов метода FrameUpdatedCallback в правильном потоке:
private
void OnFrameChanged(object
sender, EventArgs e)
{Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(FrameUpdatedCallback));
}
Все. Запускаем и смотрим анимацию.
я попробовал сделать как вы писали но к сожалению , не работает.
ОтветитьУдалитьвот код:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Interop;
using System.Windows.Threading;
namespace WpfApplication1
{
///
/// Interaction logic for MainWindow.xaml
///
public partial class MainWindow : Window
{
Bitmap _bitmap;
BitmapSource _source;
public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
private BitmapSource GetSource()
{
if (_bitmap == null)
{
_bitmap = new Bitmap("earth.gif");
}
IntPtr handle = IntPtr.Zero;
handle = _bitmap.GetHbitmap();
return Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_source = GetSource();
imBackground.Source = _source;
ImageAnimator.Animate(_bitmap, OnFrameChanged);
}
private void FrameUpdatedCallback()
{
ImageAnimator.UpdateFrames();
if (_source == null)
{
_source.Freeze();
_source = GetSource();
imBackground.Source = _source;
InvalidateVisual();
}
}
private void OnFrameChanged(object sender, EventArgs e)
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(FrameUpdatedCallback));
}
}
}
Что конкретно не работает? Не компилируется, возникает ошибка во время выполнения, не анимируется изображение?
УдалитьВсе компилируется, Появляется картинка ,
ОтветитьУдалитьно без анимации (без движения кадров).
Вы скобки неправильно расставили. Вот так должно быть:
Удалитьprivate void FrameUpdatedCallback()
{
ImageAnimator.UpdateFrames();
if (_source == null)
{
_source.Freeze();
}
_source = GetSource();
imBackground.Source = _source;
InvalidateVisual();
}
Большое Спасибо, теперь все работает.
ОтветитьУдалитьА можно (и если да то как) , так же использовать,
картинку gif для бэкграунда окна (this.Background) ?
Да.
УдалитьНужно заменить
imBackGround.Source = _source;
на
this.Background = new ImageBrush(_source);
Я делаю игру на WPF, получилось установить картинку в canvas,
ОтветитьУдалитьпри загрузки окна появляется картинка и все работает (подгружается правда, но это не страшно)
спустя определенное время (по замыслу игры) на canvas появляется textblock с кнопкой,
при нажатии на которую весь content игры обновляется (+ canvas.Children.Clear()) , картинка же пропадает,
не подскажите в чем проблема?
Этот комментарий был удален автором.
ОтветитьУдалитьстолкнулся с еще одной проблемой,
ОтветитьУдалитьесли долго воспроизводить gif файл (по вашей схеме)
и при этом есть еще потоки(музыка, таймеры...)
то на каком то этапе программа вылетает с
эксепшином GDI+ (out of memory).
не подскажите что это может быть?
Здравствуйте!
ОтветитьУдалитьВсе работает, спасибо. Только не понятно, а как теперь остановить анимацию и сменить на другую не gif картинку?
Добрый день.
УдалитьКроме метода Animate, есть еще метод StopAnimate. Он останавливает анимацию. Для замены картинки присвойте ее в imBackGround.Source
А как воспроизвести эту gif анимацию в SplashScreen ?
ОтветитьУдалитьДобрый день, у меня почему-то нет анимации, gif отображается просто как картинка и происходит утечка памяти, и исключение в этой строке ImageAnimator.UpdateFrames();
ОтветитьУдалитьДобрый день.
УдалитьКакое исключение?
Так, анимацию запустил, но происходит утечка памяти через десяток секунд и exeption OutOfMemory http://s019.radikal.ru/i635/1701/94/95fd4718cc70.png
УдалитьВсе работает, но сильно пожирает память.
ОтветитьУдалитьпри проигрывании гифки оперативка сжирается в геометрической прогрессии, в следствии чего - вылетает исключение
ОтветитьУдалитьДобрый день, уважаемый автор!
ОтветитьУдалитьХочу ответить, что частое использование метода CreateBitmapSourceFromHBitmap расточительно по отношению к памяти. Я запустил вашим способом анимацию, она проработала минут 15, потом вышло исключение, что памяти не хватает.
Используйте метод CreateBitmapSourceFromHBitmap только в редких местах. Не используйте данный метод для обновления каждого кадра. В WPF только автоматический сборщик мусора очищает память, выделенную этим методом. Вызывать вручную сборщик мусора - это косяк. Для постоянных изменений рекомендуется использовать WinForms-компоненты - там работа с памятью более адекватная. Для gif-анимаций такой способ, я считаю, не рекомендуется к использованию. Если ваша программа вылетает только из-за одной анимации - это серьезный косяк, согласитесь?
Добрый день, соглашусь. Не смотрел на производительность этого решения :( Стояла задача, реализовать. Не подскажите на какой версии Framework-а проверяли?
УдалитьЭтот комментарий был удален автором.
ОтветитьУдалить