Сплэшскрин в WPF


В .NET Framework 3.5 SP1 появилась возможность использовать в качестве сплэшскрина картинку из ресурсов. Для этого необходимо было поставить у картинки Build Action в SplashScreen, либо в коде использовать класс SplashScreen. Но данный сплэшскрин был очень ограничен в своих возможностях. На него даже невозможно было вывести текст. И если при старте программы была необходимость выводить сплэшскрины с разным текстом, то приходилось создавать множество вариантов картинок, со всеми возможными надписями. Как вариант можно было сделать отдельно фоновую картинку и отдельно картинки с надписями и показывать два сплэшскрина один поверх другого (такой метод мы использовали в ТЕХНОкоорд 5.0), но в данном случае был возможен вариант, когда пользователь ткнёт мышкой в фоновый сплэшскрин и он перекроет сплэшскрин с текстом. Также как вариант можно сначала показывать простой сплэшскрин из картинки, а потом когда загрузятся нужные сборки показывать WPF-ное окно, как сплэшскрин, так например поступает Expression Blend. Но такой вариант имеет смысл, если у вас ещё после инициализации WPF-а происходит загрузка большого количества данных, или использовать как сплэшскрин только WPF-ное окно - это вообще не вариант (мы пробовали это в 4-ом ТЕХНОкоорд-е), поскольку к тому времени, как такой сплэшскрин появится на экране, уже загрузится главное окно приложения.
Сплэшскрин в 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.splash, включает в себя анимированную картинку, текст, картинку и прогресс бар:
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;
}
Поскольку приложение пусто и запускается мгновенно, то в OnStartup я добавил несколько циклов, для замедления работы, плюс перед каждым циклом я меняю верхнюю надпись, в каждом цикле прогресс бар полностью проходит от начала до конца:
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();            
}
Данный пример хорошо показывает преимущества класса ExtendedSplashScreen-а над стандартным SplashScreen-ом. Теперь немного о недостатках. Во-первых, используется System.Drawing, который в нормальном WPF проекте как правило не нужен. Во-вторых ,время появления данного сплэшскрина немного больше, чем стандартного, впрочем экспериментальным путём я пришёл к выводу, что для конечного пользователя это почти незаметно. И в-третьих, за счёт того, что анимация затрачивает ресурсы процессора, время загрузки приложения возрастает, путём экспериментов я пришёл к тому, что для сплэшскрина приведённого в примере время загрузки возрастает примерно на 10%, но это число сильно зависит и от сложности сплэшскрина и от характеристик компьютера, поэтому точно оценить замедление невозможно.

Скачать исходники:


P.S. На данный момент у меня есть планы переработать данный класс для работы с Direct2D, поскольку Windows XP поддерживать уже не имеет смысла, а в теории Direct2D должен повысить эффективность работы сплэшскрина. Впрочем более перспективным мне кажется написать реализацию сплэшскрина на C++ и потом просто вызывать её из C#. Либо вообще написать загрузчик на C++, в котором показывать сплэшскрин, а уж потом загружать WPF приложение, это будет самым эффективным вариантом, но там есть возможность побочных эффектов, про которые я не знаю, так что этот вариант ещё под вопросом.

КОММЕНТАРИИ


Domingo S. Fugaban Jr.
Domingo S. Fugaban Jr. 07.02.2012 22:21:31 #1
Hi! I am from the Philippines, looks like the AdvancedSplashScreen.zip is missing the .csproj file. Please verify, thank you. I would also like to request for an updated zip file via email if you don't mind. I am looking forward to seeing this project in full source code. This looks very nice and clean. Keep up the good work. Many thanks!
Rikker Serg
Rikker Serg 07.02.2012 22:42:20 #2
Missing .csproj file was added to AdvancedSplashScreen.zip

НОВЫЙ КОММЕНТАРИЙ


*жирный*
_курсив_
+подчеркнутый+
! заголовок 1
!! заголовок 2
* список
** список 2
# нумерованый список
## нумерованый список 2
[url:http://www.example.com]
{"без форматирования"}
Полное описание синтаксиса