Работа с фокусом внутри окна с виртуализацией
Появилась нужда сделать виртуализацию для окна в котором создаются textbox'ы так как при создании около тысячи элементов начинает подлагивать. Виртуализация действительно помогла, но появились проблемы с фокусом элементов. Логика следующая: по нажатию на Enter фокус переключается на следующий textbox, а если это последний элемент в коллекции то создается новый и фокус падает на него. Но после добавления виртуализации фокус работает адекватно только на видимой области, и если выходит за рамки видимого окна то фокус уже не переключается на следующий элемент, но элементы при этом создаются (видно как уменьшается полоса прокрутки) если прокрутить мышью то видно новые элементы. Также при смене фокуса между уже созданными элементами фокус начинается скакать по случайным textbox'ам. Вопрос следующий можно ли корректно использовать фокусировку по элементам и если да то как это сделать? Использую Avalonia 11.0.2 c Reactive, и паттерн MVVM.
Models:
public class Elements : ReactiveObject
{
private int _id;
private string _text;
public int Id
{
get => _id;
set => this.RaiseAndSetIfChanged(ref _id, value);
}
public string Text
{
get => _text;
set => this.RaiseAndSetIfChanged(ref _text, value);
}
}
ViewModels:
public class MainWindowViewModel : ViewModelBase
{
public ReactiveCommand<Unit, Unit> AddNewRow { get; }
private ObservableCollection<Elements> _elementsList;
public ObservableCollection<Elements> ElementsList
{
get => _elementsList;
set => this.RaiseAndSetIfChanged(ref _elementsList, value);
}
public MainWindowViewModel()
{
ElementsList = new ObservableCollection<Elements>
{
new Elements { Id = 1, Text = "First item" }
};
AddNewRow = ReactiveCommand.Create(() =>
{
ElementsList.Add(new Elements
{
Id = ElementsList.Count + 1,
Text = $"Item {ElementsList.Count + 1}"
});
});
}
}
View:
<Grid RowDefinitions="40,*">
<Button Grid.Row="0" Content="New row" Command="{Binding AddNewRow}" />
<ScrollViewer Grid.Row="1">
<ItemsControl ItemsSource="{Binding ElementsList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Elements}">
<Grid ColumnDefinitions="*,*,*" Margin="5">
<TextBlock Grid.Column="0" Text="{Binding Id}" Width="100" VerticalAlignment="Center" />
<TextBox Tag="First" Grid.Column="1" Text="{Binding Text}" KeyDown="Control_KeyDown"
Loaded="Control_OnLoaded" />
<CheckBox Grid.Column="2" Focusable="False" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
CodeBehind (Логика фокуса):
private void Control_OnLoaded(object? sender, RoutedEventArgs e)
{
var tb = sender as TextBox;
if (tb.Tag == "First")
{
tb.Focus();
}
}
private void Control_KeyDown(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
var tb = sender as TextBox;
if (IsLastTextBox(tb)) // если Enter нажат в последнем TextBox-е, то нужно создать новую строку
{
CreateNewRow(tb);
}
else // иначе фокус должен переключиться на уже существующий TextBox
{
SwitchFocusToNextRow(tb);
}
}
}
private bool IsLastTextBox(TextBox? tb)
{
var nextElement = KeyboardNavigationHandler.GetNext(tb, NavigationDirection.Next);
if (IsOurTextbox(nextElement)) // если tb - последний созданный TextBox, то за ним будет следовать не "наш" элемент
{
return false;
}
return true;
}
// "нашим" является TextBox, помеченный либо First, либо Second
private bool IsOurTextbox(IInputElement? element)
{
if (element is TextBox nextTb)
{
if (nextTb.Tag?.ToString() == "First")
{
return true;
}
}
return false;
}
private void SwitchFocusToNextRow(TextBox? tb)
{
var nextElement = KeyboardNavigationHandler.GetNext(tb!, NavigationDirection.Next);
nextElement?.Focus();
}
private void CreateNewRow(TextBox? tb)
{
if (DataContext is MainWindowViewModel dc)
{
dc.AddNewRow.Execute(Unit.Default).Subscribe();
}
}
Я поменял ItemsСontrol
на ListBox
так как узнал что сам контейнер имеет виртуализацию. Все здорово, теперь я могу хоть миллион текстбоксов сюда поместить и зависаний не будет никаких. Но сейчас возникла проблема следующая: все поведение фокуса пропало, кроме события загрузки, сейчас фокус идет на TextBox
который подгружается благодаря виртуализации). А переключение фокуса между текстбоксами не работает и я считаю что это от того что я смотрю наличие текстбоксов внутри конкретного ListBox.Item
и там он как-бы один и текстбокс соответственно создается новый (по моему условию). Так что сейчас беда с фокусом, еще буду думать что можно сделать.
<ListBox Grid.Row="1" x:Name="ListBox" ItemsSource="{Binding ElementsList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="*,*,*" Margin="5">
<TextBlock Grid.Column="0" Text="{Binding Id}" Width="100" VerticalAlignment="Center" />
<TextBox Tag="First" Grid.Column="1" Text="{Binding Text}" KeyDown="Control_KeyDown"/>
<CheckBox Grid.Column="2" Focusable="False" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Также стилизовал так чтобы не было видно выбираемых элементов
<Style Selector="ListBoxItem">
<Setter Property="Template">
<ControlTemplate>
<Border Background="Transparent" BorderBrush="Transparent">
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Border>
</ControlTemplate>
</Setter>
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
</Style>
<Style Selector="ListBoxItem:selected">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
</Style>
<Style Selector="ListBoxItem:pointerover">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
Ну и вот видео соответственно как должно выглядеть (имею ввиду фокус): https://disk.yandex.ru/i/3ukrU_29pdnfng
И вот как сейчас: https://disk.yandex.ru/i/zCKgFCj3NUgjiA