Creating a Magnifier in .NET, Part 2: Making It Work

Introduction

In the previous installment, “Creating a Magnifier in .NET, Part 1: Structure,” we created the structure for this project. We set up the Methods and Properties needed. If you have not read Part 1 yet, please do so before continuing.

All we need to do now is to make this work with a Form, so let’s not waste any more time and get started.

Practical

Open the code window for your Form, and add the next few fields to it.

   private Form frmMag;
   private Thread tBackground;
   private System.Windows.Forms.Timer tmrMag;

   private IntPtr iptrMag;
   private float fMagVal;
   private RECT rectMagWin = new RECT();

   private bool blnInit;
   private bool blnNormal = true;
   private bool blnHidden = true;

Add the next few Enumerations:

   private enum WStyle
   {
      ExStyle = -20
   }
   private enum WType
   {
      Transparent = 0x20,
      Layered = 0x80000
   }

   private enum WCol
   {
      ColorKey = 0x1,
      Alpha = 0x1
   }

The Enumerations contain the settings for the Window style, its appearance, and its main color. Now, let’s add some more APIs:

   [DllImport("user32.dll", EntryPoint = "GetWindowLong")]
   private static extern int GetWindowLong(IntPtr hWnd,
      WStyle nIndex);

   [DllImport("user32.dll", EntryPoint = "SetWindowLong")]
   private static extern int SetWindowLong(IntPtr hWnd,
      WStyle nIndex, int dwNewLong);

   [DllImport("user32.dll", EntryPoint =
      "SetLayeredWindowAttributes")]
   private static extern bool SetLayeredWindowAttributes(IntPtr
      hWnd, int crKey, byte alpha, WCol dwFlags);

   [DllImport("user32.dll")]
   private static extern short GetAsyncKeyState(Keys vKey);

Add the Constructor to initialize the variables to default values:

   public Form1()
   {
      InitializeComponent();

      frmMag = this;
      fMagVal = 2.0f;
      CreateWindow();

      frmMag.Resize += new EventHandler(Form_Resize);
      frmMag.FormClosing += new
         FormClosingEventHandler(Form_FormClosing);

      tmrMag = new System.Windows.Forms.Timer();
      tmrMag.Tick += new EventHandler(Timer_Tick);

      blnInit = APIMethods.MagInitialize();

      if (blnInit)
      {
         Setup();
         tmrMag.Interval = APIMethods.USER_TIMER_MINIMUM;
         tmrMag.Enabled = true;
      }

      DetermineKeysThread();
   }

   private void CreateWindow()
   {

      DoubleBuffered = true;

      WindowState = FormWindowState.Normal;

      StartPosition = FormStartPosition.CenterScreen;

      ShowInTaskbar = false;

   }

Determine which key is pressed with the next methods:

   private void DetermineKeysThread()
   {
      tBackground = new Thread(() => DetermineKeys())
      {
         IsBackground = true,
         Priority = ThreadPriority.Normal
      };

      tBackground.Start();
   }

   private void DetermineKeys()
   {

      BeginInvoke(new MethodInvoker(delegate
      {
         Hide();
      }));

      while (true)
      {
         Thread.Sleep(10);

         foreach (Keys k in Enum.GetValues(typeof(Keys)))
         {
            if (((GetAsyncKeyState(k) & (1 << 15)) != 0))
            {

               if (k == Keys.MButton)
               {
                  if (blnHidden)
                  {
                     BeginInvoke(new MethodInvoker(delegate
                     {
                        Show();
                     }));
                     Thread.Sleep(500);


                     blnHidden = false;
                  }
                  else
                  {
                     BeginInvoke(new MethodInvoker(delegate
                     {
                        Hide();
                     }));
                     Thread.Sleep(500);

                     blnHidden = true;
                  }
               }

               if (k == Keys.Add)
               {
                  BeginInvoke(new MethodInvoker(delegate
                  {
                     Magnification++;
                  }));
                  Thread.Sleep(500);
               }

               if (k == Keys.Subtract)
               {
                  BeginInvoke(new MethodInvoker(delegate
                  {
                     if (Magnification > 1f)
                     {
                        Magnification--;
                     }
                  }));
                  Thread.Sleep(500);
               }

               if (k == Keys.Multiply)
               {
                  if (blnNormal)
                  {
                     blnNormal = false;
                     BeginInvoke(new MethodInvoker(delegate
                     {
                        WindowState = FormWindowState.Maximized;
                     }));
                     Thread.Sleep(500);
                  }
                  else
                  {
                     blnNormal = true;
                     BeginInvoke(new MethodInvoker(delegate
                     {
                        WindowState = FormWindowState.Normal;
                     }));
                     Thread.Sleep(500);
                  }
               }

               if (k == Keys.F10)
               {
                  Application.Exit();
               }
            }
         }
      }
   }

