views:

261

answers:

3

I'm creating an options dialog using WPF that lists possible keys so that the user can assign the program's hotkey. I'm attempting to filter all of the possible values of the System.Windows.Forms.Keys enumeration down to just A-Z and F1-F12, and then bind that list to a ComboBox.

Here's my code so far:

Regex filter = new Regex("(^[A-Z]$)|(^F[0-9]{1,2}$)");
IEnumerable<Key> keyList = from x in (IEnumerable<Key>)Enum.GetValues(typeof(Keys)) 
                           where filter.Match(x.ToString()).Success
                           select x;
keys.DataContext = keyList;

After executing this, keyList contains the letter "A" twice and is missing letters "P" through "U." I'm at a loss as to why.

I'm also interested in alternate approaches, if there's a better way.

+8  A: 

You had two typos - you were using System.Windows.Input.Key instead of System.Windows.Forms.Keys twice. What an unfortunate typo! I would suggest that unless you really need using directives for both WinForms and WPF in the same file, you avoid having them both there.

Here's a short but complete example that works (based on your code, but with the typos fixed):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;

class Test
{
    static void Main()        
    {
        Regex filter = new Regex("(^[A-Z]$)|(^F[0-9]{1,2}$)");
        IEnumerable<Keys> keyList = 
            from x in (IEnumerable<Keys>) Enum.GetValues(typeof(Keys)) 
            where filter.Match(x.ToString()).Success
            select x;


        foreach (var key in keyList)
        {
            Console.WriteLine(key);
        }
    }
}

Note that you can write the query expression more easily by using an explicitly typed range variable:

IEnumerable<Keys> keyList = from Keys x in Enum.GetValues(typeof(Keys)) 
                            where filter.Match(x.ToString()).Success
                            select x;

Or use dot notation to start with:

var keyList = Enum.GetValues(typeof(Keys))
                  .Cast<Keys>()
                  .Where(x => filter.Match(x.ToString()).Success);

Alternatively, you could use Unconstrained Melody and get a strongly typed list to start with:

var keyList = Enums.GetValues<Keys>()
                   .Where(x => filter.Match(x.ToString()).Success);
Jon Skeet
Thanks, Jon. A lot of great info here. I figured there I was probably doing something fairly boneheaded to get those weird results.
James Sulak
A: 

Have you considered marking the enumeration values with a custom attribute (something like 'HotKeyAttribute')

Than- filtering out the actual enum values by the attribute will be mucj more straightforward. Note that in this case your AttributeTarget should be Field.

Vitaliy
These are enum values provided by the framework - the OP doesn't get to annotate them.
Jon Skeet
Oh, didn't realize that. Embarrassing :-\
Vitaliy
+1  A: 

Jon's post pointed out the typos and different namespaces issue. It works for me as well. However, I wanted to point out a few things on the regex side of things.

Your regex can be enhanced. Currently it returns A-Z and F1-F24. To restrict it to F1-12 use this pattern instead: "^([A-Z]|F([1-9]|1[0-2]))$" - notice that I also refactored it to avoid the ^$ anchor repetition.

Additionally, you can use IsMatch instead of Match and Success:

Regex filter = new Regex("^([A-Z]|F([1-9]|1[0-2]))$");
IEnumerable<Keys> keyList =
    from x in (IEnumerable<Keys>)Enum.GetValues(typeof(Keys)) 
    where filter.IsMatch(x.ToString())
    select x;
Ahmad Mageed