Mercurial > shortcutkeyfinder
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; + } + } +}