Add the next methods to override the Form’s Shown method, and the method to resize the form:

   protected override void OnShown(EventArgs e)
   {
      base.OnShown(e);

      int wl = GetWindowLong(Handle, WStyle.ExStyle);
      wl = wl | 0x80000 | 0x20;
      SetWindowLong(Handle, WStyle.ExStyle, wl);
      SetLayeredWindowAttributes(Handle, 0, 128, WCol.Alpha);
   }

   protected virtual void ResizeMag()
   {
      if (blnInit && (iptrMag != IntPtr.Zero))
      {
         APIMethods.GetClientRect(frmMag.Handle, ref rectMagWin);
         APIMethods.SetWindowPos(iptrMag, IntPtr.Zero,
            rectMagWin.left, rectMagWin.top, rectMagWin.right,
            rectMagWin.bottom, 0);
      }
   }

   public virtual void UpdateMag()
   {
      if ((!blnInit) || (iptrMag == IntPtr.Zero))
      {
         return;
      }

      RECT rctSource = new RECT();

      Point pPos = new Point(Screen.PrimaryScreen.Bounds.Width / 2,
         Screen.PrimaryScreen.Bounds.Height / 2);

      int iWidth = (int)((rectMagWin.right - rectMagWin.left) /
         fMagVal);
      int iHeight = (int)((rectMagWin.bottom - rectMagWin.top) /
         fMagVal);

      rctSource.left = pPos.X - iWidth / 2;
      rctSource.top = pPos.Y - iHeight / 2;

      if (rctSource.left < 0)
      {
         rctSource.left = 0;
      }

      if (rctSource.left > APIMethods.GetSystemMetrics
         (APIMethods.SM_CXSCREEN) - iWidth)
      {
         rctSource.left = APIMethods.GetSystemMetrics
            (APIMethods.SM_CXSCREEN) - iWidth;
      }

      rctSource.right = rctSource.left + iWidth;

      if (rctSource.top < 0)
      {
         rctSource.top = 0;
      }

      if (rctSource.top > APIMethods.GetSystemMetrics
         (APIMethods.SM_CYSCREEN) - iHeight)
      {
         rctSource.top = APIMethods.GetSystemMetrics
            (APIMethods.SM_CYSCREEN) - iHeight;
      }

      rctSource.bottom = rctSource.top + iHeight;

      if (frmMag == null)
      {
         tmrMag.Enabled = false;
         return;
      }

      if (frmMag.IsDisposed)
      {
         tmrMag.Enabled = false;
         return;
      }

      APIMethods.MagSetWindowSource(iptrMag, rctSource);
      APIMethods.SetWindowPos(frmMag.Handle,
         APIMethods.HWND_TOPMOST, 0, 0, 0, 0,
         (int)SetWindowPosFlags.SWP_NOACTIVATE |
         (int)SetWindowPosFlags.SWP_NOMOVE |
         (int)SetWindowPosFlags.SWP_NOSIZE);
      APIMethods.InvalidateRect(iptrMag, IntPtr.Zero, true);
   }

   private float Magnification
   {
      get { return fMagVal; }
      set
      {
         if (fMagVal != value)
         {
            fMagVal = value;

            Transform tMatrix = new Transform(fMagVal);
            APIMethods.MagSetWindowTransform(iptrMag, ref
               tMatrix);
         }
      }
   }

   protected void Setup()
   {
      if (!blnInit)
      {
         return;
      }

      IntPtr hInst;

      hInst = APIMethods.GetModuleHandle(null);

      frmMag.AllowTransparency = true;
      frmMag.TransparencyKey = System.Drawing.Color.Empty;
      frmMag.Opacity = 255;

      APIMethods.GetClientRect(frmMag.Handle, ref rectMagWin);

      iptrMag = APIMethods.CreateWindow((int)ExtendedWindowStyles
            .WS_EX_TRANSPARENT, APIMethods.MAGNIFIER,
         "Form1", (int)WindowStyles.WS_CHILD |
            (int)WindowStyles.WS_VISIBLE,
         rectMagWin.left, rectMagWin.top, rectMagWin.right,
            rectMagWin.bottom, frmMag.Handle, IntPtr.Zero, hInst,
            IntPtr.Zero);

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

      Transform tMatrix = new Transform(fMagVal);
      APIMethods.MagSetWindowTransform(iptrMag, ref tMatrix);
   }

   protected void RemoveMag()
   {
      if (blnInit)
      {
         APIMethods.MagUninitialize();
      }
   }

Finally, add the last few methods:

   private void Form_FormClosing(object sender,
      FormClosingEventArgs e)
   {
      tmrMag.Enabled = false;
   }

   private void Form_Resize(object sender, EventArgs e)
   {
      ResizeMag();
   }

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

Conclusion

As developers, we are so fortunate to have Windows on our side, because making use of its built-in APIs can give us more power. I hope you have enjoyed this little series. Until next time, happy coding!

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read