C# make form hide and then show again

0

I am trying to make a Windows Forms Application be able to repeatedly show and then hide itself.

The idea is to create a small app that overlays an image on the screen every time a modifier key like Num Lock or Caps lock is pressed. I have the detection of the keyboard keys working flawlessly, but I am not having much luck with creating a form that I can show and then hide repeatedly.

The way I see it, (please correct me if I'm wrong) there are two possible ways to make the form behave like I want it to:

  • Start the form normally in Program.cs and then hold the logic for hiding and showing the form and displaying the image inside Form1.cs
    • The form would call this.Hide() and this.Show() to hide and show itself, but whenever I try to do this, I cannot get this.Hide() to hide the form; the form remains visible on top of all of the open windows.
  • Hold the logic for hiding and showing the form in Program.cs and then just hold the logic to display the image in Form1.cs
    • I've been toying around with the code from this guide to show and hide the from from Program.cs with a class wrapper for the form, but as it turns out, form.Showdialog() prevents any further code execution until the form is closed.

I've been playing around with the code myself, and neither of the above methods have worked. Am I thinking about this in the wrong way entierly? Can a WFA ever behave in the way I want it to?

The code for this is a bit messy, and I'm not sure what parts are the most relevant here, but I'll do my best to include it here:

Program.cs:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Windows.Forms;

namespace KeyboardIndicators {
  // ApplicationContext wrapper
  public abstract class TrayIconApplicationContext : ApplicationContext {
    private NotifyIcon lockIcon;
    private ContextMenu lockIconContext;

    protected TrayIconApplicationContext() {
      // Wire up the ApplicationExitHandler to ApplicationExit events
      Application.ApplicationExit += this.ApplicationExitHandler;

      lockIconContext = new ContextMenu {

      };

      // Create lockIcon tray icon and make it visible
      lockIcon = new NotifyIcon {
        ContextMenu = lockIconContext,
        Text = Application.ProductName,
        Icon = new Icon("icon.ico"),
        Visible = true
      };

    }

    protected NotifyIcon LockIcon { get { return lockIcon; } }
    protected ContextMenu LockIconContext { get { return lockIconContext; } }


    // ApplicationExit event handler
    private void ApplicationExitHandler(object sender, EventArgs e) {
      this.OnApplicationExit(e);
    }

    // Performs cleanup to end the application
    protected virtual void OnApplicationExit(EventArgs e) {
      // TODO(Neil): Add meaningful thread cleanup here soon
      if (lockIcon != null) {
        lockIcon.Visible = false;
        lockIcon.Dispose();
      }
      if (lockIconContext != null)
        LockIconContext.Dispose();
    }
  }

  // TrayIconApplicationContext wrapper for Form1 to control the activation of the form window
  class FormApplicationContext : TrayIconApplicationContext {
    public FormApplicationContext() {
      // Add Exit menu item
      MenuItem exit = new MenuItem("E&xit");
      this.LockIconContext.MenuItems.Add(exit);
      exit.Click += this.ExitContextMenuClickHandler;

      //KeyboardIndicators indicators = new KeyboardIndicators();
      //indicators.RunListener();

      {
        using(Form form = new Form1("NumLock", true))
          form.ShowDialog();
      }
    }

    private void ExitContextMenuClickHandler(object sender, EventArgs eventArgs) {
      this.ExitThread();
    }
  }

  public class KeyboardIndicators {
    class LockState {
      // Is the numlock key on?
      public bool Num;
      // Is the capslock key on?
      public bool Caps;
      // Is the scroll lock key on?
      public bool Scroll;
    }

    public void RunListener() {
      try {
        // Store the old keyboard lock state
        LockState prevState = new LockState() {
          Num = Control.IsKeyLocked(Keys.NumLock),
          Caps = Control.IsKeyLocked(Keys.CapsLock),
          Scroll = Control.IsKeyLocked(Keys.Scroll)
        };

        while (true) {
          // Store the new keyboard lock state
          LockState newState = new LockState() {
            Num = Control.IsKeyLocked(Keys.NumLock),
            Caps = Control.IsKeyLocked(Keys.CapsLock),
            Scroll = Control.IsKeyLocked(Keys.Scroll)
          };

          //TODO(Neil): Handle simultaneous presses better, i.e. queue the balloon tips
          if (newState.Num != prevState.Num) {
            Form1 form = new Form1("NumLock", newState.Num);
          } else if (newState.Caps != prevState.Caps) {
            Form1 form = new Form1("CapsLock", newState.Caps);
          } else if (newState.Scroll != prevState.Scroll) {
            Form1 form = new Form1("ScrollLock", newState.Scroll);
          }

          // Set the previous lock state to the new one in prep for the next iteration
          prevState = newState;

          // Sleep for 500ms
          Thread.Sleep(500);
        }
      } catch (ThreadAbortException) { /* No need to do anything, just catch the ThreadAbortException.*/ }
    }
  }

  static class Program {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main() {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);

      //Application.Run(new Form1("NumLock", true));
      Application.Run(new FormApplicationContext());
    }
  }
}

