Как написать скринсейвер

LONG WINAPI ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

    //Определяем необходимые переменные

    static HDC hDC;

    static HPEN hPen;

    static UINT timer_id;

    static RECT rect;

    static PAINTSTRUCT ps = {0};

    //Предвычисленные массивы для хранения значений sin/cos

    static double s[360], c[360];

    //Массивы для хранения оригинальных координат линий и координат после поворота

    static double lO[12][2][4], lR[12][2][4];

    //Размер куба

    static int side_size = 500;

    //Координаты центра куба

    static int xCenter = 0, yCenter = 0, zCenter = 0;

    //Углы поворота

    static int theta = 0, phi = 0;

    static int thetaRot = 2, phiRot = 2;

    //Массивы для хранения координат ребер куба

    static double scrX[12][2], scrY[12][2];

    static int i;

    switch(message)

    {

        case WM_CREATE:

            //Загружаем настройки в структуру

            LoadSaveSettings(FALSE);

            //Задаем цвет и толщину линий куба

            hPen = CreatePen(PS_SOLID, 5, RGB(scr_settings.r, scr_settings.g, scr_settings.b));

            //Вычисляем центры проекции куба на основе разрешения экрана

            xCenter = GetSystemMetrics (SM_CXSCREEN) / 2;

            yCenter = GetSystemMetrics (SM_CYSCREEN) / 2;

            zCenter = xCenter + yCenter 4;

            //Задаем область, которую в дальнейшем будем перерисовывать

            rect.left = xCenter side_size;

            rect.top = yCenter side_size;

            rect.right = xCenter + side_size;

            rect.bottom = yCenter + side_size;

            //Вычисляем значения sin и cos

            for(i = 0; i < 360; i++)

            {

                s[i] = sin(i * (M_PI / 180));

                c[i] = cos(i * (M_PI / 180));

            }

            //Задаем координаты ребер куба

            lO[0][0][0] = side_size; lO[0][0][1] = side_size; lO[0][0][2] = side_size;

            lO[0][1][0] = side_size;  lO[0][1][1] = side_size; lO[0][1][2] = side_size;

            lO[1][0][0] = side_size; lO[1][0][1] = side_size; lO[1][0][2] = side_size;

            lO[1][1][0] = side_size; lO[1][1][1] = side_size;  lO[1][1][2] = side_size;

            lO[2][0][0] = side_size; lO[2][0][1] = side_size; lO[2][0][2] = side_size;

            lO[2][1][0] = side_size; lO[2][1][1] = side_size; lO[2][1][2] = side_size;

            lO[3][0][0] = side_size; lO[3][0][1] = side_size; lO[3][0][2] = side_size;

            lO[3][1][0] = side_size; lO[3][1][1] = side_size;  lO[3][1][2] = side_size;

            lO[4][0][0] = side_size; lO[4][0][1] = side_size; lO[4][0][2] = side_size;

            lO[4][1][0] = side_size; lO[4][1][1] = side_size; lO[4][1][2] = side_size;

            lO[5][0][0] = side_size; lO[5][0][1] = side_size; lO[5][0][2] = side_size;

            lO[5][1][0] = side_size;  lO[5][1][1] = side_size; lO[5][1][2] = side_size;

            lO[6][0][0] = side_size; lO[6][0][1] = side_size; lO[6][0][2] = side_size;

            lO[6][1][0] = side_size;  lO[6][1][1] = side_size; lO[6][1][2] = side_size;

            lO[7][0][0] = side_size; lO[7][0][1] = side_size; lO[7][0][2] = side_size;

            lO[7][1][0] = side_size;  lO[7][1][1] = side_size; lO[7][1][2] = side_size;

            lO[8][0][0] = side_size; lO[8][0][1] = side_size; lO[8][0][2] = side_size;

            lO[8][1][0] = side_size; lO[8][1][1] = side_size;  lO[8][1][2] = side_size;

            lO[9][0][0] = side_size; lO[9][0][1] = side_size; lO[9][0][2] = side_size;

            lO[9][1][0] = side_size; lO[9][1][1] = side_size; lO[9][1][2] = side_size;

            lO[10][0][0] = side_size; lO[10][0][1] = side_size; lO[10][0][2] = side_size;

            lO[10][1][0] = side_size; lO[10][1][1] = side_size;  lO[10][1][2] = side_size;

            lO[11][0][0] = side_size; lO[11][0][1] = side_size; lO[11][0][2] = side_size;

            lO[11][1][0] = side_size; lO[11][1][1] = side_size; lO[11][1][2] = side_size;

            //Запускаем таймер, по которому будет перерисовываться куб

            timer_id = SetTimer(hWnd, 1, 500 5 * scr_settings.pos + 1, NULL);

        break;

        case WM_DESTROY:

            //При закрытии окна скринсейвера останавливаем таймер и удаляем созданный объект

            if(timer_id)

                KillTimer(hWnd, timer_id);

            if(hPen)

                DeleteObject(hPen);

            //Сообщаем системе о том, что текущий поток сделал запрос на прекращение работы

            PostQuitMessage(0);

        break;

        case WM_TIMER:

                //Вычисляем координаты ребер куба

                for(i = 0; i < 12; i++)

                {

                    lR[i][0][0] = lO[i][0][0] * s[theta] + lO[i][0][1] * c[theta];

                    lR[i][0][1] = lO[i][0][0] * c[theta] * s[phi] lO[i][0][1] * s[theta] * s[phi] lO[i][0][2] * c[phi] + 1;

                    lR[i][0][2] = lO[i][0][0] * c[theta] * c[phi] lO[i][0][1] * s[theta] * c[phi] + lO[i][0][2] * s[phi];

                    lR[i][1][0] = lO[i][1][0] * s[theta] + lO[i][1][1] * c[theta];

                    lR[i][1][1] = lO[i][1][0] * c[theta] * s[phi] lO[i][1][1] * s[theta] * s[phi] lO[i][1][2] * c[phi] + 1;

                    lR[i][1][2] = lO[i][1][0] * c[theta] * c[phi] lO[i][1][1] * s[theta] * c[phi] + lO[i][1][2] * s[phi];

                    if(lR[i][0][2] + zCenter > 0 || lR[i][0][2] + zCenter < 0)

                    {

                        scrX[i][0] = 256 * (lR[i][0][0] / (lR[i][0][2] + zCenter)) + xCenter;

                        scrY[i][0] = 256 * (lR[i][0][1] / (lR[i][0][2] + zCenter)) + yCenter;

                    }

                    if(lR[i][1][2] + zCenter > 0 || lR[i][1][2] + zCenter < 0)

                    {

                        scrX[i][1] = 256 * (lR[i][1][0] / (lR[i][1][2] + zCenter)) + xCenter;

                        scrY[i][1] = 256 * (lR[i][1][1] / (lR[i][1][2] + zCenter)) + yCenter;

                    }

                }

                //Помечаем область, содержащую куб, как требующую перерисовки

                InvalidateRect(hWnd, &rect, TRUE);

        break;

        case WM_PAINT:

                //Вызываем функцию, подготавливающую окно к выводу графической информации

                hDC = BeginPaint(hWnd, &ps);

                //fChildPreview — глобальная переменная, показывающая смотрим мы превью или полноэкранную версию скринсейвера

                if(!fChildPreview)

                {

                    //Выбираем перо, которое будет использоваться для рисования куба

                    SelectObject(hDC, hPen);

                    //Рисуем ребра куба

                    for(i = 0; i < 12; i++)

                    {

                        MoveToEx(hDC, (int)scrX[i][0], (int)scrY[i][0], NULL);

                        LineTo(hDC, (int)scrX[i][1], (int)scrY[i][1]);

                    }

                    theta = (theta + thetaRot) % 360;

                    phi = (phi + phiRot) % 360;

                }

                else

                {

                    //Выводим текст с описанием скринсейвера

                    SetBkColor(hDC, RGB(0, 0, 0));

                    SetTextColor(hDC, RGB(0, 255, 0));

                    TextOut(hDC, 35, 30, scr_name, strlen(scr_name));

                    TextOut(hDC, 50, 50, scr_auth, strlen(scr_auth));

                }

                //Завершаем рисование в окне

                EndPaint(hWnd, &ps);

        break;

        default:

            return DefScreenSaverProc(hWnd, message, wParam, lParam);

    }

    return 0;

}

