У AutoCompliteBox есть существенный недостаток. Он предполагает (AutoCompliteBox, а не недостаток), что все данные в него уже загружены и осталось только выбрать. К сожалению в Silverlight при использовании RIA сервисов возникает проблема с объемом передаваемых данных. См. здесь.
Стоит задача, сделать компонент по функционалу похожий на AutoCompliteBox, но подразумевающий, что данные в него грузятся только после того, как пользователь введет часть названия объекта который ему необходим.
Т.к. контрол предполагается использовать во внешнем мире, то от него требуется в этот самый внешний мир предоставить:
1. Поле для хранения выбранного элемента.
Как видим в основе у нас лежит все тот же AutoCompliteBox. Его поля привязаны к вышеописанным полям.
Идея такая: пользователь вводит несколько буковок и нажимает на кнопку или клавишу Enter. Во внешний мир уходит команда, по которой грузятся данные и попадают в свойство Filtereditems. А уже из них пользователь выбирает то. что ему нужно. Повторные нажатия кнопки и Enter, также вызывают запросы на обновление данных.
Что нужно еще, чтобы это заработало? На самом деле не так уж и много.
Для начала подписываем всех заинтерисованных на всякие разные события:
Собственно клик на кнопке, вызывает команду передав туда в качестве параметра текст из AutoCommpliteBox-а:
Обработчик KeyUp делает тоже самое, но только для клавиши Enter:
Ну а если нам уже вернули список элементов, то проверяем сколько там чего, если 1 - сразу выбрать, если много, развернуть список, если нет, сказать что пользователь неправ:
Ну и напоследок, при изменении выбранного элемента порадуем пользователя подсветкой:
Собственно все. Ну пара картинок как это работает:
Вводим несколько буковок и нажимаем Enter:
При вводе такой строки, которая позволяет выбрать только один объект:
и нажатии Enter:
Ну теперь точно - все.
Стоит задача, сделать компонент по функционалу похожий на AutoCompliteBox, но подразумевающий, что данные в него грузятся только после того, как пользователь введет часть названия объекта который ему необходим.
Т.к. контрол предполагается использовать во внешнем мире, то от него требуется в этот самый внешний мир предоставить:
1. Поле для хранения выбранного элемента.
///2. Поле с коллекцией объектов из которых выбирать./// Выбранный элемент /// public Entity SelectedItem { get { return (Entity)GetValue(SelectedItemProperty); } set { SetValue(SelectedItemProperty, value); } } ////// Static part of dependency property SelectedItem /// public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(Entity), typeof(ItemSelector), new PropertyMetadata(null, SelectedItem_Changed));
///3. Команду, о том, что пользователь уже соизволил повводить данные и пора бы уже поискать./// Коллекция отфильтрованных обхектов /// public IEnumerable<Entity> FilterdItems { get { return (IEnumerable<Entity>)GetValue(FilterdItemsProperty); } set { SetValue(FilterdItemsProperty, value); } } ////// Static part of dependency property FilterdItems /// public static readonly DependencyProperty FilterdItemsProperty = DependencyProperty.Register("FilterdItems", typeof(IEnumerable<Entity>), typeof(ItemSelector), new PropertyMetadata(null, FilterdItems_Changed));
///Ну интерфейс тоже получается незамысловатый:/// Команда для инициации поиска /// public ICommand FindItemsCommand { get { return (ICommand)GetValue(FindItemsCommandProperty); } set { SetValue(FindItemsCommandProperty, value); } } ////// Static part of dependency property FindItems /// public static readonly DependencyProperty FindItemsCommandProperty = DependencyProperty.Register("FindItemsCommand", typeof(ICommand), typeof(ItemSelector), new PropertyMetadata(null));
<Grid x:Name="LayoutRoot" > <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="30" /> Grid.ColumnDefinitions> <sdk:AutoCompleteBox VerticalAlignment="Center" x:Name="acbFilter" TextBoxStyle="{StaticResource AligmentStyle}" ItemsSource="{Binding Path=FilterdItems, ElementName=mainControl}" SelectedItem="{Binding Path=SelectedItem, ElementName=mainControl,Mode=TwoWay}" /> <Button x:Name="btnSearch" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center"> <Image Source="./../Images/search.png" Width="20" /> Button> Grid>Теперь как это все работает.
Как видим в основе у нас лежит все тот же AutoCompliteBox. Его поля привязаны к вышеописанным полям.
Идея такая: пользователь вводит несколько буковок и нажимает на кнопку или клавишу Enter. Во внешний мир уходит команда, по которой грузятся данные и попадают в свойство Filtereditems. А уже из них пользователь выбирает то. что ему нужно. Повторные нажатия кнопки и Enter, также вызывают запросы на обновление данных.
Что нужно еще, чтобы это заработало? На самом деле не так уж и много.
Для начала подписываем всех заинтерисованных на всякие разные события:
void ItemSelector_Loaded(object sender, RoutedEventArgs e) { acbFilter.KeyUp += new KeyEventHandler(acbFilter_KeyUp); acbFilter.SelectionChanged += new SelectionChangedEventHandler(acbFilter_SelectionChanged); acbFilter.LostFocus += new RoutedEventHandler(acbFilter_LostFocus); btnSearch.Click += new RoutedEventHandler(Button_Click); }
Собственно клик на кнопке, вызывает команду передав туда в качестве параметра текст из AutoCommpliteBox-а:
protected void Button_Click(object sender, RoutedEventArgs e) { if (FindItemsCommand != null && FindItemsCommand is DelegateCommand<string> && SelectedItem == null) { acbFilter.IsEnabled = false; (FindItemsCommand as DelegateCommand<string>).Execute(acbFilter.Text); } }
Обработчик KeyUp делает тоже самое, но только для клавиши Enter:
private void acbFilter_KeyUp(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { Button_Click(sender, null); } }
Ну а если нам уже вернули список элементов, то проверяем сколько там чего, если 1 - сразу выбрать, если много, развернуть список, если нет, сказать что пользователь неправ:
private static void FilterdItems_Changed(object sender, DependencyPropertyChangedEventArgs e) { IEnumerable<Entity> items = e.NewValue as IEnumerable<Entity>; ItemSelector current = (sender as ItemSelector); current.acbFilter.IsEnabled = true; if (items != null) { if (items.Count() == 1) { current.SelectedItem = items.First(); } else if (items.Count() > 1) { current.acbFilter.Focus(); current.Dispatcher.BeginInvoke(() => { current.acbFilter.IsDropDownOpen = true; }); } else if (items.Count() == 0) { current.acbFilter.Background = (Brush)Application.Current.Resources["NotFoundBrush"]; } } }
Ну и напоследок, при изменении выбранного элемента порадуем пользователя подсветкой:
private static void SelectedItem_Changed(object sender, DependencyPropertyChangedEventArgs e) { ItemSelector current = sender as ItemSelector; if (e.NewValue == null) { current.acbFilter.Background = null; } else { current.acbFilter.Background = (Brush)Application.Current.Resources["FoundBrush"]; } }
Собственно все. Ну пара картинок как это работает:
Вводим несколько буковок и нажимаем Enter:
При вводе такой строки, которая позволяет выбрать только один объект:
и нажатии Enter:
Ну теперь точно - все.
Комментариев нет:
Отправить комментарий