0
|
1 using Microsoft.Win32;
|
|
2 using System;
|
|
3 using System.Collections.Generic;
|
|
4 using System.ComponentModel;
|
|
5 using System.Diagnostics;
|
|
6 using System.Drawing;
|
|
7 using System.Linq;
|
|
8 using System.Security.Principal;
|
|
9 using System.Windows.Forms;
|
|
10
|
|
11 namespace ShortcutKeyFinder
|
|
12 {
|
|
13 /// <summary>Shortcut key manager main form</summary>
|
|
14 public partial class MainForm : Form
|
|
15 {
|
|
16 private const int _iconSize = 16;
|
|
17 private const int _iconMargin = 4;
|
|
18 private const string _registryKey = "Software\\ShortcutKeyManager", _showAllPreference = "ShowAllShortcuts";
|
|
19 private bool _suspendUpdates, _suspendUpdateButtons;
|
|
20 private int _firstDisplayedRowIndex;
|
|
21 private ShortcutKeyFinder _shortcutKeyFinder;
|
|
22 private BindingSource _dataSource;
|
|
23 private SolidBrush _textBrush;
|
|
24 private StringFormat _cellFormat;
|
|
25 private List<Shortcut> _oldSelection;
|
|
26 private Hotkey _hotkey;
|
|
27
|
|
28 /// <summary>List of shortcuts currently selected by the user</summary>
|
|
29 private List<Shortcut> SelectedShortcuts
|
|
30 {
|
|
31 get
|
|
32 {
|
|
33 List<Shortcut> shortcuts = new List<Shortcut>();
|
|
34 foreach (DataGridViewRow row in ShortcutGrid.SelectedRows)
|
|
35 if ((ShortcutGrid.DataSource as BindingSource).Count > row.Index)
|
|
36 shortcuts.Add((Shortcut)row.DataBoundItem);
|
|
37 return shortcuts;
|
|
38 }
|
|
39 }
|
|
40
|
|
41 /// <summary>Data source for the shortcut grid</summary>
|
|
42 private BindingList<Shortcut> Shortcuts { get { return _shortcutKeyFinder != null ? _shortcutKeyFinder.Shortcuts : null; } }
|
|
43
|
|
44 /// <summary>Constructor and form initialization</summary>
|
|
45 public MainForm()
|
|
46 {
|
|
47 InitializeComponent();
|
|
48 // Set up grid data source
|
|
49 _shortcutKeyFinder = new ShortcutKeyFinder();
|
|
50 _dataSource = new BindingSource() { DataSource = _shortcutKeyFinder.Shortcuts };
|
|
51 // Cell format for custom painting of shortcut icons and names in the same cell
|
|
52 _cellFormat = new StringFormat(StringFormatFlags.NoWrap) { LineAlignment = StringAlignment.Center, Trimming = StringTrimming.EllipsisCharacter };
|
|
53
|
|
54 // Set up grid
|
|
55 ShortcutGrid.AutoGenerateColumns = false;
|
|
56 HotkeyColumn.ReadOnly = false;
|
|
57 ShortcutGrid.DataSource = _dataSource;
|
|
58 ShortcutGrid.CellParsing += ShortcutGrid_CellParsing;
|
|
59 _shortcutKeyFinder.Shortcuts.ListChanged += Shortcuts_ListChanged;
|
|
60
|
|
61 // Set up UI
|
|
62 SetupElevateButton();
|
|
63 LoadPreferences();
|
|
64
|
|
65 // Reduce grid flicker
|
|
66 typeof(DataGridView).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty, null, ShortcutGrid, new object[] { true });
|
|
67 }
|
|
68
|
|
69 /// <summary>Refreshes data when form receives focus</summary>
|
|
70 protected override void OnActivated(EventArgs e)
|
|
71 {
|
|
72 base.OnActivated(e);
|
|
73 if (!_suspendUpdates)
|
|
74 FindShortcutWorker.RunWorkerAsync();
|
|
75 }
|
|
76
|
|
77 /// <summary>Cancels the inline editor if the user leaves the program</summary>
|
|
78 protected override void OnDeactivate(EventArgs e)
|
|
79 {
|
|
80 ShortcutGrid.CancelEdit();
|
|
81 ShortcutGrid.EndEdit();
|
|
82 }
|
|
83
|
|
84 /// <summary>Scans for shortcuts in the background</summary>
|
|
85 private void FindShortcutWorker_DoWork(object sender, DoWorkEventArgs e)
|
|
86 {
|
|
87 // Disable list change events while scan is in progress
|
|
88 _dataSource.RaiseListChangedEvents = false;
|
|
89 _shortcutKeyFinder.LoadShortcuts(IsAdministrator());
|
|
90 }
|
|
91
|
|
92 /// <summary>Updates the grid after the scan is complete</summary>
|
|
93 private void FindShortcutWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
|
|
94 {
|
|
95 _suspendUpdateButtons = true; // Prevent button flicker
|
|
96 // Save UI state
|
|
97 _firstDisplayedRowIndex = ShortcutGrid.FirstDisplayedScrollingRowIndex;
|
|
98 _oldSelection = SelectedShortcuts.ToList();
|
|
99
|
|
100 // Refresh the grid
|
|
101 _dataSource.RaiseListChangedEvents = true;
|
|
102 _dataSource.ResetBindings(false);
|
|
103 ShortcutGrid.ClearSelection();
|
|
104
|
|
105 // Restore UI state
|
|
106 if (ShortcutGrid.Rows.Count > 0)
|
|
107 ShortcutGrid.FirstDisplayedScrollingRowIndex = Math.Max(0, Math.Min(_firstDisplayedRowIndex, ShortcutGrid.Rows.Count - 1));
|
|
108 foreach (DataGridViewRow row in ShortcutGrid.Rows)
|
|
109 row.Selected = _oldSelection.Contains((Shortcut)row.DataBoundItem);
|
|
110 _suspendUpdateButtons = false;
|
|
111 UpdateButtons();
|
|
112
|
|
113 // Hide busy indicators that appear when the form is first launched
|
|
114 InitialProgressBar.Visible = false;
|
|
115 ShortcutGrid.Visible = true;
|
|
116 Cursor = Cursors.Default;
|
|
117 }
|
|
118
|
|
119 /// <summary>Updates the UI when a shortcut's properties have changed</summary>
|
|
120 void Shortcuts_ListChanged(object sender, ListChangedEventArgs e)
|
|
121 {
|
|
122 // Update the "Clear shortcut key" button since the selection's shortcut may have been set or unset
|
|
123 if (!_suspendUpdateButtons)
|
|
124 {
|
|
125 if (this.InvokeRequired)
|
|
126 this.Invoke(new Action(() => UpdateButtons()));
|
|
127 else
|
|
128 UpdateButtons();
|
|
129 }
|
|
130 // Force repainting of changed rows so the background color gets updated
|
|
131 if (e.ListChangedType == ListChangedType.ItemChanged && e.NewIndex < ShortcutGrid.Rows.Count)
|
|
132 ShortcutGrid.InvalidateRow(e.NewIndex);
|
|
133 }
|
|
134
|
|
135 /// <summary>Enables or disables the buttons depending on the current selection</summary>
|
|
136 private void UpdateButtons()
|
|
137 {
|
|
138 ClearButton.Enabled = ShortcutGrid.SelectedRows.Count > 0 && SelectedShortcuts.Any(s => s.Hotkey != null);
|
|
139 ClearAllButton.Enabled = Shortcuts.Any(s => s.Hotkey != null);
|
|
140 }
|
|
141
|
|
142 /// <summary>Sets and saves a shortcut's hotkey</summary>
|
|
143 private void SetShortcutHotkey(Shortcut shortcut, Hotkey hotkey)
|
|
144 {
|
|
145 try
|
|
146 {
|
|
147 shortcut.Hotkey = hotkey;
|
|
148 shortcut.Save();
|
|
149 }
|
|
150 catch
|
|
151 {
|
|
152 MessageBox.Show("Error setting shortcut key", "Set Shortcut Key", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
153 }
|
|
154 }
|
|
155
|
|
156 /// <summary>Sets cell background color based on its shortcut's properties, and inserts the shortcut's icon next to its name</summary>
|
|
157 private void ShortcutGrid_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
|
|
158 {
|
|
159 // Header row
|
|
160 if (e.RowIndex < 0)
|
|
161 return;
|
|
162
|
|
163 DataGridViewRow row = ShortcutGrid.Rows[e.RowIndex];
|
|
164 Shortcut shortcut = (Shortcut)row.DataBoundItem;
|
|
165
|
|
166 // Paint duplicate shortcut keys red
|
|
167 if (shortcut.HasDuplicateHotkey)
|
|
168 {
|
|
169 e.CellStyle.BackColor = Color.DarkRed;
|
|
170 e.CellStyle.ForeColor = Color.White;
|
|
171 e.CellStyle.SelectionBackColor = Color.OrangeRed;
|
|
172 e.CellStyle.SelectionForeColor = Color.White;
|
|
173 }
|
|
174 // Paint readonly shortcuts gray
|
|
175 else if (shortcut.ReadOnly)
|
|
176 {
|
|
177 e.CellStyle.BackColor = Color.LightGray;
|
|
178 e.CellStyle.ForeColor = Color.Black;
|
|
179 e.CellStyle.SelectionBackColor = Color.SteelBlue;
|
|
180 e.CellStyle.SelectionForeColor = Color.White;
|
|
181 }
|
|
182
|
|
183 // Paint the shortcut's icon next to its name
|
|
184 if (e.ColumnIndex == ShortcutColumn.Index)
|
|
185 {
|
|
186 Color textColor = row.Selected ? e.CellStyle.SelectionForeColor : e.CellStyle.ForeColor;
|
|
187 if (_textBrush == null || _textBrush.Color != textColor)
|
|
188 _textBrush = new SolidBrush(textColor);
|
|
189 e.PaintBackground(e.ClipBounds, row.Selected);
|
|
190 Rectangle textRect = new Rectangle(e.CellBounds.X + _iconSize + _iconMargin * 2, e.CellBounds.Y, e.CellBounds.Width - _iconSize - _iconMargin * 2, e.CellBounds.Height);
|
|
191 e.Graphics.DrawImage(shortcut.Icon, _iconMargin, textRect.Y + ((textRect.Height - _iconSize) / 2));
|
|
192 e.Graphics.DrawString(e.FormattedValue.ToString(), e.CellStyle.Font, _textBrush, textRect, _cellFormat);
|
|
193 e.Handled = true;
|
|
194 }
|
|
195 }
|
|
196
|
|
197 /// <summary>Triggers direct hotkey editing if the hotkey column is double clicked, or opens the shortcut's Properties window for any other column</summary>
|
|
198 private void ShortcutGrid_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
|
|
199 {
|
|
200 if (e.ColumnIndex == HotkeyColumn.Index)
|
|
201 {
|
|
202 ShortcutGrid.CurrentCell = ShortcutGrid.Rows[e.RowIndex].Cells[e.ColumnIndex];
|
|
203 ShortcutGrid.BeginEdit(true);
|
|
204 }
|
|
205 else
|
|
206 {
|
|
207 ((Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem).ShowExplorerPropertiesWindow(Handle);
|
|
208 }
|
|
209 }
|
|
210
|
|
211 /// <summary>Opens the Properties window for the selected row on Enter, or activates the inline shortcut editor on F2</summary>
|
|
212 private void ShortcutGrid_KeyDown(object sender, KeyEventArgs e)
|
|
213 {
|
|
214 if (e.KeyCode == Keys.Enter)
|
|
215 {
|
|
216 e.Handled = true;
|
|
217 if (SelectedShortcuts.Count == 1)
|
|
218 SelectedShortcuts[0].ShowExplorerPropertiesWindow(Handle);
|
|
219 }
|
|
220 else if (e.KeyCode == Keys.F2)
|
|
221 {
|
|
222 if (ShortcutGrid.SelectedRows.Count == 1)
|
|
223 {
|
|
224 ShortcutGrid.CurrentCell = ShortcutGrid.SelectedRows[0].Cells[HotkeyColumn.Index];
|
|
225 ShortcutGrid.BeginEdit(true);
|
|
226 }
|
|
227 }
|
|
228 }
|
|
229
|
|
230 /// <summary>Prepares the inline editor by clearing the text and registering event handlers</summary>
|
|
231 private void ShortcutGrid_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
|
|
232 {
|
|
233 (e.Control as TextBox).Text = "";
|
|
234 (e.Control as TextBox).KeyDown += CellEditor_KeyDown;
|
|
235 (e.Control as TextBox).PreviewKeyDown += CellEditor_PreviewKeyDown;
|
|
236 (e.Control as TextBox).Leave += CellEditor_Leave;
|
|
237 }
|
|
238
|
|
239 /// <summary>Removes event handlers from inline editor when it loses focus</summary>
|
|
240 void CellEditor_Leave(object sender, EventArgs e)
|
|
241 {
|
|
242 (sender as TextBox).KeyDown -= CellEditor_KeyDown;
|
|
243 (sender as TextBox).PreviewKeyDown -= CellEditor_PreviewKeyDown;
|
|
244 (sender as TextBox).Leave -= CellEditor_Leave;
|
|
245 }
|
|
246
|
|
247 /// <summary>Finishes inline hotkey editing on Enter</summary>
|
|
248 private void CellEditor_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
|
|
249 {
|
|
250 if (e.KeyCode == Keys.Enter)
|
|
251 ShortcutGrid.EndEdit();
|
|
252 }
|
|
253
|
|
254 /// <summary>Listens for key events when the inline editor is active and converts them to a hotkey</summary>
|
|
255 void CellEditor_KeyDown(object sender, KeyEventArgs e)
|
|
256 {
|
|
257 System.Diagnostics.Debug.WriteLine("keydown");
|
|
258 if (e.KeyCode == Keys.Back || e.KeyCode == Keys.Delete)
|
|
259 {
|
|
260 _hotkey = null;
|
|
261 (sender as TextBox).Clear();
|
|
262 }
|
|
263 else if (e.KeyCode != Keys.Enter)
|
|
264 {
|
|
265 _hotkey = new Hotkey()
|
|
266 {
|
|
267 KeyCode = (byte)e.KeyCode,
|
|
268 Control = e.Control,
|
|
269 Alt = e.Alt,
|
|
270 Shift = e.Shift
|
|
271 };
|
|
272 (sender as TextBox).Text = _hotkey.ToString();
|
|
273 }
|
|
274 e.Handled = true;
|
|
275 e.SuppressKeyPress = true;
|
|
276 }
|
|
277
|
|
278 /// <summary>Saves the new hotkey to the data source and the shortcut when inline editing is complete</summary>
|
|
279 private void ShortcutGrid_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
|
|
280 {
|
|
281 e.ParsingApplied = true;
|
|
282 e.Value = _hotkey;
|
|
283 SetShortcutHotkey((Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem, _hotkey);
|
|
284 }
|
|
285
|
|
286 /// <summary>Updates the list view to show all shorcuts or only shortcuts with hotkeys</summary>
|
|
287 private void ShowAllCheckBox_CheckedChanged(object sender, EventArgs e)
|
|
288 {
|
|
289 _shortcutKeyFinder.ShowAll = ShowAllCheckBox.Checked;
|
|
290 ShortcutGrid.ClearSelection();
|
|
291 SavePreferences();
|
|
292 }
|
|
293
|
|
294 /// <summary>Removes the hotkeys from the selected shortcuts when the Clear button is clicked</summary>
|
|
295 private void ClearButton_Click(object sender, EventArgs e)
|
|
296 {
|
|
297 foreach (Shortcut shortcut in SelectedShortcuts.Where(s => !s.ReadOnly && s.Hotkey != null))
|
|
298 SetShortcutHotkey(shortcut, null);
|
|
299 }
|
|
300
|
|
301 /// <summary>Clears shortcuts from all hotkeys when the Clear All button is clicked</summary>
|
|
302 private void ClearAllButton_Click(object sender, EventArgs e)
|
|
303 {
|
|
304 _suspendUpdates = true;
|
|
305 if (MessageBox.Show("Are you sure you want to clear all assigned shortcut keys?", "Clear Shortcut Keys", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
|
|
306 {
|
|
307 foreach (Shortcut shortcut in Shortcuts.Where(s => !s.ReadOnly && s.Hotkey != null))
|
|
308 SetShortcutHotkey(shortcut, null);
|
|
309 }
|
|
310 _suspendUpdates = false;
|
|
311 }
|
|
312
|
|
313 /// <summary>Prepares for inline shortcut editing, or prevents it if the shortcut is read only</summary>
|
|
314 private void ShortcutGrid_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
|
|
315 {
|
|
316 _hotkey = null;
|
|
317 e.Cancel = ((Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem).ReadOnly;
|
|
318 }
|
|
319
|
|
320 /// <summary>Updates the button state when the grid selection changes</summary>
|
|
321 private void ShortcutGrid_SelectionChanged(object sender, EventArgs e)
|
|
322 {
|
|
323 if (!_suspendUpdateButtons)
|
|
324 UpdateButtons();
|
|
325 }
|
|
326
|
|
327 /// <summary>Updates the button state since the hotkey of the current item may have changed</summary>
|
|
328 private void ShortcutGrid_CellEndEdit(object sender, DataGridViewCellEventArgs e)
|
|
329 {
|
|
330 if (!_suspendUpdateButtons)
|
|
331 UpdateButtons();
|
|
332 }
|
|
333
|
|
334 /// <summary>Checks whether the program is running with elevated permissions</summary>
|
|
335 /// <remarks>http://stackoverflow.com/a/11660205</remarks>
|
|
336 public static bool IsAdministrator()
|
|
337 {
|
|
338 return (new WindowsPrincipal(WindowsIdentity.GetCurrent())).IsInRole(WindowsBuiltInRole.Administrator);
|
|
339 }
|
|
340
|
|
341 /// <summary>Shows or hides the button to elevate to administrator based on whether the program is already running as an administrator</summary>
|
|
342 private void SetupElevateButton()
|
|
343 {
|
|
344 if (IsAdministrator())
|
|
345 ElevateButton.Visible = false;
|
|
346 else
|
|
347 Win32Helpers.AddUacShield(ElevateButton);
|
|
348 }
|
|
349
|
|
350 /// <summary>Restarts the program, requesting elevation</summary>
|
|
351 private void ElevateButton_Click(object sender, EventArgs e)
|
|
352 {
|
|
353 ProcessStartInfo info = new ProcessStartInfo() { UseShellExecute = true, WorkingDirectory = Environment.CurrentDirectory, FileName = Application.ExecutablePath, Verb = "runas" };
|
|
354 try
|
|
355 {
|
|
356 Process.Start(info);
|
|
357 Application.Exit();
|
|
358 }
|
|
359 catch { } // Do nothing if the user canceled the consent dialog
|
|
360 }
|
|
361
|
|
362 /// <summary>Saves checkbox state so it can be restored when the program is relaunched later</summary>
|
|
363 private void SavePreferences()
|
|
364 {
|
|
365 try
|
|
366 {
|
|
367 RegistryKey key = Registry.CurrentUser.CreateSubKey(_registryKey);
|
|
368 key.SetValue(_showAllPreference, ShowAllCheckBox.Checked, RegistryValueKind.DWord);
|
|
369 }
|
|
370 catch { } // Silently fail if we can't save preferences
|
|
371 }
|
|
372
|
|
373 /// <summary>Restores the checkbox state to the same as when the program last exited</summary>
|
|
374 private void LoadPreferences()
|
|
375 {
|
|
376 try
|
|
377 {
|
|
378 RegistryKey key = Registry.CurrentUser.OpenSubKey(_registryKey);
|
|
379 ShowAllCheckBox.Checked = Convert.ToBoolean((int)key.GetValue(_showAllPreference, 0));
|
|
380 }
|
|
381 catch { } // Silently fail if we can't load preferences
|
|
382 }
|
|
383
|
|
384 private void ShortcutGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
|
385 {
|
|
386 string tooltip = null;
|
|
387 Shortcut shortcut = (Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem;
|
|
388
|
|
389 if (shortcut.HasDuplicateHotkey)
|
|
390 tooltip = "More than one shortcut has the same hotkey assigned";
|
|
391 else if (shortcut.ReadOnly)
|
|
392 tooltip = "Administrative privileges are required to edit this shortcut." + Environment.NewLine + "To edit, click the \"Allow editing shortcuts shared by all users\" button.";
|
|
393
|
|
394 ShortcutGrid.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = tooltip;
|
|
395 }
|
|
396 }
|
|
397 }
|