Search code examples
c#winapiaccessibilitypinvokemagnification

Implement lens magnification to magnify the Windows Taskbar


I want to be able to perform lens magnificaiton on top of the windows taskbar. So far I've been unsuccessful in implementing this seeing as the taskbar will always open on top of my window. Windows built-in magnifier is able to do this so I'm hoping it is indeed possible.

I've attached two screenshots showing Windows built-in magnifier and how it is able to magnify the taskbar and how my application will render below the taskbar.

Windows built-in Magnifier:

Windows built-in Magnifier

My application:

My application

Is there any way to have my application render above the taskbar and thus magnify the taskbar?

<Window x:Class="WpfNativeTesting.MagnificationWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfNativeTesting"
        mc:Ignorable="d"
        Title="MagnificationWindow"
        
        Height="400"
        Width="400"
        
        WindowStyle="None"
        ResizeMode="NoResize"
        AllowsTransparency="true"
        ShowInTaskbar="False"
        Topmost="True">
    <Grid x:Name="ContainerGrid">
        <Grid x:Name="MagnificationGrid" />
    </Grid>
</Window>
public partial class MagnificationWindow : Window
    {
        private IntPtr HWnd;
        private IntPtr HWndMag;
        private bool MagnificationInitialized = false;
        private DispatcherTimer Timer = new DispatcherTimer();
        private RECT MagWindowRect = new RECT();
        private bool IsColorEffectSet = false;

        private float magnification = 1.0f;
        public float Magnification
        {
            get { return magnification; }
            set
            {
                if (value < 1.0f)
                {
                    value = 1.0f;
                }

                if (HWndMag != null)
                {
                    if (magnification != value)
                    {
                        magnification = value;

                        Transformation matrix = new Transformation(magnification);
                        NativeMethods.MagSetWindowTransform(HWndMag, ref matrix);
                    }
                }
            }
        }

        public MagnificationWindow()
        {
            InitializeComponent();

            Loaded += MagnificationWindow_Loaded;

            Show();
        }

        private void MagnificationWindow_Loaded(object sender, RoutedEventArgs e)
        {
            HWnd = new WindowInteropHelper(this).Handle;

            var exStyle = NativeMethods.GetWindowLong(HWnd, NativeMethods.GWL_EXSTYLE);
            exStyle |= (int)ExtendedWindowStyles.WS_EX_TOPMOST | (int)ExtendedWindowStyles.WS_EX_LAYERED | (int)ExtendedWindowStyles.WS_EX_TRANSPARENT;
            NativeMethods.SetWindowLong(HWnd, NativeMethods.GWL_EXSTYLE, exStyle);

            var style = NativeMethods.GetWindowLong(HWnd, NativeMethods.GWL_STYLE);
            style |= (int)WindowStyles.WS_CAPTION | (int)WindowStyles.WS_SYSMENU;
            NativeMethods.SetWindowLong(HWnd, NativeMethods.GWL_STYLE, exStyle);

            MagnificationInitialized = NativeMethods.MagInitialize();

            if (MagnificationInitialized)
            {
                SetupMagnifier();

                Timer.Interval = TimeSpan.FromMilliseconds(NativeMethods.USER_TIMER_MINIMUM);
                Timer.Tick += Timer_Tick;
                Timer.Start();
            }
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);

            RemoveMagnifier();
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            UpdateMaginifier();
        }


        private void SetupMagnifier()
        {
            var hInst = NativeMethods.GetModuleHandle(null);

            NativeMethods.GetClientRect(HWnd, ref MagWindowRect);

            HWndMag = NativeMethods.CreateWindow((int)ExtendedWindowStyles.WS_EX_STATICEDGE, NativeMethods.WC_MAGNIFIER,
                    "MagnificationWindow", (int)WindowStyles.WS_CHILD | (int)MagnifierStyle.MS_SHOWMAGNIFIEDCURSOR | (int)WindowStyles.WS_VISIBLE,
                    MagWindowRect.left, MagWindowRect.top, MagWindowRect.right, MagWindowRect.bottom, HWnd, IntPtr.Zero, hInst, IntPtr.Zero);

            NativeMethods.MagShowSystemCursor(false);

            if (HWndMag == IntPtr.Zero)
            {
                return;
            }

            var matrix = new Transformation(Magnification);
            NativeMethods.MagSetWindowTransform(HWndMag, ref matrix);
        }

        private void UpdateMaginifier()
        {
            if (!MagnificationInitialized || HWndMag == IntPtr.Zero)
            {
                return;
            }

            POINT mousePoint = new POINT();
            RECT sourceRect = new RECT();

            NativeMethods.GetCursorPos(ref mousePoint);

            int width = (int)((MagWindowRect.right - MagWindowRect.left) / Magnification);
            int height = (int)((MagWindowRect.bottom - MagWindowRect.top) / Magnification);

            sourceRect.left = mousePoint.x - width / 2;
            sourceRect.top = mousePoint.y - height / 2;

            NativeMethods.MagSetWindowSource(HWndMag, sourceRect);

            POINT mouse = new POINT();
            NativeMethods.GetCursorPos(ref mouse);

            NativeMethods.SetWindowPos(HWnd, NativeMethods.HWND_TOPMOST, mouse.x - (int)(magnification * width / 2), mouse.y - (int)(magnification * height / 2), width, height, 
                (int)SetWindowPosFlags.SWP_NOACTIVATE | 
                (int)SetWindowPosFlags.SWP_NOSIZE);

            NativeMethods.InvalidateRect(HWndMag, IntPtr.Zero, true);
        }

        public void RemoveMagnifier()
        {
            if (MagnificationInitialized)
            {
                NativeMethods.MagUninitialize();

                MagnificationInitialized = false;
            }
        }    
    
        // ...
   }

Solution

  • I posted this question in the Microsoft Q/A forum and got a solution that got it working.

    https://learn.microsoft.com/en-us/answers/questions/54196/magnifier-control-unable-to-magnify-the-taskbar-st.html


    We need to make it a Accessibility app by setting uiAcess=true in the manifest, sign the executable, and placing it in a secure location (e.g. Program Files) as described below:

    1. Set uiAccess=true in the manifest Set this option in Visual Studio by setting Linker | Manifest File | UAC Bypass UI Protection to Yes

    2. Sign the executable See https://learn.microsoft.com/en-us/previous-versions/bb756995(v=msdn.10)

    3. Place it in a secure location See https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/user-account-control-only-elevate-uiaccess-applications-that-are-installed-in-secure-locations


    I placed the application in a secure location before I signed it and used the following commands to create the certificate and sign the application.

    makecert /n "CN=Company, O=Company, C=SE" /r /pe /h 0 /eku "1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13" /e 01/01/2021 /sv Company.pvk Company.cer

    Pvk2Pfx /pvk Company.pvk /pi "password" /spc Company.cer /pfx Company.pfx /po "password" /pi "password"

    and finally

    signtool sign /f "Company.pfx" /p "password" "application".exe

    And that's it!