A lot of applications which are using multiple windows show them within a "Window" menu. This article shows how you can implement such a feature in your WPF application. The result can be look like the following screen shot (which isn’t very nice, but it does the job):
First of all: Is there no ready-to-use implementation in WPF? Yes there is. Application.Current.Windows provides a collection of all open windows of you application. The disadvantage is that the collection isn’t an ObservableCollection, so you can’t use data binding and we have to implement our own system.
The first implementation step is to create a simple data class which will hold a reference to an open window. In this case it’s just a simple wrapper but there might be added some features in future.
public class WindowInformation
{
private Window _window;
public WindowInformation(Window window)
{
_window = window;
}
public Window Window
{
get { return _window; }
}
public int WindowHashCode
{
get { return _window.GetHashCode(); }
}
public String Title
{
get { return _window.Title; }
}
public ImageSource Icon
{
get { return _window.Icon; }
}
}
To get the possibility to use data binding, we create a simple collection which inherits ObervableCollection and holds a list of WindowInformation-objects.
public class WindowInformationCollection :
ObservableCollection<WindowInformation>
{
}
The next step is to create a manager class which enables some functionality to manage access to the collection of windows as well as features to remove and get windows. It is implemented using the singleton pattern so that there can be only one instance within your whole application.
public class WindowManager
{
private static WindowManager _manager;
private WindowInformationCollection _openWindows =
new WindowInformationCollection();
private WindowManager() { }
public static WindowManager GetInstance
{
get
{
if (_manager == null)
_manager = new WindowManager();
return _manager;
}
}
public WindowInformationCollection OpenWindows
{
get { return _openWindows; }
}
public Window GetOpenWindow(int hashCode)
{
foreach (WindowInformation wi in _openWindows)
{
if (wi.WindowHashCode == hashCode)
{
return wi.Window;
}
}
return null;
}
public bool RemoveOpenWindow(int hashCode)
{
WindowInformation windowToRemove = null;
foreach (WindowInformation wi in _openWindows)
{
if (wi.WindowHashCode == hashCode)
{
windowToRemove = wi;
break;
}
}
if (windowToRemove != null)
{
_openWindows.Remove(windowToRemove);
return true;
}
return false;
}
}
As you can see, I am working with the hash code of each window. This code should be unique in most cases. It is very bad to work with the title of a window as it can change and the "reference" breaks.
Now we need something like handler. This handler is used to mark a window to be handled by our system:
public class WindowInformationHandler
{
public static readonly DependencyProperty IsHandledProperty =
DependencyProperty.RegisterAttached("IsHandled",
typeof(bool), typeof(WindowInformationHandler),
new FrameworkPropertyMetadata((bool)false,
new PropertyChangedCallback(OnIsHandledChanged)));
private static void OnIsHandledChanged(
DependencyObject dObj,
DependencyPropertyChangedEventArgs e)
{
Window openWindow = dObj as Window;
if (openWindow != null)
{
openWindow.Loaded += new RoutedEventHandler(WindowLoaded);
openWindow.Closed += new EventHandler(WindowClosed);
}
}
public static bool GetIsManaged(DependencyObject dObj)
{
return (bool)dObj.GetValue(IsHandledProperty);
}
public static void SetIsManaged(DependencyObject dObj, bool value)
{
dObj.SetValue(IsHandledProperty, value);
}
private static void WindowClosed(object sender, EventArgs e)
{
Window closedWindow = sender as Window;
if (closedWindow != null)
{
WindowManager.GetInstance.RemoveOpenWindow(closedWindow.GetHashCode());
openWindow.Loaded -= new RoutedEventHandler(WindowLoaded);
openWindow.Closed -= new EventHandler(WindowClosed);
}
}
private static void WindowLoaded(object sender, RoutedEventArgs e)
{
Window openWindow = sender as Window;
if (openWindow != null)
{
WindowInformation winInfo = new WindowInformation(openWindow);
WindowManager.GetInstance.OpenWindows.Add(winInfo);
}
}
}
This class includes a dependency property so that we are able to use this easily from our XAML file. If the property IsHandled is set to true, two events of the window will be registered: Loaded and Closed. If the window is loaded we create an instance of our type WindowInformation which is added to our collection of all open windows.
If the window is closed, the reference will be removed from the collection of open windows and the registered events are unregistered.
To get a window handled by this system you have to add the following XAML to the Window element:
local:WindowInformationHandler.IsManaged="true"
That’s all.
The example application uses the following XAML:
<Window.Resources>
<DataTemplate x:Key="WindowTemplate">
<MenuItem Header="{Binding Title}"
Icon="{Binding WindowIcon}"
Click="ShowWindow"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Menu>
<MenuItem Header="File">
<MenuItem Header="New Window …"
Click="NewWindow"/>
</MenuItem>
<MenuItem Header="Edit"/>
<MenuItem x:Name="mnuWindows"
Header="Windows"
ItemTemplate="{StaticResource WindowTemplate}"/>
</Menu>
</Grid>
The code behind file looks as follows (please note that you can set the data binding also within the XAML markup):
public MainWindow()
{
InitializeComponent();
mnuWindows.ItemsSource = WindowManager.GetInstance.OpenWindows;
}
private void NewWindow(object sender, RoutedEventArgs e)
{
MainWindow window = new MainWindow();
window.Title = String.Format("Open Window {0}",
Application.Current.Windows.Count);
window.Show();
}
private void ShowWindow(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null)
{
WindowInformation wi = mi.DataContext
as WindowInformation;
if (wi != null)
{
wi.Window.Focus();
}
}
}
If you have any suggestions, please let me know.