在高度设置为自动的网格单元中使用ListView SelectionChanged时,为什么行为不正确?

如何解决在高度设置为自动的网格单元中使用ListView SelectionChanged时,为什么行为不正确?

上下文

Xaml:

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        xmlns:local="clr-namespace:WpfApp2"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        Title="MainWindow" Height="350" Width="400">

    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>

        <TextBlock Grid.Row="0" Text="The ListView:"/>
        <ListView 
            Grid.Row="1"
            ItemsSource="{Binding ItemCollection}"
            SelectionMode="Single">

            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectionChanged">
                    <i:InvokeCommandAction 
                        Command="{Binding Path=ProcessChangeCommand}"
                        CommandParameter="{Binding Path=SelectedItem,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ItemsControl}}}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>            
        </ListView>
    </Grid>
</Window>

后面的代码:

public partial class MainWindow : Window {
    public MainWindow () {
        InitializeComponent ();
    }
}

public class ViewModel : INotifyPropertyChanged {
    bool colorList;
    string[] colors = { "blue","yellow","green","orange","black" };
    string[] towns = { "Dakar","Berlin","Toronto" };
    private ObservableCollection<string> itemCollection;
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<string> ItemCollection {
        get => itemCollection;
        set {
            itemCollection = value;
            RaisePropertyChanged (nameof (ItemCollection));
        }
    }

    public ICommand ProcessChangeCommand { get; private set; }

    public ViewModel () {
        ProcessChangeCommand = new RelayCommand<string> (ProcessChange);
        itemCollection = new ObservableCollection<string> (colors);
    }

    public void ProcessChange (string parameter) {
        if (parameter == null) return;
        Debug.WriteLine ($"Selected: {parameter}");
        ItemCollection = new ObservableCollection<string> (colorList ? colors : towns);
        RaisePropertyChanged (nameof (ItemCollection));
        colorList = !colorList;
    }

    void RaisePropertyChanged ([CallerMemberName] string propertyName = null) {
        PropertyChanged?.Invoke (this,new PropertyChangedEventArgs (propertyName));
    }
}

public class RelayCommand<T> : ICommand {
    readonly Action<T> _execute = null;
    public event EventHandler CanExecuteChanged {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }
    public RelayCommand (Action<T> excute) { _execute = excute; }
    public bool CanExecute (object parameter) => true;
    public void Execute (object parameter) => _execute ((T) parameter);
}

使用.Net Framework 4.8。
将软件包Microsoft.Xaml.Behaviors.Wpf添加到项目中。

ListView显示一个列表。完成选择后,其值将显示在控制台上并交换列表(交替显示两个列表)。

问题

在“颜色”列表是比“镇”列表更长,并且当orangeblack被选择,被示出在控制台上之后选择和列表被交换(正常),触发了城镇列表的第一项Dakar(意外)。调试时,单击orange后,ProcessChange被调用4次:

  • 带有参数orange(预期),
  • 带有参数null(代码中出乎意料,但可以理解和丢弃。该调用是在ProcessChange处理orange时发生的可重入调用)
  • 带有参数Dakar(意外和错误),
  • 具有null(与第二个项目符号相同,也可重入,在处理意外的Dakar调用时发生)

结果控制台输出:

enter image description here

enter image description here


观察:如果以此方式设置网格行,则此双重事件异常消失:

<Grid.RowDefinitions>
    <RowDefinition Height="auto"/>
    <RowDefinition Height="*"/>         <!-- height set to * -->
</Grid.RowDefinitions>

(或者如果在事件处理程序中引入了100ms延迟或设置了断点)。


问题是

Dakar之后orange出现在控制台上的原因是什么?

解决方法

您无需考虑更改列表后重置选择并在不完成当前执行的情况下再次触发命令的情况。

在命令执行期间必须排除其重复执行。 要重新检查命令,您需要从代码中调用其事件。 这要求该命令的实现稍有不同。

基类:

    using System;
    using System.Windows;
    using System.Windows.Input;

    namespace Common
    {
        #region Delegates for WPF Command Methods
        /// <summary>Delegate of the executive team method.</summary>
        /// <param name="parameter">Command parameter.</param>
        public delegate void ExecuteHandler(object parameter);
        /// <summary>Command сan execute method delegate.</summary>
        /// <param name="parameter">Command parameter.</param>
        /// <returns><see langword="true"/> if command execution is allowed.</returns>
        public delegate bool CanExecuteHandler(object parameter);
        #endregion

        #region Class commands - RelayCommand
        /// <summary>A class that implements the ICommand interface for creating WPF commands.</summary>
        public class RelayCommand : ICommand
        {
            private readonly CanExecuteHandler _canExecute;
            private readonly ExecuteHandler _onExecute;
            private readonly EventHandler _requerySuggested;

            public event EventHandler CanExecuteChanged;

            /// <summary>Command constructor.</summary>
            /// <param name="execute">Executable command method.</param>
            /// <param name="canExecute">Method allowing command execution.</param>
            public RelayCommand(ExecuteHandler execute,CanExecuteHandler canExecute = null)
            {
                _onExecute = execute;
                _canExecute = canExecute;

                _requerySuggested = (o,e) => Invalidate();
                CommandManager.RequerySuggested += _requerySuggested;
            }

            public void Invalidate()
                => Application.Current.Dispatcher.BeginInvoke
                (
                    new Action(() => CanExecuteChanged?.Invoke(this,EventArgs.Empty)),null
                );

            public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute.Invoke(parameter);

            public void Execute(object parameter) => _onExecute?.Invoke(parameter);
        }

        #endregion

    }

