The next version of rubberduck is going to include a pretty nifty goto anything / find symbol navigation tool that lets the user enter an identifier name in a combobox:UITo achieve the templated dropdown I had to introduce some WPF interop. Here's the markup:<UserControl.CommandBindings>    <CommandBinding Command=local:FindSymbolControl.GoCommand                     Executed=CommandBinding_OnExecuted                    CanExecute=CommandBinding_OnCanExecute/></UserControl.CommandBindings><Grid>    <Grid.ColumnDefinitions>        <ColumnDefinition Width=* />        <ColumnDefinition Width=32 />    </Grid.ColumnDefinitions>    <ComboBox IsEditable=True              ItemsSource={Binding MatchResults}              SelectedItem={Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}              IsTextSearchCaseSensitive=False              IsTextSearchEnabled=True              TextSearch.Text={Binding SearchString, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}              TextSearch.TextPath=IdentifierName>        <ComboBox.ItemsPanel>            <ItemsPanelTemplate>                <VirtualizingStackPanel />            </ItemsPanelTemplate>        </ComboBox.ItemsPanel>        <ComboBox.ItemTemplate>            <DataTemplate DataType=local:SearchResult>                <StackPanel Orientation=Horizontal VerticalAlignment=Center>                    <Image Height=16 Width=16 Margin=2,0,2,0 Source={Binding Icon} />                    <TextBlock Margin=2,0,2,0 Text={Binding IdentifierName} FontWeight=Bold MinWidth=160 VerticalAlignment=Center />                    <TextBlock Margin=2,0,2,0 Text={Binding Location} VerticalAlignment=Center />                </StackPanel>            </DataTemplate>        </ComboBox.ItemTemplate>    </ComboBox>    <Button Grid.Column=1            Command=local:FindSymbolControl.GoCommand>        <Image Height=16 Source=pack://application:,,,/Rubberduck;component/Resources/arrow.png />    </Button></Grid>...and the code-behind:namespace Rubberduck.UI.FindSymbol{    /// <summary>    /// Interaction logic for FindSymbolControl.xaml    /// </summary>    public partial class FindSymbolControl : UserControl    {        public FindSymbolControl()        {            InitializeComponent();        }        private FindSymbolViewModel ViewModel { get { return (FindSymbolViewModel)DataContext; } }        private static readonly ICommand _goCommand = new RoutedCommand();        public static ICommand GoCommand { get { return _goCommand; } }        private void CommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e)        {            ViewModel.Execute();        }        private void CommandBinding_OnCanExecute(object sender, CanExecuteRoutedEventArgs e)        {            if (ViewModel == null)            {                return;            }            e.CanExecute = ViewModel.CanExecute();            e.Handled = true;        }    }}The WPF control is embedded inside a WinForms host, which is basically only responsible for setting the ViewModel:namespace Rubberduck.UI.FindSymbol{    public partial class FindSymbolDialog : Form    {        public FindSymbolDialog(FindSymbolViewModel viewModel)            : this()        {            findSymbolControl1.DataContext = viewModel;        }        public FindSymbolDialog()        {            InitializeComponent();        }    }}LogicI implemented the logic inside the ViewModel class:namespace Rubberduck.UI.FindSymbol{    public class FindSymbolViewModel : INotifyPropertyChanged    {        private static readonly DeclarationType[] ExcludedTypes =        {            DeclarationType.Control,             DeclarationType.ModuleOption        };        public FindSymbolViewModel(IEnumerable<Declaration> declarations, SearchResultIconCache cache)        {            _declarations = declarations;            _cache = cache;            var initialResults = _declarations                .Where(declaration => !ExcludedTypes.Contains(declaration.DeclarationType))                .OrderBy(declaration => declaration.IdentifierName.ToLowerInvariant())                .Select(declaration => new SearchResult(declaration, cache[declaration]))                .ToList();            MatchResults = new ObservableCollection<SearchResult>(initialResults);        }        public event EventHandler<NavigateCodeEventArgs> Navigate;        public bool CanExecute()        {            return _selectedItem != null;        }        public void Execute()        {            OnNavigate();        }        public void OnNavigate()        {            var handler = Navigate;            if (handler != null && _selectedItem != null)            {                var arg = new NavigateCodeEventArgs(_selectedItem.Declaration);                handler(this, arg);            }        }        private readonly IEnumerable<Declaration> _declarations;        private readonly SearchResultIconCache _cache;        private void Search(string value)        {            var lower = value.ToLowerInvariant();            var results = _declarations                .Where(declaration => !ExcludedTypes.Contains(declaration.DeclarationType)                                        && (string.IsNullOrEmpty(value) || declaration.IdentifierName.ToLowerInvariant().Contains(lower)))                .OrderBy(declaration => declaration.IdentifierName.ToLowerInvariant())                .Select(declaration => new SearchResult(declaration, _cache[declaration]))                .ToList();            MatchResults = new ObservableCollection<SearchResult>(results);        }        private string _searchString;        public string SearchString        {            get { return _searchString; }            set            {                _searchString = value;                 Search(value);            }        }        private SearchResult _selectedItem;        public SearchResult SelectedItem        {            get { return _selectedItem; }            set             {                 _selectedItem = value;                 OnPropertyChanged();            }        }        private ObservableCollection<SearchResult> _matchResults;        public ObservableCollection<SearchResult> MatchResults        {            get { return _matchResults; }            set { _matchResults = value; OnPropertyChanged(); }        }        public event PropertyChangedEventHandler PropertyChanged;        [NotifyPropertyChangedInvocator]        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)        {            var handler = PropertyChanged;            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));        }    }}A SearchResult is simply wrapping up a Declaration for presentation purposes:namespace Rubberduck.UI.FindSymbol{    public class SearchResult    {        private readonly Declaration _declaration;        private readonly BitmapImage _icon;        public SearchResult(Declaration declaration, BitmapImage icon)        {            _declaration = declaration;            _icon = icon;        }        public Declaration Declaration { get { return _declaration; } }        public string IdentifierName { get { return _declaration.IdentifierName; } }        public string Location { get { return _declaration.Scope; } }        public BitmapImage Icon { get { return _icon; } }    }}I originally had an IValueConverter to convert a Declaration into a BitmapImage at the XAML level, but that meant creating an instance of a bitmap for every single search result, and felt inefficient.So I implemented this cache instead:namespace Rubberduck.UI.FindSymbol{    public class SearchResultIconCache    {        private readonly IDictionary<Tuple<DeclarationType, Accessibility>, BitmapImage> _images;        public SearchResultIconCache()        {            var types = Enum.GetValues(typeof (DeclarationType)).Cast<DeclarationType>();            var accessibilities = Enum.GetValues(typeof (Accessibility)).Cast<Accessibility>();            _images = types.SelectMany(t => accessibilities.Select(a => Tuple.Create(t, a)))                .ToDictionary(key => key, key => new BitmapImage(GetIconUri(key.Item1, key.Item2)));        }        public BitmapImage this[Declaration declaration]        {            get            {                var key = Tuple.Create(declaration.DeclarationType, declaration.Accessibility);                return _images[key];            }        }        private Uri GetIconUri(DeclarationType declarationType, Accessibility accessibility)        {            const string baseUri = @../../Resources/Microsoft/PNG/;            string path;            switch (declarationType)            {                case DeclarationType.Module:                    path = VSObject_Module.png;                    break;                case DeclarationType.Document | DeclarationType.Class:                     path = document.png;                    break;                case DeclarationType.UserForm | DeclarationType.Class | DeclarationType.Control:                    path = VSProject_Form.png;                    break;                case DeclarationType.Class | DeclarationType.Module:                    path = VSProject_Class.png;                    break;                case DeclarationType.Procedure | DeclarationType.Member:                case DeclarationType.Function | DeclarationType.Member:                    if (accessibility == Accessibility.Private)                    {                        path = VSObject_Method_Private.png;                        break;                    }                    if (accessibility == Accessibility.Friend)                    {                        path = VSObject_Method_Friend.png;                        break;                    }                    path = VSObject_Method.png;                    break;                case DeclarationType.PropertyGet | DeclarationType.Property | DeclarationType.Function:                case DeclarationType.PropertyLet | DeclarationType.Property | DeclarationType.Procedure:                case DeclarationType.PropertySet | DeclarationType.Property | DeclarationType.Procedure:                    if (accessibility == Accessibility.Private)                    {                        path = VSObject_Properties_Private.png;                        break;                    }                    if (accessibility == Accessibility.Friend)                    {                        path = VSObject_Properties_Friend.png;                        break;                    }                    path = VSObject_Properties.png;                    break;                case DeclarationType.Parameter:                    path = VSObject_Field_Shortcut.png;                    break;                case DeclarationType.Variable:                    if (accessibility == Accessibility.Private)                    {                        path = VSObject_Field_Private.png;                        break;                    }                    if (accessibility == Accessibility.Friend)                    {                        path = VSObject_Field_Friend.png;                        break;                    }                    path = VSObject_Field.png;                    break;                case DeclarationType.Constant:                    if (accessibility == Accessibility.Private)                    {                        path = VSObject_Constant_Private.png;                        break;                    }                    if (accessibility == Accessibility.Friend)                    {                        path = VSObject_Constant_Friend.png;                        break;                    }                    path = VSObject_Constant.png;                    break;                case DeclarationType.Enumeration:                    if (accessibility == Accessibility.Private)                    {                        path = VSObject_Enum_Private.png;                        break;                    }                    if (accessibility == Accessibility.Friend)                    {                        path = VSObject_Enum_Friend.png;                        break;                    }                    path = VSObject_Enum.png;                    break;                case DeclarationType.EnumerationMember | DeclarationType.Constant:                    path = VSObject_EnumItem.png;                    break;                case DeclarationType.Event:                    if (accessibility == Accessibility.Private)                    {                        path = VSObject_Event_Private.png;                        break;                    }                    if (accessibility == Accessibility.Friend)                    {                        path = VSObject_Event_Friend.png;                        break;                    }                    path = VSObject_Event.png;                    break;                case DeclarationType.UserDefinedType:                    if (accessibility == Accessibility.Private)                    {                        path = VSObject_ValueTypePrivate.png;                        break;                    }                    if (accessibility == Accessibility.Friend)                    {                        path = VSObject_ValueType_Friend.png;                        break;                    }                    path = VSObject_ValueType.png;                    break;                case DeclarationType.UserDefinedTypeMember | DeclarationType.Variable:                    path = VSObject_Field.png;                    break;                case DeclarationType.LibraryProcedure | DeclarationType.Procedure:                case DeclarationType.LibraryFunction | DeclarationType.Function:                    path = VSObject_Method_Shortcut.png;                    break;                case DeclarationType.LineLabel:                    path = VSObject_Constant_Shortcut.png;                    break;                case DeclarationType.Project:                    path = VSObject_Library.png;                    break;                default:                    path = VSObject_Structure.png;                    break;            }            return new Uri(baseUri + path, UriKind.Relative);        }    }}UsageThe calling code (a context menu button) uses it like this:private void FindSymbolContextMenuClick(CommandBarButton Ctrl, ref bool CancelDefault){    var declarations = _parser.Parse(IDE.ActiveVBProject, this).Declarations;    var vm = new FindSymbolViewModel(declarations.Items.Where(item => !item.IsBuiltIn), _iconCache);    vm.Navigate += vm_Navigate;    using (var view = new FindSymbolDialog(vm))    {        view.ShowDialog();    }    vm.Navigate -= vm_Navigate;}private void vm_Navigate(object sender, NavigateCodeEventArgs e){    if (e.QualifiedName.Component == null)    {        return;    }    try    {        e.QualifiedName.Component.CodeModule.CodePane.SetSelection(e.Selection);    }    catch (COMException)    {    }}For these bitwise-or operations to work I had to modify the DeclarationType enum a ...bit (pun not intended), by making it a [Flags] enum:namespace Rubberduck.Parsing.Symbols{    [Flags]    public enum DeclarationType    {        Project = 1 << 0,        Module = 1 << 1,        Class = 1 << 2,        Control = 1 << 3,        UserForm = 1 << 4,        Document = 1 << 5,        ModuleOption = 1 << 6,        Member = 1 << 7,        Procedure = 1 << 8 | Member,        Function = 1 << 9 | Member,        Property = 1 << 10 | Member,        PropertyGet = 1 << 11 | Property | Function,        PropertyLet = 1 << 12 | Property | Procedure,        PropertySet = 1 << 13 | Property | Procedure,        Parameter = 1 << 14,        Variable = 1 << 15,        Constant = 1 << 16,        Enumeration = 1 << 17,        EnumerationMember = 1 << 18 | Constant,        Event = 1 << 19,        UserDefinedType = 1 << 20,        UserDefinedTypeMember = 1 << 21 | Variable,        LibraryFunction = 1 << 22 | Function,        LibraryProcedure = 1 << 23 | Procedure,        LineLabel = 1 << 24    }}
Rubberduck GoTo Anything Navigation
c#;winforms;wpf;rubberduck
It looks like you can do the filtering and sorting of _declarations just once in the view-model constructor (parse results don't change, and ExcludedTypes doesn't change):_declarations = declarations    .Where(declaration => !ExcludedTypes.Contains(declaration.DeclarationType))    .OrderBy(declaration => declaration.IdentifierName.ToLowerInvariant())    .ToList();While we're at it, it should be more efficient to use the overload of OrderBy that takes an IComparer<T>, so we're not making lots of throw-away strings:_declarations = declarations    .Where(declaration => !ExcludedTypes.Contains(declaration.DeclarationType))    .OrderBy(declaration => declaration.IdentifierName, StringComparer.OrdinalIgnoreCase)    .ToList();This also lets us simplify Search:private void Search(string value){    var declarations = _declarations;    if (!string.IsNullOrEmpty(value))    {        declarations = declarations            .Where(declaration => declaration.IdentifierName.IndexOf(value, StringComparison.OrdinalIgnoreCase) != -1);    }    var results = declarations        .Select(declaration => new SearchResult(declaration, _cache[declaration]));    MatchResults = new ObservableCollection<SearchResult>(results);}Here I've used IndexOf instead of Contains so we can pass it a StringComparer, again saving on the creation of string objects.(Consider using string.IsNullOrWhiteSpace instead of string.IsNullOrEmpty depending on the behaviour you want.)We can save a call to Search if _searchString hasn't changed. Not sure if it will help much in this case, but it's something to consider.set{    if (_searchString == value)    {        return;    }    _searchString = value;     Search(value);}