Derick
Derick

Reputation: 105

WPF display data on multi monitors

I have a PC with 1-4 monitors. Every monitor may have a different resolution. I get screens(monitors) and their resolution with System.Windows.Forms.Screen.AllScreens. For every screen I open a new fullscreen window. I have a list of data items (which may take different height when displayed). I want to show this data items using ItemsControl. When all space(height) is used on 1st monitor it must go to second and so on...but only full data item must be on screen. enter image description here

How can I do this? Or do i need to use only 1 window stretched to many screens?

Upvotes: 2

Views: 1348

Answers (1)

PauLEffect
PauLEffect

Reputation: 423

I'm a noob, but i'll try to help. I'm working on a wpf fullscreen dual monitor app. (i want the taskbar to be visible).

XAML

<Window x:Class="SlideWpf.SecondWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="500" Width="800" BorderThickness="0" AllowsTransparency="True" WindowStyle="None">
    <Window.Background>
        <SolidColorBrush Opacity="0"></SolidColorBrush>
    </Window.Background>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="55*" Name="GRowTop"/>
            <RowDefinition Height="409*" Name="GRowContent"/>
            <RowDefinition Height="36*" Name="GRowBottom"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="89*" Name="GColLeft"/>
            <ColumnDefinition Width="659*" Name="GColMid"/>
            <ColumnDefinition Width="52*" Name="GColRight"/>
        </Grid.ColumnDefinitions>

        <Border BorderThickness="2" BorderBrush="Red" Grid.Row="0" Grid.Column="1" Grid.RowSpan="2">
            <Border BorderThickness="10" BorderBrush="Black">
                <Grid Name="g1" Background="AntiqueWhite"></Grid>
            </Border>
        </Border>
        <Border BorderThickness="2" BorderBrush="Red" Grid.Row="1" Grid.Column="0" Grid.RowSpan="2">
            <Border BorderThickness="10" BorderBrush="Black">
                <Grid Name="g2"  Background="Beige"></Grid>
            </Border>
        </Border>
    </Grid>
</Window>