Приветствую всех! Многие из вас, да и я в том числе пользуюсь заставками на своем компьютере, их такое множество, и не которые из них действительно требуют хороших навыков программирования. Сегодня я покажу пример который позволит вам с легкостью написать свой собственный скринсейвер. Для начала нам необходимо придумать что мы будет отображать можно использовать картинку или анимацию, возможно так же вам потребуется какая нибудь фоновая музыка. Я не долго думая в поисковике нашел гифвку водопада, а так же скачать соответствующую фоновую музыку. На этом наша предварительная работа закончена.

Приступаем части по написанию кода. А именно нам необходимо создать проект winform приложение и в свойствах проекта указать целевую платформу .Net Framework 2.0

Далее нам необходимо на форме в конструкторе разместить PictureBox размер и прочие настройки нас пока не особо беспокоят. Теперь нажмите два раза по форме, что бы создалась событие Form1_Load.

Далее приступаем к уже написанию кода.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

using System;

using System.Drawing;

using System.Media;

using System.Windows.Forms;

using WaterfallScreenSaver.Properties;

namespace WaterfallScreenSaver

{

    public partial class Form1 : Form

    {

        private Point MouseXY;

        private int ScreenNumber;

        SoundPlayer simpleSound;

        public Form1(int scrn)

        {

            InitializeComponent();

            this.FormBorderStyle = FormBorderStyle.None; //Убираем рамку вокруг формы

            pictureBox1.Dock = DockStyle.Fill; //заполняем размером по всей форме

            ScreenNumber = scrn;//это пригодится если у вас заставка будет иметь какие либо параметры

        }

        private void Form1_Load(object sender, EventArgs e)

        {

            this.Bounds = Screen.AllScreens[ScreenNumber].Bounds; //размер  во весь экран

            Cursor.Hide(); //прячем курсор

            TopMost = true; //отображаем форму поверх других

            simpleSound = new SoundPlayer(Resources.zvuk); //загрузка звука

            simpleSound.Play(); //воспроизведение звука

        }

        private void OnMouseEvent(object sender, System.Windows.Forms.MouseEventArgs e)

        {

            if (!MouseXY.IsEmpty) //вслучаи событий на мышь закрываем форму и приложение

            {

                if (MouseXY != new Point(e.X, e.Y))

                {

                    Close();

                    simpleSound.Stop(); //останавливаем звук

                }

                if (e.Clicks > 0)

                {

                    Close();

                    simpleSound.Stop();

                }

            }

            MouseXY = new Point(e.X, e.Y);

        }

    }

}

В общем то это и есть основной код, я постарался и за комментировал почти все, однако хочу акцентировать внимание на методе OnMouseEvent который связан с событиями PictureBox, его необходимо привязать к событию на MouseClick и MouseMove MouseDown это все необходимо что бы выйти из запущенного скринсейвера. Так же вам необходимо добавь изображение и музыку в папку ресурсы. Сделать это можно как показано ниже:

Теперь нам необходимо перейти в фаил Program.cs и заменить код на следующий:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

using System;

using System.Windows.Forms;

namespace WaterfallScreenSaver

{

    static class Program

    {

        /// &lt;summary>

        /// Главная точка входа для приложения.

        /// &lt;/summary>

        [STAThread]

        static void Main(string[] args)

        {

            if (args.Length > 0)

            {

                if (args[0].ToLower().Trim().Substring(0, 2) == «/c»)

                {

                    MessageBox.Show(«Эта заставка не имеет опций, которые вы можете установить.», «.NET Screen Saver», MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

                }

                else if (args[0].ToLower() == «/s»)

                {

                    for (int i = Screen.AllScreens.GetLowerBound(0); i &lt;= Screen.AllScreens.GetUpperBound(0); i++)

                        System.Windows.Forms.Application.Run(new Form1(i));

                }

            }

            else

            {

                for (int i = Screen.AllScreens.GetLowerBound(0); i &lt;= Screen.AllScreens.GetUpperBound(0); i++)

                    System.Windows.Forms.Application.Run(new Form1(i));

            }

        }

    }

}

Теперь нам необходимо собрать весь проект и врезультате мы получим приложение .EXE В котором необходимо изменить расширение на .scr

Теперь дело за малым, необходимо перенести этот фаил в папку WindowsSystem32

Все готовы, осталось выбрать свою заставку и посмотреть, что же у нас получилось 🙂

Вот, что в итоге у меня вышло, делитесь своими работами, будет интересно 🙂 Мою работу вы можете скачать ниже:

Время на прочтение
10 мин

Количество просмотров 3.2K

Предисловие

Этот материал никоим образом не призывает читателя к написанию скринсейверов, а предназначается, прежде всего, для обзора некоторых возможностей библиотеки wxWidgets. Тем, кто еще не знаком с wxWidgets, но хочет узнать больше, можно почитать статьи на CodeProject.com (эту и эту)

Мозг – это то, что нам нужно

Итак, о том, как собрать wxWidgets и о том, как создать приложение с использованием этой библиотеки я уже писал неоднократно, поэтому начнем сразу с примера. Для начала нам необходимо минимальное приложение с одной формой. Это будет скелет для создания скринсейвера.
Изобретать велосипед мы сегодня также не будем, а возьмем в качестве иллюстрации простейший скринсейвер с бегущими символами а-ля Matrix.

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

  • Координаты
  • Отображаемое значение
  • Скорость перемещения
  • Цвет

Для хранения информации о символе создадим такой класс:

  1. class MatrixSymbol
  2. {
  3.   wxPoint m_Position;
  4.   wxChar m_Symbol;
  5.   int m_Speed;
  6.   wxColour m_Colour;
  7. public:
  8.   MatrixSymbol()
  9.     : m_Position(wxDefaultPosition), m_Symbol(wxT('0')),
  10.     m_Speed(1), m_Colour(*wxGREEN) {}
  11.   MatrixSymbol(const wxPoint & position, wxChar symbol,
  12.     int speed, const wxColour & colour)
  13.     : m_Position(position), m_Symbol(symbol),
  14.     m_Speed(speed), m_Colour(colour) {}
  15.  
  16.   const wxPoint & GetPosition() {return m_Position;}
  17.   void SetPosition(const wxPoint & value) {m_Position = value;}
  18.   wxChar GetSymbol() {return m_Symbol;}
  19.   void SetSymbol(wxChar value) {m_Symbol = value;}
  20.   int GetSpeed() {return m_Speed;}
  21.   void SetSpeed(int value) {m_Speed = value;}
  22.   const wxColour & GetColour() {return m_Colour;}
  23.   void SetColour(const wxColour & value) {m_Colour = value;}
  24. };

* This source code was highlighted with Source Code Highlighter.

Таких символов у нас на экране должно быть много и, соответственно, для хранения всего этого добра нам необходим массив:

  1. #include <wx/dynarray.h>
  2. ...
  3. WX_DECLARE_OBJARRAY(MatrixSymbol, MatrixSymbolArray);
  4. ...
  5. #include <wx/arrimpl.cpp>
  6.  
  7. WX_DEFINE_OBJARRAY(MatrixSymbolArray);

* This source code was highlighted with Source Code Highlighter.

Лепим графический интерфейс

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

