Как одновременно выделить элемент listView(если элемент textbox) и одновременно работать с редактором textbox с привязкой алгоритма к keyBinding
Есть ListView которая отображает элементы ObservableCollection как textbox'ы. Я сделал кейБиндинг на enter, привязал к команде чтобы создавался новый элемент в ObservableCollection по нажатию.
И теперь я хочу нажимая на backspace удалять текст из какого-нибудь textbox'a, и когда textbox станет пустым удалить сам элемент listView т.е. из ObservableCollection для этого я сделал кейБиндинг на BackSpace для проверки на пустоту выделенного элемента.
Проблема в том, что когда я нажимаю на textbox, чтобы удалять текст там появляется каретка для того, чтобы можно было удалять и писать текст, но сам элемент listView не выделяется, т.е. не попадает в привязанное свойство, по которому я и должен определить какой элемент удалить в случае если он пустой. Он выделяется если нажать в область по бокам где нет textbox'a тогда да выделится элемент.
Но я хочу отрисовать свой textbox на всю ширину и высоту, и там не будет зазоров, чтобы выделить элемент, да и не удобно два раза нажимать, чтобы все правильно работало.
Вопрос??? Как можно сделать так чтобы при нажатие на textBox появилась каретка редактирование текста и выделился элемент для выбранного свойства?
Ответы (1 шт):
Очень надеюсь, что я понял ваш вопрос правильно и не потратил в пустую час своего времени. На будущее, пожалуйста, предоставляйте минимальный пример кода, который воспроизведет вашу проблему, а не пытайтесь объяснить все словами.
Ну ладно, допустим у нас такой 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.
