Open WPF window next to multi-monitor notification area
My wpf app should open a window next to the notification area. When the user clicks on the tray icon, a window opens. So I need a window on the same screen as the notification area. So far, I just get the location of the taskbar: https://stackoverflow.com/a/119497/and a rectangle rectangle on the screen on StackOverflow question and then I use it to find my window, like this:
//xaml.cs
var taskBarLocation = GetTaskBarLocation();
var taskBarPosition = GetTaskbarPosition();
this.Left = taskBarLocation == Location.Left
? taskBarPosition.Width
: SystemParameters.WorkArea.Width - Width;
this.Top = taskBarLocation == Location.Top
? taskBarPosition.Height
: SystemParameters.WorkArea.Height - Height;
Works only if the notification area fits on the main screen. As I understood, I have to use WinApi to position my window on another monitor, but I don't even know how to find the notification area screen. Any help would be appreciated.
source to share
Ok, since
1) Wpf window location doesn't care about monitors (it works with "control panel coordinates \ All control panel items \ Display \ Screen resolution", not current screen coordinates)
2) WinApi SHAppBarMessage (5, ref data) method also works in global coordinates
3) Each display object has a Bounds property that represents the location of the screen in global coordinates
The solution is pretty simple:
public static class TaskBarLocationProvider
{
// P/Invoke goo:
private const int ABM_GETTASKBARPOS = 5;
[DllImport("shell32.dll")]
private static extern IntPtr SHAppBarMessage(int msg, ref AppBarData data);
/// <summary>
/// Where is task bar located (at top of the screen, at bottom (default), or at the one of sides)
/// </summary>
private enum Dock
{
Left = 0,
Top = 1,
Right = 2,
Bottom = 3
}
private struct Rect
{
public int Left, Top, Right, Bottom;
}
private struct AppBarData
{
public int cbSize;
public IntPtr hWnd;
public int uCallbackMessage;
public Dock Dock;
public Rect rc;
public IntPtr lParam;
}
private static Rectangle GetTaskBarCoordinates(Rect rc)
{
return new Rectangle(rc.Left, rc.Top,
rc.Right - rc.Left, rc.Bottom - rc.Top);
}
private static AppBarData GetTaskBarLocation()
{
var data = new AppBarData();
data.cbSize = Marshal.SizeOf(data);
IntPtr retval = SHAppBarMessage(ABM_GETTASKBARPOS, ref data);
if (retval == IntPtr.Zero)
{
throw new Win32Exception("WinAPi Error: does'nt work api method SHAppBarMessage");
}
return data;
}
private static Screen FindScreenWithTaskBar(Rectangle taskBarCoordinates)
{
foreach (var screen in Screen.AllScreens)
{
if (screen.Bounds.Contains(taskBarCoordinates))
{
return screen;
}
}
return Screen.PrimaryScreen;
}
/// <summary>
/// Calculate wpf window position for place it near to taskbar area
/// </summary>
/// <param name="windowWidth">target window height</param>
/// <param name="windowHeight">target window width</param>
/// <param name="left">Result left coordinate <see cref="System.Windows.Window.Left"/></param>
/// <param name="top">Result top coordinate <see cref="System.Windows.Window.Top"/></param>
public static void CalculateWindowPositionByTaskbar(double windowWidth, double windowHeight, out double left, out double top)
{
var taskBarLocation = GetTaskBarLocation();
var taskBarRectangle = GetTaskBarCoordinates(taskBarLocation.rc);
var screen = FindScreenWithTaskBar(taskBarRectangle);
left = taskBarLocation.Dock == Dock.Left
? screen.Bounds.X + taskBarRectangle.Width
: screen.Bounds.X + screen.WorkingArea.Width - windowWidth;
top = taskBarLocation.Dock == Dock.Top
? screen.Bounds.Y + taskBarRectangle.Height
: screen.Bounds.Y + screen.WorkingArea.Height - windowHeight;
}
and use in xaml.cs
private void SetWindowPosition()
{
double left, right;
TaskBarLocationProvider.CalculateWindowPositionByTaskbar(this.Width, this.Height, out left, out right);
Left = left;
Top = right;
}
source to share