Mercurial > shortcutkeyfinder
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:209d9210c18f |
---|---|
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 } |