Как сделать новогоднюю открытку на Silverlight?

Открытки, как известно, бывают самые-самые разные, поэтому сразу от слов к делу. Открытка будет вот такая:

image

Падающий снег, мигающая звездочка, встроенные DeepZoom, несколько ссылок и открывающееся окошко с благодарностями.

Исходники – в самом конце (их можно свободно использовать на свое усмотрение, а также на свой страх и риск :)).

Поехали!

0. Создаем новый проект в Visual Studio 2008

Мой проект будет называться “New Year Card”. На втором окошке оставляем опцию “по умолчанию” – создавать отдельный web-проект.

image image

 

Visual Studio сразу откроет страницу Page.xaml, в ней мы сделаем несколько предварительных правок – изменим размер и цвет фона:

 <UserControl x:Class="New_Year_Card.Page"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
     Width="800" Height="600">
     <Grid x:Name="LayoutRoot" Background="Black">
  
     </Grid>
 </UserControl>

1. Добавляем снег

В принципе, понятно, что написать снегопад самому – дело недолгое, я буду базироваться на готовом решении, предложенном Mike Snow – с минимальными правками (исходную версию снегопада можно найти в его блоге – пост Making it Snow in Silverlight).

Для справки: можно также попробовать прикрутить и реализацию, предложенную в блоге Expression Blend and Design.

1.1. Делаем снежинку

Добавляем в проект новый Silverlight User Control – SnowFlakeControl. Внешний вид снежинки не сильно критичен – она все равно будет маленькой. Главное, чтобы было что-то похожее… Это может быть хоть обычный кружок, хоть картинка снежинки с прозрачными пикселями.

В моем случае это будет 6 треугольников:

image

Проще всего их нарисовать, используя Blend. В XAML коде это выглядит следующим образом:

 <UserControl x:Class="New_Year_Card.SnowFlakeControl"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" >
     <Canvas Height="Auto" Width="Auto">
         <Path x:Name="Flake" Height="20" Width="20" Fill="#FFFFFFFF" Stretch="Fill" Stroke="{x:Null}" StrokeThickness="0" Data="M2.2893419,6.5464864 L4.4832935,5.3514934 L4.839685,9.0923414 z M1.573923,3.8447626 L4.0540414,5.4034495 L0.34655577,7 z M4.6263771,5.0397563 L8.3465557,7 L5.0079346,7.4297428 z M3.9586527,2.1302068 L4.0063472,5.0397563 L0,2.5660043 z M7.9554572,1.7229731 L6.7726345,5.9230118 L4.5786829,4.7280192 z M4.2215557,0 L6.2479935,2.9095504 L4.2925153,4.6760626 z" RenderTransformOrigin="0,0">
             <Path.RenderTransform>
                 <ScaleTransform x:Name="SnowScale" ScaleX="0.25" ScaleY="0.25"/>
             </Path.RenderTransform>
         </Path>
     </Canvas>
 </UserControl>

 

 

Обратите внимание на прописанный в коде RenderTransform. Значения  ScaleX и ScaleY можно установить и в “1.0” – тогда нужно будет уменьшить рамер самой снежинки.

Что важно, ScaleTransform именован (SnowScale) – так к нему проще обращаться из кода.

