Повторение записи в ячейке (подсказка) DataGrid wpf

202
01 сентября 2018, 07:00

Подскажите, как сделать, чтобы текст, раннее введенный в ячейку DataGrid, повторялся после следующего ввода в другую ячейку (примерно как в Excel, см. анимацию)?

Answer 1

Подобное поведение есть в штатном ComboBox (в режиме IsEditable="True"), можно этим воспользоваться. Я покопался в шаблоне и выбросил всё лишнее, получается вот что:

<ComboBox IsEditable="True" Padding="0" ItemsSource="{Binding Strings}">
    <ComboBox.Template>
        <ControlTemplate>
            <TextBox x:Name="PART_EditableTextBox" Margin="{TemplateBinding Padding}"
                     HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                     VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
        </ControlTemplate>
    </ComboBox.Template>
</ComboBox>

В ItemsSource привязываете коллекцию строк из которых надо автодополнять, результат ввода можно забирать из свойства Text:

Итак, применить этот трюк с ComboBox совместно с DataGrid мне до конца не удалось (с другой стороны, пихать ComboBox внутрь шаблона TextBox может быть не слишком уж и рационально), поэтому предлагаю другое решение — написать самостоятельно функционал автодополнения (на самом деле он довольно прост в написании) или взять готовый.

Я взял по одной из первых ссылок в google этот пакет: WPFTextBoxAutoComplete (здесь можно посмотреть как его использовать).

Пишем VM-часть. Мы должны подготовить строки для автодополнения. Пусть у меня такая простая сущность:

class ItemVm : Vm
{
    string name;
    public string Name
    {
        get => name;
        set => Set(ref name, value);
    }
}

А в главной VM коллекция этих сущностей:

public ObservableCollection<ItemVm> Items { get; } = new ObservableCollection<ItemVm>();

Тогда я могу в главной VM выставить такое свойство:

public IEnumerable<string> Strings => Items.Select(i => i.Name).Distinct().OrderBy(s => s);

Остается только как-то оповещать при изменениях строк, это не сильно сложно:

public MainVm()
{
    Items.CollectionChanged += OnItemsCollectionChanged;
}
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    switch (e.Action)
    {
        case NotifyCollectionChangedAction.Add:
            foreach (ItemVm item in e.NewItems)
                item.PropertyChanged += OnItemPropertyChanged;
            break;
        case NotifyCollectionChangedAction.Remove:
            foreach (ItemVm item in e.OldItems)
                item.PropertyChanged -= OnItemPropertyChanged;
            break;
        // Остальные Action
    }
    NotifyPropertyChanged(nameof(Strings));
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == nameof(ItemVm.Name))
        NotifyPropertyChanged(nameof(Strings));
}

Готово. Теперь представление:

<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}">
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

Ячейка во время редактирования превращается в TextBox без границ, переопределяем стиль для нее:

            <DataGridTextColumn.EditingElementStyle>
                <Style TargetType="TextBox">
                    <Setter Property="BorderThickness" Value="0"/>
                    <Setter Property="Padding" Value="0"/>
                    <Setter Property="behaviors:AutoCompleteBehavior.AutoCompleteItemsSource"
                            Value="{Binding DataContext.Strings, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                </Style>
            </DataGridTextColumn.EditingElementStyle>

где xmlns:behaviors="clr-namespace:WPFTextBoxAutoComplete;assembly=WPFTextBoxAutoComplete" — пространство имен, которое вам необходимо подключить

Пример почти работает, за исключением первого нажатия в ячейке — автодополнение срабатывает, но при этом слетает выделение, что ведет к неудобству использования, вы столкнетесь с этим независимо от того, будете ли писать свое автодополнение или возьмете готовое.

Я решил это следующим образом, добавим в стиль обработчик события Loaded (да, TextBox еще не загружен в том момент, когда он обработал первое нажатие! и при загрузке сбрасывает выделение):

                    <EventSetter Event="Loaded" Handler="TextBox_Loaded"/>

В обработчике пишем:

    private void TextBox_Loaded(object sender, RoutedEventArgs e)
    {
        var textBox = (TextBox)sender;
        if (textBox.IsSelectionActive)
            textBox.SelectionLength = textBox.Text.Length - textBox.SelectionStart;
    }

т.е. просто восстанавливаем выделение.

READ ALSO
Перевод SQL запроса в LINQ

Перевод SQL запроса в LINQ

ЗдраствуйтеЕсть такой SQL запрос

180
Как организовать очередь к файлу между сессиями

Как организовать очередь к файлу между сессиями

Допустим есть файлВ один момент времени им может пользоваться один инстанс программы

186
Как перехватить ошибку в методе PHP?

Как перехватить ошибку в методе PHP?

В каком-то месте "падает" метод:

187
Построчная запись и удаление из БД

Построчная запись и удаление из БД

Допустим в поле БД надо поместить список id пользователей, для этого, вероятнее всего, надо юзать массив с разделителями

175