using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Interop; using System.Windows.Threading; using System.Timers; using Yaulw.Thread; namespace Yaulw.WPF { /// /// Class used to keep WPF Windows Hidden permanently. /// This should ideally be called in the Constructor of a window you want to keep hidden. /// /// /// private static KeepHidden _HiddenWindow = null; /// public HiddenMainWindow() /// { /// // * Force this window to remain hidden indefinitly* /// _HiddenWindow = new KeepHidden(this); /// } /// /// /// public class KeepHidden { #region Private Statics // Static Map of All our Hidden Windows private static Dictionary s_hWndToWindowMap = new Dictionary(); private static List s_WindowList = new List(); private Window _wpfWindow = null; private object _lockObj = new Object(); #endregion #region Public Properties /// /// Get the WPF Object That is bound to this Keep Hidden Instance /// public Window wpfWindow { get { return _wpfWindow; } } /// /// Get the HWND Handle to the WPF Object bound to this Keep Hidden Instance /// public IntPtr hwndWindow { get { if (_wpfWindow != null) { WindowInteropHelper interop = new WindowInteropHelper(_wpfWindow); return interop.Handle; } return IntPtr.Zero; } } #endregion #region Construction /// /// When Passing in a Wpf Window in this Construction, it will ensure that /// This Window will remain hidden during it's life span. If the Window is Not Loaded /// yet, call KeepHidden's Show() method to show it, after construction. /// /// a WPF Window Object that you want to keep Hidden public KeepHidden(Window wpfWindow) { if (wpfWindow != null && !wpfWindow.IsLoaded) { // Assign the Load Event Handler we care about wpfWindow.Loaded += new RoutedEventHandler(wpfWindow_Loaded); } else if (wpfWindow != null && wpfWindow.IsLoaded) { // Call the Loaded Event Handler Directly wpfWindow_Loaded((object)wpfWindow, null); } else throw new ArgumentException("Please pass valid WPF Window Object"); // Make sure we aren't already tracking this window if (s_WindowList.Contains(wpfWindow)) throw new ArgumentException("WPF Window Object already Kept Hidden"); // Assign remaining stuff wpfWindow.Closing += new System.ComponentModel.CancelEventHandler(wpfWindow_Closing); AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit); _wpfWindow = wpfWindow; // Add to WPF Window List s_WindowList.Add(wpfWindow); // Call this upon construction ~Done Way before Show() get's called HideWPFWindow(_wpfWindow); } #endregion #region Public Methods /// /// Call this to execute a Show() call on the WPF Window that you want to Keep Hidden /// public void Show() { if (_wpfWindow != null && !_wpfWindow.IsLoaded) { lock (_lockObj) { // Make sure it can't be seen or interacted with HideWPFWindow(_wpfWindow); // Show it _wpfWindow.Show(); // Again... make sure... HideWPFWindow(_wpfWindow); // Hide it immediatly, make sure it can't be interacted with _wpfWindow.Hide(); } } } /// /// Call this to execute a Show() call on the WPF Window that you want to Keep Hidden /// ~However, it will delay call the show allowing the hidden window's constructor to finish /// /// Milliseconds to delay show the HiddenWindow (at least 250 recommended) public void Show(uint nDelayInMilliseconds) { // Calling Show() directly in the Constructor causes a .Net Run-time Crash // upon first launch only. Calling it .5 seconds later does the trick of avoiding that error, // because by then the object is fully constructed ElapsedEventHandler DelayShowHiddenWindow = delegate(object sender, ElapsedEventArgs e) { Timer timer = (Timer)sender; timer.Stop(); // * Show the Hidden Window * Show(); timer.Dispose(); timer = null; }; TTimerDisp timerDelayShowHiddenWindow = new TTimerDisp(DelayShowHiddenWindow, (int) nDelayInMilliseconds, true); } /// /// Call this to execute a Close() call on the WPF Window that you previously had hidden /// ~Should only call this when you are done with the Keep Hidden Object /// public void Close() { if (_wpfWindow != null && _wpfWindow.IsLoaded) { _wpfWindow.Close(); _wpfWindow = null; } } #endregion #region Public Static Methods /// /// Call this function to determine if the passed in Window is a Hidden Window Object * Tracked by KeepHidden * /// /// pass in a wpf window object to check /// public static bool IsHiddenWindow(Window _wpfWindow) { if (_wpfWindow != null) { bool bIsFound = s_WindowList.Contains(_wpfWindow); return bIsFound; } return false; } #endregion #region Private Keep Hidden Event Handlers /// /// Called by the WPFWindow's OnLoad Event /// private void wpfWindow_Loaded(object sender, RoutedEventArgs e) { lock (_lockObj) { Window wpfWindow = (Window)sender; HideWPFWindow(wpfWindow); // Add Window Source Message Hook * We'll track the hWnd for the Message Hook // vie s_hWndToWindowMap WindowInteropHelper interop = new WindowInteropHelper(wpfWindow); s_hWndToWindowMap[interop.Handle] = wpfWindow; HwndSource source = HwndSource.FromHwnd(interop.Handle); source.AddHook(new HwndSourceHook(MessageHook)); } } /// /// Called by the WPFWindow's Closing Event /// private void wpfWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { lock (_lockObj) { // Delete from the hWndMap, so Message Hook // Stops processing messages Window wpfWindow = (Window)sender; if (s_hWndToWindowMap.ContainsValue(wpfWindow)) { foreach (IntPtr hWnd in s_hWndToWindowMap.Keys) { if (s_hWndToWindowMap[hWnd] == wpfWindow) { s_hWndToWindowMap.Remove(hWnd); break; } } } // * Imp * Delete internal wpfWindow Reference, // but DON'T Delete from the s_WindowList. Caller could still call IsHiddenWindow(), // and we MUST return true if it is _wpfWindow = null; } } /// /// Called by the Process Exiting * We want this to happen last * /// This allows all unload events for each window to happen first /// void CurrentDomain_ProcessExit(object sender, EventArgs e) { // Make sure to clear all windows s_hWndToWindowMap.Clear(); // Make sure to clear all windows s_WindowList.Clear(); } /// /// This Message Hook is for the WpfWindow to absolutely ensure that it remains Hidden. /// We want to make sure that it is never displayed *It can occur that explorer.exe can show it, without this hook* /// ~i.e. when explorer.exe crashes, and get's restarted, it can cause inconsitencies /// private IntPtr MessageHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { // Get the Proper WPF Object and make sure it is Hidden if (s_hWndToWindowMap.ContainsKey(hwnd)) { Window wpfWindow = s_hWndToWindowMap[hwnd]; if (wpfWindow != null) HideWPFWindow(wpfWindow); } handled = false; return System.IntPtr.Zero; } #endregion #region Private Hide Window Functionality /// /// * Core * 'Hiding' Function. Makes sure that the Window is and /// stays hidden. /// /// /// Height="2" Width="2" AllowsTransparency="True" Background="{x:Null}" WindowStyle="None" WindowStartupLocation="Manual" WindowState="Normal" ResizeMode="NoResize" Foreground="{x:Null}" ShowInTaskbar="False" ShowActivated="False" Left="0" Top="0" MaxWidth="2" MaxHeight="2" MinHeight="2" MinWidth="2" Visibility="Hidden" Opacity="0" IsHitTestVisible="False" IsTabStop="False" Focusable="False" IsEnabled="False" /// /// a WPF window object private void HideWPFWindow(Window wpfWindow) { // Force a tiny Window in top-left corner wpfWindow.Width = 2; wpfWindow.Height = 2; wpfWindow.MinWidth = 2; wpfWindow.MinHeight = 2; wpfWindow.MaxWidth = 2; wpfWindow.MaxHeight = 2; wpfWindow.Top = 0; wpfWindow.Left = 0; // Force transparency if (!wpfWindow.IsLoaded) // must not be already loaded wpfWindow.AllowsTransparency = true; wpfWindow.Background = null; wpfWindow.Foreground = null; // Force no user interaction wpfWindow.WindowStartupLocation = WindowStartupLocation.Manual; wpfWindow.WindowState = WindowState.Normal; wpfWindow.ResizeMode = ResizeMode.NoResize; wpfWindow.ShowInTaskbar = false; wpfWindow.ShowActivated = false; wpfWindow.Visibility = Visibility.Hidden; wpfWindow.IsHitTestVisible = false; wpfWindow.IsTabStop = false; wpfWindow.Focusable = false; // Force Opacity wpfWindow.WindowStyle = WindowStyle.None; wpfWindow.Opacity = 0.0; } #endregion } }