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!
WS_EX_LAYERED
andWS_EX_TRANSPARENT
might be just what you're looking for. – Caresse