【问题标题】:WPF ListView Select and get Item Focus automaticallyWPF ListView 自动选择并获取项目焦点
【发布时间】:2018-09-18 20:03:09
【问题描述】:

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

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

当 PC/SC 读卡器修改项目时(将此视为输入),下一个元素应像这样聚焦:

我想留在 MVVM 中,因此在 ViewModel 中没有引用 View。以下是我当前的代码。

Model :主要目的是使用 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 芯片的数据与当前选定的项目配对。当 RFID 芯片从 PC/SC Reader 中移除时,下一个元素被很好地选择但没有被聚焦。

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”,按照here 的建议,将数据绑定到我的模型属性“IsSelected”,但我最想念的还有一些我还不明白的东西。

<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,虽然项目选择得很好,但没有重点。

【问题讨论】:

  • 根据您的集合的默认视图绑定到 listcollectionview。 Set 与列表视图上的当前项目同步,因此当前项目将是选定的项目,反之亦然。添加一个关注所选项目的行为。谷歌,你会发现它很容易。这是一个数据网格,但数据网格和列表视图都是项目控件。 social.technet.microsoft.com/wiki/contents/articles/…
  • @Andy :ListCollectionView 看起来很有希望,我会马上试一试。感谢您为我指明正确的方向

标签: wpf


【解决方案1】:

我终于明白了。以下是我当前的工作代码。

Model 中,我刚刚将标志 IsSelected 更改为 IsCurrent 以避免与 ListViewItem 内置属性混淆,但它可能只是一个实现细节。

public class SmartDeviceModel : INotifyPropertyChanged
{
    public bool IsCurrent;
    [...]
}

ViewModel中的BindingList与OP中的大部分相同:

public class ScanDeviceViewModel : INotifyPropertyChanged
{
   public BindingList<SmartDeviceModel> ReaderList { get; internal set; }
   [...]
}

NB : BindingList 似乎减少了 OnNotifyPropertyChange 的需求,但其他类型的 List 应该使用一点点额外的代码。我还注意到 BindingList 可能不适合大型列表场景。

View 使用上面的 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();
    }
}

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2016-03-12
      • 2015-04-27
      • 2012-07-29
      • 1970-01-01
      • 2015-05-18
      • 1970-01-01
      • 2011-07-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多