How to create a snowstorm on your Windows desktop?
Asked Answered
D

1

11

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?

Donough answered 11/1, 2011 at 13:47 Comment(6)
I can satisfy all 5 of the required features by leaving my computer on the steps in front of my house in NYC this evening.Landtag
@Larry Lustig - point taken, however I was hoping for a more software solution, what with posting it on SO and all...Donough
To the naysayer (close voter) - what don't you understand? Shall I clarify more?Donough
Were layered windows around when you did this in your "early days"? A window the extended style flags WS_EX_LAYERED and WS_EX_TRANSPARENT might be just what you're looking for.Caresse
@Cody Gray - no, they were not. That was 10 years ago and I had barely begun my path down the code road. :)Donough
This seems like quite a tall order to address in a single question and answer. Perhaps you could try to break this out into smaller questions, like a separate question for the desired click-through behavior, etc.Massotherapy
H
6

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!

Hames answered 11/1, 2011 at 20:19 Comment(3)
I see that your trick too is WS_EX_TRANSPARENT (aka layered windows). Thanks for confirming that this works! :)Donough
Any idea how to make it in Java?Conterminous
@Conterminous - That is a much trickier question than can be answered in a comment. Why don't you post a separate question?Hames

© 2022 - 2024 — McMap. All rights reserved.