  1. class MatrixCanvas: public wxWindow
  2. {  
  3.   ...
  4.   void InitMatrix();
  5.   ...
  6.   MatrixSymbolArray m_Symbols;
  7.   ...
  8. };
  9. void MatrixCanvas::InitMatrix()
  10. {
  11.   int width(0), height(0);
  12.   int sw(0), sh(0);
  13.   GetTextExtent(wxT("0"), &sw, &sh);
  14.   GetClientSize(&width, &height);
  15.   m_Symbols.Clear();
  16.   for(int x = 0; x < width; x += sw+2)
  17.   {
  18.     m_Symbols.Add(MatrixSymbol(
  19.       wxPoint(x, 0),
  20.       rand()%2 ? wxT('0') : wxT('1'),
  21.       3+rand()%5,
  22.       wxColour(0, rand()%200+56, 0)));
  23.   }
  24. }

* This source code was highlighted with Source Code Highlighter.

Что у нас делает метод InitMatrix()? В зависимости от размеров клиентской области компонента в массив добавляется некоторое количество объектов MatrixSymbol со случайными координатами, отображаемым значением «0» или «1», и различными цветами (выбирается случайная градация зеленого).
Теперь нам нужно обеспечить отображение символов на экране. Для этого создадим обработчики событий wxEVT_PAINT и wxEVT_ERASE_BACKGROUND.

  1. BEGIN_EVENT_TABLE( MatrixCanvas, wxWindow )
  2.   EVT_PAINT( MatrixCanvas::OnPaint )
  3.   EVT_ERASE_BACKGROUND( MatrixCanvas::OnEraseBackground )
  4. END_EVENT_TABLE()
  5.  
  6. void MatrixCanvas::OnPaint( wxPaintEvent& event )
  7. {
  8.   wxBufferedPaintDC dc(this);
  9.   dc.SetBackground(wxBrush(GetBackgroundColour()));
  10.   dc.Clear();
  11.   wxFont font = GetFont();
  12. #if defined(__WXWINCE__)
  13.   int fontSize = 14;
  14. #else
  15.   int fontSize = 48;
  16. #endif
  17.   font.SetPointSize(fontSize);
  18.   dc.SetFont(font);
  19.   dc.SetTextForeground(wxColour(00, 20, 00));
  20.   dc.DrawLabel(wxT("http://wxwidgets.info"),
  21.     wxRect(0, 0, dc.GetSize().GetWidth(), dc.GetSize().GetHeight()),
  22.     wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL);
  23.   dc.SetFont(GetFont());
  24.   for(size_t i = 0; i < m_Symbols.Count(); i++)
  25.   {
  26.     dc.SetTextForeground(m_Symbols[i].GetColour());
  27.     dc.DrawText(wxString::Format(wxT("%c"), m_Symbols[i].GetSymbol()),
  28.       m_Symbols[i].GetPosition());
  29.   }
  30. }
  31.  
  32. void MatrixCanvas::OnEraseBackground( wxEraseEvent& event )
  33. {
  34. }

* This source code was highlighted with Source Code Highlighter.

Обработчик события wxEVT_ERASE_BACKGROUND пустой (без вызова event.Skip()). Это обеспечит нам перерисовку компонента без мерцания.
В обработчике события wxEVT_PAINT у нас создается контекст устройства, устанавливается цвет фона равный цвету фона нашего компонента, затем происходит очистка (это равноценно заливке цветом). После этого в центре компонента отрисовывается надпись и затем в цикле происходит отрисовка всех символов из массива.
Далее нам необходимо добавить обработчик события wxEVT_SIZE для того чтобы при изменении размера компонента символы отображались по всей ширине. В обработчике мы просто будем вызывать метод InitMatrix(), который заполняет массив символами.

  1. BEGIN_EVENT_TABLE( MatrixCanvas, wxWindow )
  2.   ...
  3.   EVT_SIZE( MatrixCanvas::OnSize )
  4. END_EVENT_TABLE()
  5. ...
  6. void MatrixCanvas::OnSize( wxSizeEvent& event )
  7. {
  8.   InitMatrix();
  9.   Refresh();
  10. }

* This source code was highlighted with Source Code Highlighter.

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

