CountdownTimer
MainWindow.xaml
ボタンのコンテンツをApp.configから直でバインドしてるあたりがダメダメだが面倒なのでそのままになっている。
TaskbarItemInfoはタスクバー上のボタンに進捗を表示するためのもの。
不要なコメントが散見されるが試行錯誤の後なので、そのままにしておく。
<Window x:Class="CountdownTimer.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:CountdownTimer.ViewModel" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:p="clr-namespace:CountdownTimer.Properties" Title="{Binding CounterText, StringFormat=\{0\} Countdown Timer}" Width="320" Height="200" Background="Black" Icon="Stopwatch.ico"> <Window.DataContext> <vm:MainViewModel /> </Window.DataContext> <Window.Resources> <!--<sys:Int32 x:Key="min1">1</sys:Int32> <sys:Int32 x:Key="min2">3</sys:Int32> <sys:Int32 x:Key="min3">5</sys:Int32> <sys:Int32 x:Key="min4">25</sys:Int32>--> <!--<p:Settings x:Key="settings" />--> </Window.Resources> <Window.TaskbarItemInfo> <TaskbarItemInfo ProgressState="{Binding ProgressState, Mode=OneWay}" ProgressValue="{Binding ProgressValue, Mode=OneWay}" /> </Window.TaskbarItemInfo> <Grid Margin="5"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="4*" /> <RowDefinition Height="4*" /> <RowDefinition Height="4*" /> </Grid.RowDefinitions> <Viewbox Grid.Row="0" Grid.ColumnSpan="3" Margin="-15" HorizontalAlignment="Right" VerticalAlignment="Center"> <Label Content="{Binding Path=CounterText, UpdateSourceTrigger=PropertyChanged}" Foreground="Lime" FontFamily="OCR A" /> </Viewbox> <Viewbox Grid.Row="1" Grid.ColumnSpan="3" Margin="-15" HorizontalAlignment="Right" VerticalAlignment="Center"> <Label Content="{Binding Path=EstimatedTimeText, UpdateSourceTrigger=PropertyChanged}" Foreground="LightGray" FontFamily="OCR A" /> </Viewbox> <Button Grid.Row="0" Grid.Column="3" Margin="5" Content="中止" Command="{Binding StopCommand}" /> <!--<Button Grid.Row="1" Grid.Column="3" Margin="5">一時停止</Button>--> <!--<Button Grid.Row="2" Grid.Column="0" Margin="5" Command="{Binding StartCommand}" CommandParameter="{StaticResource min1}" Content="{StaticResource min1}" ContentStringFormat="{}{0:0}分" />--> <!--<Button Grid.Row="2" Grid.Column="0" Margin="5" Command="{Binding StartCommand}" CommandParameter="{Binding Source={StaticResource settings}, Path=Default.Min1}" Content="{Binding Source={StaticResource settings}, Path=Default.Min1}" ContentStringFormat="{}{0:0}分" />--> <Button Grid.Row="2" Grid.Column="0" Margin="5" Command="{Binding StartCommand}" CommandParameter="{Binding Source={x:Static p:Settings.Default}, Path=Min1}" Content="{Binding Source={x:Static p:Settings.Default}, Path=Min1}" ContentStringFormat="{}{0:0}分" /> <Button Grid.Row="2" Grid.Column="1" Margin="5" Command="{Binding StartCommand}" CommandParameter="{Binding Source={x:Static p:Settings.Default}, Path=Min2}" Content="{Binding Source={x:Static p:Settings.Default}, Path=Min2}" ContentStringFormat="{}{0:0}分" /> <Button Grid.Row="2" Grid.Column="2" Margin="5" Command="{Binding StartCommand}" CommandParameter="{Binding Source={x:Static p:Settings.Default}, Path=Min3}" Content="{Binding Source={x:Static p:Settings.Default}, Path=Min3}" ContentStringFormat="{}{0:0}分" /> <Button Grid.Row="2" Grid.Column="3" Margin="5" Command="{Binding StartCommand}" CommandParameter="{Binding Source={x:Static p:Settings.Default}, Path=Min4}" Content="{Binding Source={x:Static p:Settings.Default}, Path=Min4}" ContentStringFormat="{}{0:0}分" /> </Grid> </Window>
MainWindow.xaml.cs
他の作業中にカウントダウン終了だからといって前面に出てくるのは邪魔なので、代わりに点滅させている。
点滅はアンマネージのFlashWindowExを使用している。
ウィンドウがアクティブの場合は点滅しない。アクティブになるまで繰り返し点滅させる。
usingは整理してないから多分不要なのがある。
これはコードビハインドでやっていい処理だと思う。が、ピュアMVVMでやる方法があれば知りたいところ。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; 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.Windows.Shell; using CountdownTimer.Helper; using CountdownTimer.ViewModel; namespace CountdownTimer { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { #region コンストラクタ /// <summary> /// クラスのインスタンスを初期化します。 /// </summary> public MainWindow() { InitializeComponent(); helper = new FlashWindowHelper(Application.Current); viewModel = this.DataContext as MainViewModel; viewModel.Completed += ViewModel_Completed; this.Activated += MainWindow_Activated; //foreach (var item in Fonts.SystemFontFamilies) { // System.Diagnostics.Debug.WriteLine(item); //} } #endregion #region 変数 /// <summary> /// タスクバーボタンの点滅処理用ヘルパー(Win32API) /// </summary> private FlashWindowHelper helper; /// <summary> /// 点滅停止用フラグ /// </summary> private bool preventBlink = true; /// <summary> /// ViewModel /// </summary> private MainViewModel viewModel; #endregion #region メソッド /// <summary> /// タスクバーボタンの点滅を開始します。 /// </summary> private async void StartBlink() { if (this.IsActive || viewModel.IsEnabled) { return; } preventBlink = false; while (!preventBlink && !viewModel.IsEnabled) { helper.FlashApplicationWindow(); await Task.Run(() => Thread.Sleep(5000)); } helper.StopFlashing(); } /// <summary> /// タスクバーボタンの点滅を終了します。 /// </summary> private void StopBlink() { preventBlink = true; helper.StopFlashing(); } #endregion #region イベントハンドラー /// <summary> /// ウィンドウが前面ウィンドウになるときのイベントハンドラー /// </summary> /// <param name="sender">イベントのソース。</param> /// <param name="e">イベント データを格納している EventArgs。</param> void MainWindow_Activated(object sender, EventArgs e) { StopBlink(); } /// <summary> /// タイマーが終了するときのイベントハンドラー /// </summary> /// <param name="sender">イベントのソース。</param> /// <param name="e">イベント データを格納している EventArgs。</param> void ViewModel_Completed(object sender, EventArgs e) { StartBlink(); } #endregion } }
MainViewModel.cs
VM。モデルはDispatcherTimer。前述の点滅用にCompletedイベントがある。BindableBase、RelayCommandは拾い物(後述)。特筆するような点はない。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Shell; using System.Windows.Threading; using CountdownTimer.Common; namespace CountdownTimer.ViewModel { public class MainViewModel : BindableBase { #region イベント /// <summary> /// タイマーが終了すると発生します。 /// </summary> public event EventHandler<EventArgs> Completed; #endregion #region 変数 /// <summary> /// タイマー /// </summary> DispatcherTimer timer; /// <summary> /// カウントダウン開始時刻 /// </summary> DateTime startTime; #endregion #region コンストラクタ /// <summary> /// クラスのインスタンスを初期化します。 /// </summary> public MainViewModel() { timer = new DispatcherTimer(); timer.Interval = new TimeSpan(0, 0, 1); timer.Tick += new EventHandler(timer_Tick); counter = TimeSpan.Zero; } #endregion #region プロパティ private TimeSpan counter; /// <summary> /// カウントダウン用カウンターの値を取得します。 /// </summary> public TimeSpan Counter { get { return counter; } private set { counter = value; this.OnPropertyChanged("CounterText"); this.OnPropertyChanged("ProgressValue"); } } /// <summary> /// カウンターの表示用文字列を取得します。 /// </summary> public string CounterText { get { return this.Counter.ToString(@"h\:mm\:ss"); } } /// <summary> /// 経過時間の合計を取得します。 /// </summary> public TimeSpan Elapsed { get { if (timer.IsEnabled) { return DateTime.Now - startTime; } return TimeSpan.Zero; } } /// <summary> /// カウントダウン終了予定時刻 /// </summary> public string EstimatedTimeText { get { if (timer.IsEnabled) { return startTime.AddMinutes(PresetMinutes).ToString("H:mm:ss"); } else { return "--:--:--"; } } } /// <summary> /// タイマーが実行されているかどうかを示す値を取得します。 /// </summary> public bool IsEnabled { get { return timer.IsEnabled; } } /// <summary> /// 設定時間(分)を取得します。 /// </summary> public int PresetMinutes { get; private set; } /// <summary> /// Windows タスク バーのプログレス インジケーターの状態に指定する値を取得します。 /// </summary> public TaskbarItemProgressState ProgressState { get { if (timer.IsEnabled) { System.Diagnostics.Debug.WriteLine("**** ProgressState Normal"); return TaskbarItemProgressState.Normal; } System.Diagnostics.Debug.WriteLine("**** ProgressState None"); return TaskbarItemProgressState.None; } } /// <summary> /// タスク バー ボタンでプログレス インジケーターの完了状態を示す値を取得します。 /// </summary> public double ProgressValue { get { return 1d - Counter.TotalSeconds / (PresetMinutes * 60d); } } #endregion #region コマンド // ウィンドウのInitializeComponent()あたりでCanExecute用のパラメータがnullで来てしまうのでnullableにしている(未調査) private RelayCommand<int?> startCommand; public RelayCommand<int?> StartCommand { get { return startCommand = startCommand ?? new RelayCommand<int?>(Start, CanStart); } } private RelayCommand stopCommand; public RelayCommand StopCommand { get { return stopCommand = stopCommand ?? new RelayCommand(Stop, CanStop); } } #endregion #region メソッド /// <summary> /// 開始可能かどうかを示す値を取得します。 /// </summary> /// <param name="minutes">使用しません。</param> /// <returns>開始可能の場合はtrue。それ以外はfalse。</returns> public bool CanStart(int? minutes) { return !timer.IsEnabled; } /// <summary> /// 中止可能かどうかを示す値を取得します。 /// </summary> /// <returns>中止可能の場合はtrue。それ以外はfalse。</returns> public bool CanStop() { return timer.IsEnabled; } /// <summary> /// Completedイベントを発行します。 /// </summary> protected virtual void OnCompleted() { Completed(this, new EventArgs()); } /// <summary> /// カウントダウンを開始します。 /// </summary> /// <param name="minutes">カウントダウンする時間(分)</param> public void Start(int? minutes) { startTime = DateTime.Now; //// TODO 実験 //startTime = DateTime.Now.AddSeconds(-50); var m = minutes.Value; PresetMinutes = m; Counter = new TimeSpan(0, m, 0); timer.Start(); this.OnPropertyChanged("EstimatedTimeText"); this.OnPropertyChanged("ProgressState"); StartCommand.RaiseCanExecuteChanged(); StopCommand.RaiseCanExecuteChanged(); } /// <summary> /// カウントダウンを中止します。 /// </summary> public void Stop() { timer.Stop(); Counter = TimeSpan.Zero; this.OnPropertyChanged("EstimatedTimeText"); this.OnPropertyChanged("ProgressState"); StartCommand.RaiseCanExecuteChanged(); StopCommand.RaiseCanExecuteChanged(); } #endregion #region イベントハンドラ /// <summary> /// タイマー間隔経過のイベントハンドラー /// </summary> /// <param name="sender">イベントのソース。</param> /// <param name="e">イベント データを格納している EventArgs。</param> private void timer_Tick(object sender, EventArgs e) { var ts = Elapsed; if (ts.Minutes >= PresetMinutes || ts.Ticks < 0) { Stop(); OnCompleted(); } else { Counter = new TimeSpan(0, PresetMinutes, 0) - new TimeSpan(ts.Days, ts.Hours, ts.Minutes, ts.Seconds); } } #endregion } }
App.config
Visual StudioでソリューションエクスプローラからSettings.settingsを開いて
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" > <section name="CountdownTimer.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </sectionGroup> </configSections> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <applicationSettings> <CountdownTimer.Properties.Settings> <setting name="Min1" serializeAs="String"> <value>1</value> </setting> <setting name="Min2" serializeAs="String"> <value>3</value> </setting> <setting name="Min3" serializeAs="String"> <value>5</value> </setting> <setting name="Min4" serializeAs="String"> <value>25</value> </setting> </CountdownTimer.Properties.Settings> </applicationSettings> </configuration>
以下拾い物
Common\BindableBase.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; // WPFでもBindableBaseを使ってINotifyPropertyChangedを実装する - SourceChord // http://sourcechord.hatenablog.com/entry/20130303/1362315081 namespace CountdownTimer.Common { /// <summary> /// モデルを簡略化するための <see cref="INotifyPropertyChanged"/> の実装。 /// </summary> public abstract class BindableBase : INotifyPropertyChanged { /// <summary> /// プロパティの変更を通知するためのマルチキャスト イベント。 /// </summary> public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// プロパティが既に目的の値と一致しているかどうかを確認します。必要な場合のみ、 /// プロパティを設定し、リスナーに通知します。 /// </summary> /// <typeparam name="T">プロパティの型。</typeparam> /// <param name="storage">get アクセス操作子と set アクセス操作子両方を使用したプロパティへの参照。</param> /// <param name="value">プロパティに必要な値。</param> /// <param name="propertyName">リスナーに通知するために使用するプロパティの名前。 /// この値は省略可能で、 /// CallerMemberName をサポートするコンパイラから呼び出す場合に自動的に指定できます。</param> /// <returns>値が変更された場合は true、既存の値が目的の値に一致した場合は /// false です。</returns> protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null) { if (object.Equals(storage, value)) return false; storage = value; this.OnPropertyChanged(propertyName); return true; } /// <summary> /// プロパティ値が変更されたことをリスナーに通知します。 /// </summary> /// <param name="propertyName">リスナーに通知するために使用するプロパティの名前。 /// この値は省略可能で、 /// <see cref="CallerMemberNameAttribute"/> をサポートするコンパイラから呼び出す場合に自動的に指定できます。</param> protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { var eventHandler = this.PropertyChanged; if (eventHandler != null) { eventHandler(this, new PropertyChangedEventArgs(propertyName)); } } } }http://sourcechord.hatenablog.com/entry/20130303/1362315081
Common\RelayCommand.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; // XAMLからViewModelのメソッドにバインドする〜RelayCommand〜 - SourceChord // http://sourcechord.hatenablog.com/entry/2014/01/13/200039 namespace CountdownTimer.Common { /// <summary> /// その機能を中継することのみを目的とするコマンド /// デリゲートを呼び出すことにより、他のオブジェクトに対して呼び出します。 ///CanExecute メソッドの既定の戻り値は 'true' です。 /// <see cref="RaiseCanExecuteChanged"/> は、次の場合は必ず呼び出す必要があります。 /// <see cref="CanExecute"/> は、別の値を返すことが予期されます。 /// </summary> public class RelayCommand : ICommand { private readonly Action _execute; private readonly Func<bool> _canExecute; /// <summary> /// RaiseCanExecuteChanged が呼び出されたときに生成されます。 /// </summary> public event EventHandler CanExecuteChanged; /// <summary> /// 常に実行可能な新しいコマンドを作成します。 /// </summary> /// <param name="execute">実行ロジック。</param> public RelayCommand(Action execute) : this(execute, null) { } /// <summary> /// 新しいコマンドを作成します。 /// </summary> /// <param name="execute">実行ロジック。</param> /// <param name="canExecute">実行ステータス ロジック。</param> public RelayCommand(Action execute, Func<bool> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } /// <summary> /// 現在の状態でこの <see cref="RelayCommand"/> が実行できるかどうかを判定します。 /// </summary> /// <param name="parameter"> /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。 /// </param> /// <returns>このコマンドが実行可能な場合は true、それ以外の場合は false。</returns> public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(); } /// <summary> /// 現在のコマンド ターゲットに対して <see cref="RelayCommand"/> を実行します。 /// </summary> /// <param name="parameter"> /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。 /// </param> public void Execute(object parameter) { _execute(); } /// <summary> /// <see cref="CanExecuteChanged"/> イベントを発生させるために使用されるメソッド /// <see cref="CanExecute"/> の戻り値を表すために /// メソッドが変更されました。 /// </summary> public void RaiseCanExecuteChanged() { var handler = CanExecuteChanged; if (handler != null) { handler(this, EventArgs.Empty); } } } /// <summary> /// 任意の型の引数を1つ受け付けるRelayCommand /// </summary> /// <typeparam name="T"></typeparam> public class RelayCommand<T> : ICommand { private readonly Action<T> _execute; private readonly Func<T, bool> _canExecute; /// <summary> /// RaiseCanExecuteChanged が呼び出されたときに生成されます。 /// </summary> public event EventHandler CanExecuteChanged; /// <summary> /// 常に実行可能な新しいコマンドを作成します。 /// </summary> /// <param name="execute">実行ロジック。</param> public RelayCommand(Action<T> execute) : this(execute, null) { } /// <summary> /// 新しいコマンドを作成します。 /// </summary> /// <param name="execute">実行ロジック。</param> /// <param name="canExecute">実行ステータス ロジック。</param> public RelayCommand(Action<T> execute, Func<T, bool> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } /// <summary> /// 現在の状態でこの <see cref="RelayCommand"/> が実行できるかどうかを判定します。 /// </summary> /// <param name="parameter"> /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。 /// </param> /// <returns>このコマンドが実行可能な場合は true、それ以外の場合は false。</returns> public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute((T)parameter); } /// <summary> /// 現在のコマンド ターゲットに対して <see cref="RelayCommand"/> を実行します。 /// </summary> /// <param name="parameter"> /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。 /// </param> public void Execute(object parameter) { _execute((T)parameter); } /// <summary> /// <see cref="CanExecuteChanged"/> イベントを発生させるために使用されるメソッド /// <see cref="CanExecute"/> の戻り値を表すために /// メソッドが変更されました。 /// </summary> public void RaiseCanExecuteChanged() { var handler = CanExecuteChanged; if (handler != null) { handler(this, EventArgs.Empty); } } } }http://sourcechord.hatenablog.com/entry/2014/01/13/200039
Helper\FlashWindowHelper.cs
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows; // .net - Force window to blink when a particular event occurs in C# / WPF - Stack Overflow // http://stackoverflow.com/questions/8924556/force-window-to-blink-when-a-particular-event-occurs-in-c-sharp-wpf // Usage // var helper = new FlashWindowHelper(Application.Current); // // Flashes the window and taskbar 5 times and stays solid // // colored until user focuses the main window // helper.FlashApplicationWindow(); // // Cancels the flash at any time // helper.StopFlashing(); namespace CountdownTimer.Helper { public class FlashWindowHelper { private IntPtr mainWindowHWnd; private Application theApp; public FlashWindowHelper(Application app) { this.theApp = app; } public void FlashApplicationWindow() { InitializeHandle(); Flash(this.mainWindowHWnd, 5); } public void StopFlashing() { InitializeHandle(); if (Win2000OrLater) { FLASHWINFO fi = CreateFlashInfoStruct(this.mainWindowHWnd, FLASHW_STOP, uint.MaxValue, 0); FlashWindowEx(ref fi); } } private void InitializeHandle() { if (this.mainWindowHWnd == IntPtr.Zero) { // Delayed creation of Main Window IntPtr as Application.Current passed in to ctor does not have the MainWindow set at that time var mainWindow = this.theApp.MainWindow; this.mainWindowHWnd = new System.Windows.Interop.WindowInteropHelper(mainWindow).Handle; } } [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool FlashWindowEx(ref FLASHWINFO pwfi); [StructLayout(LayoutKind.Sequential)] private struct FLASHWINFO { /// <summary> /// The size of the structure in bytes. /// </summary> public uint cbSize; /// <summary> /// A Handle to the Window to be Flashed. The window can be either opened or minimized. /// </summary> public IntPtr hwnd; /// <summary> /// The Flash Status. /// </summary> public uint dwFlags; /// <summary> /// The number of times to Flash the window. /// </summary> public uint uCount; /// <summary> /// The rate at which the Window is to be flashed, in milliseconds. If Zero, the function uses the default cursor blink rate. /// </summary> public uint dwTimeout; } /// <summary> /// Stop flashing. The system restores the window to its original stae. /// </summary> public const uint FLASHW_STOP = 0; /// <summary> /// Flash the window caption. /// </summary> public const uint FLASHW_CAPTION = 1; /// <summary> /// Flash the taskbar button. /// </summary> public const uint FLASHW_TRAY = 2; /// <summary> /// Flash both the window caption and taskbar button. /// This is equivalent to setting the FLASHW_CAPTION | FLASHW_TRAY flags. /// </summary> public const uint FLASHW_ALL = 3; /// <summary> /// Flash continuously, until the FLASHW_STOP flag is set. /// </summary> public const uint FLASHW_TIMER = 4; /// <summary> /// Flash continuously until the window comes to the foreground. /// </summary> public const uint FLASHW_TIMERNOFG = 12; /// <summary> /// Flash the spacified Window (Form) until it recieves focus. /// </summary> /// <param name="hwnd"></param> /// <returns></returns> public static bool Flash(IntPtr hwnd) { // Make sure we're running under Windows 2000 or later if (Win2000OrLater) { FLASHWINFO fi = CreateFlashInfoStruct(hwnd, FLASHW_ALL | FLASHW_TIMERNOFG, uint.MaxValue, 0); return FlashWindowEx(ref fi); } return false; } private static FLASHWINFO CreateFlashInfoStruct(IntPtr handle, uint flags, uint count, uint timeout) { FLASHWINFO fi = new FLASHWINFO(); fi.cbSize = Convert.ToUInt32(Marshal.SizeOf(fi)); fi.hwnd = handle; fi.dwFlags = flags; fi.uCount = count; fi.dwTimeout = timeout; return fi; } /// <summary> /// Flash the specified Window (form) for the specified number of times /// </summary> /// <param name="hwnd">The handle of the Window to Flash.</param> /// <param name="count">The number of times to Flash.</param> /// <returns></returns> public static bool Flash(IntPtr hwnd, uint count) { if (Win2000OrLater) { FLASHWINFO fi = CreateFlashInfoStruct(hwnd, FLASHW_ALL | FLASHW_TIMERNOFG, count, 0); return FlashWindowEx(ref fi); } return false; } /// <summary> /// A boolean value indicating whether the application is running on Windows 2000 or later. /// </summary> private static bool Win2000OrLater { get { return Environment.OSVersion.Version.Major >= 5; } } } }http://stackoverflow.com/questions/8924556/force-window-to-blink-when-a-particular-event-occurs-in-c-sharp-wpf