Теперь давайте сделаем так, чтобы снежинка падала, двигалась вправо-влево и можно было отслеживать, когда она вышла за пределы окна. Также в движение и параметры снежинки можно привнести разнообразные случайные характеристики – размер, прозрачность, скорость движения, смена направления движения – и еще добавить учет ветра.

    1: namespace New_Year_Card
    2: {
    3:     /// <summary>
    4:     /// Контрол "Снежинка"
    5:     /// </summary>
    6:     public partial class SnowFlakeControl : UserControl
    7:     {
    8:         private double _posLeft = 0.0;
    9:         private double _posTop = 0.0;
   10:  
   11:         // Пределы перемещения
   12:         private double _floorValue = 600;
   13:         private double _horizontalRange = 800;
   14:  
   15:         // Генератор случайных чисел
   16:         private Random _rand = new Random(DateTime.Now.Millisecond);
   17:  
   18:         // Признак завершенности
   19:         private bool _completed = false;
   20:  
   21:         // Горизонтальное и вертикальное приращения движения (фактически, это скорость)
   22:         private double _horzInc = 0.0;
   23:         private double _vertInc = 1.0;
   24:  
   25:         /// <summary>
   26:         /// Признак, что снежинка "свое отработала"
   27:         /// </summary>
   28:         public bool Completed
   29:         {
   30:             get { return _completed; }
   31:         }
   32:  
   33:         /// <summary>
   34:         /// Контрол для отображения снежинки
   35:         /// </summary>
   36:         /// <param name="left">Сдвиг слева</param>
   37:         /// <param name="top">Сдвиг сверху</param>
   38:         public SnowFlakeControl(double left, double top)
   39:         {
   40:             InitializeComponent();
   41:  
   42:             _posLeft = left;
   43:             _posTop = top;
   44:  
   45:             this.SetValue(Canvas.LeftProperty, _posLeft);
   46:             this.SetValue(Canvas.TopProperty, _posTop);
   47:  
   48:             Random rndSize = new Random(DateTime.Now.Millisecond);
   49:  
   50:             // Скорость падения задается случайно
   51:             _vertInc = rndSize.Next(1, 6);
   52:             // Быстрые снежинки находятся ближе и они больше по размеру, медленные - дальше и они меньше
   53:             SnowScale.ScaleX = SnowScale.ScaleY = 0.05 * _vertInc;
   54:             // Делаем снежинки немного прозрачными, дальние -- более прозрачные
   55:             this.Opacity = 1 - 1 / (_vertInc + 1);
   56:         }
   57:  
   58:         public void Fall(double windValue)
   59:         {
   60:             int value = _rand.Next(50);
   61:  
   62:             if (value == 25) // Небольшой шанс (1 к 50) изменить направление движения
   63:             {
   64:                 if (_horzInc >= 0.2)
   65:                     _horzInc = 0;
   66:                 else if (_horzInc <= -0.2)
   67:                     _horzInc = 0;
   68:                 else
   69:                 {
   70:                     value = _rand.Next(3);
   71:                     if (value == 1) _horzInc = 0.4;
   72:                     else if (value == 2) _horzInc = -0.4;
   73:                     else if (value > 0) _horzInc = 0.0;
   74:                 }
   75:             }
   76:  
   77:             double horzValue = _horzInc + (windValue * 0.1);
   78:  
   79:             _posLeft += horzValue;
   80:             _posTop += _vertInc;
   81:  
   82:             this.SetValue(Canvas.LeftProperty, _posLeft);
   83:             this.SetValue(Canvas.TopProperty, _posTop);
   84:  
   85:             _completed = (_posLeft >= _horizontalRange || _posLeft <= 0 || _posTop >= _floorValue);
   86:         }
   87:     }
   88: }

Параметры _floorValue и _horizontalRange завязаны на размер окна с открыткой, но ничто не мешает сделать так, чтобы они передавались в конструкторе.

Снежинка готова!

 

1.2. Делаем снегопад