  1. class MatrixCanvas: public wxWindow
  2. {  
  3.   ...
  4.   wxTimer * m_MovementTimer;
  5. };
  6. MatrixCanvas::MatrixCanvas(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
  7. {
  8.   Init();
  9.   Create(parent, id, pos, size, style);
  10. }
  11.  
  12. bool MatrixCanvas::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
  13. {
  14.   wxWindow::Create(parent, id, pos, size, style);
  15.   CreateControls();
  16.   return true;
  17. }
  18.  
  19. MatrixCanvas::~MatrixCanvas()
  20. {
  21.   wxDELETE(m_MovementTimer);
  22. }
  23.  
  24. void MatrixCanvas::Init()
  25. {
  26.   m_PreviewMode = false;
  27. }
  28.  
  29. void MatrixCanvas::CreateControls()
  30. {  
  31.   this->SetForegroundColour(wxColour(0, 255, 0));
  32.   this->SetBackgroundColour(wxColour(0, 0, 0));
  33.   int timerID = wxNewId();
  34.   m_MovementTimer = new wxTimer(this, timerID);
  35.   Connect(timerID, wxEVT_TIMER,
  36.     wxTimerEventHandler(MatrixCanvas::OnMovementTimer));
  37.   InitMatrix();
  38.   Refresh();
  39.   m_MovementTimer->Start(30);
  40. }
  41.  
  42. void MatrixCanvas::OnMovementTimer( wxTimerEvent & event )
  43. {
  44.   for(size_t i = 0; i < m_Symbols.Count(); i++)
  45.   {
  46.     int y = m_Symbols[i].GetPosition().y + m_Symbols[i].GetSpeed();
  47.     if(y > GetClientSize().GetHeight())
  48.     {
  49.       y = -20;
  50.       m_Symbols[i].SetSpeed(3+rand()%5);
  51.       m_Symbols[i].SetColour(wxColour(0, rand()%200+56, 0));
  52.       m_Symbols[i].SetSymbol(rand()%2 ? wxT('0') : wxT('1'));
  53.     }
  54.     m_Symbols[i].SetPosition(wxPoint(
  55.       m_Symbols[i].GetPosition().x, y));
  56.   }
  57.   Refresh();
  58. }

* This source code was highlighted with Source Code Highlighter.

Как видно, в методе CreateControls() создается таймер и с помощью метода Connect() ему назначается обработчик события. В деструкторе таймер удаляется.
Отлично. Скринсейвер может работать в обычном режиме и в режиме предварительного просмотра. В обычном режиме нам необходимо обеспечить реакцию на действия пользователя, а именно на нажатия клавиш, а также на клик мышкой. Для этого мы создадим переменную-член класса bool m_PreviewMode и, в зависимости от ее значения, в обработчиках событий нажатия клавиш и нажатия кнопок мыши будем закрывать главную форму приложения.

  1. void MatrixCanvas::OnMouse( wxMouseEvent& event )
  2. {
  3.   if(event.LeftDown() || event.MiddleDown() || event.RightDown())
  4.   {
  5.     if(!m_PreviewMode)
  6.     {
  7.       wxFrame * frame = wxDynamicCast(wxTheApp->GetTopWindow(), wxFrame);
  8.       if(frame) frame->Close();
  9.     }
  10.   }
  11. }
  12.  
  13. void MatrixCanvas::OnChar( wxKeyEvent& event )
  14. {
  15.   if(!m_PreviewMode)
  16.   {
  17.     wxFrame * frame = wxDynamicCast(wxTheApp->GetTopWindow(), wxFrame);
  18.     if(frame) frame->Close();
  19.   }
  20. }

* This source code was highlighted with Source Code Highlighter.

Собственно, на этом работа над компонентом завершена. Теперь надо поместить его на главную форму:

  1. void MatrixEffectMainFrame::CreateControls()
  2. {  
  3.   MatrixEffectMainFrame* itemFrame1 = this;
  4.  
  5.   wxBoxSizer* itemBoxSizer2 = new wxBoxSizer(wxVERTICAL);
  6.   itemFrame1->SetSizer(itemBoxSizer2);
  7.  
  8.   m_Canvas = new MatrixCanvas( itemFrame1, ID_CANVAS, wxDefaultPosition, wxSize(100, 100), wxNO_BORDER );
  9.   itemBoxSizer2->Add(m_Canvas, 1, wxGROW, 0);
  10. }

* This source code was highlighted with Source Code Highlighter.

Вообще супер. На этом работу над графическим интерфейсом мы закончим.

Обработка параметров командной строки

Итак, теперь нам осталась самая интересная часть работы, а именно обработка параметров командной строки.
Скринсейвер в Windows может запускаться с тремя различными аргументами командной строки:

  • “/s” или “/S” – непосредственно для запуска скринсейвера
  • “/c:” – для вызова окна настройки скринсейвера, где — численное представление дескриптора родительского окна.
    “/p:” – для запуска скринсейвера в режиме предварительного просмотра, где — численное представление дескриптора родительского окна.

    Для обработки аргументов командной строки в wxWidgets существует класс wxCmdLineParser. Его мы и будем использовать.

    1. bool wxMatrixEffectApp::OnInit()
    2. {
    3. #if defined(__WXMSW__) && !defined(__WXWINCE__)
    4.   wxCmdLineParser parser(argc, argv);
    5.   parser.AddSwitch(wxT("S"), wxEmptyString,
    6.     wxEmptyString, wxCMD_LINE_PARAM_OPTIONAL);
    7.   parser.AddSwitch(wxT("s"), wxEmptyString,
    8.     wxEmptyString, wxCMD_LINE_PARAM_OPTIONAL);
    9.   parser.AddOption(wxT("c"), wxEmptyString,
    10.     wxEmptyString, wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL);
    11.   parser.AddOption(wxT("p"), wxEmptyString,
    12.     wxEmptyString, wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL);
    13.   if(parser.Parse(false) == 0)
    14.   {
    15.     long parentHWND(0);
    16.     if(parser.Found(wxT("S")) || parser.Found(wxT("s")))
    17.     {
    18.       MatrixEffectMainFrame* mainWindow =
    19.         new MatrixEffectMainFrame( NULL );
    20.       mainWindow->ShowFullScreen(true);
    21.       return true;
    22.     }
    23.     else if(parser.Found(wxT("c")))
    24.     {
    25.       wxMessageBox(
    26.         _("No settings for this screensaver. For more information visit wxwidgets.info"));
    27.       return false;
    28.     }
    29.     else if(parser.Found(wxT("p"), &parentHWND))
    30.     {
    31.       wxWindow * parent = new wxWindow;
    32.       parent->SetHWND((HWND)parentHWND);
    33.       RECT r;
    34.       GetWindowRect((HWND)parent->GetHWND(), &r);
    35.       MatrixCanvas* mainWindow = new MatrixCanvas(
    36.         parent, ID_MATRIXEFFECTMAINFRAME,
    37.         wxPoint(0,0), wxSize(r.right-r.left, r.bottom-r.top),
    38.         wxNO_BORDER);
    39.       mainWindow->SetPreviewMode(true);
    40.       SetTopWindow(mainWindow);
    41.       mainWindow->Show(true);
    42.       return true;
    43.     }
    44.   }
    45.   return false;
    46. #else
    47.   MatrixEffectMainFrame* mainWindow =
    48.     new MatrixEffectMainFrame( NULL );
    49.   SetTopWindow(mainWindow);
    50. #if defined(__WXWINCE__)
    51.   mainWindow->Show(true);
    52. #else
    53.   mainWindow->ShowFullScreen(true);
    54. #endif
    55.   return true;
    56. #endif
    57. }

    * This source code was highlighted with Source Code Highlighter.

    Как видно из кода, в методе OnInit() класса приложения мы создаем объект wxCmdLineParser, добавляем описания всех возможных параметров:

    • Метод AddSwitch() указывает что командная строка может содержать аргумент вида /<argument> или —<argument> без дополнительных параметров.
    • Метод AddOption() указывает, что командная строка может содержать аргумент вида /<argument>=<value> или —= или /<argument>:<value>, т.е. параметр командной строки, содержащий значение (строковое, численное, дату).

      После настройки объекта wxCmdLineParser происходит вызов метода Parse(), который проверяет соответствие командной строки указанным настройкам.
      Затем происходит проверка наличия параметров.

      • Если программа запущена с параметром “/s” или “/S”, то отображается главное окно приложения.
      • Если программа запущена с параметром “/c”, то отображается окно сообщения о том, что наш скринсейвер не требует настройки.
      • Если программа запущена с параметром “/p”, то извлекается дескриптор родительского окна, из него создается объект wxWindow и вместо главного окна приложения создается компонент MatrixCanvas, который размещается на родительском окне.

      Еще раз посмотрим на код, создающий wxWindow из дескриптора окна:
      wxWindow * parent = new wxWindow;
      parent->SetHWND((HWND)parentHWND);

      На самом деле это очень полезный прием и может быть использован для взаимодействия wxWidgets-приложений с приложениями, написанными с помощью других библиотек.
      Ну вот, собственно и все. В результате мы получим что-то вроде этого:

      PDF-версия статьи
      Исходный код примера, проект для Win32 и Windows Mobile, исполняемый файл для Win32.

Сегодня попытаемся написать простенькую заставку для ОС Windows на языке программирования C#. Прошу меня строго не судить, т.к. я не специализируюсь в программировании на данном языке. Иногда, для разнообразия, пишу по мелочам на нем ;-).

В качестве среды разработке мы воспользуемся Microsoft Visual Studio 2015. А также нам понадобятся такие файлы как изображение для нашего логотипа (например, размером 300×300 пикселей и в формате png) и любая иконка (16×16 пикселей) в формате ico.

Я не буду описывать все подробно, а остановлюсь лишь на важных моментах. Для удобства исходный код максимально покрою комментариями.

Итак, при первом запуске среды разработки создаем приложение типа Windows Forms Application. Для названия проекта и его решения я выбрал следующее название — SampleScreenSaver. Имя файла основной формы мы изменим на FormMain. Дополнительно добавим еще одну форму в наш проект и назовем ее как FormConfigure.

Свойства формы FormMain

Название свойства Значение свойства
(Name) frmMain
FormBorderStyle None
BackColor White
WindowState Maximized

Я не стал описывать подробнее указанные свойства, т.к. здесь все итак понятно. При возникновении вопросов всегда существует возможность воспользоваться справочной системой среды программирования (клавиша F1).

Пример ScreenSaver

Рис. 1. Пример расположения элементов управления на основной форме

Свойства элемента управления PictureBox

Название свойства Значение свойства
(Name) pbLogo
BackColor Transparent
Image ваш файл с изображением
SizeMode AutoSize

Пример моего изображения см. на рис. 1.

Свойства элемента управления Label

Название свойства Значение свойства
(Name) lblCopyright
ForeColor Silver
Text Программирование и не только

Свойства элемента управления Timer

Название свойства Значение свойства
(Name) tmrTimer
Interval 300

Свойства формы FormConfigure

Название свойства Значение свойства
(Name) FormConfigure
Icon ваш файл с иконкой
ShowInTaskbar false
Text Настройки
StartPosition CenterScreen
MaximizeBox false
MinimizeBox false

Сразу скажу, насчет формы FormConfigure, я не предусматриваю какие-либо настройки для нашей заставки. При желании вы самостоятельно сможете добавить свои настройки (например, сохранять значения настроек в реестре Windows).

Пришла очередь рассмотреть исходный код форм и нашего проекта.

Файл Program.cs.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SampleScreenSaver
{
static class Program
{
/// <summary>
/// Главная точка входа для приложения.
/// </summary>
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0)
{
if (args[0].ToLower().Trim().Substring(0, 2) == "/s")
{
// Отобразить заставку
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ShowScreenSaver();
Application.Run();
}
else if (args[0].ToLower().Trim().Substring(0, 2) == "/p")
{
// Предварительный просмотр заставки
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmMain(new IntPtr(long.Parse(args[1]))));
}
else if (args[0].ToLower().Trim().Substring(0, 2) == "/c")
{
// Отображение окна с настройками заставки
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FormConfigure());
}
}
else
{
// Отобразить заставку
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ShowScreenSaver();
Application.Run();
}
}

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

  • Download PixieSaver demo (requires .NET 2.0) — 25.5 Kb
  • Download source code for .NET 2.0 — 6.05 Kb
  • Download source code for .NET 1.1 — 6.06 Kb