不带参数的命令:

namespace Common
{
    #region Delegates for WPF Parameterless Command Methods
    /// <summary>Delegate to the execution method of a command without a parameter.</summary>
    public delegate void ExecuteActionHandler();
    /// <summary>Command state method delegate without parameter.</summary>
    /// <returns><see langword="true"/> - if command execution is allowed.</returns>
    public delegate bool CanExecuteActionHandler();


    #endregion

    /// <summary>Class for commands without parameters.</summary>
    public class RelayActionCommand : RelayCommand
    {
        /// <summary>Command constructor.</summary>
        /// <param name="execute">Command execution method.</param>
        /// <param name="canExecute">Method allowing command execution.</param>
        public RelayActionCommand(ExecuteActionHandler execute,CanExecuteActionHandler canExecute = null)
            : base(_ => execute(),_ => canExecute()) { }

    }
}

键入参数命令

using System.ComponentModel;

namespace Common
{
    #region Delegates for WPF Command Methods
    /// <summary>Delegate of the executive team method.</summary>
    /// <param name="parameter">Command parameter.</param>
    public delegate void ExecuteHandler<T>(T parameter);
    /// <summary>Command сan execute method delegate.</summary>
    /// <param name="parameter">Command parameter.</param>
    /// <returns><see langword="true"/> if command execution is allowed.</returns>
    public delegate bool CanExecuteHandler<T>(T parameter);
    #endregion

    /// <summary>Class for typed parameter commands.</summary>
    public class RelayCommand<T> : RelayCommand
    {

        /// <summary>Command constructor.</summary>
        /// <param name="execute">Executable command method.</param>
        /// <param name="canExecute">Method allowing command execution.</param>
        public RelayCommand(ExecuteHandler<T> execute,CanExecuteHandler<T> canExecute = null)
            : base
        (
                  p => execute
                  (
                      p is T t
                      ? t
                      : TypeDescriptor.GetConverter(typeof(T)).IsValid(p)
                        ? (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(p) 
                        : default
                  ),p => 
                  canExecute == null
                  || (p is T t 
                        ? canExecute(t)
                        : TypeDescriptor.GetConverter(typeof(T)).IsValid(p) && canExecute((T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(p))
                    )
        )
        {}

    }
}

ViewModel

using Common;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;

namespace WpfApp2
{
    public class ViewModel
    {
        bool isColorList;
        string[] colors = { "blue","yellow","green","orange","black" };
        string[] towns = { "Dakar","Berlin","Toronto" };
        private bool isExecutedProcessChange = false;

        public ObservableCollection<string> ItemCollection { get; }
            = new ObservableCollection<string>();

        public RelayCommand ProcessChangeCommand { get; }

        public ViewModel()
        {
            ProcessChangeCommand = new RelayCommand<string>(ProcessChange,CanProcessChange);
            Array.ForEach(colors,color => ItemCollection.Add(color));
        }

        private bool CanProcessChange(string parameter)
            =>! isExecutedProcessChange;

        public void ProcessChange(string parameter)
        {
            isExecutedProcessChange = true;
            ProcessChangeCommand.Invalidate();

            if (parameter != null)
            {
                Debug.WriteLine($"Selected: {parameter}");
                ItemCollection.Clear();
                Array.ForEach(isColorList ? colors : towns,color => ItemCollection.Add(color));
                isColorList = !isColorList;
            }
            isExecutedProcessChange = false;
            ProcessChangeCommand.Invalidate();
        }

    }

}

XAML不需要更改。

要找到重复选择的原因,我在方法中添加了一个延迟:

    public async void ProcessChange(string parameter)
    {
        isExecutedProcessChange = true;
        ProcessChangeCommand.Invalidate();

        if (parameter != null)
        {
            Debug.WriteLine($"Selected: {parameter}");
            ItemCollection.Clear();
            Array.ForEach(isColorList ? colors : towns,color => ItemCollection.Add(color));
            isColorList = !isColorList;
            await Task.Delay(500);
        }
        isExecutedProcessChange = false;
        ProcessChangeCommand.Invalidate();
    }

双重选择几乎结束了。 但是有时候还是会发生。 显然,这是由于鼠标按钮的弹跳所致。 即,有时会接收到两个信号,而不是一个从鼠标按下键的信号。 设置一个小的延迟(500毫秒)就能过滤掉其中一些双重事件。 该过滤器还有助于避免两次运行该命令,但仍会选择新列表中的项目。 并且您无法再次选择它。 为了使命令生效,您必须选择另一个元素。

解决方案可能是增加延迟并在更改列表之前将其推迟。 检查在这种情况下使用起来有多方便:

    public async void ProcessChange(string parameter)
    {
        isExecutedProcessChange = true;
        ProcessChangeCommand.Invalidate();

        if (parameter != null)
        {
            Debug.WriteLine($"Selected: {parameter}");
            await Task.Delay(1000); // It is necessary to select the optimal delay time
            ItemCollection.Clear();
            Array.ForEach(isColorList ? colors : towns,color => ItemCollection.Add(color));
            isColorList = !isColorList;
        }
        isExecutedProcessChange = false;
        ProcessChangeCommand.Invalidate();
    }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-