Для этого создаем новый контрол – SnowFallControl. В XAML-коде создаем Canvas, внутри которого будут падать снежинки, также можно задать увеличение прозрачности слоя внизу – снежинки будут как бы “пропадать”:

 <UserControl x:Class="New_Year_Card.SnowFallControl"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" >
     <Canvas x:Name="SnowCanvas" Margin="0,0,0,0"  VerticalAlignment="Stretch">
         <Canvas.OpacityMask>
             <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                 <GradientStop Color="#FF000000"/>
                 <GradientStop Color="#FFFFFFFF" Offset="0.781"/>
                 <GradientStop Color="#00FFFFFF" Offset="1"/>
             </LinearGradientBrush>
         </Canvas.OpacityMask>
     </Canvas>
 </UserControl>

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

    1: namespace New_Year_Card
    2: {
    3:     /// <summary>
    4:     /// Контрол "Снегопад"
    5:     /// </summary>
    6:     public partial class SnowFallControl : UserControl
    7:     {
    8:         // Коллекция снежинок
    9:         List<SnowFlakeControl> _snowFlakes = new List<SnowFlakeControl>();
   10:  
   11:         // Таймер для анимации
   12:         Storyboard _snowflakeTimer = new Storyboard();
   13:  
   14:         // Генератор случаных чисел
   15:         Random _rand = new Random(DateTime.Now.Millisecond);
   16:  
   17:         // Прирост снежинок
   18:         int _newFlakeCount = 5;
   19:  
   20:         // Ветер
   21:         int _wind = 0;
   22:  
   23:         /// <summary>
   24:         /// Контрол для отображения снегопада
   25:         /// </summary>
   26:         public SnowFallControl()
   27:         {
   28:             // Required to initialize variables
   29:             InitializeComponent();
   30:                         
   31:             _snowflakeTimer.Duration = TimeSpan.FromMilliseconds(10);
   32:             _snowflakeTimer.Completed += new EventHandler(SnowFlakeTimer);
   33:             _snowflakeTimer.Begin();
   34:  
   35:         }
   36:  
   37:         private void SnowFlakeTimer(object sender, EventArgs e)
   38:         {
   39:             MoveSnowFlakes();
   40:             CreateSnowFlakes();
   41:         }
   42:  
   43:         private void MoveSnowFlakes()
   44:         {
   45:             List<SnowFlakeControl> _flakesToRemove = new List<SnowFlakeControl>();
   46:  
   47:             foreach (SnowFlakeControl flake in _snowFlakes)
   48:             {
   49:                 flake.Fall(_wind);
   50:                 if (true == flake.Completed)
   51:                     _flakesToRemove.Add(flake);
   52:             }
   53:  
   54:             foreach (SnowFlakeControl flake in _flakesToRemove)
   55:             {
   56:                 _snowFlakes.Remove(flake);
   57:                 SnowCanvas.Children.Remove(flake);
   58:             }
   59:  
   60:             _snowflakeTimer.Begin();
   61:         }
   62:  
   63:         private void CreateSnowFlakes()
   64:         {
   65:             int count = _rand.Next(0, _newFlakeCount);
   66:             for (int i = 0; i < count; i++)
   67:             {
   68:                 SnowFlakeControl flake = new SnowFlakeControl(_rand.Next(0, 800), 0.0);
   69:                 _snowFlakes.Add(flake);
   70:                 SnowCanvas.Children.Add(flake);
   71:             }
   72:  
   73:             if (_wind < 0)
   74:             {
   75:                 for (int i = 0; i < count; i++)
   76:                 {
   77:                     SnowFlakeControl flake = new SnowFlakeControl(800, _rand.Next(0, 700));
   78:                     _snowFlakes.Add(flake);
   79:                     SnowCanvas.Children.Add(flake);
   80:                 }
   81:             }
   82:  
   83:             else if (_wind > 0)
   84:             {
   85:                 for (int i = 0; i < count; i++)
   86:                 {
   87:                     SnowFlakeControl flake = new SnowFlakeControl(0, _rand.Next(0, 700));
   88:                     _snowFlakes.Add(flake);
   89:                     SnowCanvas.Children.Add(flake);
   90:                 }
   91:             }
   92:         }  
   93:     }
   94: }

Далее возвращаемся в Page.xaml и прописываем туда снегопад :)

 <UserControl x:Class="New_Year_Card.Page"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:New_Year_Card="clr-namespace:New_Year_Card"
     Width="800" Height="600">
     <Grid x:Name="LayoutRoot" Background="Black">
         <New_Year_Card:SnowFallControl />
     </Grid>
 </UserControl>

Обратите внимание на добавленное пространство имен – New_Year_Card. Результат:

image

Так, снегопад готов!

 

2. Добавляем картинки

