WPF C# - DataGrid select 用户按键上的项目
WPF C# - DataGrid select item on user keypress
我将此功能设为 select 用户按键时 DataGrid 上的项目。如果用户密钥是 "A",它将 select 用户名以字母 "A" 开头的第一项。如果用户密钥再次为 "A",它将 select 用户名以字母 "A" 开头的下一个项目,依此类推。该功能效果很好,但我想要的是当没有更多用户名以 "A" 开头的项目重新开始时(select 第一项),它目前保留在用户名开头的最后一个项目上字母 "A".
private static Key lastKey;
private static int lastFoundIndex = 0;
public static void AccountsDataGrid_SearchByKey(object sender, KeyEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if ((dataGrid.Items.Count == 0) && !(e.Key >= Key.A && e.Key <= Key.Z))
{
return;
}
if ((lastKey != e.Key) || (lastFoundIndex == dataGrid.Items.Count - 1))
{
lastFoundIndex = 0;
}
for (int i = lastFoundIndex; i < dataGrid.Items.Count; i++)
{
if (dataGrid.SelectedIndex == i)
{
continue;
}
Account account = dataGrid.Items[i] as Account;
if (account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.CurrentCulture))
{
dataGrid.ScrollIntoView(account);
dataGrid.SelectedItem = account;
lastFoundIndex = i;
break;
}
}
lastKey = e.Key;
}
更新(含解决方案):
受 Danielle 想法的启发,我修改了如下代码,效果非常好。
private static Key lastKey;
private static int lastFoundIndex = 0;
public static void AccountsDataGrid_SearchByKey(object sender, KeyEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if ((dataGrid.Items.Count == 0) && !(e.Key >= Key.A && e.Key <= Key.Z))
{
return;
}
if ((lastKey != e.Key) || (lastFoundIndex == dataGrid.Items.Count - 1))
{
lastFoundIndex = 0;
}
Func<object, bool> itemCompareMethod = (item) =>
{
Account account = item as Account;
if (account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.CurrentCulture))
{
return true;
}
return false;
};
lastFoundIndex = FindDataGridRecordWithinRange(dataGrid, lastFoundIndex, dataGrid.Items.Count, itemCompareMethod);
if (lastFoundIndex == -1)
{
lastFoundIndex = FindDataGridRecordWithinRange(dataGrid, 0, dataGrid.Items.Count, itemCompareMethod);
}
if (lastFoundIndex != -1)
{
dataGrid.ScrollIntoView(dataGrid.Items[lastFoundIndex]);
dataGrid.SelectedIndex = lastFoundIndex;
}
if (lastFoundIndex == -1)
{
lastFoundIndex = 0;
dataGrid.SelectedItem = null;
}
lastKey = e.Key;
}
private static int FindDataGridRecordWithinRange(DataGrid dataGrid, int min, int max, Func<object, bool> itemCompareMethod)
{
for (int i = min; i < max; i++)
{
if (dataGrid.SelectedIndex == i)
{
continue;
}
if (itemCompareMethod(dataGrid.Items[i]))
{
return i;
}
}
return -1;
}
您的代码表示要从头开始重新搜索,lastFoundIndex 应等于 dataGrid.Items.Count - 1(假设再次按下相同的键)。
但是您最后找到的 Account 可能不在 Items 集合的末尾,因此在按下前一个键时 lastFoundIndex 未设置为 dataGrid.Items.Count - 1。这种情况下你重新开始的条件不满足。
尝试像这样更改最后发现的检查和 "for" 循环:
bool found = false;
bool lastFound = true;
if (lastKey != e.Key || lastFound)
{
lastFoundIndex = 0;
}
for (int i = lastFoundIndex; i < dataGrid.Items.Count; i++)
{
if (dataGrid.SelectedIndex == i)
{
continue;
}
Account account = dataGrid.Items[i] as Account;
if (account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.CurrentCulture))
{
if (!found)
{
dataGrid.ScrollIntoView(account);
dataGrid.SelectedItem = account;
lastFoundIndex = i;
found = true;
}
else
{
lastFound = false;
break;
}
}
}
基本上它会尝试再找到一个项目,看看它是否是最后一场比赛。
您可以将 for 循环提取到一个单独的方法中,以确定是否找到了另一项。
public int FindRecordWithinRange(DataGrid dataGrid, int min, int max)
{
for (int i = min; i < max; i++)
{
if (dataGrid.SelectedIndex == i)
continue;
Account account = dataGrid.Items[i] as Account;
if (account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.CurrentCulture))
{
dataGrid.ScrollIntoView(account);
dataGrid.SelectedItem = account;
return i;
}
}
return -1;
}
然后使用类似这样的方式调用它:
lastFoundIndex = FindRecordWithinRange(dataGrid, lastFoundIndex, dataGrid.Items.Count);
if (lastFoundIndex == -1)
lastFoundIndex = FindRecordWithinRange(dataGrid, 0, dataGrid.Items.Count);
if (lastFoundIndex == -1)
dataGrid.SelectedItem = null;
这基本上会尝试从头开始搜索列表,并且还会通过清除选择来处理未找到任何项目的情况。在这种情况下,您可能还想滚动到开头,此时的处理取决于您要做什么。
你可能想在这里做的另一件事是提取你的 ScrollIntoView 和选择逻辑,并在索引确定后处理它。
如果我像下面这样更改我的功能,它将在字母 "A" 的最后一项停留 2 次,然后再转到字母 "A" 的第一项(假设用户键仍然是 "A").
private static Key lastKey;
private static int lastFoundIndex = 0;
public static void AccountsDataGrid_SearchByKey(object sender, KeyEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if ((dataGrid.Items.Count == 0) && !(e.Key >= Key.A && e.Key <= Key.Z))
{
return;
}
if ((lastKey != e.Key) || (lastFoundIndex == dataGrid.Items.Count - 1))
{
lastFoundIndex = 0;
}
for (int i = lastFoundIndex; i < dataGrid.Items.Count; i++)
{
if ((lastFoundIndex > 0) && (lastFoundIndex == i))
{
lastFoundIndex = 0;
}
if (dataGrid.SelectedIndex == i)
{
continue;
}
Account account = dataGrid.Items[i] as Account;
if (account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.CurrentCulture))
{
dataGrid.ScrollIntoView(account);
dataGrid.SelectedItem = account;
lastFoundIndex = i;
break;
}
}
lastKey = e.Key;
}
您最终采用的解决方案过于复杂,并且会检查不需要检查的行。也不需要维护状态的两个静态变量。试试这个:
public void MainGrid_SearchByKey(object sender, KeyEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if (dataGrid.Items.Count == 0 || e.Key < Key.A || e.Key > Key.Z)
{
return;
}
Func<object, bool> doesItemStartWithChar = (item) =>
{
Account account = item as Account;
return account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.InvariantCulture);
};
int currentIndex = dataGrid.SelectedIndex;
int foundIndex = currentIndex;
// Search in following rows
foundIndex = FindMatchingItemInRange(dataGrid, currentIndex, dataGrid.Items.Count - 1, doesItemStartWithChar);
// If not found, search again in the previous rows
if (foundIndex == -1)
{
foundIndex = FindMatchingItemInRange(dataGrid, 0, currentIndex - 1, doesItemStartWithChar);
}
if (foundIndex > -1) // Found
{
dataGrid.ScrollIntoView(dataGrid.Items[foundIndex]);
dataGrid.SelectedIndex = foundIndex;
}
}
private static int FindMatchingItemInRange(DataGrid dataGrid, int min, int max, Func<object, bool> doesItemStartWithChar)
{
for (int i = min; i <= max; i++)
{
if (dataGrid.SelectedIndex == i) // Skip the current selection
{
continue;
}
if (doesItemStartWithChar(dataGrid.Items[i])) // If current item matches the string, return index
{
return i;
}
}
return -1;
}
关于您的评论,只需添加此检查:
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
{
return;
}
我将此功能设为 select 用户按键时 DataGrid 上的项目。如果用户密钥是 "A",它将 select 用户名以字母 "A" 开头的第一项。如果用户密钥再次为 "A",它将 select 用户名以字母 "A" 开头的下一个项目,依此类推。该功能效果很好,但我想要的是当没有更多用户名以 "A" 开头的项目重新开始时(select 第一项),它目前保留在用户名开头的最后一个项目上字母 "A".
private static Key lastKey;
private static int lastFoundIndex = 0;
public static void AccountsDataGrid_SearchByKey(object sender, KeyEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if ((dataGrid.Items.Count == 0) && !(e.Key >= Key.A && e.Key <= Key.Z))
{
return;
}
if ((lastKey != e.Key) || (lastFoundIndex == dataGrid.Items.Count - 1))
{
lastFoundIndex = 0;
}
for (int i = lastFoundIndex; i < dataGrid.Items.Count; i++)
{
if (dataGrid.SelectedIndex == i)
{
continue;
}
Account account = dataGrid.Items[i] as Account;
if (account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.CurrentCulture))
{
dataGrid.ScrollIntoView(account);
dataGrid.SelectedItem = account;
lastFoundIndex = i;
break;
}
}
lastKey = e.Key;
}
更新(含解决方案):
受 Danielle 想法的启发,我修改了如下代码,效果非常好。
private static Key lastKey;
private static int lastFoundIndex = 0;
public static void AccountsDataGrid_SearchByKey(object sender, KeyEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if ((dataGrid.Items.Count == 0) && !(e.Key >= Key.A && e.Key <= Key.Z))
{
return;
}
if ((lastKey != e.Key) || (lastFoundIndex == dataGrid.Items.Count - 1))
{
lastFoundIndex = 0;
}
Func<object, bool> itemCompareMethod = (item) =>
{
Account account = item as Account;
if (account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.CurrentCulture))
{
return true;
}
return false;
};
lastFoundIndex = FindDataGridRecordWithinRange(dataGrid, lastFoundIndex, dataGrid.Items.Count, itemCompareMethod);
if (lastFoundIndex == -1)
{
lastFoundIndex = FindDataGridRecordWithinRange(dataGrid, 0, dataGrid.Items.Count, itemCompareMethod);
}
if (lastFoundIndex != -1)
{
dataGrid.ScrollIntoView(dataGrid.Items[lastFoundIndex]);
dataGrid.SelectedIndex = lastFoundIndex;
}
if (lastFoundIndex == -1)
{
lastFoundIndex = 0;
dataGrid.SelectedItem = null;
}
lastKey = e.Key;
}
private static int FindDataGridRecordWithinRange(DataGrid dataGrid, int min, int max, Func<object, bool> itemCompareMethod)
{
for (int i = min; i < max; i++)
{
if (dataGrid.SelectedIndex == i)
{
continue;
}
if (itemCompareMethod(dataGrid.Items[i]))
{
return i;
}
}
return -1;
}
您的代码表示要从头开始重新搜索,lastFoundIndex 应等于 dataGrid.Items.Count - 1(假设再次按下相同的键)。 但是您最后找到的 Account 可能不在 Items 集合的末尾,因此在按下前一个键时 lastFoundIndex 未设置为 dataGrid.Items.Count - 1。这种情况下你重新开始的条件不满足。
尝试像这样更改最后发现的检查和 "for" 循环:
bool found = false;
bool lastFound = true;
if (lastKey != e.Key || lastFound)
{
lastFoundIndex = 0;
}
for (int i = lastFoundIndex; i < dataGrid.Items.Count; i++)
{
if (dataGrid.SelectedIndex == i)
{
continue;
}
Account account = dataGrid.Items[i] as Account;
if (account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.CurrentCulture))
{
if (!found)
{
dataGrid.ScrollIntoView(account);
dataGrid.SelectedItem = account;
lastFoundIndex = i;
found = true;
}
else
{
lastFound = false;
break;
}
}
}
基本上它会尝试再找到一个项目,看看它是否是最后一场比赛。
您可以将 for 循环提取到一个单独的方法中,以确定是否找到了另一项。
public int FindRecordWithinRange(DataGrid dataGrid, int min, int max)
{
for (int i = min; i < max; i++)
{
if (dataGrid.SelectedIndex == i)
continue;
Account account = dataGrid.Items[i] as Account;
if (account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.CurrentCulture))
{
dataGrid.ScrollIntoView(account);
dataGrid.SelectedItem = account;
return i;
}
}
return -1;
}
然后使用类似这样的方式调用它:
lastFoundIndex = FindRecordWithinRange(dataGrid, lastFoundIndex, dataGrid.Items.Count);
if (lastFoundIndex == -1)
lastFoundIndex = FindRecordWithinRange(dataGrid, 0, dataGrid.Items.Count);
if (lastFoundIndex == -1)
dataGrid.SelectedItem = null;
这基本上会尝试从头开始搜索列表,并且还会通过清除选择来处理未找到任何项目的情况。在这种情况下,您可能还想滚动到开头,此时的处理取决于您要做什么。
你可能想在这里做的另一件事是提取你的 ScrollIntoView 和选择逻辑,并在索引确定后处理它。
如果我像下面这样更改我的功能,它将在字母 "A" 的最后一项停留 2 次,然后再转到字母 "A" 的第一项(假设用户键仍然是 "A").
private static Key lastKey;
private static int lastFoundIndex = 0;
public static void AccountsDataGrid_SearchByKey(object sender, KeyEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if ((dataGrid.Items.Count == 0) && !(e.Key >= Key.A && e.Key <= Key.Z))
{
return;
}
if ((lastKey != e.Key) || (lastFoundIndex == dataGrid.Items.Count - 1))
{
lastFoundIndex = 0;
}
for (int i = lastFoundIndex; i < dataGrid.Items.Count; i++)
{
if ((lastFoundIndex > 0) && (lastFoundIndex == i))
{
lastFoundIndex = 0;
}
if (dataGrid.SelectedIndex == i)
{
continue;
}
Account account = dataGrid.Items[i] as Account;
if (account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.CurrentCulture))
{
dataGrid.ScrollIntoView(account);
dataGrid.SelectedItem = account;
lastFoundIndex = i;
break;
}
}
lastKey = e.Key;
}
您最终采用的解决方案过于复杂,并且会检查不需要检查的行。也不需要维护状态的两个静态变量。试试这个:
public void MainGrid_SearchByKey(object sender, KeyEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if (dataGrid.Items.Count == 0 || e.Key < Key.A || e.Key > Key.Z)
{
return;
}
Func<object, bool> doesItemStartWithChar = (item) =>
{
Account account = item as Account;
return account.Username.StartsWith(e.Key.ToString(), true, CultureInfo.InvariantCulture);
};
int currentIndex = dataGrid.SelectedIndex;
int foundIndex = currentIndex;
// Search in following rows
foundIndex = FindMatchingItemInRange(dataGrid, currentIndex, dataGrid.Items.Count - 1, doesItemStartWithChar);
// If not found, search again in the previous rows
if (foundIndex == -1)
{
foundIndex = FindMatchingItemInRange(dataGrid, 0, currentIndex - 1, doesItemStartWithChar);
}
if (foundIndex > -1) // Found
{
dataGrid.ScrollIntoView(dataGrid.Items[foundIndex]);
dataGrid.SelectedIndex = foundIndex;
}
}
private static int FindMatchingItemInRange(DataGrid dataGrid, int min, int max, Func<object, bool> doesItemStartWithChar)
{
for (int i = min; i <= max; i++)
{
if (dataGrid.SelectedIndex == i) // Skip the current selection
{
continue;
}
if (doesItemStartWithChar(dataGrid.Items[i])) // If current item matches the string, return index
{
return i;
}
}
return -1;
}
关于您的评论,只需添加此检查:
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
{
return;
}