Sample image

Introduction

Screensavers are nice. They don’t serve much of a practical purpose anymore now that monitors don’t «burn» images onto the screen, but they do turn your computer into a pretty decoration when you’re not around, and it’s a lot more healthy to be staring at screensavers all day than to be hooked on MMORPGs.

What’s not so nice about them is the way a lot of them behave, and the way they have to be written. Screensavers seem easy enough to set up; on Windows, all you have to do is to rename an .exe to .scr, and voila. But there’s a bunch of quirks along the way. Here are some of the most bothersome ones for users:

  • Multiple monitor support is often flakey, especially when monitors aren’t of equal sizes.
  • Previews often don’t show up properly in the Display Properties dialog box.
  • There’s often no Settings box, not even a really simple one.
  • Some screensavers use the «game loop» design, crippling background applications and causing a lot of modern CPUs to overheat.
  • Behavior in response to mouse and keyboard input is inconsistent; some screensavers don’t snap out until you actually click on the mouse.

And here are some of the troubles for developers:

  • You decide you want to use a timer to avoid pinning the CPU, maybe System.Timers.Timer, but on Windows 9x/2K/XP, it doesn’t provide enough precision to run a screensaver at even 30 frames per second.
  • Debugging screensavers can be a nightmare when they’re running in full screen.
  • You’ve written your whole screensaver in a Windows Form… only to realize that you have to use a native handle in order for it to show up in the preview window.
  • When you’re writing a screensaver, you want to get right down to the fun stuff while the inspiration’s still fresh in your mind. You don’t want to deal with boring initialization and shutdown code.

Screensavers are luxury items, not necessities. Any one of these problems is enough to discourage people from using them. There are so many awesome screensavers out there, yet screensavers as a whole category of apps is becoming less and less popular as computer setups become more diverse and problems are becoming more exposed.

The Screensaver base class attempts to lessen these problems and make screensaver development easier by providing a backbone that handles the mechanical aspects of writing screensavers. Its aim is not to create a whole new API, but simply to help make things work properly.

Using the code

A basic example

To avoid cluttering deployment, the Screensaver class and all of its satellite objects are contained in a single file, Screensaver.cs, which you can drop right in your project. Compiled with the default settings using Visual Studio or Microsoft’s C# compiler (csc.exe), it adds up to about 32 KB. Though some neat freaks may say that this is «bloated», I feel it’s a fair price to pay for consistent behavior and reusable code, two things that too many screensavers lack.

Here is a basic example screensaver using the Screensaver class:

class MyCoolScreensaver : Screensaver
{
   public MyCoolScreensaver()
   {
      Initialize += new EventHandler(MyCoolScreensaver_Initialize);
      Update += new EventHandler(MyCoolScreensaver_Update);
      Exit += new EventHandler(MyCoolScreensaver_Exit);
   }

   void MyCoolScreensaver_Initialize(object sender, EventArgs e)
   {
   }

   void MyCoolScreensaver_Update(object sender, EventArgs e)
   {
      Graphics0.Clear(Color.Black);
      Graphics0.DrawString(
         DateTime.Now.ToString(),
         SystemFonts.DefaultFont, Brushes.Blue,
         0, 0);
   }

   void MyCoolScreensaver_Exit(object sender, EventArgs e)
   {
   }

   [STAThread]
   static void Main()
   {
      Screensaver ss = new MyCoolScreensaver();
      ss.Run();
   }
}

And that’s it! This example prints the current time in the top left corner of the primary monitor, in blue. The Initialize event is fired right after the windows are created. Update is fired 30 times per second, by default; this speed can be changed by changing the value of Framerate. Exit is called just before the windows are closed.

Run mode

The run mode is determined automatically by Screensaver.Run(). Without any parameters, the screensaver will show the Settings window if the file extension is .scr; if it is .exe, it will show the screensaver in a window 9/10th the size of the screen.

You can change this by sending a ScreensaverMode value to Run(): Normal will start the screensaver in full screen, Settings will start the Settings dialog, and Windowed will start the screensaver in windowed mode. The value can’t be Preview; preview mode only works when the Windows Display Properties gives it the right parameters (the handle to the preview control to be specific). The run mode is overridden by the command line arguments which are passed to the screensaver by the Display Properties.

Rendering with System.Drawing

Graphics0 provides the System.Drawing.Graphics object for the primary window. To paint in the other windows, you can use Windows[n].Graphics, where n is the number of the monitor. Note that you are responsible for clearing the screen first.

Double buffering is enabled, by default, but to avoid excess overhead, it doesn’t actually kick in until the Window.Graphics object or the Window.DoubleBuffer property is first used. It’s a good idea to turn it off explicitly if you are using other rendering methods, such as DirectX. Double buffering is not as efficient in the .NET 1.1 version as it is in the .NET 2.0 version.

Installing the screensaver

To install a screensaver on Windows XP, all you have to do is to change the screensaver executable’s file extension to .scr, right click in the shell, and click Install. On older versions of Windows, the .scr goes into the Windows System32 folder. The same can be done on XP to have it always appear in the screensaver menu, but I prefer not to touch the Windows folder.

You can have Visual Studio automatically make a .scr copy of the executable, by adding this command to the post-build event under the project properties:

copy "$(TargetFileName)" "$(TargetName).scr" /y

A slightly more complex example with Direct3D

Any of the available rendering technologies can be used with the Screensaver class — DirectX, OpenGL, WPF (Avalon) etc.. Here is an example of using Managed Direct3D with the Screensaver class.

Setting up Managed Direct3D is not much trouble, assuming you have some knowledge of DirectX. If not, there are plenty of good tutorials out there that you can find with a Google search. Skim through a few, and study one that you like.

class MyCoolScreensaver : Screensaver
{
   public MyCoolScreensaver()
      : base(FullscreenMode.MultipleWindows)
   {
      Initialize += new EventHandler(MyCoolScreensaver_Initialize);
      Update += new EventHandler(MyCoolScreensaver_Update);
      Exit += new EventHandler(MyCoolScreensaver_Exit);
   }

   Device device;

   Microsoft.DirectX.Direct3D.Font font;
   int updates;

   void MyCoolScreensaver_Initialize(object sender, EventArgs e)
   {
      PresentParameters pp = new PresentParameters();

      if (this.Mode != ScreensaverMode.Normal)
         pp.Windowed = true;
      else
      {
         pp.Windowed = false;
         pp.BackBufferCount = 1;
         pp.BackBufferWidth =
            Manager.Adapters[Window0.DeviceIndex].CurrentDisplayMode.Width;
         pp.BackBufferHeight =
            Manager.Adapters[Window0.DeviceIndex].CurrentDisplayMode.Height;
         pp.BackBufferFormat = 
            Manager.Adapters[Window0.DeviceIndex].CurrentDisplayMode.Format;
      }

      pp.SwapEffect = SwapEffect.Flip;
      device = new Device(
         Window0.DeviceIndex, DeviceType.Hardware,
         Window0.Handle, CreateFlags.HardwareVertexProcessing, pp);
      
      Window0.DoubleBuffer = false;
      font = new Microsoft.DirectX.Direct3D.Font(
         device, System.Drawing.SystemFonts.DefaultFont);
   }

