bugfixes related to fast open closes of stm, refactor KeyboardInput, refactor explorer sort, other minor refactor, version 254

This commit is contained in:
Markus Hofknecht 2020-03-08 13:21:15 +01:00
parent 0023079bb1
commit 120bb1274d
11 changed files with 512 additions and 472 deletions

View file

@ -34,7 +34,7 @@ namespace SystemTrayMenu
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
this.labelTitle = new SystemTrayMenu.LabelNoCopy();
this.labelTitle = new global::SystemTrayMenu.LabelNoCopy();
this.dgv = new System.Windows.Forms.DataGridView();
this.ColumnIcon = new System.Windows.Forms.DataGridViewImageColumn();
this.ColumnText = new System.Windows.Forms.DataGridViewTextBoxColumn();
@ -174,7 +174,7 @@ namespace SystemTrayMenu
}
#endregion
private SystemTrayMenu.LabelNoCopy labelTitle;
private global::SystemTrayMenu.LabelNoCopy labelTitle;
private System.Windows.Forms.DataGridView dgv;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel;
private System.Windows.Forms.DataGridViewImageColumn ColumnIcon;

View file

@ -11,6 +11,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using SystemTrayMenu.Handler;
using SystemTrayMenu.Helper;
using TAFactory.IconPack;

422
Handler/KeyboardInput.cs Normal file
View file

