Сплэшскрин в WPF
11 июля 2011 - 20:17
В .NET Framework 3.5 SP1 появилась возможность использовать в качестве сплэшскрина картинку из ресурсов. Для этого необходимо было поставить у картинки Build Action в SplashScreen, либо в коде использовать класс SplashScreen. Но данный сплэшскрин был очень ограничен в своих возможностях. На него даже невозможно было вывести текст. И если при старте программы была необходимость выводить сплэшскрины с разным текстом, то приходилось создавать множество вариантов картинок, со всеми возможными надписями. Как вариант можно было сделать отдельно фоновую картинку и отдельно картинки с надписями и показывать два сплэшскрина один поверх другого (такой метод мы использовали в ТЕХНОкоорд 5.0), но в данном случае был возможен вариант, когда пользователь ткнёт мышкой в фоновый сплэшскрин и он перекроет сплэшскрин с текстом. Также как вариант можно сначала показывать простой сплэшскрин из картинки, а потом когда загрузятся нужные сборки показывать WPF-ное окно, как сплэшскрин, так например поступает Expression Blend. Но такой вариант имеет смысл, если у вас ещё после инициализации WPF-а происходит загрузка большого количества данных, или использовать как сплэшскрин только WPF-ное окно - это вообще не вариант (мы пробовали это в 4-ом ТЕХНОкоорд-е), поскольку к тому времени, как такой сплэшскрин появится на экране, уже загрузится главное окно приложения.
А потом появилась бета-версия Microsoft Office 2010, с очень красивым анимированным сплэшскрином, и мне захотелось использовать похожие сплэшскрины в моих приложениях на WPF-е. Поэтому был создан класс ExtendedSplashScreen, который позволяет создавать гораздо более богатые сплэшскрины, чем стандартный WPF-ный SplashScreen. Модифицированная версия этого класса применяется в тестовых сборках ТЕХНОкоорд-а 6.0.
Функционально данный сплэшскрин работает почти также, как стандартный - создаёт окно используя WinApi и рисует на нём. Правда в отличии от майкрософтского варианта я использую System.Drawing а не GDI (на самом деле это не очень хорошо, поскольку перед появлением сплэшскрина приложению приходиться загружать на одну сборку больше, но у меня так и не дошли руки переписать это на GDI). Зато в отличии от стандартного сплэшскрина он отображает не одну единственную картинку, а поддерживает отображение таких компонент, как текст, картинка, анимированная картинка (набор картинок, которые последовательно меняются, с частотой 20 кадров в секунду), произвольная картинка (произвольным образом отображает одну картинку из набора) и прогресс бар (тоже набор картинок, отображением которых можно управлять). Задаётся данный набор компонент в текстовом файле используя следующий формат:
Здесь:
SPLASH - обязательная начальная строка, после неё через разделитель "|" задаются ширина и высота сплэшскрина (сплэшскрин всегда отображается по центру экрана;
TEXT - выводит текст, через разделитель задаются следующие параметры: идентификатор (используя данный идентификатор возможно функцией SetText менять надпись), положение текста (левая верхняя координата) относительно левой верхней точки сплэшскрина, название шрифта, размер шрифта, возможность вывода шрифта полужирным/курсивом/подчёркнутым и надпись;
IMAGE - выводит изображение из ресурсов, через разделитель задаются следующие параметры: идентификатор (на данный момент не используется, введён для единообразия), положение изображения (левая верхняя координата) относительно левой верхней точки сплэшскрина и путь к ресурсу с изображением;
RANDOM - выводит произвольное изображение из набора изображений лежащих в ресурсах, через разделитель задаются следующие параметры: идентификатор (на данный момент не используется, введён для единообразия), положение изображения (левая верхняя координата) относительно левой верхней точки сплэшскрина, количество изображений и путь к ресурсу с изображением, в формате использующемся в String.Format;
RANDOM - выводит изображение из набора изображений лежащих в ресурсах, через разделитель задаются следующие параметры: идентификатор (используя данный идентификатор возможно функцией SetProgress менять екущую картинку), положение изображения (левая верхняя координата) относительно левой верхней точки сплэшскрина, количество изображений и путь к ресурсу с изображением, в формате использующемся в String.Format;
ANIMATION -последовательно выводит изображения из набора изображений лежащих в ресурсах, через разделитель задаются следующие параметры: идентификатор (на данный момент не используется, введён для единообразия), положение изображения (левая верхняя координата) относительно левой верхней точки сплэшскрина, количество изображений, путь к ресурсу с изображением, в формате использующемся в String.Format и опционально номер кадра, с которого нужно начинать повтор анимации, когда она дойдёт до конца.
Первой строкой обязательно должен идти SPLASH, и больше он не должен повторяться, далее могут следовать TEXT, IMAGE, RANDOM, PROGRESS и ANIMATION в произвольной последовательности и в любом количестве. Пути к набору изображений должны задаваться в виде: Images/Image-{0:D2}.png , что будет представлять собой следующий набор изображений: Images/Image-00.png, Images/Image-01.png , Images/Image-02.png и т.д. Изначально ещё планировалось ввести в данный сплэшскрин возможность использования Aero и поддержку кнопок (как в Office 2010), но из-за нехватки времени это так и не было сделано.
В качестве примера использования я создал пустое WPF приложение. Затем в конструктор класса App я добавил создание и отображением сплэшскрина из ресурса Splash.splash:
Файл Splash.splash, включает в себя анимированную картинку, текст, картинку и прогресс бар:
В OnStartup я добавил создание главного окна приложения и повесил обработчки на OnLoaded окна в котором буду скрывать сплэшскрин:
Поскольку приложение пусто и запускается мгновенно, то в OnStartup я добавил несколько циклов, для замедления работы, плюс перед каждым циклом я меняю верхнюю надпись, в каждом цикле прогресс бар полностью проходит от начала до конца:
Данный пример хорошо показывает преимущества класса ExtendedSplashScreen-а над стандартным SplashScreen-ом. Теперь немного о недостатках. Во-первых, используется System.Drawing, который в нормальном WPF проекте как правило не нужен. Во-вторых ,время появления данного сплэшскрина немного больше, чем стандартного, впрочем экспериментальным путём я пришёл к выводу, что для конечного пользователя это почти незаметно. И в-третьих, за счёт того, что анимация затрачивает ресурсы процессора, время загрузки приложения возрастает, путём экспериментов я пришёл к тому, что для сплэшскрина приведённого в примере время загрузки возрастает примерно на 10%, но это число сильно зависит и от сложности сплэшскрина и от характеристик компьютера, поэтому точно оценить замедление невозможно.
Скачать исходники:
P.S. На данный момент у меня есть планы переработать данный класс для работы с Direct2D, поскольку Windows XP поддерживать уже не имеет смысла, а в теории Direct2D должен повысить эффективность работы сплэшскрина. Впрочем более перспективным мне кажется написать реализацию сплэшскрина на C++ и потом просто вызывать её из C#. Либо вообще написать загрузчик на C++, в котором показывать сплэшскрин, а уж потом загружать WPF приложение, это будет самым эффективным вариантом, но там есть возможность побочных эффектов, про которые я не знаю, так что этот вариант ещё под вопросом.
А потом появилась бета-версия Microsoft Office 2010, с очень красивым анимированным сплэшскрином, и мне захотелось использовать похожие сплэшскрины в моих приложениях на WPF-е. Поэтому был создан класс ExtendedSplashScreen, который позволяет создавать гораздо более богатые сплэшскрины, чем стандартный WPF-ный SplashScreen. Модифицированная версия этого класса применяется в тестовых сборках ТЕХНОкоорд-а 6.0.
Функционально данный сплэшскрин работает почти также, как стандартный - создаёт окно используя WinApi и рисует на нём. Правда в отличии от майкрософтского варианта я использую System.Drawing а не GDI (на самом деле это не очень хорошо, поскольку перед появлением сплэшскрина приложению приходиться загружать на одну сборку больше, но у меня так и не дошли руки переписать это на GDI). Зато в отличии от стандартного сплэшскрина он отображает не одну единственную картинку, а поддерживает отображение таких компонент, как текст, картинка, анимированная картинка (набор картинок, которые последовательно меняются, с частотой 20 кадров в секунду), произвольная картинка (произвольным образом отображает одну картинку из набора) и прогресс бар (тоже набор картинок, отображением которых можно управлять). Задаётся данный набор компонент в текстовом файле используя следующий формат:
SPLASH|{width}|{height} TEXT|{id}|{left}|{top}|{font}|{font size}|[BOLD]|[ITALIC]|[UNDERLINE]|[{initial text}] IMAGE|{id}|{left}|{top}|{image path} RANDOM|{id}|{left}|{top}|{images count}|{image path} PROGRESS|{id}|{left}|{top}|{images count}|{image path} ANIMATION|{id}|{left}|{top}|{frames count}|{image path}|[{repeat frame number}]
Здесь:
SPLASH - обязательная начальная строка, после неё через разделитель "|" задаются ширина и высота сплэшскрина (сплэшскрин всегда отображается по центру экрана;
TEXT - выводит текст, через разделитель задаются следующие параметры: идентификатор (используя данный идентификатор возможно функцией SetText менять надпись), положение текста (левая верхняя координата) относительно левой верхней точки сплэшскрина, название шрифта, размер шрифта, возможность вывода шрифта полужирным/курсивом/подчёркнутым и надпись;
IMAGE - выводит изображение из ресурсов, через разделитель задаются следующие параметры: идентификатор (на данный момент не используется, введён для единообразия), положение изображения (левая верхняя координата) относительно левой верхней точки сплэшскрина и путь к ресурсу с изображением;
RANDOM - выводит произвольное изображение из набора изображений лежащих в ресурсах, через разделитель задаются следующие параметры: идентификатор (на данный момент не используется, введён для единообразия), положение изображения (левая верхняя координата) относительно левой верхней точки сплэшскрина, количество изображений и путь к ресурсу с изображением, в формате использующемся в String.Format;
RANDOM - выводит изображение из набора изображений лежащих в ресурсах, через разделитель задаются следующие параметры: идентификатор (используя данный идентификатор возможно функцией SetProgress менять екущую картинку), положение изображения (левая верхняя координата) относительно левой верхней точки сплэшскрина, количество изображений и путь к ресурсу с изображением, в формате использующемся в String.Format;
ANIMATION -последовательно выводит изображения из набора изображений лежащих в ресурсах, через разделитель задаются следующие параметры: идентификатор (на данный момент не используется, введён для единообразия), положение изображения (левая верхняя координата) относительно левой верхней точки сплэшскрина, количество изображений, путь к ресурсу с изображением, в формате использующемся в String.Format и опционально номер кадра, с которого нужно начинать повтор анимации, когда она дойдёт до конца.
Первой строкой обязательно должен идти SPLASH, и больше он не должен повторяться, далее могут следовать TEXT, IMAGE, RANDOM, PROGRESS и ANIMATION в произвольной последовательности и в любом количестве. Пути к набору изображений должны задаваться в виде: Images/Image-{0:D2}.png , что будет представлять собой следующий набор изображений: Images/Image-00.png, Images/Image-01.png , Images/Image-02.png и т.д. Изначально ещё планировалось ввести в данный сплэшскрин возможность использования Aero и поддержку кнопок (как в Office 2010), но из-за нехватки времени это так и не было сделано.
В качестве примера использования я создал пустое WPF приложение. Затем в конструктор класса App я добавил создание и отображением сплэшскрина из ресурса Splash.splash:
public App() { nativeSplash = new ExtendedSplashScreen("Splash.splash"); nativeSplash.Open(); }
SPLASH|520|360 ANIMATION|SPLASH_ANIMATION|0|0|17|Images/Frame-{0:D2}.png|0 TEXT|SPLASH_CAPTION|25|18|Segoe UI|10|#FF000000|Инициализация IMAGE|SPLASH_ICON|24|323|Images/Claw.png TEXT|SPLASH_AUTHOR|41|321|Segoe UI|10|#FF000000|(C) Rikker Serg. 2011 PROGRESS|SPLASH_PROGRESS|340|323|16|Images/ProgressBar{0:D2}.png|0
В OnStartup я добавил создание главного окна приложения и повесил обработчки на OnLoaded окна в котором буду скрывать сплэшскрин:
private void OnStartup(object sender, StartupEventArgs e) { MainWindow wnd = new MainWindow(); wnd.Loaded += OnLoaded; wnd.Show(); } private void OnLoaded(object sender, EventArgs e) { if (nativeSplash != null) { nativeSplash.Close(100); nativeSplash = null; } (sender as Window).Loaded -= OnLoaded; }
private void OnStartup(object sender, StartupEventArgs e) { MainWindow wnd = new MainWindow(); wnd.Loaded += OnLoaded; int d = 0; nativeSplash.SetText("SPLASH_CAPTION","Этап 1"); for (int i = 0; i < 10000000; i++) { d += (int)Math.Sqrt(i); if (i % 625000 == 0) nativeSplash.SetProgress("SPLASH_PROGRESS", i / 625000); } nativeSplash.SetText("SPLASH_CAPTION", "Этап 2"); for (int i = 0; i < 10000000; i++) { d += (int)Math.Sqrt(i); if (i % 625000 == 0) nativeSplash.SetProgress("SPLASH_PROGRESS", i / 625000); } nativeSplash.SetText("SPLASH_CAPTION", "Этап 3"); for (int i = 0; i < 10000000; i++) { d += (int)Math.Sqrt(i); if (i % 625000 == 0) nativeSplash.SetProgress("SPLASH_PROGRESS", i / 625000); } nativeSplash.SetText("SPLASH_CAPTION", "Завершение"); wnd.Show(); }
Скачать исходники:
P.S. На данный момент у меня есть планы переработать данный класс для работы с Direct2D, поскольку Windows XP поддерживать уже не имеет смысла, а в теории Direct2D должен повысить эффективность работы сплэшскрина. Впрочем более перспективным мне кажется написать реализацию сплэшскрина на C++ и потом просто вызывать её из C#. Либо вообще написать загрузчик на C++, в котором показывать сплэшскрин, а уж потом загружать WPF приложение, это будет самым эффективным вариантом, но там есть возможность побочных эффектов, про которые я не знаю, так что этот вариант ещё под вопросом.