   void MyCoolScreensaver_Update(object sender, EventArgs e)
   {
      System.IO.StringWriter writer = new System.IO.StringWriter();
      writer.WriteLine("Time: " + DateTime.Now);
      writer.WriteLine("Achieved framerate: " + this.AchievedFramerate);
      writer.WriteLine("Update count: " + updates++);
      writer.WriteLine("Device: " + Window0.DeviceIndex);

      device.Clear(ClearFlags.Target, System.Drawing.Color.Black, 0, 0);

      device.BeginScene();

      font.DrawText(null, writer.ToString(), 0, 0, 
                    System.Drawing.Color.Blue.ToArgb());

      device.EndScene();
      device.Present();
   }

   void MyCoolScreensaver_Exit(object sender, EventArgs e)
   {
      device.Dispose();
   }

   [STAThread]
   static void Main()
   {
      Screensaver ss = new MyCoolScreensaver();
      ss.Run();
   }
}

To compile this example, you need the Managed DirectX Runtime. You can get the latest (as of May 2006) version here. Add references to Microsoft.DirectX, Microsoft.DirectX.Direct3D, and Microsoft.DirectX.Direct3DX.

Initializing DirectX for screensavers is, actually, somewhat easier than for a typical application, because you don’t have to worry about device resets and resizes and such. Just be sure to dispose the device at the end, or you’ll get erratic shutdowns.

Fullscreen mode

An overridden constructor of the Screensaver class takes in a FullscreenMode value: SingleWindow to cover all screens with one single window, and MultipleWindows to cover each screen with a window of its own. For DirectX, we want to use MultipleWindows, and draw in just one window. The default value is MultipleWindows, but in this example, I specify it explicitly just to demonstrate.

Note that ScreensaverMode.Normal is the only mode in which the screensaver will run in full screen mode. Though you can skip most of that full screen initialization and just run it as a windowed app, it’s a good idea to initialize full screen properly to get the extra performance boost.

The Window class

Just as Graphics0 is an alias to Windows[0].Graphics, Window0 is an alias to Windows[0]. The Screensaver.Window class encapsulates either a Windows form or just a window handle if the screensaver is running in preview mode. Various properties related to the graphical aspects are available in these objects.

Miscellaneous functionality

Default settings dialog box

  • Settings dialog — you can either use the default Settings dialog, which simply shows a message box with some text gathered from the assembly information, or you can show your own dialog box by overriding ShowSettingsDialog(). If you choose to stick with the default dialog, you can enter some additional text by setting the SettingsText property. In this example, I set it to my e-mail address.
  • Keyboard and mouse events — the full array of Windows Forms keyboard and mouse events are available in the Window class. Additionally, you can use the Form property to access the underlying Windows Form, but this value will not necessarily be set, so these are provided as well for convenience.
  • Frame rate settings — the AchievedFramerate property retrieves the actual number of frames processed in the past second, while the Framerate property sets the target frame rate. The Screensaver class uses the multimedia timer, so you can expect it to be quite precise. Note, however, that there is no frame skipping.
  • CloseOnClick, CloseOnMouseMove, CloseOnKeyboardInput — these properties can be set to change the conditions on which to close the screensaver. By default, in normal mode, all three of these properties are set to true.
  • Debug mode differences — to make life easier, certain behaviors are slightly different when the app is compiled in debug mode. Here are the differences for the current version:
    • Windows are not topmost in debug mode. This is so that you can see your debugger.
    • Run() offers to launch a debugger when the screensaver is started in preview mode.

PixieSaver sample application

PixieSaver is a simple, fully functional screensaver, written using the Screensaver base class, in around 150 lines of code including whitespace. It uses the SingleWindow full screen mode, and draws using System.Drawing. Each pixie starts at the bottom, flickering as it makes its way to the top. For cuteness value, each one has its own tendency to drift a little to the left or to the right. This is the screensaver that I’m using right now. I’m quite fond of it since I have my computer in my room, and flashy screensavers tend to keep me awake.

Let me know if you have any questions or suggestions, and let me know if you make any cool screensavers.

History

  • 2006.05.12 — First release.
  • 2006.05.16 — Uploaded .NET 1.1 compatible version of the code. Minor change in shutdown code to match the .NET 1.1 version; should not have any real effect.

This article, along with any associated source code and files, is licensed under The MIT License

The cows are here to take me home now…

  • Download PixieSaver demo (requires .NET 2.0) — 25.5 Kb
  • Download source code for .NET 2.0 — 6.05 Kb
  • Download source code for .NET 1.1 — 6.06 Kb

Sample image

Introduction

Screensavers are nice. They don’t serve much of a practical purpose anymore now that monitors don’t «burn» images onto the screen, but they do turn your computer into a pretty decoration when you’re not around, and it’s a lot more healthy to be staring at screensavers all day than to be hooked on MMORPGs.

What’s not so nice about them is the way a lot of them behave, and the way they have to be written. Screensavers seem easy enough to set up; on Windows, all you have to do is to rename an .exe to .scr, and voila. But there’s a bunch of quirks along the way. Here are some of the most bothersome ones for users:

  • Multiple monitor support is often flakey, especially when monitors aren’t of equal sizes.
  • Previews often don’t show up properly in the Display Properties dialog box.
  • There’s often no Settings box, not even a really simple one.
  • Some screensavers use the «game loop» design, crippling background applications and causing a lot of modern CPUs to overheat.
  • Behavior in response to mouse and keyboard input is inconsistent; some screensavers don’t snap out until you actually click on the mouse.

And here are some of the troubles for developers:

  • You decide you want to use a timer to avoid pinning the CPU, maybe System.Timers.Timer, but on Windows 9x/2K/XP, it doesn’t provide enough precision to run a screensaver at even 30 frames per second.
  • Debugging screensavers can be a nightmare when they’re running in full screen.
  • You’ve written your whole screensaver in a Windows Form… only to realize that you have to use a native handle in order for it to show up in the preview window.
  • When you’re writing a screensaver, you want to get right down to the fun stuff while the inspiration’s still fresh in your mind. You don’t want to deal with boring initialization and shutdown code.

Screensavers are luxury items, not necessities. Any one of these problems is enough to discourage people from using them. There are so many awesome screensavers out there, yet screensavers as a whole category of apps is becoming less and less popular as computer setups become more diverse and problems are becoming more exposed.

The Screensaver base class attempts to lessen these problems and make screensaver development easier by providing a backbone that handles the mechanical aspects of writing screensavers. Its aim is not to create a whole new API, but simply to help make things work properly.

Using the code

A basic example

To avoid cluttering deployment, the Screensaver class and all of its satellite objects are contained in a single file, Screensaver.cs, which you can drop right in your project. Compiled with the default settings using Visual Studio or Microsoft’s C# compiler (csc.exe), it adds up to about 32 KB. Though some neat freaks may say that this is «bloated», I feel it’s a fair price to pay for consistent behavior and reusable code, two things that too many screensavers lack.

Here is a basic example screensaver using the Screensaver class:

class MyCoolScreensaver : Screensaver
{
   public MyCoolScreensaver()
   {
      Initialize += new EventHandler(MyCoolScreensaver_Initialize);
      Update += new EventHandler(MyCoolScreensaver_Update);
      Exit += new EventHandler(MyCoolScreensaver_Exit);
   }

   void MyCoolScreensaver_Initialize(object sender, EventArgs e)
   {
   }

   void MyCoolScreensaver_Update(object sender, EventArgs e)
   {
      Graphics0.Clear(Color.Black);
      Graphics0.DrawString(
         DateTime.Now.ToString(),
         SystemFonts.DefaultFont, Brushes.Blue,
         0, 0);
   }

   void MyCoolScreensaver_Exit(object sender, EventArgs e)
   {
   }

   [STAThread]
   static void Main()
   {
      Screensaver ss = new MyCoolScreensaver();
      ss.Run();
   }
}

And that’s it! This example prints the current time in the top left corner of the primary monitor, in blue. The Initialize event is fired right after the windows are created. Update is fired 30 times per second, by default; this speed can be changed by changing the value of Framerate. Exit is called just before the windows are closed.

Run mode