Breakdown

  • Borderless form
  • Allow transparency : true, window transparency set via solidcolorbrush so the controls in this window won't inherit the opacity.
  • a grid with columns and rows that will hold the first and second monitor space. note that they are double bordered.
  • the borders are temporary, so i could align them properly (and see where i've messed up)

wpf

Xaml.cs

if (ScreensUtils.Bounds(1).Left < ScreensUtils.Bounds(0).Left)
            {
                Left = ScreensUtils.Location(1).X;
                Top = Math.Min(ScreensUtils.Location(1).Y, ScreensUtils.Location(0).Y);

                Width = ScreensUtils.Size(0).Width + ScreensUtils.Size(1).Width;
                Height = Math.Max(ScreensUtils.Bounds(0).Bottom, ScreensUtils.Bounds(1).Top + ScreensUtils.Size(1).Height);


                GRowTop.Height = new GridLength(ScreensUtils.Location(1).Y);

                GColLeft.Width = new GridLength(ScreensUtils.Size(1).Width);
                GColMid.Width = new GridLength(ScreensUtils.Size(0).Width);

                GRowBottom.Height = new GridLength(ScreensUtils.Bounds(1).Top + ScreensUtils.Size(1).Height - ScreensUtils.Bounds(0).Bottom);
            }

This handles my specific case: i've a secondary monitor, left of the primary one, smaller and a little lower than the first.

desktop

Now you can probably make some sense of the grid.

Breakdown

ScreensUtils could totally be a method. I plan on extending it a little, so I made it a class.

class ScreensUtils
    {
        public static Size Size(int index)
        {
            return Screen.AllScreens[index].WorkingArea.Size;
        }
        public static Point Location(int index)
        {
            return Screen.AllScreens[index].WorkingArea.Location;
        }
        public static Rectangle Bounds(int index)
        {
            return new Rectangle( Screen.AllScreens[index].WorkingArea.Left, Screen.AllScreens[index].WorkingArea.Top, Screen.AllScreens[index].WorkingArea.Left + Screen.AllScreens[index].WorkingArea.Size.Width , Screen.AllScreens[index].WorkingArea.Top + Screen.AllScreens[index].WorkingArea.Size.Height);
        }
    }
  • Basically we get the size of the second monitor and the position. The same for the first.
  • We compare and resize grid columns and rows as needed.
  • We may also need to change the placement of the content holding areas, depending on the size, number and placement of the columns.

This still needs a title bar. Either stretched across monitors or on primary only. Should be no problem for anyone(a simple gridrow or a grid + the already present content grids in a stack/dock panel. )

There is the problem of auto hidden task bars. (that space will appear empty when the taskbar hides).

For this there is:

class TaskbarUtils
{
    public enum TaskbarPosition
    {
        Unknown = -1,
        Left,
        Top,
        Right,
        Bottom,
    }

    public sealed class Taskbar
    {
        private const string ClassName = "Shell_TrayWnd";

        public Rectangle Bounds
        {
            get;
            private set;
        }
        public TaskbarPosition Position
        {
            get;
            private set;
        }
        public Point Location
        {
            get
            {
                return this.Bounds.Location;
            }
        }
        public Size Size
        {
            get
            {
                return this.Bounds.Size;
            }
        }
        //Always returns false under Windows 7
        public bool AlwaysOnTop
        {
            get;
            private set;
        }
        public bool AutoHide
        {
            get;
            private set;
        }

        public Taskbar()
        {
            IntPtr taskbarHandle = User32.FindWindow(Taskbar.ClassName, null);

            APPBARDATA data = new APPBARDATA();
            data.cbSize = (uint)Marshal.SizeOf(typeof(APPBARDATA));
            data.hWnd = taskbarHandle;
            IntPtr result = Shell32.SHAppBarMessage(ABM.GetTaskbarPos, ref data);
            if (result == IntPtr.Zero)
                throw new InvalidOperationException();

            this.Position = (TaskbarPosition)data.uEdge;
            this.Bounds = Rectangle.FromLTRB(data.rc.left, data.rc.top, data.rc.right, data.rc.bottom);

            data.cbSize = (uint)Marshal.SizeOf(typeof(APPBARDATA));
            result = Shell32.SHAppBarMessage(ABM.GetState, ref data);
            int state = result.ToInt32();
            this.AlwaysOnTop = (state & ABS.AlwaysOnTop) == ABS.AlwaysOnTop;
            this.AutoHide = (state & ABS.Autohide) == ABS.Autohide;
        }
    }

    public enum ABM : uint
    {
        New = 0x00000000,
        Remove = 0x00000001,
        QueryPos = 0x00000002,
        SetPos = 0x00000003,
        GetState = 0x00000004,
        GetTaskbarPos = 0x00000005,
        Activate = 0x00000006,
        GetAutoHideBar = 0x00000007,
        SetAutoHideBar = 0x00000008,
        WindowPosChanged = 0x00000009,
        SetState = 0x0000000A,
    }

    public enum ABE : uint
    {
        Left = 0,
        Top = 1,
        Right = 2,
        Bottom = 3
    }

    public static class ABS
    {
        public const int Autohide = 0x0000001;
        public const int AlwaysOnTop = 0x0000002;
    }

    public static class Shell32
    {
        [DllImport("shell32.dll", SetLastError = true)]
        public static extern IntPtr SHAppBarMessage(ABM dwMessage, [In] ref APPBARDATA pData);
    }

    public static class User32
    {
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct APPBARDATA
    {
        public uint cbSize;
        public IntPtr hWnd;
        public uint uCallbackMessage;
        public ABE uEdge;
        public RECT rc;
        public int lParam;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }
}

You use it like

var _taskbar = new TaskbarUtils;
if(_taskbar.AlwaysOnTop) {}

Output

This is a print screen.

desktop

Final Notes

I've found found that Screen.AllScreens[index].WorkingArea.Bottom reports a messed up value. I use Screen.AllScreens[index].WorkingArea.Top + Screen.AllScreens[index].WorkingArea.Size.Height

The code I've provided sucks. So does ScreenUtils. It's done on the fly and with no consideration to other cases besides my own. But it's something. As I'll actually DoWork() on this, i'll update my answer.

You should be totally capable of adapting to your needs.

If you find something wrong or if you improve, please post your findings.

Best of luck!

PS: the taskbar class was taken from here

Upvotes: 2

Related Questions