Раз речь идет о Silverlight, то картинки, конечно, будут DeepZoom`ными!

2.1. Подбираем картинки

В данном пункте я не придумал ничего умнее, как отправится на Flickr и искать хорошие картинки там. Картинки, конечно, брались не все подряд, а те, которые можно использовать (Advanced Search –> Creative Common –> …). Всех авторов запоминаем отдельно, потом на них сошлемся.

Набрав нужно количество фотографий (у меня 80 штук) и приведя их к одному размеру, например 800х600 (тогда из них можно будет сделать сетку изображений),

image

отправляемся в DeepZoom Composer (последнюю версию можно скачать в MS Downloads Center).

2.2. Делаем коллекцию изображения для DeepZoom

Import. Импортируем. Можно выбрать файлы или просто перекинуть в композер, используя Drag&Drop.

image

Compose. Перетаскиваем все в рабочую область, не снимая выделение, жмем правую кнопку –> Arrange  --> Arrange into a Grid…

 image image

 

 

 

Export. Выбираем Custom. Если есть готовый код для вставки и управления DeepZoom, то достаточно опции Images, если кода у вас нет, нужно выбрать Silverlight DeepZoom.

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

image image

 

После завершения процедуры экспорта появится окошко с предложение посмотреть на результат. Нам понадобится папка DeepZoomProjectWeb\ClientBin\GeneratedImages –копируем ее в папку New Year Card.Web\ClientBin.

2.3. Делаем DeepZoom-контрол для фотографий

Чтобы сделать DeepZoom-контрол, я воспользуюсь готовым кодом, сгенерированным композером.

Для справки: если вы раньше пробовали DeepZoom Composer, но давно не обновляли – вы будете приятно удивлены: обновился шаблон для экспорта. Теперь он с симпатичными кнопочками для изменения масштаба, возврата в исходное положение и для просмотра в полноэкранном режиме.

image

 

Итак, идем в проект, сгенерированный композером. Из этого проекта нам понадобятся иконки, файл MouseWheelHelper.cs (чтобы работало масштабирование колесиком мыши), а также исходный код файлов Page.xaml и Page.xaml.cs.

В своем проекте я добавляю папку Images (там будут все иконки) и импортирую в него иконки из проекта DeepZoomProject.

Примечание: чтобы дальше сильно не заморачиваться с привязкой и изменением размеров контролов, я не буду добавлять FullScreen-режим. Размер открытки фиксированный – 800х600.

Аналогично импортируется файл MouseWheelHelper.cs. Для удобства я поменяю в нем Namespace на пространство имен моего проекта:

    1: namespace New_Year_Card
    2: {
    3:         // Courtesy of Pete Blois
    4:         public class MouseWheelEventArgs : EventArgs

Далее создаем новый контрол – DeepZoomControl. Копируем в cs-файл все содержимое файла Page.xaml.cs из DeepZoomProject, оставив наш Namespace и наше имя контрола.

Аналогично, копируем полностью Page.xaml в DeepZoomControl.xaml, оставив только привязку к нашему классу (первая строчка):

 <UserControl x:Class="New_Year_Card.DeepZoomControl"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
     <UserControl.Resources>

Дальше немного танцев с бубнами :) Заменяем во всех тегах Image в начале атрибута Source приписываем  Images/ (мы же храним все иконки в папке Images!), например,

 <Image Height="26" x:Name="normal" Width="26" Source="Images/zoomin_rest.png" Stretch="Fill"/>
                 <Image Height="26" x:Name="down" Width="26" Opacity="0" Source="Images/zoomin_pressed.png" Stretch="Fill"/>
                 <Image Height="26" x:Name="hover" Width="26" Opacity="0" Source="Images/zoomin_hover.png" Stretch="Fill"/>

Добавляем DeepZoomControl на нашу открытку:

 <UserControl x:Class="New_Year_Card.Page"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:New_Year_Card="clr-namespace:New_Year_Card"
     Width="800" Height="600">
     <Grid x:Name="LayoutRoot" Background="Black">
         <New_Year_Card:DeepZoomControl Margin="8,8,8,8" />
         <New_Year_Card:SnowFallControl />
     </Grid>
 </UserControl>

Запускаем и смотрим результат:

image

Уже неплохо! Но нужно сделать несколько изменений:

  • убрать fullscreen-режим;
  • переместить кнопки управления наверх влево, сделав их вертикальными;
  • задать начальное положение для DeepZoomControl (укрупнить одну из картинок на все окошко открытки).

2.4. Доводим DeepZoomControl для ума

Для первой задачи выкидываем (или закомментируем, кому как больше нравится) код, отвечающий за fullscreen-режим в XAML-коде:

 <!--Button Height="30" x:Name="fullScreen" Width="42" Template="{StaticResource fullScreenTemplate}" Content="Button" Canvas.Left="287" Canvas.Top="4" Click="GoFullScreenClick"/ -->
  

То же самое делаем с кодом темплейта для кнопки

 

 

 

 

 

 

 <!--ControlTemplate x:Key="fullScreenTemplate" TargetType="Button">
     <Grid>
         <vsm:VisualStateManager.VisualStateGroups>
             <vsm:VisualStateGroup x:Name="FocusStates">
                 <vsm:VisualStateGroup.Transitions>
                     <vsm:VisualTransition GeneratedDuration="00:00:00.1000000"/>
                 </vsm:VisualStateGroup.Transitions>
                 ...
 </ControlTemplate-->

В DeepZoomControl.xaml.cs, прежде всего, убираем функцию-реакцию на нажатие этой кнопки:

    1: //private void GoFullScreenClick(object sender, System.Windows.RoutedEventArgs e)
    2:         //{
    3:         //    if (!Application.Current.Host.Content.IsFullScreen)
    4:         //    {
    5:         //        Application.Current.Host.Content.IsFullScreen = true;
    6:         //    }
    7:         //    else
    8:         //    {
    9:         //        Application.Current.Host.Content.IsFullScreen = false;
   10:         //    }
   11:         //}

Для второй задачи придется немного поменять xaml-файл:

 <Canvas Height="124" HorizontalAlignment="Left" Margin="0,0,0,0" x:Name="buttonCanvas" VerticalAlignment="Top" Width="58" Opacity="1" Background="{x:Null}">
     <Button Height="30" x:Name="zoomIn" Width="42" Canvas.Left="8" Canvas.Top="8" Template="{StaticResource zoomInTemplate}" Content="Button" Click="ZoomInClick"/>
     <Button Height="30" x:Name="zoomOut" Width="42" Template="{StaticResource zoomOutTemplate}" Content="Button" Canvas.Left="8" Canvas.Top="48" Click="ZoomOutClick"/>
     <Button Height="30" x:Name="goHome" Width="42" Template="{StaticResource homeTemplate}" Content="Button" Canvas.Left="8" Canvas.Top="86" Click="GoHomeClick"/>
     <!--Button Height="30" x:Name="fullScreen" Width="42" Template="{StaticResource fullScreenTemplate}" Content="Button" Canvas.Left="287" Canvas.Top="4" Click="GoFullScreenClick"/ -->
 </Canvas>

Думаю, понятно, что чем править ручками проще всего воспользоваться Blend и выставить все нужные значения в визуальном режиме. Проверяем, что все работает.

image image

Так как снизу будет надпись и несколько иконок, добавляем небольшое затемнение (точнее, увеличиваем прозрачность):

 <MultiScaleImage x:Name="msi" Source="../GeneratedImages/dzc_output.xml">
             <MultiScaleImage.OpacityMask>
                 <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                     <GradientStop Color="#FF000000"/>
                     <GradientStop Color="#FFFFFFFF" Offset="0.781"/>
                     <GradientStop Color="#00FFFFFF" Offset="1"/>
                 </LinearGradientBrush>
             </MultiScaleImage.OpacityMask>
         </MultiScaleImage>

 

 

 

Для третьей задачи придется немного пошаманить, подбирая нужный ракурс :)

В событии msi_ImageOpenSucceeded в конце добавляем что-нибудь вроде:

 Zoom(10, msi.ElementToLogicalPoint(new Point(.22 * msi.ActualWidth, .455 * msi.ActualHeight)));
  

10 – подходящий масштаб

.22 и .455 – относительные координаты в окне msi (от 0 до 1), по горизонтали и вертикали соответственно. Шаманить нужно с ними ;)

У меня получилось вот так:

image

С DeepZoom закончили! Что дальше? Иконки, лампочка, ссылки.

 

3. Добавляем иконку RSS и ссылку на себя любимого :)

 

Небольшое отступление: все инонки найдены через сайт https://www.smashingmagazine.com (рекомендую, там много вкусного пишут!). Иконки выбирались те, которые можно бесплатное юзать, даже если и со ссылкой на авторов.

Итак, в правый нижний угол помещаем иконку RSS (не забываем импортировать иконки в проект!) и ставим на нее ссылку на фид своего блога:

 <HyperlinkButton NavigateUri="https://blogs.msdn.com/kichinsky/rss.xml" Margin="0,0,8,8" VerticalAlignment="Bottom" Width="79" Height="93" HorizontalAlignment="Right">
             <HyperlinkButton.Content>
                 <Canvas>
                     <Image Height="93" HorizontalAlignment="Right" Margin="0,0,0,0" VerticalAlignment="Bottom" Width="79" Source="Images/rss-ny_15.png" Stretch="Fill" x:Name="RSSIcon"/>
                 </Canvas>
             </HyperlinkButton.Content>

Вуаля!

image

 

4. Добавляем елочку и окошко с благодарностями

4.1. Добавляем елочку

Добавить саму елочку – проще простого: добавляем картинку, позиционируем – и готово:

 <Image Height="128" HorizontalAlignment="Left" Margin="8,0,0,8" VerticalAlignment="Bottom" Width="128" Source="Images/10_SocNet.png" Stretch="Fill" x:Name="Tree"/>
  

 

4.2. Добавляем окошко с благодарностями

Для окошка с благодарностями и отдельными поздравлениями делаем специальный контрол ThanksControl. В этом окошке будет написано, как мы благодарным пользователям фликера за их фотографии и авторам иконок за иконки.

Что важно, туда нужно как-то вместить ссылки… А вот со вставкой ссылкок в текст у Silverlight 2 проблемы – он этого сам делать не умееет. Пока мы надеемся, что разработчики Silverlight прислушаются к просьбам страждущих – и добавят RichTextBox.

Но это же непонятно когда будет, а Новый Год, пардон, не ждет!

 

Для решения данной задачи я воспользовался специальным контролом LinkLabel, достыпным в исходниках на Codeplex. Контрол этот берет на себя задачу перевода текста с ссылками внутри во WrapPanel с элементами текста и ссылок.

Чтобы все заработало, в проект нужно добавить ссылки на библиотеки LinkLabel.dll и WrapPanel.dll.

Xaml-код получается примерно таким (чтобы не загромождать оставил по одной ссылке):

 <UserControl x:Class="New_Year_Card.ThanksControl"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:New_Year_Card="clr-namespace:New_Year_Card" 
     xmlns:local="clr-namespace:CompletIT.Windows.Controls;assembly=LinkLabel">
     <Grid x:Name="LayoutRoot">
         <Rectangle Fill="#E51A1A1A" Stroke="{x:Null}" RadiusX="10" RadiusY="10"/>
         <Image Height="124.017" HorizontalAlignment="Right" Margin="0,0,0.015,0.017" VerticalAlignment="Bottom" Width="111.015" Source="Images/ded5.png" Stretch="Fill"/>
         <StackPanel Margin="24,24,24,24">
             <TextBlock Foreground="White" Height="112" TextWrapping="Wrap"><Run Text="Мы хотим отдельно поздравить с Новым Годом и Рождеством пользователей интернета, размещающих свои фотографии на Flickr с использованием лицензий Creative Commons и, таким образом, позволяющих другим пользователям пользоваться их трудами :)"/><LineBreak/><Run Text=""/><LineBreak/><Run Text="БОЛЬШОЕ ВАМ СПАСИБО!"/><LineBreak/><Run Text=""/><LineBreak/><Run Text="В частности, мы благодарим следующих пользователей "/><Run Text="Flickr:"/><LineBreak/><Run Text=""/><LineBreak/><Run Text=""/><LineBreak/><Run Text=""/></TextBlock>
             <local:LinkLabel x:Name="MyLinkLabel" Foreground="White"
                 Text="[link=&quot;https://www.flickr.com/photos/sally_12/&quot;]*Sally M*[/link],..." Height="132"/>
             <TextBlock Text="Также выражаем нашу благодарность авторам чудесных иконок, которые мы использовали в нашей открытке:" Foreground="#FFFFFFFF"/>
             <local:LinkLabel x:Name="MyLinkLabel2" Foreground="White"
                 Text="[link=&quot;https://www.icondrawer.com&quot;]IconDrawer Team[/link],..." Height="38" />
             <TextBlock Text="Ваше здоровье! Веселых праздников!" Foreground="#FFFFFFFF"/>
         </StackPanel>
         <New_Year_Card:CloseButtonControl Margin="702,8,8,503" x:Name="Close"/>
     </Grid>
 </UserControl>
  
  

Тут стоит обратить внимание на то, что добавилось еще одно пространство имен – local, через которое осуществляется доступ к LinkLabel.

В конце упоминается CloseButtonControl – это самый простой контрол с крестиком – для закрытия окна с благодарностями. Делается это следующим образом:

    1: public ThanksControl()
    2:         {
    3:             // Required to initialize variables
    4:             InitializeComponent();
    5:  
    6:             this.Close.MouseLeftButtonUp += new MouseButtonEventHandler(Close_MouseLeftButtonUp);
    7:         }
    8:  
    9:         void Close_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
   10:         {
   11:             this.Visibility = Visibility.Collapsed;
   12:         }

Осталось добавить это окошко в открытку (в скрытом режиме)

 <New_Year_Card:ThanksControl x:Name="Thanks" Margin="31.897,26.069,26.103,30.931" Visibility="Collapsed"/>
  

и повесить событие на открытие этого окна по клику на елочку:

    1: public partial class Page : UserControl
    2:    {
    3:        public Page()
    4:        {
    5:            InitializeComponent();
    6:  
    7:            this.Tree.MouseLeftButtonUp += new MouseButtonEventHandler(Tree_MouseLeftButtonUp);
    8:        }
    9:  
   10:        void Tree_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
   11:        {
   12:            this.Thanks.Visibility = Visibility.Visible;
   13:        }
   14:    }

Смотрим результат:

image image

4.3. Да будет свет!

Елочка у нас не простая. Елочка со звездой. А фигли она не светится, а дети на нее пялятся? Непорядок. Елочку надо зажечь!

Делаем новый контрол – LightControl.

Свет будет представляться пульсирующим сиянием вокруг звезды. Для этого в XAML-коде делаем круг и прописываем его закраску:

 <UserControl x:Class="New_Year_Card.LightControl"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" >
     <Grid x:Name="LayoutRoot">
         <Ellipse x:Name="Light" Stroke="{x:Null}">
             <Ellipse.Fill>
                 <RadialGradientBrush>
                     <GradientStop x:Name="gradientStop1" Color="#7FFFF500"/>
                     <GradientStop x:Name="gradientStop2" Color="#00FFFFFF" Offset="1"/>
                     <GradientStop x:Name="gradientStop3" Color="#22FFFCB9" Offset="0.5"/>
                 </RadialGradientBrush>
             </Ellipse.Fill>
         </Ellipse>
     </Grid>
 </UserControl>

 

 

В коде мы будем периодически (ага, синусоидально с небольшими случайностями) менять относительные положения градинтных стоперов:

    1: public partial class LightControl : UserControl
    2:     {
    3:         Random _rand = new Random(DateTime.Now.Millisecond);
    4:  
    5:         Storyboard _lightTimer = new Storyboard();
    6:  
    7:         public LightControl()
    8:         {
    9:             // Required to initialize variables
   10:             InitializeComponent();
   11:  
   12:             _lightTimer.Duration = TimeSpan.FromMilliseconds(10);
   13:             _lightTimer.Completed += new EventHandler(LightTimer);
   14:             _lightTimer.Begin();
   15:         }
   16:  
   17:         private void LightTimer(object sender, EventArgs e)
   18:         {
   19:             this.gradientStop2.Offset = 1.0 - 0.1 * _rand.NextDouble();
   20:             this.gradientStop3.Offset = 0.1 + 0.6 * Math.Abs(Math.Sin(DateTime.Now.Millisecond));
   21:  
   22:             _lightTimer.Begin();
   23:         }
   24:     }

 

Осталось повесить лампочку к елочке (заодно добавим наверх вправо елочную игрушку и снеговика).

 <New_Year_Card:LightControl Margin="32,434,688,86"/>
         <Image Margin="0,8,7.793,0" Width="128" Source="Images/Snowman.png" Stretch="Fill" VerticalAlignment="Top" Height="128" HorizontalAlignment="Right"/>

И самое главное: надпись “С Новым Годом!

 <New_Year:LightControl Margin="32,434,688,86"/>
         <Image Margin="0,8,7.793,0" Width="128" Source="Images/Snowman.png" Stretch="Fill" VerticalAlignment="Top" Height="128" HorizontalAlignment="Right"/>
         <TextBlock Height="60.565" Margin="158.286,0,123.714,30.721" VerticalAlignment="Bottom" FontSize="48" TextAlignment="Center" TextWrapping="Wrap" FontFamily="Verdana"><TextBlock.Foreground>
                 <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                     <GradientStop Color="#FFB9E0FF"/>
                     <GradientStop Color="#FFFFFFFF" Offset="0.973"/>
                 </LinearGradientBrush>
             </TextBlock.Foreground><Run Text="С "/><Run Text="Н"/><Run Text="овым "/><Run Text="Г"/><Run Text="одом!"/>
         </TextBlock>

 

Открытка готова! Ура!

image

5. Публикуем открытку!

Тут, прежде всего, надо заметить, что нет ничего сложного в том, чтобы опубликовать открытку на любом своем сервере – лишь бы он нормально отдавал  xap-файлы :)

Да, еще нужно место под фотографии, подготовленные композером для DeepZoom – понадобится место :) Больше фотографий, лучше качество –> больше места.

На мои 80 размером 800х600 требуется примерно 24 Мб.

В связи с этим ниже описывается способ “как выложить на сервера Microsoft”.

5.1. Архивируем приложение в zip-архив

Все, что находится в папке New Year Card.Web\ClientBin нужно запоковать в какой-нибудь zip-архив.

Это займет какое-то время, там много файлов с картинками :)

image

5.2. Создаем приложение в https://silverlight.live.com

Заходим на сайт под своим LiveId (возможно, придется создать аккаунт). Выбираем Manage Applications.

Дальше Upload an Application. Вводим имя приложения, далее выбираем свой архив (у меня это ClientBin.zip).

Жмем Upload. Ждем, пока загрузится.

5.3. Редактируем манифест приложения

После окончания загрузки будет предложено настроить манифест приложения (Configure this Application). Жмем Create, прописываем название, размер, фон и другие параметры по мере необходимости.

Update.

5.4. Просмотр

Теперь можно просмотреть , как смотрится приложение на тестовой странице или получить код для вставки на страницу (например, в блог):

image image

Результат можно посмотреть здесь https://silverlight.services.live.com/invoke/27881/New%20Year%20Card/iframe.html

 

P.S. Среди фоторгафий вы найдете фото академической команды MS Russia (если есть вопросы, пишите письма) :)

Пользуясь случаем от имени команды, отдельно поздравляю с Новым Годом всех студентов, аспирантов, преподавателей и сотрудников вузов и научных учреждений!

 

Исходники

Исходники можно свободно использовать на свое усмотрение, а также на свой страх и риск :) Буду рад, если сошлетесь на меня.

Проект, включая изображения для DeepZoom  (папка generatedImages) – 25.5 Мб

Открыть New Year Card.zip

New Year Card.zip
Сжатая ZIP-папка
25,5 МБ

 

Проект без фотографий (можно подставить любой другой набор изображений) – 1.4 Мб

Открыть New Year Card - Without Photos.zip

New Year Card - Without Photos.zip
Сжатая ZIP-папка
1,4 МБ

Удачного программирования!