The run mode is determined automatically by Screensaver.Run(). Without any parameters, the screensaver will show the Settings window if the file extension is .scr; if it is .exe, it will show the screensaver in a window 9/10th the size of the screen.

You can change this by sending a ScreensaverMode value to Run(): Normal will start the screensaver in full screen, Settings will start the Settings dialog, and Windowed will start the screensaver in windowed mode. The value can’t be Preview; preview mode only works when the Windows Display Properties gives it the right parameters (the handle to the preview control to be specific). The run mode is overridden by the command line arguments which are passed to the screensaver by the Display Properties.

Rendering with System.Drawing

Graphics0 provides the System.Drawing.Graphics object for the primary window. To paint in the other windows, you can use Windows[n].Graphics, where n is the number of the monitor. Note that you are responsible for clearing the screen first.

Double buffering is enabled, by default, but to avoid excess overhead, it doesn’t actually kick in until the Window.Graphics object or the Window.DoubleBuffer property is first used. It’s a good idea to turn it off explicitly if you are using other rendering methods, such as DirectX. Double buffering is not as efficient in the .NET 1.1 version as it is in the .NET 2.0 version.

Installing the screensaver

To install a screensaver on Windows XP, all you have to do is to change the screensaver executable’s file extension to .scr, right click in the shell, and click Install. On older versions of Windows, the .scr goes into the Windows System32 folder. The same can be done on XP to have it always appear in the screensaver menu, but I prefer not to touch the Windows folder.

You can have Visual Studio automatically make a .scr copy of the executable, by adding this command to the post-build event under the project properties:

copy "$(TargetFileName)" "$(TargetName).scr" /y

A slightly more complex example with Direct3D

Any of the available rendering technologies can be used with the Screensaver class — DirectX, OpenGL, WPF (Avalon) etc.. Here is an example of using Managed Direct3D with the Screensaver class.

Setting up Managed Direct3D is not much trouble, assuming you have some knowledge of DirectX. If not, there are plenty of good tutorials out there that you can find with a Google search. Skim through a few, and study one that you like.

class MyCoolScreensaver : Screensaver
{
   public MyCoolScreensaver()
      : base(FullscreenMode.MultipleWindows)
   {
      Initialize += new EventHandler(MyCoolScreensaver_Initialize);
      Update += new EventHandler(MyCoolScreensaver_Update);
      Exit += new EventHandler(MyCoolScreensaver_Exit);
   }

   Device device;

   Microsoft.DirectX.Direct3D.Font font;
   int updates;

   void MyCoolScreensaver_Initialize(object sender, EventArgs e)
   {
      PresentParameters pp = new PresentParameters();

      if (this.Mode != ScreensaverMode.Normal)
         pp.Windowed = true;
      else
      {
         pp.Windowed = false;
         pp.BackBufferCount = 1;
         pp.BackBufferWidth =
            Manager.Adapters[Window0.DeviceIndex].CurrentDisplayMode.Width;
         pp.BackBufferHeight =
            Manager.Adapters[Window0.DeviceIndex].CurrentDisplayMode.Height;
         pp.BackBufferFormat = 
            Manager.Adapters[Window0.DeviceIndex].CurrentDisplayMode.Format;
      }

      pp.SwapEffect = SwapEffect.Flip;
      device = new Device(
         Window0.DeviceIndex, DeviceType.Hardware,
         Window0.Handle, CreateFlags.HardwareVertexProcessing, pp);
      
      Window0.DoubleBuffer = false;
      font = new Microsoft.DirectX.Direct3D.Font(
         device, System.Drawing.SystemFonts.DefaultFont);
   }

   void MyCoolScreensaver_Update(object sender, EventArgs e)
   {
      System.IO.StringWriter writer = new System.IO.StringWriter();
      writer.WriteLine("Time: " + DateTime.Now);
      writer.WriteLine("Achieved framerate: " + this.AchievedFramerate);
      writer.WriteLine("Update count: " + updates++);
      writer.WriteLine("Device: " + Window0.DeviceIndex);

      device.Clear(ClearFlags.Target, System.Drawing.Color.Black, 0, 0);

      device.BeginScene();

      font.DrawText(null, writer.ToString(), 0, 0, 
                    System.Drawing.Color.Blue.ToArgb());

      device.EndScene();
      device.Present();
   }

   void MyCoolScreensaver_Exit(object sender, EventArgs e)
   {
      device.Dispose();
   }

   [STAThread]
   static void Main()
   {
      Screensaver ss = new MyCoolScreensaver();
      ss.Run();
   }
}

To compile this example, you need the Managed DirectX Runtime. You can get the latest (as of May 2006) version here. Add references to Microsoft.DirectX, Microsoft.DirectX.Direct3D, and Microsoft.DirectX.Direct3DX.

Initializing DirectX for screensavers is, actually, somewhat easier than for a typical application, because you don’t have to worry about device resets and resizes and such. Just be sure to dispose the device at the end, or you’ll get erratic shutdowns.

Fullscreen mode

An overridden constructor of the Screensaver class takes in a FullscreenMode value: SingleWindow to cover all screens with one single window, and MultipleWindows to cover each screen with a window of its own. For DirectX, we want to use MultipleWindows, and draw in just one window. The default value is MultipleWindows, but in this example, I specify it explicitly just to demonstrate.

Note that ScreensaverMode.Normal is the only mode in which the screensaver will run in full screen mode. Though you can skip most of that full screen initialization and just run it as a windowed app, it’s a good idea to initialize full screen properly to get the extra performance boost.

The Window class

Just as Graphics0 is an alias to Windows[0].Graphics, Window0 is an alias to Windows[0]. The Screensaver.Window class encapsulates either a Windows form or just a window handle if the screensaver is running in preview mode. Various properties related to the graphical aspects are available in these objects.

Miscellaneous functionality

Default settings dialog box

  • Settings dialog — you can either use the default Settings dialog, which simply shows a message box with some text gathered from the assembly information, or you can show your own dialog box by overriding ShowSettingsDialog(). If you choose to stick with the default dialog, you can enter some additional text by setting the SettingsText property. In this example, I set it to my e-mail address.
  • Keyboard and mouse events — the full array of Windows Forms keyboard and mouse events are available in the Window class. Additionally, you can use the Form property to access the underlying Windows Form, but this value will not necessarily be set, so these are provided as well for convenience.
  • Frame rate settings — the AchievedFramerate property retrieves the actual number of frames processed in the past second, while the Framerate property sets the target frame rate. The Screensaver class uses the multimedia timer, so you can expect it to be quite precise. Note, however, that there is no frame skipping.
  • CloseOnClick, CloseOnMouseMove, CloseOnKeyboardInput — these properties can be set to change the conditions on which to close the screensaver. By default, in normal mode, all three of these properties are set to true.
  • Debug mode differences — to make life easier, certain behaviors are slightly different when the app is compiled in debug mode. Here are the differences for the current version:
    • Windows are not topmost in debug mode. This is so that you can see your debugger.
    • Run() offers to launch a debugger when the screensaver is started in preview mode.

PixieSaver sample application

PixieSaver is a simple, fully functional screensaver, written using the Screensaver base class, in around 150 lines of code including whitespace. It uses the SingleWindow full screen mode, and draws using System.Drawing. Each pixie starts at the bottom, flickering as it makes its way to the top. For cuteness value, each one has its own tendency to drift a little to the left or to the right. This is the screensaver that I’m using right now. I’m quite fond of it since I have my computer in my room, and flashy screensavers tend to keep me awake.

Let me know if you have any questions or suggestions, and let me know if you make any cool screensavers.

History

  • 2006.05.12 — First release.
  • 2006.05.16 — Uploaded .NET 1.1 compatible version of the code. Minor change in shutdown code to match the .NET 1.1 version; should not have any real effect.

This article, along with any associated source code and files, is licensed under The MIT License

The cows are here to take me home now…

Screensaver is normal *.exe file. Just change extension to *.scr, and you will be able to install it.
However you should handle 3 command line arguments to show it correctly in settings.

