我不知道如何在ListView中自动更改焦点项目。
当我将“IsSelected”属性更改为数据仓列表中的其他元素时,我希望视图中的焦点项自动更改:
当PC / SC卡读卡器修改某个项目时(请参阅此输入),下一个元素应该像这样聚焦:
我想留在MVVM中,因此没有在ViewModel中引用View。以下是我目前的代码。
模型:主要目的是使用IsSelected属性扩展DTO并实现INotifyPropertyChanged
public class SmartDeviceModel : INotifyPropertyChanged
{
public bool IsSelected;
private DtoReader _dtoReader;
public SmartDeviceModel(DtoReader _reader)
{
_dtoReader = _reader;
}
public string DisplayName => _dtoReader.DisplayName;
public string Uid
{
get
{
return _dtoReader.Uid;
}
set
{
_dtoReader.Uid = value;
OnPropertyChanged("Uid");
}
}
public long RadioId
{
get
{
return _dtoReader.RadioId : _dtoMarker.RadioId;
}
set
{
_dtoReader.RadioId = value;
OnPropertyChanged("RadioId");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
ViewModel 接收PC / SC读卡器的事件,以便将来自RFID芯片的数据与当前所选项目配对。当从PC / SC读取器中取出RFID芯片时,下一个元素选择得很好但没有聚焦。
public class ScanDeviceViewModel : BaseViewModel
{
public BindingList<SmartDeviceModel> ReaderList { get; }
public int SelectedReaderIndex;
private ITagReaderInput _rfidReader;
public ScanDeviceViewModel()
{
//Get Data listener for RFID Tag
_rfidReader = new IdentivTagReader.IdentivTagReader();
// Data Source of DTO
SiteInteractor siteInterractor = new SiteInteractor();
// List used for DataBinding
ReaderList = new BindingList<SmartDeviceModel>();
foreach (DtoReader m in SiteInteractor.GetReaders().OrderBy(x => x.DisplayName))
{
ReaderList.Add(new SmartDeviceModel(m));
}
if (ReaderList.Count() > 0)
{
for (var i = 0; i < ReaderList.Count(); i++)
{
if (String.IsNullOrEmpty(ReaderList[i].Uid))
{
SelectedReaderIndex = i;
ReaderList[i].IsSelected = true;
break;
}
}
}
_rfidReader.LabelDetected += RfidTagDetected;
_rfidReader.LabelRemoved += RfidRemoved;
}
private void RfidTagDetected(ITagLabel tag)
{
if (ReaderList[SelectedReaderIndex] != null && string.IsNullOrEmpty(ReaderList[SelectedReaderIndex].Uid))
{
ReaderList[SelectedReaderIndex].IsSelected = true;
ReaderList[SelectedReaderIndex].Uid = tag.Uid;
ReaderList[SelectedReaderIndex].RadioId = tag.RadioId;
}
}
private void RfidRemoved(ITagLabel tag)
{
if (ReaderList[SelectedReaderIndex].Uid == tag.Uid)
{
ReaderList[SelectedReaderIndex].IsSelected = false;
while (ReaderList.Count >= SelectedReaderIndex + 1)
{
SelectedReaderIndex++;
if (String.IsNullOrEmpty(ReaderList[SelectedReaderIndex].Uid)){
ReaderList[SelectedReaderIndex].IsSelected = true;
break;
}
}
}
}
}
查看我正在使用“Setter”使用数据绑定到我的模型属性“IsSelected”,如建议的here但我最想念其他一些我还不了解的东西。
<ListView ItemsSource="{Binding ReaderList}"
Margin="5" x:Name="listViewReader" SelectionMode="Single"
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="BorderBrush" Value="LightGray" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Viewbox Grid.Row ="0" Stretch="Uniform" HorizontalAlignment="Left" VerticalAlignment="Bottom" MaxHeight="90">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="{Binding DisplayName}" />
<DockPanel Grid.Row="1">
<Label Content="UID"/>
<Label Content="{Binding Uid}"/>
</DockPanel>
<DockPanel Grid.Row="1" Grid.Column="1">
<Label Content="RadioID" />
<Label Content="{Binding RadioId}"/>
</DockPanel>
</Grid>
</Viewbox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
我尝试了几种方法like this answer,虽然项目选择得很好,但并没有集中注意力。
答案 0 :(得分:0)
我终于明白了。以下是我目前的工作代码。
在模型中,我刚刚将IsSelected标志更改为IsCurrent,以避免与ListViewItem内置属性混淆,但它可能只是一个实现细节。
public class SmartDeviceModel : INotifyPropertyChanged
{
public bool IsCurrent;
[...]
}
ViewModel 中的BindingList与OP中的大致相同:
public class ScanDeviceViewModel : INotifyPropertyChanged
{
public BindingList<SmartDeviceModel> ReaderList { get; internal set; }
[...]
}
注意:BindingList似乎减少了OnNotifyPropertyChange的需要,但其他类型的List应该只需要一些额外的代码。我还注意到BindingList可能不适合大型列表方案。
视图然后使用上面的ViewModel作为DataContext,因此将 ItemSource 绑定到BindingList。然后ListViewItem样式设置器使用模型中的IsCurrent属性。
<ListView ItemsSource="{Binding ReaderList}"
SelectionMode="Single"
SelectionChanged="OnListViewSelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsCurrent}" />
</Style>
</ListView.ItemContainerStyle>
[...]
最后,下面的 View 代码主要是根据用户输入来模拟焦点,否则elemant会被选中但不会聚焦,可能会超出可见项目范围:
private void OnListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView listView = e.Source as ListView;
if (listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem) is FrameworkElement container)
{
container.Focus();
}
}
答案 1 :(得分:0)
根据 MVVM,您可以实现自定义交互行为:
导入到 XAML:xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
(如果您使用的是 .NET Core 3.1 - 5)
添加到内容正文:
<ListView ...>
<b:Interaction.Behaviors>
<local:AutoScrollToLastItemBehavior />
</b:Interaction.Behaviors>
</ListView>
最后添加下一个类:
public sealed class AutoScrollToLastItemBehavior : Microsoft.Xaml.Behaviors.Behavior<ListView>
{
// Need to track whether we've attached to the collection changed event
bool _collectionChangedSubscribed = false;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += SelectionChanged;
// The ItemSource of the listView will not be set yet,
// so get a method that we can hook up to later
AssociatedObject.DataContextChanged += DataContextChanged;
}
private void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ScrollIntoView();
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ScrollIntoView();
}
private void DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The ObservableCollection implements the INotifyCollectionChanged interface
// However, if this is bound to something that doesn't then just don't hook the event
var collection = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (collection != null && !_collectionChangedSubscribed)
{
// The data context has been changed, so now hook
// into the collection changed event
collection.CollectionChanged += CollectionChanged;
_collectionChangedSubscribed = true;
}
}
private void ScrollIntoView()
{
int count = AssociatedObject.Items.Count;
if (count > 0)
{
var last = AssociatedObject.Items[count - 1];
AssociatedObject.ScrollIntoView(last);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= SelectionChanged;
AssociatedObject.DataContextChanged -= DataContextChanged;
// Detach from the collection changed event
var collection = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (collection != null && _collectionChangedSubscribed)
{
collection.CollectionChanged -= CollectionChanged;
_collectionChangedSubscribed = false;
}
}
}