view 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 source

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;
        }
    }
}