patterncsharpMinor
Rubberduck "GoTo Anything" Navigation
Viewed 0 times
anythingnavigationgotorubberduck
Problem
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:
UI
To achieve the templated dropdown I had to introduce some WPF interop. Here's the markup:
...and the code-behind:
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()
UI
To achieve the templated dropdown I had to introduce some WPF interop. Here's the markup:
...and the code-behind:
namespace Rubberduck.UI.FindSymbol
{
///
/// Interaction logic for FindSymbolControl.xaml
///
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()
Solution
It looks like you can do the filtering and sorting of
While we're at it, it should be more efficient to use the overload of
This also lets us simplify
Here I've used
(Consider using
We can save a call to
_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, 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(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);
}Code Snippets
_declarations = declarations
.Where(declaration => !ExcludedTypes.Contains(declaration.DeclarationType))
.OrderBy(declaration => declaration.IdentifierName.ToLowerInvariant())
.ToList();_declarations = declarations
.Where(declaration => !ExcludedTypes.Contains(declaration.DeclarationType))
.OrderBy(declaration => declaration.IdentifierName, StringComparer.OrdinalIgnoreCase)
.ToList();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);
}set
{
if (_searchString == value)
{
return;
}
_searchString = value;
Search(value);
}Context
StackExchange Code Review Q#91499, answer score: 4
Revisions (0)
No revisions yet.