s — Start screen saver in full screen (usual case). This is when you don’t move your mouse and keyboard for a while, or if user click preview in screen saver settings, or if user double click screen saver file.

c or no argument at all — Show settings window specific for your screen saver. This is when user will click «settings» button in «Screen Saver Settings» window. You can just display MessageBox saying that there are no settings.

p 1234 — Run screen saver in preview window. This is when user will open Screen Saver Settings window, and select your screen saver. In this case you should display your screen saver in window 1234 (this number is just example). Number will be in decimal. Convert it to int, and cast to HWND, and use for rendering.
Some people like to create they own window, and place it over window give to you by Windows. Both options have some downsides.

Example. For simplicity I didn’t do any error checking, and you should really do error checking:

#include <Windows.h>
#include <d3d11.h>
#include <string>

/// DirectX 11 lib (works only in Visual Studio, in other compilers add this lib in linker settings)
/// this is just for DirectX. Skip if you are going to use different API.
#pragma comment(lib, "d3d11")


/// function for handling window messages
LRESULT WINAPI wndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ){
    switch(msg){
        case WM_MOUSEMOVE:{
            int x= LOWORD( lParam ); /// new mouse position
            int y= HIWORD( lParam );
            static int startX; /// mouse position at start of screen saver
            static int startY;
            static int timesCalled= 0; /// WM_MOUSEMOVE message is sent at creation of window, and later every time user move mouse
            if( timesCalled < 1 ){ /// remember starting position at first call
                startX= x;
                startY= y;
            }
            else if( startX != x && startY != y ){ /// if mouse was moved to different position, then exit
                ::PostQuitMessage( 0 );
            }
            timesCalled++;
        }break;
        case WM_KEYDOWN:{
            ::PostQuitMessage( 0 ); /// exit when user press any key
        }break;
        case WM_DESTROY:{ /// standard exiting from winapi window
            ::PostQuitMessage(0);
            return 0;}
    }
    return ::DefWindowProc(hWnd, msg, wParam, lParam);
}


/// starting point
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ){
    int width;
    int height;
    HWND hwnd= NULL;
    const char* windowClassName= "simpleScreenSaverInDirectX11";
    MSG msg= {};
    WNDCLASSEX wc= {};
    bool isInPreviewWindow= false; /// are we in preview in screen saver settings window
    
    /// variables for directX (ignore if you are planning to use different API or library)
    ID3D11Device* device= NULL;
    ID3D11DeviceContext* context= NULL;
    IDXGISwapChain* swapChain= NULL;
    ID3D11RenderTargetView* renderTargetView= NULL;

    /// read command line arguments
    {
        bool showSettingsDialog= true;

        /// read command line
        std::string s= std::string(lpCmdLine).substr( 0, 2 ); /// first 2 letters from command line argument
        if( s=="\c" || s=="\C" || s=="/c" || s=="/C" || s=="" ){
            showSettingsDialog= true;
        }else if( s=="\s" || s=="\S" || s=="/s" || s=="/S" ){
            showSettingsDialog= false;
        }else if( s=="\p" || s=="\P" || s=="/p" || s=="/P" ){
            showSettingsDialog= false;
            isInPreviewWindow= true;
            hwnd= (HWND)atoi(lpCmdLine+3);
        }

        /// show screen server settings window
        if( showSettingsDialog ){
            ::MessageBox( NULL, "There are no settings for this", "Info", MB_OK );
            return 0;
        }
    }

    /// check are we the only instance
    /// sometimes windows starts screen saver multiple times over time
    if( !isInPreviewWindow && FindWindow( windowClassName, NULL ) ){
        return -1;
    }

    /// register windows class
    if( !isInPreviewWindow ){
        wc= {sizeof(WNDCLASSEX), CS_CLASSDC, wndProc, 0, 0, hInstance, NULL, NULL, NULL, NULL, windowClassName, NULL};
        ::RegisterClassEx( &wc );
    }

    /// create window or read size
    if( !isInPreviewWindow ){
        width= GetSystemMetrics(SM_CXSCREEN);
        height= GetSystemMetrics(SM_CYSCREEN);
        hwnd= ::CreateWindow( wc.lpszClassName, "DirectX 11 Screensaver", WS_POPUP|WS_VISIBLE, 0, 0, width, height, NULL, NULL, wc.hInstance, NULL );
    }else{
        RECT rc; GetWindowRect( hwnd, &rc );
        width= rc.right - rc.left;
        height= rc.bottom - rc.top;
    }

    /// init DirectX (ignore if you are planning to use different API or library)
    {
        DXGI_SWAP_CHAIN_DESC swapChainDesc= {};
        swapChainDesc.BufferCount= 2;
        swapChainDesc.BufferDesc.Width= 0;
        swapChainDesc.BufferDesc.Height= 0;
        swapChainDesc.BufferDesc.Format= DXGI_FORMAT_R8G8B8A8_UNORM;
        swapChainDesc.BufferDesc.RefreshRate.Numerator= 60;
        swapChainDesc.BufferDesc.RefreshRate.Denominator= 1;
        swapChainDesc.Flags= DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
        swapChainDesc.BufferUsage= DXGI_USAGE_RENDER_TARGET_OUTPUT;
        swapChainDesc.SampleDesc.Count= 1;
        swapChainDesc.SampleDesc.Quality= 0;
        swapChainDesc.Windowed= TRUE;
        swapChainDesc.SwapEffect= DXGI_SWAP_EFFECT_DISCARD;
        swapChainDesc.OutputWindow= hwnd;

        D3D_FEATURE_LEVEL featureLevel;
        const D3D_FEATURE_LEVEL featureLevelArray[]= {D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1, };
        D3D11CreateDeviceAndSwapChain( NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_DEBUG, featureLevelArray, 3, D3D11_SDK_VERSION, &swapChainDesc, &swapChain, &device, &featureLevel, &context);

        ID3D11Texture2D* pBackBuffer;
        swapChain->GetBuffer( 0, IID_PPV_ARGS( &pBackBuffer ) );
        device->CreateRenderTargetView( pBackBuffer, NULL, &renderTargetView );
        pBackBuffer->Release( );

        D3D11_VIEWPORT viewport;
        viewport.Width= float( width );
        viewport.Height= float( height );
        viewport.MinDepth= 0;
        viewport.MaxDepth= 1;
        viewport.TopLeftX= 0;
        viewport.TopLeftY= 0;
        context->RSSetViewports( 1u, &viewport );
    }

    /// show window and hide cursor
    if( !isInPreviewWindow ){
        ::ShowWindow( hwnd, SW_SHOWDEFAULT );
        ::UpdateWindow( hwnd );
        ::ShowCursor( false );
    }

    /// main loop
    while( msg.message != WM_QUIT ){
        if( ::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) ){
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
            continue;
        }

        /// draw single color on whole window
        float clear_color[]= { (rand()%100)/100.0, (rand()%100)/100.0, (rand()%100)/100.0, 0.0 };
        context->ClearRenderTargetView( renderTargetView, (float*)clear_color );
        if( swapChain->Present( 1, 0 ) != S_OK )
            break; /// probably we were in preview and user have closed settings window. exiting.
    }

    /// shutdown
    renderTargetView->Release();
    swapChain->Release();
    context->Release();
    device->Release();
    if( !isInPreviewWindow ){
        ::ShowCursor( true );
        ::DestroyWindow( hwnd );
        ::UnregisterClass( windowClassName, hInstance );
    }
    return msg.wParam;
}

If you want to display your screensaver on multiple monitors you have to create multiple windows, one for each monitor, and render each window separately (sometimes you can share resources between windows). Exactly like in normal application using multiple monitors.

Понравилась статья? Поделить с друзьями:
  • Как написать скоро увидимся
  • Как написать скорлупа
  • Как написать скорейшего выздоровления своими словами
  • Как написать скорейшего выздоровления или выздоравления
  • Как написать скорейшего вам выздоровления