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

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

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