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 }