@ -0,0 +1,422 @@
using Clearcove.Logging;
using System;
using System.Linq;
using System.Windows.Forms;
using SystemTrayMenu.Controls;
using SystemTrayMenu.Helper;
namespace SystemTrayMenu.Handler
{
class KeyboardInput : IDisposable
{
public event EventHandler HotKeyPressed;
public event EventHandler ClosePressed;
public Action<DataGridView, int> RowSelected;
public Action<int, int, DataGridView> RowDeselected;
public event EventHandler Cleared;
Logger log = new Logger(nameof(KeyboardInput));
private Menu[] menus;
KeyboardHook hook = new KeyboardHook();
Timer timerKeySearch = new Timer();
public int iRowKey = -1;
public int iMenuKey = 0;
string KeySearchString = string.Empty;
public KeyboardInput(Menu[] menus)
{
this.menus = menus;
timerKeySearch.Interval = MenuDefines.KeySearchInterval;
timerKeySearch.Tick += TimerKeySearch_Tick;
void TimerKeySearch_Tick(object sender, EventArgs e)
{
// this search has expired, reset search
timerKeySearch.Stop();
KeySearchString = string.Empty;
}
}
public void Dispose()
{
hook.Dispose();
}
internal void RegisterHotKey()
{
if (!string.IsNullOrEmpty(Properties.Settings.Default.HotKey))
{
var cvt = new KeysConverter();
var key = (Keys)cvt.ConvertFrom(
Properties.Settings.Default.HotKey);
try
{
hook.RegisterHotKey(
KeyboardHookModifierKeys.Control |
KeyboardHookModifierKeys.Alt,
key);
hook.KeyPressed += hook_KeyPressed;
void hook_KeyPressed(object sender, KeyPressedEventArgs e)
{
HotKeyPressed?.Invoke();
}
}
catch (Exception ex)
{
log.Info($"key:'{key.ToString()}'");
log.Error($"{ex.ToString()}");
Properties.Settings.Default.HotKey = string.Empty;
Properties.Settings.Default.Save();
MessageBox.Show(ex.Message);
}
}
}
internal void ResetSelectedByKey()
{
iRowKey = -1;
iMenuKey = 0;
}
internal void CmdKeyProcessed(Keys keys)
{
SelectByKey(keys);
}
/// <summary>
/// While menu is open user presses a key to search for specific entries.
/// </summary>
/// <param name="sender">not used</param>
/// <param name="e">Key data of the pressed key</param>
internal void KeyPress(object sender, KeyPressEventArgs e)
{
if (char.IsLetterOrDigit(e.KeyChar) ||
char.IsPunctuation(e.KeyChar) ||
char.IsWhiteSpace(e.KeyChar) ||
char.IsSeparator(e.KeyChar))
{
string letter = e.KeyChar.ToString();
timerKeySearch.Stop();
if (string.IsNullOrEmpty(KeySearchString))
{
// no search string set, start new search with initial letter
KeySearchString += letter;
SelectByKey(Keys.None, KeySearchString);
}
else if (KeySearchString.Length == 1 && KeySearchString.LastOrDefault().ToString() == letter)
{
// initial letter pressed again, jump to next element
SelectByKey(Keys.None, letter);
}
else
{
// new character for the search string, narrow down the search
KeySearchString += letter;
SelectByKey(Keys.None, KeySearchString, true);
}
// give user some time to continue with this search
timerKeySearch.Start();
e.Handled = true;
}
}
internal bool IsAnyMenuSelectedByKey()
{
Menu menu = null;
DataGridView dgv = null;
string textselected = string.Empty;
return IsAnyMenuSelectedByKey(ref dgv, ref menu, ref textselected);
}
private bool IsAnyMenuSelectedByKey(
ref DataGridView dgv,
ref Menu menuFromSelected,
ref string textselected)
{
Menu menu = menus[iMenuKey];
bool isStillSelected = false;
if (menu != null &&
iRowKey > -1)
{
dgv = menu.GetDataGridView();
if (dgv.Rows.Count > iRowKey)
{
RowData rowData = (RowData)dgv.
Rows[iRowKey].Tag;
if (rowData.IsSelectedByKeyboard)
{
isStillSelected = true;
menuFromSelected = rowData.SubMenu;
#warning refactor datagridviewrow get
textselected = dgv.Rows[iRowKey].
Cells[1].Value.ToString();
}
}
}
return isStillSelected;
}
private void SelectByKey(Keys keys, string keyInput = "", bool KeepSelection = false)
{
int iRowBefore = iRowKey;
int iMenuBefore = iMenuKey;
Menu menu = menus[iMenuKey];
DataGridView dgv = null;
DataGridView dgvBefore = null;
Menu menuFromSelected = null;
string textselected = string.Empty;
bool isStillSelected = IsAnyMenuSelectedByKey(ref dgv, ref menuFromSelected, ref textselected);
if (isStillSelected)
{
if (KeepSelection)
{
// Is current selection is still valid for this search then skip selecting different item
if (textselected.ToLower().StartsWith(keyInput.ToLower()))
return;
}
dgvBefore = dgv;
}
else
{
ResetSelectedByKey();
menu = menus[iMenuKey];
dgv = menu.GetDataGridView();
}
bool toClear = false;
switch (keys)
{
case Keys.Enter:
if (iRowKey > -1 &&
dgv.Rows.Count > iRowKey)
{
RowData trigger = (RowData)dgv.Rows[iRowKey].Tag;
trigger.MouseDown(dgv, null);
//trigger.DoubleClick();
}
break;
case Keys.Up:
if (SelectMatchedReverse(dgv, iRowKey) ||
SelectMatchedReverse(dgv, dgv.Rows.Count - 1))
{
RowDeselected(iMenuBefore, iRowBefore, dgvBefore);
RowSelected(dgv, iRowKey);
toClear = true;
}
break;
case Keys.Down:
if (SelectMatched(dgv, iRowKey) ||
SelectMatched(dgv, 0))
{
RowDeselected(iMenuBefore, iRowBefore, dgvBefore);
RowSelected(dgv, iRowKey);
toClear = true;
}
break;
case Keys.Left:
int iMenuKeyNext = iMenuKey + 1;
if (isStillSelected)
{
if (menuFromSelected != null &&
menuFromSelected == menus[iMenuKeyNext])
{
dgv = menuFromSelected.GetDataGridView();
if (dgv.Rows.Count > 0)
{
iMenuKey += 1;
iRowKey = -1;
if (SelectMatched(dgv, iRowKey) ||
SelectMatched(dgv, 0))
{
RowDeselected(iMenuBefore,
iRowBefore, dgvBefore);
RowSelected(dgv, iRowKey);
toClear = true;
}
}
}
}
else
{
iRowKey = -1;
iMenuKey = menus.Where(m => m != null).Count() - 1;
if (menus[iMenuKey] != null)
{
dgv = menus[iMenuKey].GetDataGridView();
if (SelectMatched(dgv, iRowKey) ||
SelectMatched(dgv, 0))
{
RowDeselected(iMenuBefore, iRowBefore, dgvBefore);
RowSelected(dgv, iRowKey);
toClear = true;
}
}
else
{
log.Info("indexMenuByKey = menus.Where(m => m != null).Count()" +
"=> menus[iMenuKey] == null");
}
}
break;
case Keys.Right:
if (iMenuKey > 0)
{
if (menus[iMenuKey - 1] != null)
{
iMenuKey -= 1;
iRowKey = -1;
menu = menus[iMenuKey];
dgv = menu.GetDataGridView();
if (SelectMatched(dgv, dgv.SelectedRows[0].Index) ||
SelectMatched(dgv, 0))
{
RowDeselected(iMenuBefore, iRowBefore, dgvBefore);
RowSelected(dgv, iRowKey);
toClear = true;
}
}
}
else
{
RowDeselected(iMenuBefore, iRowBefore, dgvBefore);
iMenuKey = 0;
iRowKey = -1;
toClear = true;
Cleared?.Invoke();
}
break;
case Keys.Escape:
RowDeselected(iMenuBefore, iRowBefore, dgvBefore);
iMenuKey = 0;
iRowKey = -1;
toClear = true;
ClosePressed?.Invoke();
break;
default:
if (!string.IsNullOrEmpty(keyInput))
{
if (SelectMatched(dgv, iRowKey, keyInput) ||
SelectMatched(dgv, 0, keyInput))
{
RowDeselected(iMenuBefore, iRowBefore, null);
RowSelected(dgv, iRowKey);
toClear = true;
}
else if (isStillSelected)
{
iRowKey = iRowBefore - 1;
if (SelectMatched(dgv, iRowKey, keyInput) ||
SelectMatched(dgv, 0, keyInput))
{
RowDeselected(iMenuBefore, iRowBefore, null);
RowSelected(dgv, iRowKey);
}
else
{
iRowKey = iRowBefore;
}
}
}
break;
}
if (isStillSelected && toClear)
{
ClearIsSelectedByKey(iMenuBefore, iRowBefore);
}
}
private bool SelectMatched(DataGridView dgv,
int indexStart, string keyInput = "")
{
bool found = false;
for (int i = indexStart; i < dgv.Rows.Count; i++)
{
if (Select(dgv, i, keyInput))
{
found = true;
break;
}
}
return found;
}
private bool SelectMatchedReverse(DataGridView dgv,
int indexStart, string keyInput = "")
{
bool found = false;
for (int i = indexStart; i > -1; i--)
{
if (Select(dgv, i, keyInput))
{
found = true;
break;
}
}
return found;
}
private bool Select(DataGridView dgv, int i,
string keyInput = "")
{
bool found = false;
if (i > -1 &&
i != iRowKey &&
dgv.Rows.Count > i)
{
DataGridViewRow row = dgv.Rows[i];
RowData rowData = (RowData)row.Tag;
string text = row.Cells[1].Value.ToString();
if (text.ToLower().StartsWith(keyInput.ToLower()))
{
iRowKey = rowData.RowIndex;
rowData.IsSelectedByKeyboard = true;
row.Selected = false; //event trigger
row.Selected = true; //event trigger
if (row.Index < dgv.FirstDisplayedScrollingRowIndex)
{
dgv.FirstDisplayedScrollingRowIndex = row.Index;
}
else if (row.Index >=
dgv.FirstDisplayedScrollingRowIndex +
dgv.DisplayedRowCount(false))
{
dgv.FirstDisplayedScrollingRowIndex = row.Index -
dgv.DisplayedRowCount(false) + 1;
}
found = true;
}
}
return found;
}
internal void ClearIsSelectedByKey()
{
ClearIsSelectedByKey(iMenuKey, iRowKey);
}
private void ClearIsSelectedByKey(int menuIndex, int rowIndex)
{
Menu menu = menus[menuIndex];
if (menu != null && rowIndex > -1)
{
DataGridView dgv = menu.GetDataGridView();
if (dgv.Rows.Count > rowIndex)
{
DataGridViewRow row = dgv.Rows[rowIndex];
RowData rowData = (RowData)row.Tag;
rowData.IsSelectedByKeyboard = false;
row.Selected = false; //event trigger
}
}
}
}
}

