WPF ListView自动选择并获取项目焦点

时间:2018-04-09 12:10:52

标签: wpf

我不知道如何在ListView中自动更改焦点项目。

当我将“IsSelected”属性更改为数据仓列表中的其他元素时,我希望视图中的焦点项自动更改:

ListView with selected item

当PC / SC卡读卡器修改某个项目时(请参阅此输入),下一个元素应该像这样聚焦:

enter image description here

我想留在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,虽然项目选择得很好,但并没有集中注意力。

2 个答案:

答案 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,您可以实现自定义交互行为:

  1. 导入到 XAML:xmlns:b="http://schemas.microsoft.com/xaml/behaviors"(如果您使用的是 .NET Core 3.1 - 5

  2. 添加到内容正文:

    <ListView ...>
      <b:Interaction.Behaviors>
        <local:AutoScrollToLastItemBehavior />
      </b:Interaction.Behaviors>
    </ListView>
    
  3. 最后添加下一个类:

    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;
    
        }
    }
    }