Mit XAML kann man angeblich alles so designen, wie man will. Die Vielzahl der Möglichkeiten erschlägt einen auch gerade zu. Dennoch gibt es immer wieder Sachen, bei denen ich feststellen muss, dass man doch nicht alles rein mit XAML lösen kann.

Eine solche Sache ist zum Beispiel eine rahmenlose Anwendung im Vollbild-Modus. Ok. Das stimmt nicht ganz. Denn das ist noch ganz einfach, wenn man mit dem WindowState das Fenster maximiert und dann den WindowStyle auf none setzt.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        WindowState="Maximized"
        WindowStyle="None"
        >
...
</Window>

Wenn man ein solches Fenster startet, wird man aber schnell feststellen, dass das nicht immer das ist, was man braucht. Die Taskleiste verschwindet ebenso, wie der Rahmen des Fensters. Den Fensterrahmen wollen wir ja auch nicht haben. Aber die Taskleiste hat ja nichts mit der eigenen Anwendung zu tun und sollte daher auch da bleiben wo sie ist.

Damit man das hinbekommt, muss man leider im Quellcode ein bisschen nachhelfen. Ich hab wegen der Wiederverwendbarkeit eine kleine Basisklasse für meine Fenster geschrieben, die genau das übernimmt. Das heißt, dass ich in Xaml meine Fenster nicht mehr mit dem herkömmlichen Window-Tag umschließe, sondern meine eigene Basisklasse WindowBase.

So. Dann kommen wir mal zu dieser Basisklasse. Damit wir das Fenster auf die richtige Größe bekommen, müssen wir uns ein paar Informationen aus der Windows API holen, damit wir überhaupt wissen, welcher Platz verfügbar ist. Das ist dann quasi die WorkingArea. Zu guter Letzt wird das Fenster dann auf die entsprechende Größe gesetzt indem wir die MaxWidth und MaxHeight entsprechend setzen.

Erst einmal ein paar Definitionen, die wir dafür brauchen. Fangen wir mit den API Methoden an, die wir aufrufen werden

[DllImport("user32.dll")]
static extern IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags);

[DllImport("user32.dll")]
public static extern bool GetMonitorInfo(HandleRef hmonitor, [In, Out] MONITORINFOEX monitorInfo);

API Aufrufe haben leider immer den Nachteil, dass sie irgendwelche bestimmte Strukturen brauchen, die man übergeben muss. Daher definieren wir die jetzt als nächstes.

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

[StructLayout(LayoutKind.Sequential)]
public class MONITORINFOEX
{
    public int cbSize;
    public RECT rcMonitor; // Total area
    public RECT rcWork; // Working area
    public int dwFlags;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
    public char[] szDevice;
}

Und zu guter Letzt die SourceInitialized Methode. Diese müssen wir dann lediglich noch in der Xaml-Datei für das Event SourceInitialized aufrufen. Natürlich könnte man das auch noch automatisieren, in dem man das Event im Konstruktor der Basisklasse zuweist, aber das hab ich in meinem Beispiel absichtlich nicht gemacht, weil man so noch direkt vom Xaml aus steuern kann, wie sich das Fenster verhalten kann. Interessant wäre vielleicht noch, wenn man das ganze über eine Attached Property löst, aber das macht ich vielleicht irgendwann einmal.

void Window_SourceInitialized(object sender, EventArgs e)
{
	// Make window borderless
	this.WindowStyle = WindowStyle.None;
	this.ResizeMode = ResizeMode.NoResize;

	// Get handle for nearest monitor to this window
	WindowInteropHelper wih = new WindowInteropHelper(this);
	IntPtr hMonitor = MonitorFromWindow(wih.Handle, MONITOR_DEFAULTTONEAREST);

	// Get monitor info
	MONITORINFOEX monitorInfo = new MONITORINFOEX();
	monitorInfo.cbSize = Marshal.SizeOf(monitorInfo);
	GetMonitorInfo(new HandleRef(this, hMonitor), monitorInfo);

	// Create working area dimensions, converted to DPI-independent values
	HwndSource source = HwndSource.FromHwnd(wih.Handle);
	if (source == null) return; // Should never be null
	if (source.CompositionTarget == null) return; // Should never be null
	System.Windows.Media.Matrix matrix = source.CompositionTarget.TransformFromDevice;
	RECT workingArea = monitorInfo.rcWork;
	Point dpiIndependentSize =
		matrix.Transform(
		new Point(
			workingArea.Right - workingArea.Left,
			workingArea.Bottom - workingArea.Top));

	// Maximize the window to the device-independent working area ie
	// the area without the taskbar.
	// NOTE - window state must be set to Maximized as this adds certain
	// maximized behaviors eg you can't move a window while it is maximized,
	// such as by calling Window.DragMove
	this.Top = 0;
	this.Left = 0;
	this.MaxWidth = dpiIndependentSize.X;
	this.MaxHeight = dpiIndependentSize.Y;
	this.WindowState = WindowState.Maximized;
}

So. Nachdem man dann im Xaml das Event zugewiesen hat, hat man ein schönes Vollbild-Fenster und kann dennoch auch noch mit der Taskleiste arbeiten.