Как одновременно выделить элемент listView(если элемент textbox) и одновременно работать с редактором textbox с привязкой алгоритма к keyBinding

Есть ListView которая отображает элементы ObservableCollection как textbox'ы. Я сделал кейБиндинг на enter, привязал к команде чтобы создавался новый элемент в ObservableCollection по нажатию.

И теперь я хочу нажимая на backspace удалять текст из какого-нибудь textbox'a, и когда textbox станет пустым удалить сам элемент listView т.е. из ObservableCollection для этого я сделал кейБиндинг на BackSpace для проверки на пустоту выделенного элемента.

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

Но я хочу отрисовать свой textbox на всю ширину и высоту, и там не будет зазоров, чтобы выделить элемент, да и не удобно два раза нажимать, чтобы все правильно работало.

Вопрос??? Как можно сделать так чтобы при нажатие на textBox появилась каретка редактирование текста и выделился элемент для выбранного свойства?


Ответы (1 шт):

Автор решения: EvgeniyZ

Очень надеюсь, что я понял ваш вопрос правильно и не потратил в пустую час своего времени. На будущее, пожалуйста, предоставляйте минимальный пример кода, который воспроизведет вашу проблему, а не пытайтесь объяснить все словами.


Ну ладно, допустим у нас такой C# код, в котором есть некий объект, ViewModel с некими данными и свойствами для привязки, ну и код основного окна, где задается DataContext.

public partial class Item : ObservableObject
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

public partial class MainViewModel : ObservableObject
{
    public List<Item> Items { get; set; }

    [ObservableProperty]
    private Item? selectedItem;

    public MainViewModel()
    {
        Items = new Faker<Item>()
            .RuleFor(i => i.Id, f => f.IndexFaker)
            .RuleFor(i => i.Name, f => f.Name.FullName())
            .Generate(100);
    }

    partial void OnSelectedItemChanged(Item? value)
    {
        Debug.WriteLine($"Selected Item Changed: {value?.Id} - {value?.Name}");
    }
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
}

В XAML такая табличка:

<Grid>
    <ListView ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
        <ListView.View>
            <GridView>
                <GridViewColumn DisplayMemberBinding="{Binding Id}" Header="ID" />
                <GridViewColumn Header="Name">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>
</Grid>

Задача - синхронизовать выделение TextBox и выделение ListView. Как нам поступить? Все довольно просто. Нам нужно подписаться на событие GotFocus у TextBox и в нем найти предка (ListView), которому установить SelectedItem на текущий DataContext текстового поля.

Для таких целей отлично подходят так называемые Attached Property (присоединяемые свойства). Вот давайте сделаем такое свойство, у которого будет нужное нам поведение. Получим что-то такое:

public static class FocusSelectBehavior
{
    public static bool GetEnableSelectOnFocus(DependencyObject obj) =>
        (bool)obj.GetValue(EnableSelectOnFocusProperty);

    public static void SetEnableSelectOnFocus(DependencyObject obj, bool value) =>
        obj.SetValue(EnableSelectOnFocusProperty, value);

    public static readonly DependencyProperty EnableSelectOnFocusProperty =
        DependencyProperty.RegisterAttached(
            "EnableSelectOnFocus",
            typeof(bool),
            typeof(FocusSelectBehavior),
            new UIPropertyMetadata(false, OnEnableSelectOnFocusChanged));

    private static void OnEnableSelectOnFocusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is UIElement uiElement)
        {
            if ((bool)e.NewValue)
                uiElement.GotFocus += OnGotFocus;
            else
                uiElement.GotFocus -= OnGotFocus;
        }
    }

    private static void OnGotFocus(object sender, RoutedEventArgs e)
    {
        if (sender is DependencyObject focusedElement)
        {
            var selector = FindAncestor<Selector>(focusedElement);
            if (selector != null && focusedElement is FrameworkElement frameworkElement && frameworkElement.DataContext != null)
            {
                selector.SelectedItem = frameworkElement.DataContext;
            }
        }
    }

    private static T? FindAncestor<T>(DependencyObject current) where T : DependencyObject
    {
        while (current != null && current is not T)
            current = VisualTreeHelper.GetParent(current);
        return current as T;
    }
}
  • Регистрируем Attached Property с нужным названием (в моем случае EnableSelectOnFocus).
  • Подписываемся на изменения значения этого свойства, прописав в UIPropertyMetadata нужный Callback.
  • В методе, что указали в качестве Callback'а берем объект на котором свойство висит, сверяем его с ожидаемым типом (можем сразу TextBox, а можем что-то более низкоуровневое, чтобы была универсальность, например, UIElement), а дальше подписываемся на GotFocus.
  • В обработчике события проверяем тип sender (также, можем взять конкретно TextBox, а можем взять что-то универсальное).
  • Если тип подходит, то ищем предка.
  • Если предок найден (не null) и подходит по параметрам, то устанавливаем ему SelectedItem на DataContext текущего объекта.

Остается добавить это свойство в XAML:

  • Устанавливаем окну/контролу нужный namespace в котором лежит класс со свойством: xmlns:behaviors="clr-namespace:SomeWpfApp"
  • Дописываем TextBox behaviors:FocusSelectBehavior.EnableSelectOnFocus="True"

Все, запускаем, проверяем. Теперь при клике на TextBox должен автоматически выделяться и нужный объект ListView.

Result Gif

→ Ссылка