diff ShortcutKeyFinder/MainForm.cs @ 0:209d9210c18f default tip

It works.
author Brad Greco <brad@bgreco.net>
date Sat, 25 Jun 2016 13:42:54 +1000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/MainForm.cs	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,397 @@
+using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Linq;
+using System.Security.Principal;
+using System.Windows.Forms;
+
+namespace ShortcutKeyFinder
+{
+    /// <summary>Shortcut key manager main form</summary>
+    public partial class MainForm : Form
+    {
+        private const int _iconSize = 16;
+        private const int _iconMargin = 4;
+        private const string _registryKey = "Software\\ShortcutKeyManager", _showAllPreference = "ShowAllShortcuts";
+        private bool _suspendUpdates, _suspendUpdateButtons;
+        private int _firstDisplayedRowIndex;
+        private ShortcutKeyFinder _shortcutKeyFinder;
+        private BindingSource _dataSource;
+        private SolidBrush _textBrush;
+        private StringFormat _cellFormat;
+        private List<Shortcut> _oldSelection;
+        private Hotkey _hotkey;
+
+        /// <summary>List of shortcuts currently selected by the user</summary>
+        private List<Shortcut> SelectedShortcuts
+        {
+            get
+            {
+                List<Shortcut> shortcuts = new List<Shortcut>();
+                foreach (DataGridViewRow row in ShortcutGrid.SelectedRows)
+                    if ((ShortcutGrid.DataSource as BindingSource).Count > row.Index)
+                        shortcuts.Add((Shortcut)row.DataBoundItem);
+                return shortcuts;
+            }
+        }
+
+        /// <summary>Data source for the shortcut grid</summary>
+        private BindingList<Shortcut> Shortcuts { get { return _shortcutKeyFinder != null ? _shortcutKeyFinder.Shortcuts : null; } }
+
+        /// <summary>Constructor and form initialization</summary>
+        public MainForm()
+        {
+            InitializeComponent();
+            // Set up grid data source
+            _shortcutKeyFinder = new ShortcutKeyFinder();
+            _dataSource = new BindingSource() { DataSource = _shortcutKeyFinder.Shortcuts };
+            // Cell format for custom painting of shortcut icons and names in the same cell
+            _cellFormat = new StringFormat(StringFormatFlags.NoWrap) { LineAlignment = StringAlignment.Center, Trimming = StringTrimming.EllipsisCharacter };
+
+            // Set up grid
+            ShortcutGrid.AutoGenerateColumns = false;
+            HotkeyColumn.ReadOnly = false;
+            ShortcutGrid.DataSource = _dataSource;
+            ShortcutGrid.CellParsing += ShortcutGrid_CellParsing;
+            _shortcutKeyFinder.Shortcuts.ListChanged += Shortcuts_ListChanged;
+
+            // Set up UI
+            SetupElevateButton();
+            LoadPreferences();
+
+            // Reduce grid flicker
+            typeof(DataGridView).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty, null, ShortcutGrid, new object[] { true });
+        }
+
+        /// <summary>Refreshes data when form receives focus</summary>
+        protected override void OnActivated(EventArgs e)
+        {
+            base.OnActivated(e);
+            if (!_suspendUpdates)
+                FindShortcutWorker.RunWorkerAsync();
+        }
+
+        /// <summary>Cancels the inline editor if the user leaves the program</summary>
+        protected override void OnDeactivate(EventArgs e)
+        {
+            ShortcutGrid.CancelEdit();
+            ShortcutGrid.EndEdit();
+        }
+
+        /// <summary>Scans for shortcuts in the background</summary>
+        private void FindShortcutWorker_DoWork(object sender, DoWorkEventArgs e)
+        {
+            // Disable list change events while scan is in progress
+            _dataSource.RaiseListChangedEvents = false;
+            _shortcutKeyFinder.LoadShortcuts(IsAdministrator());
+        }
+
+        /// <summary>Updates the grid after the scan is complete</summary>
+        private void FindShortcutWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
+        {
+            _suspendUpdateButtons = true; // Prevent button flicker
+            // Save UI state
+            _firstDisplayedRowIndex = ShortcutGrid.FirstDisplayedScrollingRowIndex;
+            _oldSelection = SelectedShortcuts.ToList();
+
+            // Refresh the grid
+            _dataSource.RaiseListChangedEvents = true;
+            _dataSource.ResetBindings(false);
+            ShortcutGrid.ClearSelection();
+
+            // Restore UI state
+            if (ShortcutGrid.Rows.Count > 0)
+                ShortcutGrid.FirstDisplayedScrollingRowIndex = Math.Max(0, Math.Min(_firstDisplayedRowIndex, ShortcutGrid.Rows.Count - 1));
+            foreach (DataGridViewRow row in ShortcutGrid.Rows)
+                row.Selected = _oldSelection.Contains((Shortcut)row.DataBoundItem);
+            _suspendUpdateButtons = false;
+            UpdateButtons();
+
+            // Hide busy indicators that appear when the form is first launched
+            InitialProgressBar.Visible = false;
+            ShortcutGrid.Visible = true;
+            Cursor = Cursors.Default;
+        }
+
+        /// <summary>Updates the UI when a shortcut's properties have changed</summary>
+        void Shortcuts_ListChanged(object sender, ListChangedEventArgs e)
+        {
+            // Update the "Clear shortcut key" button since the selection's shortcut may have been set or unset
+            if (!_suspendUpdateButtons)
+            {
+                if (this.InvokeRequired)
+                    this.Invoke(new Action(() => UpdateButtons()));
+                else
+                    UpdateButtons();
+            }
+            // Force repainting of changed rows so the background color gets updated
+            if (e.ListChangedType == ListChangedType.ItemChanged && e.NewIndex < ShortcutGrid.Rows.Count)
+                ShortcutGrid.InvalidateRow(e.NewIndex);
+        }
+
+        /// <summary>Enables or disables the buttons depending on the current selection</summary>
+        private void UpdateButtons()
+        {
+            ClearButton.Enabled = ShortcutGrid.SelectedRows.Count > 0 && SelectedShortcuts.Any(s => s.Hotkey != null);
+            ClearAllButton.Enabled = Shortcuts.Any(s => s.Hotkey != null);
+        }
+
+        /// <summary>Sets and saves a shortcut's hotkey</summary>
+        private void SetShortcutHotkey(Shortcut shortcut, Hotkey hotkey)
+        {
+            try
+            {
+                shortcut.Hotkey = hotkey;
+                shortcut.Save();
+            }
+            catch
+            {
+                MessageBox.Show("Error setting shortcut key", "Set Shortcut Key", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+
+        /// <summary>Sets cell background color based on its shortcut's properties, and inserts the shortcut's icon next to its name</summary>
+        private void ShortcutGrid_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
+        {
+            // Header row
+            if (e.RowIndex < 0)
+                return;
+
+            DataGridViewRow row = ShortcutGrid.Rows[e.RowIndex];
+            Shortcut shortcut = (Shortcut)row.DataBoundItem;
+
+            // Paint duplicate shortcut keys red
+            if (shortcut.HasDuplicateHotkey)
+            {
+                e.CellStyle.BackColor = Color.DarkRed;
+                e.CellStyle.ForeColor = Color.White;
+                e.CellStyle.SelectionBackColor = Color.OrangeRed;
+                e.CellStyle.SelectionForeColor = Color.White;
+            }
+            // Paint readonly shortcuts gray
+            else if (shortcut.ReadOnly)
+            {
+                e.CellStyle.BackColor = Color.LightGray;
+                e.CellStyle.ForeColor = Color.Black;
+                e.CellStyle.SelectionBackColor = Color.SteelBlue;
+                e.CellStyle.SelectionForeColor = Color.White;
+            }
+
+            // Paint the shortcut's icon next to its name
+            if (e.ColumnIndex == ShortcutColumn.Index)
+            {
+                Color textColor = row.Selected ? e.CellStyle.SelectionForeColor : e.CellStyle.ForeColor;
+                if (_textBrush == null || _textBrush.Color != textColor)
+                    _textBrush = new SolidBrush(textColor);
+                e.PaintBackground(e.ClipBounds, row.Selected);
+                Rectangle textRect = new Rectangle(e.CellBounds.X + _iconSize + _iconMargin * 2, e.CellBounds.Y, e.CellBounds.Width - _iconSize - _iconMargin * 2, e.CellBounds.Height);
+                e.Graphics.DrawImage(shortcut.Icon, _iconMargin, textRect.Y + ((textRect.Height - _iconSize) / 2));
+                e.Graphics.DrawString(e.FormattedValue.ToString(), e.CellStyle.Font, _textBrush, textRect, _cellFormat);
+                e.Handled = true;
+            }
+        }
+
+        /// <summary>Triggers direct hotkey editing if the hotkey column is double clicked, or opens the shortcut's Properties window for any other column</summary>
+        private void ShortcutGrid_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
+        {
+            if (e.ColumnIndex == HotkeyColumn.Index)
+            {
+                ShortcutGrid.CurrentCell = ShortcutGrid.Rows[e.RowIndex].Cells[e.ColumnIndex];
+                ShortcutGrid.BeginEdit(true);
+            }
+            else
+            {
+                ((Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem).ShowExplorerPropertiesWindow(Handle);
+            }
+        }
+
+        /// <summary>Opens the Properties window for the selected row on Enter, or activates the inline shortcut editor on F2</summary>
+        private void ShortcutGrid_KeyDown(object sender, KeyEventArgs e)
+        {
+            if (e.KeyCode == Keys.Enter)
+            {
+                e.Handled = true;
+                if (SelectedShortcuts.Count == 1)
+                    SelectedShortcuts[0].ShowExplorerPropertiesWindow(Handle);
+            }
+            else if (e.KeyCode == Keys.F2)
+            {
+                if (ShortcutGrid.SelectedRows.Count == 1)
+                {
+                    ShortcutGrid.CurrentCell = ShortcutGrid.SelectedRows[0].Cells[HotkeyColumn.Index];
+                    ShortcutGrid.BeginEdit(true);
+                }
+            }
+        }
+
+        /// <summary>Prepares the inline editor by clearing the text and registering event handlers</summary>
+        private void ShortcutGrid_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
+        {
+            (e.Control as TextBox).Text = "";
+            (e.Control as TextBox).KeyDown += CellEditor_KeyDown;
+            (e.Control as TextBox).PreviewKeyDown += CellEditor_PreviewKeyDown;
+            (e.Control as TextBox).Leave += CellEditor_Leave;
+        }
+
+        /// <summary>Removes event handlers from inline editor when it loses focus</summary>
+        void CellEditor_Leave(object sender, EventArgs e)
+        {
+            (sender as TextBox).KeyDown -= CellEditor_KeyDown;
+            (sender as TextBox).PreviewKeyDown -= CellEditor_PreviewKeyDown;
+            (sender as TextBox).Leave -= CellEditor_Leave;
+        }
+
+        /// <summary>Finishes inline hotkey editing on Enter</summary>
+        private void CellEditor_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
+        {
+            if (e.KeyCode == Keys.Enter)
+                ShortcutGrid.EndEdit();
+        }
+
+        /// <summary>Listens for key events when the inline editor is active and converts them to a hotkey</summary>
+        void CellEditor_KeyDown(object sender, KeyEventArgs e)
+        {
+            System.Diagnostics.Debug.WriteLine("keydown");
+            if (e.KeyCode == Keys.Back || e.KeyCode == Keys.Delete)
+            {
+                _hotkey = null;
+                (sender as TextBox).Clear();
+            }
+            else if (e.KeyCode != Keys.Enter)
+            {
+                _hotkey = new Hotkey()
+                {
+                    KeyCode = (byte)e.KeyCode,
+                    Control = e.Control,
+                    Alt = e.Alt,
+                    Shift = e.Shift
+                };
+                (sender as TextBox).Text = _hotkey.ToString();
+            }
+            e.Handled = true;
+            e.SuppressKeyPress = true;
+        }
+
+        /// <summary>Saves the new hotkey to the data source and the shortcut when inline editing is complete</summary>
+        private void ShortcutGrid_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
+        {
+            e.ParsingApplied = true;
+            e.Value = _hotkey;
+            SetShortcutHotkey((Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem, _hotkey);
+        }
+
+        /// <summary>Updates the list view to show all shorcuts or only shortcuts with hotkeys</summary>
+        private void ShowAllCheckBox_CheckedChanged(object sender, EventArgs e)
+        {
+            _shortcutKeyFinder.ShowAll = ShowAllCheckBox.Checked;
+            ShortcutGrid.ClearSelection();
+            SavePreferences();
+        }
+
+        /// <summary>Removes the hotkeys from the selected shortcuts when the Clear button is clicked</summary>
+        private void ClearButton_Click(object sender, EventArgs e)
+        {
+            foreach (Shortcut shortcut in SelectedShortcuts.Where(s => !s.ReadOnly && s.Hotkey != null))
+                SetShortcutHotkey(shortcut, null);
+        }
+
+        /// <summary>Clears shortcuts from all hotkeys when the Clear All button is clicked</summary>
+        private void ClearAllButton_Click(object sender, EventArgs e)
+        {
+            _suspendUpdates = true;
+            if (MessageBox.Show("Are you sure you want to clear all assigned shortcut keys?", "Clear Shortcut Keys", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
+            {
+                foreach (Shortcut shortcut in Shortcuts.Where(s => !s.ReadOnly && s.Hotkey != null))
+                    SetShortcutHotkey(shortcut, null);
+            }
+            _suspendUpdates = false;
+        }
+
+        /// <summary>Prepares for inline shortcut editing, or prevents it if the shortcut is read only</summary>
+        private void ShortcutGrid_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
+        {
+            _hotkey = null;
+            e.Cancel = ((Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem).ReadOnly;
+        }
+
+        /// <summary>Updates the button state when the grid selection changes</summary>
+        private void ShortcutGrid_SelectionChanged(object sender, EventArgs e)
+        {
+            if (!_suspendUpdateButtons)
+                UpdateButtons();
+        }
+
+        /// <summary>Updates the button state since the hotkey of the current item may have changed</summary>
+        private void ShortcutGrid_CellEndEdit(object sender, DataGridViewCellEventArgs e)
+        {
+            if (!_suspendUpdateButtons)
+                UpdateButtons();
+        }
+
+        /// <summary>Checks whether the program is running with elevated permissions</summary>
+        /// <remarks>http://stackoverflow.com/a/11660205</remarks>
+        public static bool IsAdministrator()
+        {
+            return (new WindowsPrincipal(WindowsIdentity.GetCurrent())).IsInRole(WindowsBuiltInRole.Administrator);
+        }
+
+        /// <summary>Shows or hides the button to elevate to administrator based on whether the program is already running as an administrator</summary>
+        private void SetupElevateButton()
+        {
+            if (IsAdministrator())
+                ElevateButton.Visible = false;
+            else
+                Win32Helpers.AddUacShield(ElevateButton);
+        }
+
+        /// <summary>Restarts the program, requesting elevation</summary>
+        private void ElevateButton_Click(object sender, EventArgs e)
+        {
+            ProcessStartInfo info = new ProcessStartInfo() { UseShellExecute = true, WorkingDirectory = Environment.CurrentDirectory, FileName = Application.ExecutablePath, Verb = "runas" };
+            try
+            {
+                Process.Start(info);
+                Application.Exit();
+            }
+            catch { } // Do nothing if the user canceled the consent dialog
+        }
+
+        /// <summary>Saves checkbox state so it can be restored when the program is relaunched later</summary>
+        private void SavePreferences()
+        {
+            try
+            {
+                RegistryKey key = Registry.CurrentUser.CreateSubKey(_registryKey);
+                key.SetValue(_showAllPreference, ShowAllCheckBox.Checked, RegistryValueKind.DWord);
+            }
+            catch { } // Silently fail if we can't save preferences
+        }
+
+        /// <summary>Restores the checkbox state to the same as when the program last exited</summary>
+        private void LoadPreferences()
+        {
+            try
+            {
+                RegistryKey key = Registry.CurrentUser.OpenSubKey(_registryKey);
+                ShowAllCheckBox.Checked = Convert.ToBoolean((int)key.GetValue(_showAllPreference, 0));
+            }
+            catch { } // Silently fail if we can't load preferences
+        }
+
+        private void ShortcutGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
+        {
+            string tooltip = null;
+            Shortcut shortcut = (Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem;
+
+            if (shortcut.HasDuplicateHotkey)
+                tooltip = "More than one shortcut has the same hotkey assigned";
+            else if (shortcut.ReadOnly)
+                tooltip = "Administrative privileges are required to edit this shortcut." + Environment.NewLine + "To edit, click the \"Allow editing shortcuts shared by all users\" button.";
+
+            ShortcutGrid.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = tooltip;
+        }
+    }
+}