Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a snowstorm on your Windows desktop?

Practical uses aside, how (if it is possible at all) could you create a "snowing" effect on your desktop PC running Windows? Preferably with nothing but raw C/C++ and WinAPI.

The requirements for the snow are:

  • Appears over everything else shown (Note: always-on-top windows may get on top of snow still, that's OK. I understand that there can be no "absolute on top" flag for any app)
  • Snowflakes are small, possibly simple dots or clusters of a few white pixels;
  • Does not bother working with the computer (clicking a snowflake sends the click through to the underlying window);
  • Plays nicely with users dragging windows;
  • Multi-monitor capable.

Bonus points for any of the following features:

  • Snow accumulates on the lower edge of the window or the taskbar (if it's at the bottom of the screen);
  • Snow accumulates also on top-level windows. Or perhaps some snow accumulates, some continues down, accumulating on every window with a title bar;
  • Snow accumulated on windows gets "shaken off" when windows are dragged;
  • Snow accumulated on taskbar is aware of the extended "Start" button under Vista/7.
  • Snowflakes have shadows/outlines, so they are visible on white backgrounds;
  • Snowflakes have complex snowflike-alike shapes (they must still be tiny).
  • Clicking on a snowflake does send the click through to the underlying window, but the snowflake evaporates with a little cool animation;

Most of these effects are straightforward enough, except the part where snow is click-through and plays nicely with dragging of windows. In my early days I've made an implementation that draws on the HDC you get from GetDesktopWindow(), which was click-through, but had problems with users dragging windows (snowflakes rendered on them got "dragged along").

The solution may use Vista/7 Aero features, but, of course, a universal solution is preferred. Any ideas?

like image 883
Vilx- Avatar asked Jan 11 '11 13:01

Vilx-


1 Answers

For the sake of brevity and simplicity, this answer has been trimmed to a limited set of requirements. It is trivial to expand this and make it more robust.

This answer uses WPF on Windows XP. It should work on up to 2 monitors, and should work on other Windows systems as well.

It starts with a simple window:

<Window x:Class="TestDump.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" WindowStartupLocation="Manual" Loaded="Window_Loaded"
WindowStyle="None" AllowsTransparency="True" Background="Transparent"
>
    <Grid x:Name="FieldOfSnow"/>
</Window>

To this window, we will add Snowflakes defined as follows:

<UserControl x:Class="TestDump.SnowFlake"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="5" Width="5">
    <Border Background="White" CornerRadius="2" BorderThickness="1" BorderBrush="LightGray"/>
</UserControl>

The snowflakes have the default UserControl code-behind with no changes.

Finally, the Window CodeBehind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
using System.Windows.Interop;

namespace TestDump
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private TimeSpan _lastRender;

        public Window1()
        {
            InitializeComponent();
            _lastRender = TimeSpan.FromTicks(0);
            CompositionTarget.Rendering += SnowflakeTick;
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.Topmost = true;
            this.Top = 0;
            this.Left = 0;

            this.Width = System.Windows.SystemParameters.PrimaryScreenWidth;
            this.Height = System.Windows.SystemParameters.PrimaryScreenHeight;

            if (System.Windows.Forms.SystemInformation.MonitorCount == 2)
            {
                System.Drawing.Rectangle SecondScreenArea = System.Windows.Forms.Screen.AllScreens[1].Bounds;

                this.Width += SecondScreenArea.Width;
                this.Height = this.Height > SecondScreenArea.Height ? this.Height : SecondScreenArea.Height;
            }
        }

        public const int WS_EX_TRANSPARENT = 0x00000020;
        public const int GWL_EXSTYLE = (-20);

        [DllImport("user32.dll")]
        public static extern int GetWindowLong(IntPtr hwnd, int index);

        [DllImport("user32.dll")]
        public static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);

        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);

            // Get this window's handle
            IntPtr hwnd = new WindowInteropHelper(this).Handle;

            // Change the extended window style to include WS_EX_TRANSPARENT
            int extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
            SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT);
        }

        List<TranslateTransform> Flakes = new List<TranslateTransform>();
        Random rand = new Random();

        private void SnowflakeTick(object sender, EventArgs e)
        {
            RenderingEventArgs renderingArgs = (RenderingEventArgs)e;
            TimeSpan dTime = (renderingArgs.RenderingTime - _lastRender);
            double deltaTime = dTime.TotalMilliseconds;
            _lastRender = renderingArgs.RenderingTime;

            if ( _lastRender.Milliseconds < deltaTime)
            {
                TranslateTransform SnowPos = new TranslateTransform(this.Width * rand.Next(1000) / 1000.0 - this.Width/2, -this.Height/2);

                SnowFlake sf = new SnowFlake();
                sf.RenderTransform = SnowPos;

                // The flakes are centered when added, so all positions must be translated with this in mind.
                FieldOfSnow.Children.Add(sf);
                Flakes.Add(SnowPos);
            }

            foreach (TranslateTransform Flake in Flakes)
            {
                double ScreenHeight = this.Height / 2 - 2;

                if (Flake.Y < ScreenHeight)
                {
                    Flake.Y += deltaTime / 50;
                }
            }
        }
    }
}

I had to use a bit of forms code to get the multiscreen stuff, and I had to include the references to the Assemblies in my project.

I haven't tested it much, but it does work on my system, and the snow sits at the bottom of the screen when done.

I used this reference for the click through behavior.

A more dedicated fellow than I should be able to adapt this, plus some edge detection to the task of getting the snow to sit elsewhere.

Please be aware that snowflakes are never cleaned up in this example, and after running it long enough, you may notice some slowdown.

Have Fun!

like image 91
Andres Avatar answered Oct 01 '22 03:10

Andres