View file

@ -1,7 +1,7 @@
using System;
using Timer = System.Windows.Forms.Timer;
namespace SystemTrayMenu.Helper
namespace SystemTrayMenu.Handler
{
class WaitFastLeave : IDisposable
{

View file

@ -1,7 +1,7 @@
using System;
using Timer = System.Windows.Forms.Timer;
namespace SystemTrayMenu.Helper
namespace SystemTrayMenu.Handler
{
class WaitMenuOpen : IDisposable
{

View file

@ -1,29 +0,0 @@
using System.Windows.Forms;
namespace SystemTrayMenu.Helper
{
public class ScreenMouse
{
public static Screen GetScreen()
{
return Screen.PrimaryScreen;
// Maybe we need for later feature
// Show on mouse position
//return GetScreenFromMousePosition(
// Cursor.Position.X, Cursor.Position.Y);
}
//public static Screen GetScreenFromMousePosition(int x, int y)
//{
// Screen clickedScreen = null;
// foreach (Screen screen in Screen.AllScreens)
// {
// if (screen.Bounds.Contains(new Point(x, y)))
// {
// clickedScreen = screen;
// }
// }
// return clickedScreen;
//}
}
}

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace SystemTrayMenu.Helper
{
class WindowsExplorerSort : IComparer<string>
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
static extern int StrCmpLogicalW(String x, String y);
public int Compare(string x, string y)
{
return StrCmpLogicalW(x, y);
}
}
}

View file

@ -55,8 +55,7 @@ namespace SystemTrayMenu
try
{
bool cancelAppRun = false;
using (SystemTrayMenuHandler stm =
new SystemTrayMenuHandler(ref cancelAppRun))
using (new SystemTrayMenu(ref cancelAppRun))
{
if (!cancelAppRun)
{

View file

@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.9.1.253")]
[assembly: AssemblyFileVersion("0.9.1.253")]
[assembly: AssemblyVersion("0.9.1.254")]
[assembly: AssemblyFileVersion("0.9.1.254")]

View file

@ -8,9 +8,9 @@ using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using SystemTrayMenu.Controls;
using SystemTrayMenu.Handler;
using SystemTrayMenu.Helper;
namespace SystemTrayMenu
@ -19,21 +19,19 @@ namespace SystemTrayMenu
//MethodBase m = MethodBase.GetCurrentMethod();
//log.Debug($"Executing {m.ReflectedType.Name}, {m.Name}");
#endregion
class SystemTrayMenuHandler : IDisposable
class SystemTrayMenu : IDisposable
{
Logger log = new Logger(nameof(SystemTrayMenuHandler));
Logger log = new Logger(nameof(SystemTrayMenu));
IShellDispatch4 iShellDispatch4 = null;
KeyboardHook hook = new KeyboardHook();
Timer timerKeySearch = new Timer();
int iRowKey = -1;
int iMenuKey = 0;
string KeySearchString = string.Empty;
MessageFilter messageFilter = new MessageFilter();
bool messageFilterAdded = false;
MenuNotifyIcon menuNotifyIcon = null;
WaitFastLeave fastLeave = new WaitFastLeave();
Menu[] menus = new Menu[MenuDefines.MenusMax];
KeyboardInput keyboardInput;
enum OpenCloseState { Default, Opening, Closing };
OpenCloseState openCloseState = OpenCloseState.Default;
@ -41,7 +39,7 @@ namespace SystemTrayMenu
BackgroundWorker worker = new BackgroundWorker();
Screen screen = null;
public SystemTrayMenuHandler(ref bool cancelAppRun)
public SystemTrayMenu(ref bool cancelAppRun)
{
log.Info("Application Start " +
Assembly.GetExecutingAssembly().
@ -58,42 +56,23 @@ namespace SystemTrayMenu
}
Config.UpgradeIfNotUpgraded();
if (!string.IsNullOrEmpty(Properties.Settings.Default.HotKey))
keyboardInput = new KeyboardInput(menus);
keyboardInput.RegisterHotKey();
keyboardInput.HotKeyPressed += SwitchOpenClose;
keyboardInput.ClosePressed += MenusFadeOut;
keyboardInput.RowSelected += KeyboardInputRowSelected;
void KeyboardInputRowSelected(DataGridView dgv, int rowIndex)
{
var cvt = new KeysConverter();
var key = (Keys)cvt.ConvertFrom(
Properties.Settings.Default.HotKey);
try
{
hook.RegisterHotKey(
KeyboardHookModifierKeys.Control |
KeyboardHookModifierKeys.Alt,
key);
hook.KeyPressed += hook_KeyPressed;
}
catch (Exception ex)
{
log.Info($"key:'{key.ToString()}'");
log.Error($"{ex.ToString()}");
Properties.Settings.Default.HotKey = string.Empty;
Properties.Settings.Default.Save();
MessageBox.Show(ex.Message);
}
FadeInIfNeeded();
CheckMenuOpenerStart(dgv, rowIndex);
}
keyboardInput.RowDeselected += CheckMenuOpenerStop;
keyboardInput.Cleared += FadeHalfOrOutIfNeeded;
menuNotifyIcon = new MenuNotifyIcon();
void hook_KeyPressed(object sender, KeyPressedEventArgs e)
{
SwitchOpenClose();
}
timerKeySearch.Interval = MenuDefines.KeySearchInterval;
timerKeySearch.Tick += TimerKeySearch_Tick;
menus[0] = new Menu();
menus[0].Dispose();
MessageFilter messageFilter = new MessageFilter();
Application.AddMessageFilter(messageFilter);
menuNotifyIcon.Exit += Application.Exit;
menuNotifyIcon.HandleClick += SwitchOpenClose;
@ -113,15 +92,12 @@ namespace SystemTrayMenu
worker.CancelAsync();
}
}
else if (menus[0].Visible)
{
openCloseState = OpenCloseState.Default;
ActivateMenu();
}
else
{
openCloseState = OpenCloseState.Opening;
screen = ScreenMouse.GetScreen();
#region NotifyIconIsInteededToBeOutside
// Idea is either to show always outside like dropbox
// (is nok due to windows rules, maybe only allowed when asking user?)
// (or to give at least a hint that drag drop possible?)
//bool IsNotifyIconInTaskbar()
//{
// bool isNotifyIconInTaskbar = false;
@ -141,11 +117,18 @@ namespace SystemTrayMenu
// // Program.Translate("buttonOk"));
// //hintForm.Show();
//}
while (!menus[0].IsDisposed)
#endregion
while (menus[0].Visible &&
!menus[0].IsDisposed &&
worker.IsBusy)
{
Application.DoEvents();
}
openCloseState = OpenCloseState.Opening;
screen = Screen.PrimaryScreen;
menuNotifyIcon.LoadingStart();
worker.RunWorkerAsync();
}
@ -165,7 +148,7 @@ namespace SystemTrayMenu
void Worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
ResetSelectedByKey();
keyboardInput.ResetSelectedByKey();
menuNotifyIcon.LoadingStop();
MenuData menuData = (MenuData)e.Result;
if (menuData.Validity == MenuDataValidity.Valid)
@ -174,6 +157,11 @@ namespace SystemTrayMenu
menus[0].AdjustLocationAndSize(screen);
ActivateMenu();
menus[0].AdjustLocationAndSize(screen);
if (!messageFilterAdded)
{
Application.AddMessageFilter(messageFilter);
messageFilterAdded = true;
}
}
}
@ -230,6 +218,7 @@ namespace SystemTrayMenu
cancelAppRun = true;
}
}
void FadeInIfNeeded()
{
if (menus[0].Visible &&
@ -250,12 +239,13 @@ namespace SystemTrayMenu
!Menus().Any(m => m.IsMouseOn(mousePosition));
bool isAnyMenuActive = IsAnyMenuActive();
if (isMouseOnAnyMenu)
if (menus[0].Visible &&
isMouseOnAnyMenu)
{
if (isAnyMenuActive &&
!(openCloseState == OpenCloseState.Closing))
{
if (!IsAnyMenuSelectedByKey())
if (!keyboardInput.IsAnyMenuSelectedByKey())
{
Menus().ToList().ForEach(menu => menu.FadeHalf());
}
@ -267,15 +257,9 @@ namespace SystemTrayMenu
}
}
private void ResetSelectedByKey()
{
iRowKey = -1;
iMenuKey = 0;
}
public void Dispose()
{
hook.Dispose();
keyboardInput.Dispose();
menuNotifyIcon.Dispose();
fastLeave.Dispose();
DisposeMenu(menus[0]);
@ -313,7 +297,6 @@ namespace SystemTrayMenu
void AdjustSubMenusLocationAndSize()
{
Screen screen = ScreenMouse.GetScreen();
int heightMax = screen.Bounds.Height -
new Taskbar().Size.Height;
Menu menuPredecessor = menus[0];
@ -336,7 +319,7 @@ namespace SystemTrayMenu
widthPredecessors -= newWith;
}
}
else if (screen.Bounds.Width <
else if (Screen.PrimaryScreen.Bounds.Width <
widthPredecessors + menuPredecessor.Width + menu.Width)
{
directionToRight = true;
@ -385,24 +368,15 @@ namespace SystemTrayMenu
IsAnyMenuActive();
}
void Activated(object sender, EventArgs e)
{
Menu triggeredMenu = (Menu)sender;
menus[0].SetTitleColorActive();
}
bool IsAnyMenuActive()
{
bool isAnyMenuActive;
Form activeForm = Form.ActiveForm;
//isAnyMenuActive = Menus().Any(m => m.IsActive(activeForm));
isAnyMenuActive = activeForm is Menu;
bool isAnyMenuActive = Form.ActiveForm is Menu;
if (!isAnyMenuActive)
{
menus[0].SetTitleColorDeactive();
CheckMenuOpenerStop(iMenuKey, iRowKey);
ClearIsSelectedByKey(iMenuKey, iRowKey);
ResetSelectedByKey();
CheckMenuOpenerStop(keyboardInput.iMenuKey,
keyboardInput.iRowKey);
keyboardInput.ClearIsSelectedByKey();
}
else
{
@ -720,6 +694,12 @@ namespace SystemTrayMenu
void MenusFadeOut()
{
if (messageFilterAdded)
{
Application.RemoveMessageFilter(messageFilter);
messageFilterAdded = false;
}
Menus().ToList().ForEach(menu =>
{
if (menu.Level > 0)
@ -755,361 +735,20 @@ namespace SystemTrayMenu
dgv.MouseDoubleClick += Dgv_MouseDoubleClick;
dgv.MouseDown += Dgv_MouseDown;
dgv.SelectionChanged += Dgv_SelectionChanged;
menu.KeyPress += KeyPress;
menu.CmdKeyProcessed += CmdKeyProcessed;
menu.Activated += Activated;
menu.KeyPress += keyboardInput.KeyPress;
menu.CmdKeyProcessed += keyboardInput.CmdKeyProcessed;
menu.Activated += Activated;
void Activated(object sender, EventArgs e)
{
menus[0].SetTitleColorActive();
}
menu.Deactivated += fastLeave.Start;
menu.VisibleChanged += DisposeWhenHidden;
AddItemsToMenu(menuData.RowDatas, menu);
return menu;
}
/// <summary>
/// While menu is open user presses a key to search for specific entries.
/// </summary>
/// <param name="sender">not used</param>
/// <param name="e">Key data of the pressed key</param>
private void KeyPress(object sender, KeyPressEventArgs e)
{
if (char.IsLetterOrDigit(e.KeyChar) ||
char.IsPunctuation(e.KeyChar) ||
char.IsWhiteSpace(e.KeyChar) ||
char.IsSeparator(e.KeyChar))
{
string letter = e.KeyChar.ToString();
timerKeySearch.Stop();
if (string.IsNullOrEmpty(KeySearchString))
{
// no search string set, start new search with initial letter
KeySearchString += letter;
SelectByKey(Keys.None, KeySearchString);
}
else if (KeySearchString.Length == 1 && KeySearchString.LastOrDefault().ToString() == letter)
{
// initial letter pressed again, jump to next element
SelectByKey(Keys.None, letter);
}
else
{
// new character for the search string, narrow down the search
KeySearchString += letter;
SelectByKey(Keys.None, KeySearchString, true);
}
// give user some time to continue with this search
timerKeySearch.Start();
e.Handled = true;
}
}
private void TimerKeySearch_Tick(object sender, EventArgs e)
{
// this search has expired, reset search
timerKeySearch.Stop();
KeySearchString = string.Empty;
}
private void CmdKeyProcessed(Keys keys)
{
SelectByKey(keys);
}
private bool IsAnyMenuSelectedByKey()
{
Menu menu = null;
DataGridView dgv = null;
string textselected = string.Empty;
return IsAnyMenuSelectedByKey(ref dgv, ref menu, ref textselected);
}
private bool IsAnyMenuSelectedByKey(
ref DataGridView dgv,
ref Menu menuFromSelected,
ref string textselected)
{
Menu menu = menus[iMenuKey];
bool isStillSelected = false;
if (menu != null &&
iRowKey > -1)
{
dgv = menu.GetDataGridView();
if (dgv.Rows.Count > iRowKey)
{
RowData rowData = (RowData)dgv.
Rows[iRowKey].Tag;
if (rowData.IsSelectedByKeyboard)
{
isStillSelected = true;
menuFromSelected = rowData.SubMenu;
#warning refactor datagridviewrow get
textselected = dgv.Rows[iRowKey].
Cells[1].Value.ToString();
}
}
}
return isStillSelected;
}
private void SelectByKey(Keys keys, string keyInput = "", bool KeepSelection = false)
{
int iRowBefore = iRowKey;
int iMenuBefore = iMenuKey;
Menu menu = menus[iMenuKey];
DataGridView dgv = null;
DataGridView dgvBefore = null;
Menu menuFromSelected = null;
string textselected = string.Empty;
bool isStillSelected = IsAnyMenuSelectedByKey(ref dgv, ref menuFromSelected, ref textselected);
if (isStillSelected)
{
if (KeepSelection)
{
// Is current selection is still valid for this search then skip selecting different item
if (textselected.ToLower().StartsWith(keyInput.ToLower()))
return;
}
dgvBefore = dgv;
}
else
{
ResetSelectedByKey();
menu = menus[iMenuKey];
dgv = menu.GetDataGridView();
}
bool toClear = false;
switch (keys)
{
case Keys.Enter:
if (iRowKey > -1 &&
dgv.Rows.Count > iRowKey)
{
RowData trigger = (RowData)dgv.Rows[iRowKey].Tag;
trigger.MouseDown(dgv, null);
//trigger.DoubleClick();
}
break;
case Keys.Up:
FadeInIfNeeded();
if (SelectMatchedReverse(dgv, iRowKey) ||
SelectMatchedReverse(dgv, dgv.Rows.Count - 1))
{
CheckMenuOpenerStop(iMenuBefore, iRowBefore, dgvBefore);
CheckMenuOpenerStart(dgv, iRowKey);
toClear = true;
}
break;
case Keys.Down:
FadeInIfNeeded();
if (SelectMatched(dgv, iRowKey) ||
SelectMatched(dgv, 0))
{
CheckMenuOpenerStop(iMenuBefore, iRowBefore, dgvBefore);
CheckMenuOpenerStart(dgv, iRowKey);
toClear = true;
}
break;
case Keys.Left:
FadeInIfNeeded();
int iMenuKeyNext = iMenuKey + 1;
if (isStillSelected)
{
if (menuFromSelected != null &&
menuFromSelected == menus[iMenuKeyNext])
{
dgv = menuFromSelected.GetDataGridView();
if (dgv.Rows.Count > 0)
{
iMenuKey += 1;
iRowKey = -1;
if (SelectMatched(dgv, iRowKey) ||
SelectMatched(dgv, 0))
{
CheckMenuOpenerStop(iMenuBefore,
iRowBefore, dgvBefore);
CheckMenuOpenerStart(dgv, iRowKey);
toClear = true;
}
}
}
}
else
{
iRowKey = -1;
iMenuKey = menus.Where(m => m != null).Count() - 1;
if (menus[iMenuKey] != null)
{
dgv = menus[iMenuKey].GetDataGridView();
if (SelectMatched(dgv, iRowKey) ||
SelectMatched(dgv, 0))
{
CheckMenuOpenerStop(iMenuBefore, iRowBefore, dgvBefore);
CheckMenuOpenerStart(dgv, iRowKey);
toClear = true;
}
}
else
{
log.Info("indexMenuByKey = menus.Where(m => m != null).Count()" +
"=> menus[iMenuKey] == null");
}
}
break;
case Keys.Right:
if (iMenuKey > 0)
{
if (menus[iMenuKey - 1] != null)
{
iMenuKey -= 1;
iRowKey = -1;
menu = menus[iMenuKey];
dgv = menu.GetDataGridView();
if (SelectMatched(dgv, dgv.SelectedRows[0].Index) ||
SelectMatched(dgv, 0))
{
CheckMenuOpenerStop(iMenuBefore, iRowBefore, dgvBefore);
CheckMenuOpenerStart(dgv, iRowKey);
toClear = true;
}
}
}
else
{
CheckMenuOpenerStop(iMenuBefore, iRowBefore, dgvBefore);
iMenuKey = 0;
iRowKey = -1;
toClear = true;
FadeHalfOrOutIfNeeded();
}
break;
case Keys.Escape:
CheckMenuOpenerStop(iMenuBefore, iRowBefore, dgvBefore);
iMenuKey = 0;
iRowKey = -1;
toClear = true;
MenusFadeOut();
break;
default:
if (!string.IsNullOrEmpty(keyInput))
{
if (SelectMatched(dgv, iRowKey, keyInput) ||
SelectMatched(dgv, 0, keyInput))
{
FadeInIfNeeded();
CheckMenuOpenerStop(iMenuBefore, iRowBefore);
CheckMenuOpenerStart(dgv, iRowKey);
toClear = true;
}
else if (isStillSelected)
{
iRowKey = iRowBefore - 1;
if (SelectMatched(dgv, iRowKey, keyInput) ||
SelectMatched(dgv, 0, keyInput))
{
FadeInIfNeeded();
CheckMenuOpenerStop(iMenuBefore, iRowBefore);
CheckMenuOpenerStart(dgv, iRowKey);
}
else
{
iRowKey = iRowBefore;
}
}
}
break;
}
if (isStillSelected && toClear)
{
ClearIsSelectedByKey(iMenuBefore, iRowBefore);
}
}
private bool SelectMatched(DataGridView dgv,
int indexStart, string keyInput = "")
{
bool found = false;
for (int i = indexStart; i < dgv.Rows.Count; i++)
{
if (Select(dgv, i, keyInput))
{
found = true;
break;
}
}
return found;
}
private bool SelectMatchedReverse(DataGridView dgv,
int indexStart, string keyInput = "")
{
bool found = false;
for (int i = indexStart; i > -1; i--)
{
if (Select(dgv, i, keyInput))
{
found = true;
break;
}
}
return found;
}
private bool Select(DataGridView dgv, int i,
string keyInput = "")
{
bool found = false;
if (i > -1 &&
i != iRowKey &&
dgv.Rows.Count > i)
{
DataGridViewRow row = dgv.Rows[i];
RowData rowData = (RowData)row.Tag;
string text = row.Cells[1].Value.ToString();
if (text.ToLower().StartsWith(keyInput.ToLower()))
{
iRowKey = rowData.RowIndex;
rowData.IsSelectedByKeyboard = true;
row.Selected = false; //event trigger
row.Selected = true; //event trigger
if (row.Index < dgv.FirstDisplayedScrollingRowIndex)
{
dgv.FirstDisplayedScrollingRowIndex = row.Index;
}
else if (row.Index >=
dgv.FirstDisplayedScrollingRowIndex +
dgv.DisplayedRowCount(false))
{
dgv.FirstDisplayedScrollingRowIndex = row.Index -
dgv.DisplayedRowCount(false) + 1;
}
found = true;
}
}
return found;
}
private void ClearIsSelectedByKey(int menuIndex, int rowIndex)
{
Menu menu = menus[menuIndex];
if (menu != null && rowIndex > -1)
{
DataGridView dgv = menu.GetDataGridView();
if (dgv.Rows.Count > rowIndex)
{
DataGridViewRow row = dgv.Rows[rowIndex];
RowData rowData = (RowData)row.Tag;
rowData.IsSelectedByKeyboard = false;
row.Selected = false; //event trigger
}
}
}
private void AddItemsToMenu(List<RowData> data, Menu menu)
{
@ -1171,15 +810,4 @@ namespace SystemTrayMenu
}
}
}
class WindowsExplorerSort : IComparer<string>
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
static extern int StrCmpLogicalW(String x, String y);
public int Compare(string x, string y)
{
return StrCmpLogicalW(x, y);
}
}
}

View file

@ -167,8 +167,9 @@
<Compile Include="Helper\MessageFilter.cs" />
<Compile Include="Helper\ShellContextMenu.cs" />
<Compile Include="Helper\File\FileUrl.cs" />
<Compile Include="Helper\WaitFastLeave.cs" />
<Compile Include="Helper\WaitMenuOpen.cs" />
<Compile Include="Handler\WaitFastLeave.cs" />
<Compile Include="Handler\WaitMenuOpen.cs" />
<Compile Include="Helper\WindowsExplorerSort.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
@ -196,8 +197,8 @@
<Compile Include="Controls\AppNotifyIcon.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Helper\ScreenMouse.cs" />
<Compile Include="SystemTrayMenuHandler.cs" />
<Compile Include="Handler\KeyboardInput.cs" />
<Compile Include="SystemTrayMenu.cs" />
<EmbeddedResource Include="Controls\AboutBox.resx">
<DependentUpon>AboutBox.cs</DependentUpon>
</EmbeddedResource>
@ -296,6 +297,7 @@
<ItemGroup>
<WCFMetadata Include="Connected Services\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.