Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF C# - DataGrid select item on user keypress

I made this function to select the item on DataGrid on user keypress. If the user key is "A" it will select the first item where username starts with letter "A". If the user key is again "A" it will select the next item where username starts with letter "A" and so on. The function works great, but what I want is when there are no more items where username starts with "A" to start over (select the first item), it currently remains on the last item where username start with letter "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;
}

Update (with solution):

Inspired by Danielle's idea, I have changed my code like below, works like a charm.

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;
}
like image 637
Cristian Olaru Avatar asked Nov 30 '25 06:11

Cristian Olaru


1 Answers

The solution you wound up with is needlessly complex and checks rows it doesn't need to check. The two static variables to maintain state are not needed either. Try this instead:

    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;
    }

Regarding your comment, just add this check:

        if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
        {
            return;
        }
like image 52
janb Avatar answered Dec 02 '25 20:12

janb