In a WPF application I have the following event handler for PreviewTextInput
event in ComboBox
control. The main purpose of this code is to select an item in ComboBox
via pressing a letter key. There is duplicated logic in this code and I want it removed.
private void OnTextComboBoxPreviewTextInput(object sender, TextCompositionEventArgs e)
{
var comboBox = sender as ComboBox;
if (!comboBox.IsDropDownOpen)
{
return;
}
if (!comboBox.IsEditable || !comboBox.IsReadOnly)
{
return;
}
foreach (var item in comboBox.Items)
{
if (item == null)
{
continue;
}
var textSearch = TextSearch.GetTextPath(comboBox);
var stringItem = item as string;
if (stringItem != null)
{
if (stringItem.StartsWith(e.Text, StringComparison.InvariantCultureIgnoreCase))
{
SelectItemInComboBox(comboBox, item);
break;
}
continue;
}
var dependencyObjItem = item as DependencyObject;
if (dependencyObjItem != null)
{
var textMember = TextSearch.GetText(dependencyObjItem);
if (!string.IsNullOrEmpty(textMember))
{
var selectorFunc = ExpressionHelper.GetMemberFunction(dependencyObjItem, textMember);
var textValue = selectorFunc(dependencyObjItem);
if (textValue.ToString().StartsWith(e.Text, StringComparison.InvariantCultureIgnoreCase))
{
SelectItemInComboBox(comboBox, item);
break;
}
continue;
}
}
if (!string.IsNullOrEmpty(textSearch))
{
var selectorFunc = ExpressionHelper.GetMemberFunction(item, textSearch);
var textValue = selectorFunc(item);
if (textValue.ToString().StartsWith(e.Text, StringComparison.InvariantCultureIgnoreCase))
{
SelectItemInComboBox(comboBox, item);
break;
}
}
}
e.Handled = true;
}
private void SelectItemInComboBox(ComboBox comboBox, object item)
{
var comboBoxItem = comboBox.ItemContainerGenerator.ContainerFromItem(item) as ComboBoxItem;
comboBoxItem.IsSelected = true;
}
1 Answer 1
I prefer a simple cast over
as
when I don't expect an object to be of any other type. If you do expect this, useas
and be sure to check fornull
.var comboBox = (ComboBox)sender;
When it makes sense to group code together do so.
if (!comboBox.IsDropDownOpen || !combobox.IsEditable || !combobox.IsReadOnly)
Try to use LINQ where possible.
Place variables close to where they are being used. (
textSearch
)Don't overuse
var
. This is subjective, and there are several discussion about it. (also see comments in the code underneath)Using
continue
can make it more difficult to get an overview of the code. In some occasions it is useful, but look at the code below to see how you could replace it with normalif else
conditions instead.Identify redundant code, see what you can eliminate and what you can't. You might have to introduce new intermediate variables. (e.g.
textToCompare
)
Here is the complete reworked example:
private void OnTextComboBoxPreviewTextInput(object sender, TextCompositionEventArgs e)
{
var comboBox = (ComboBox)sender;
if (!comboBox.IsDropDownOpen || !combobox.IsEditable || !combobox.IsReadOnly)
{
return;
}
foreach (var item in comboBox.Items.Where( i => i != null ))
{
string textToCompare = null;
if (item is string)
{
textToCompare = (string)item;
}
else if (item is DependencyObject)
{
DependencyObject dependencyObjItem = (DependencyObject)item;
string textMember = TextSearch.GetText(dependencyObjItem);
if (!string.IsNullOrEmpty(textMember))
{
// From this sample, I don't know what the following type is!
// I wouldn't use var here.
var selectorFunc = ExpressionHelper.GetMemberFunction(dependencyObjItem, textMember);
textToCompare = selectorFunc(dependencyObjItem);
}
}
else
{
string textSearch = TextSearch.GetTextPath(comboBox);
if (!string.IsNullOrEmpty(textSearch))
{
var selectorFunc = ExpressionHelper.GetMemberFunction(item, textSearch);
textToCompare = selectorFunc(item);
}
}
if (textToCompare != null && textToCompare.StartsWith(e.Text, StringComparison.InvariantCultureIgnoreCase))
{
SelectItemInComboBox(comboBox, item);
break;
}
}
e.Handled = true;
}
private void SelectItemInComboBox(ComboBox comboBox, object item)
{
var comboBoxItem = comboBox.ItemContainerGenerator.ContainerFromItem(item) as ComboBoxItem;
comboBoxItem.IsSelected = true;
}
I will leave it up to you as an exercise whether you can further eliminate the duplicate code from selectorFunc
, and whether it would be useful.
-
\$\begingroup\$
break
could of course be replaced with awhile
statement. But in this case I don't see the problem with it as thebreak
statement is near the end of thefor
, clearly indicating an early out. \$\endgroup\$Steven Jeuris– Steven Jeuris2011年11月02日 20:11:40 +00:00Commented Nov 2, 2011 at 20:11 -
3\$\begingroup\$ As a minor subtle (again subjective) side note. If
SelectItemInComboBox
is only called from one place, I wouldn't separate it in a function. \$\endgroup\$Steven Jeuris– Steven Jeuris2011年11月03日 10:53:38 +00:00Commented Nov 3, 2011 at 10:53 -
\$\begingroup\$ @Steven Jeuris Thank you for good answer, I can answer for you comment in code // From this sample, I don't know what the following type is!//I wouldn't use var here var selectorFunc - will be a type of Func<object,object> \$\endgroup\$Sergey K– Sergey K2011年11月03日 11:03:44 +00:00Commented Nov 3, 2011 at 11:03
-
\$\begingroup\$ and this var selectorFunc = ExpressionHelper.GetMemberFunction(item, textSearch); textToCompare = selectorFunc(item); I've extracted in method to remove duplication . \$\endgroup\$Sergey K– Sergey K2011年11月03日 11:13:10 +00:00Commented Nov 3, 2011 at 11:13