Form1.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace KeyboardIndicators {
  public partial class Form1 : Form {
    public Form1(String activatedModifier, bool lockState) {
      InitializeComponent();
      ShowForm(activatedModifier);

      //this.Show();
      //this.Hide();
    }

    public void ShowForm(String activatedModifier) {
      PictureBox pictureBox = new PictureBox();

      Image myBitmap = Image.FromFile("cube.png");
      Size bitmapSize = new Size(myBitmap.Width, myBitmap.Height);

      switch (activatedModifier) {
        case "NumLock":
          break;
        case "CapsLock":
          break;
        case "ScrollLock":
          break;
      }

      this.Size = bitmapSize;
      pictureBox.ClientSize = bitmapSize;

      pictureBox.Image = myBitmap;
      pictureBox.Dock = DockStyle.Fill;
      this.Controls.Add(pictureBox);
      this.FormBorderStyle = FormBorderStyle.None;
    }

    protected override CreateParams CreateParams {
      get {
        CreateParams createParams = base.CreateParams;
        createParams.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT

        return createParams;
      }
    }
  }
}
c#
winforms
asked on Stack Overflow Feb 28, 2018 by ifconfig

1 Answer

0

As an example I have created a Winforms Application which displays "NUM" and / or "CAPS" if any of those keys is pressed otherwise the form is hidden. The detection of the keys is based on the C# Low Level Keyboard Hook.

Program.cs

using System;
using System.Windows.Forms;

namespace WinFormsKeyHook
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Form1.cs

Note: Add a label named 'label1' to Form1 in Designer.

using System.Collections.Generic;
using System.Windows.Forms;

namespace WinFormsKeyHook
{
    public partial class Form1 : Form
    {
        private static bool _caps;
        private static bool _num;

        public Form1()
        {
            InitializeComponent();

            KeyboardHook kh = new KeyboardHook();
            kh.KeysToObserve.AddRange(new List<Keys> { Keys.CapsLock, Keys.NumLock });
            kh.InstallHook();
            kh.KeyDown = key => ProcessKeyDown(key);

            _caps = Control.IsKeyLocked(Keys.CapsLock);
            _num = Control.IsKeyLocked(Keys.NumLock);
        }

        private void ProcessKeyDown(Keys key)
        {

            if (key == Keys.CapsLock)
            {
                _caps = !_caps;
            }
            if (key == Keys.NumLock)
            {
                _num = !_num;
            }

            this.ShowState(_num, _caps);
        }

        internal void ShowState(bool num, bool caps)
        {
            if (!num && !caps)
            {
                this.Hide();
                return;
            }

            this.label1.Text = "";
            this.label1.Text += num ? "NUM " : "";
            this.label1.Text += caps ? "CAPS" : "";

            if (!this.Visible)
            {
                this.Show();
            }
        }
    }
}

KeyboardHook.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WinFormsKeyHook
{
    public class KeyboardHook
    {
        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;
        private IntPtr _hookID = IntPtr.Zero;

        public List<Keys> KeysToObserve { get; set; } = new List<Keys>();

        public Action<Keys> KeyDown;

        public void InstallHook()
        {
            _hookID = SetHook(HookCallback);
        }

        ~KeyboardHook()
        {
            UnhookWindowsHookEx(_hookID);
        }

        public IntPtr SetHook(LowLevelKeyboardProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
            }
        }

        public delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

        public IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
             if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
            {
                int vkCode = Marshal.ReadInt32(lParam);
                var key = (Keys)vkCode;
                Console.WriteLine(key);
                KeyDown(key);
            }

            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);
    }
}
answered on Stack Overflow Mar 1, 2018 by Marius

User contributions licensed under CC BY-SA 3.0