2014-12-08 20 views
0

我已经阅读了许多关于将ObservableCollection绑定到具有类似问题的人的ListView的帖子;不过,我还没找到解决方案。ObservableCollection在异步命令执行绑定到ListView时的奇怪行为

在下面的测试应用程序中,我有一个简单的ListView和一个按钮。启动时,ListView被初始化,即创建2列和30行,值为0-29。 30行中的一半(即15行)是可见的。要查看剩余的15个项目,我必须使用滚动条向下滚动。

该按钮绑定到异步命令使用异步命令this article。点击该按钮后(请参阅Start_Click),将随机数写入ListView的这30行。这是在一个单独的Thread的无限循环中完成的(请参阅AsynchronousCommand)。

现在,当我点击按钮时,我希望所有ListView项目瞬间变为随机值。但是,这不是发生了什么。相反,只有那些不可见的项目(即ScrollBar之外的15个项目)正在改变它们的值。有时候,没有任何项目会改变它的值。

这里的XAML:

<Window x:Class="ListViewTestApp.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="MainWindow" Height="350" Width="614"> 
    <Grid x:Name="LayoutRoot"> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="38*"/> 
      <ColumnDefinition Width="9*"/> 
     </Grid.ColumnDefinitions> 
     <ListView HorizontalAlignment="Left" ItemsSource="{Binding MyList}" Height="261" Margin="28,24,0,0" VerticalAlignment="Top" Width="454" Grid.ColumnSpan="2"> 
      <ListView.View> 
       <GridView> 
        <GridViewColumn Width="100" Header="Name" DisplayMemberBinding="{Binding Name}" /> 
        <GridViewColumn Width="325" Header="Data" DisplayMemberBinding="{Binding Data}" /> 
       </GridView> 
      </ListView.View> 
     </ListView> 
     <Button Content="Start" Command="{Binding StartCommand}" Grid.Column="1" HorizontalAlignment="Left" Margin="21.043,42,0,0" VerticalAlignment="Top" Width="75"/>   
    </Grid> 
</Window> 

这是我的代码(查看的代码隐藏,视图模型,控制器逻辑和模型):

/// <summary> 
/// This is the CodeBehind of my View 
/// </summary> 
public partial class MainWindow : Window 
{ 
    public MainWindow() 
    { 
     InitializeComponent(); 
     LayoutRoot.DataContext = new ViewModel(); 
    } 
} 

/// <summary> 
/// This is my ViewModel 
/// </summary> 
public class ViewModel : NotifyPropertyChanged 
{   
    private ObservableCollection<Document> _myList; 
    private Logic _logic; 
    private AsynchronousCommand _startCommand;   

    public ViewModel() 
    { 
     _myList = new ObservableCollection<Document>(); 
     _logic = new Logic(this); 
     _startCommand = new AsynchronousCommand(_logic.Start_Click, true); 
    } 

    public ObservableCollection<Document> MyList 
    { 
     get { return _myList; } 
     set 
     { 
      if (_myList != value) 
      { 
       _myList = value; 
       RaisePropertyChangedEvent("MyList"); 
      } 
     } 
    } 

    public AsynchronousCommand StartCommand 
    { 
     get 
     { 
      return _startCommand; 
     } 
     set 
     { 
      _startCommand = value; 
     } 
    } 
} 

public class NotifyPropertyChanged : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected void RaisePropertyChangedEvent(string propertyName) 
    { 
     var handler = PropertyChanged; 
     if (handler != null) 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

/// <summary> 
/// This is my Controller 
/// </summary> 
public class Logic 
{ 
    private ViewModel _viewModel; 
    private Random _rnd; 

    public Logic(ViewModel vm) 
    { 
     _viewModel = vm; 
     _rnd = new Random(); 

     for (int i = 0; i < 30; i++) 
     { 
      Document newDocument = new Document("Name " + i.ToString(), "Data " + i.ToString()); 
      _viewModel.MyList.Add(newDocument); 
     } 
    } 

    public void Start_Click(object obj) 
    { 
     while (true) 
     { 
      int idx = _rnd.Next(0, 29); 

      _viewModel.StartCommand.ReportProgress(() => 
      { 
       _viewModel.MyList[idx].Name = "New Name"; 
       _viewModel.MyList[idx].Data = "New Data"; 
      }); 

      System.Threading.Thread.Sleep(20); 
     } 
    } 
} 

/// <summary> 
/// This is my Model 
/// </summary> 
public class Document 
{ 
    public string Name { get; set; } 
    public string Data { get; set; } 

    public Document(string name, string data) 
    { 
     Name = name; 
     Data = data; 
    } 
} 

这是代码为我AsynchronousCommand,取自Dave Kerr's article on CodeProject

/// <summary> 
/// The ViewModelCommand class - an ICommand that can fire a function. 
/// </summary> 
public class Command : ICommand 
{ 
    /// <summary> 
    /// Initializes a new instance of the <see cref="Command"/> class. 
    /// </summary> 
    /// <param name="action">The action.</param> 
    /// <param name="canExecute">if set to <c>true</c> [can execute].</param> 
    public Command(Action action, bool canExecute = true) 
    { 
     // Set the action. 
     this.action = action; 
     this.canExecute = canExecute; 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="Command"/> class. 
    /// </summary> 
    /// <param name="parameterizedAction">The parameterized action.</param> 
    /// <param name="canExecute">if set to <c>true</c> [can execute].</param> 
    public Command(Action<object> parameterizedAction, bool canExecute = true) 
    { 
     // Set the action. 
     this.parameterizedAction = parameterizedAction; 
     this.canExecute = canExecute; 
    } 

    /// <summary> 
    /// Executes the command. 
    /// </summary> 
    /// <param name="param">The param.</param> 
    public virtual void DoExecute(object param) 
    { 
     // Invoke the executing command, allowing the command to be cancelled. 
     CancelCommandEventArgs args = new CancelCommandEventArgs() { Parameter = param, Cancel = false }; 
     InvokeExecuting(args); 
     // If the event has been cancelled, bail now. 
     if (args.Cancel) 
      return; 
     // Call the action or the parameterized action, whichever has been set. 
     InvokeAction(param); 
     // Call the executed function. 
     InvokeExecuted(new CommandEventArgs() { Parameter = param }); 
    } 

    protected void InvokeAction(object param) 
    { 
     Action theAction = action; 
     Action<object> theParameterizedAction = parameterizedAction; 
     if (theAction != null) 
      theAction(); 
     else if (theParameterizedAction != null) 
      theParameterizedAction(param); 
    } 

    protected void InvokeExecuted(CommandEventArgs args) 
    { 
     CommandEventHandler executed = Executed; 
     // Call the executed event. 
     if (executed != null) 
      executed(this, args); 
    } 

    protected void InvokeExecuting(CancelCommandEventArgs args) 
    { 
     CancelCommandEventHandler executing = Executing; 
     // Call the executed event. 
     if (executing != null) 
      executing(this, args); 
    } 

    /// <summary> 
    /// The action (or parameterized action) that will be called when the command is invoked. 
    /// </summary> 
    protected Action action = null; 
    protected Action<object> parameterizedAction = null; 

    /// <summary> 
    /// Bool indicating whether the command can execute. 
    /// </summary> 
    private bool canExecute = false; 

    /// <summary> 
    /// Gets or sets a value indicating whether this instance can execute. 
    /// </summary> 
    /// <value> 
    /// <c>true</c> if this instance can execute; otherwise, <c>false</c>. 
    /// </value> 
    public bool CanExecute 
    { 
     get { return canExecute; } 
     set 
     { 
      if (canExecute != value) 
      { 
       canExecute = value; 
       EventHandler canExecuteChanged = CanExecuteChanged; 
       if (canExecuteChanged != null) 
        canExecuteChanged(this, EventArgs.Empty); 
      } 
     } 
    } 

    #region ICommand Members 

    /// <summary> 
    /// Defines the method that determines whether the command can execute in its current state. 
    /// </summary> 
    /// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param> 
    /// <returns> 
    /// true if this command can be executed; otherwise, false. 
    /// </returns> 
    /// 
    bool ICommand.CanExecute(object parameter) 
    { 
     return canExecute; 
    } 

    /// <summary> 
    /// Defines the method to be called when the command is invoked. 
    /// </summary> 
    /// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param> 
    void ICommand.Execute(object parameter) 
    { 
     this.DoExecute(parameter); 
    } 

    #endregion 


    /// <summary> 
    /// Occurs when can execute is changed. 
    /// </summary> 
    public event EventHandler CanExecuteChanged; 

    /// <summary> 
    /// Occurs when the command is about to execute. 
    /// </summary> 
    public event CancelCommandEventHandler Executing; 

    /// <summary> 
    /// Occurs when the command executed. 
    /// </summary> 
    public event CommandEventHandler Executed; 
} 

/// <summary> 
/// The CommandEventHandler delegate. 
/// </summary> 
public delegate void CommandEventHandler(object sender, CommandEventArgs args); 

/// <summary> 
/// The CancelCommandEvent delegate. 
/// </summary> 
public delegate void CancelCommandEventHandler(object sender, CancelCommandEventArgs args); 

/// <summary> 
/// CommandEventArgs - simply holds the command parameter. 
/// </summary> 
public class CommandEventArgs : EventArgs 
{ 
    /// <summary> 
    /// Gets or sets the parameter. 
    /// </summary> 
    /// <value>The parameter.</value> 
    public object Parameter { get; set; } 
} 

/// <summary> 
/// CancelCommandEventArgs - just like above but allows the event to 
/// be cancelled. 
/// </summary> 
public class CancelCommandEventArgs : CommandEventArgs 
{ 
    /// <summary> 
    /// Gets or sets a value indicating whether this <see cref="CancelCommandEventArgs"/> command should be cancelled. 
    /// </summary> 
    /// <value><c>true</c> if cancel; otherwise, <c>false</c>.</value> 
    public bool Cancel { get; set; } 
} 

/// <summary> 
/// The AsynchronousCommand is a Command that runs on a thread from the thread pool. 
/// </summary> 
public class AsynchronousCommand : Command, INotifyPropertyChanged 
{ 
    /// <summary> 
    /// Initializes a new instance of the <see cref="AsynchronousCommand"/> class. 
    /// </summary> 
    /// <param name="action">The action.</param> 
    /// <param name="canExecute">if set to <c>true</c> the command can execute.</param> 
    public AsynchronousCommand(Action action, bool canExecute = true) 
     : base(action, canExecute) 
    { 
     // Initialise the command. 
     Initialise(); 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="AsynchronousCommand"/> class. 
    /// </summary> 
    /// <param name="parameterizedAction">The parameterized action.</param> 
    /// <param name="canExecute">if set to <c>true</c> [can execute].</param> 
    public AsynchronousCommand(Action<object> parameterizedAction, bool canExecute = true) 
     : base(parameterizedAction, canExecute) 
    { 
     // Initialise the command. 
     Initialise(); 
    } 

    /// <summary> 
    /// Initialises this instance. 
    /// </summary> 
    private void Initialise() 
    { 
     // Construct the cancel command. 
     cancelCommand = new Command(
     () => 
     { 
      // Set the Is Cancellation Requested flag. 
      IsCancellationRequested = true; 
     }, true); 
    } 

    /// <summary> 
    /// Executes the command. 
    /// </summary> 
    /// <param name="param">The param.</param> 
    public override void DoExecute(object param) 
    { 
     // If we are already executing, do not continue. 
     if (IsExecuting) 
      return; 
     // Invoke the executing command, allowing the command to be cancelled. 
     CancelCommandEventArgs args = new CancelCommandEventArgs() { Parameter = param, Cancel = false }; 
     InvokeExecuting(args); 
     // If the event has been cancelled, bail now. 
     if (args.Cancel) 
      return; 
     // We are executing. 
     IsExecuting = true; 

     // Store the calling dispatcher. 
     callingDispatcher = Dispatcher.CurrentDispatcher; 

     // Run the action on a new thread from the thread pool (this will therefore work in SL and WP7 as well). 
     ThreadPool.QueueUserWorkItem(
     (state) => 
     { 
      // Invoke the action. 
      InvokeAction(param); 
      // Fire the executed event and set the executing state. 
      ReportProgress(
      () => 
      { 
       // We are no longer executing. 
       IsExecuting = false; 
       // If we were cancelled, invoke the cancelled event - otherwise invoke executed. 
       if (IsCancellationRequested) 
        InvokeCancelled(new CommandEventArgs() { Parameter = param }); 
       else 
        InvokeExecuted(new CommandEventArgs() { Parameter = param }); 
       // We are no longer requesting cancellation. 
       IsCancellationRequested = false; 
      } 
      ); 
     } 
     ); 
    } 

    /// <summary> 
    /// Raises the property changed event. 
    /// </summary> 
    /// <param name="propertyName">Name of the property.</param> 
    private void NotifyPropertyChanged(string propertyName) 
    { 
     // Store the event handler - in case it changes between 
     // the line to check it and the line to fire it. 
     PropertyChangedEventHandler propertyChanged = PropertyChanged; 
     // If the event has been subscribed to, fire it. 
     if (propertyChanged != null) 
      propertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 

    /// <summary> 
    /// Reports progress on the thread which invoked the command. 
    /// </summary> 
    /// <param name="action">The action.</param> 
    public void ReportProgress(Action action) 
    { 
     if (IsExecuting) 
     { 
      if (callingDispatcher.CheckAccess()) 
       action(); 
      else 
       callingDispatcher.BeginInvoke(((Action)(() => { action(); }))); 
     } 
    } 

    /// <summary> 
    /// Cancels the command if requested. 
    /// </summary> 
    /// <returns>True if the command has been cancelled and we must return.</returns> 
    public bool CancelIfRequested() 
    { 
     // If we haven't requested cancellation, there's nothing to do. 
     if (IsCancellationRequested == false) 
      return false; 
     // We're done. 
     return true; 
    } 

    /// <summary> 
    /// Invokes the cancelled event. 
    /// </summary> 
    /// <param name="args">The <see cref="Apex.MVVM.CommandEventArgs"/> instance containing the event data.</param> 
    protected void InvokeCancelled(CommandEventArgs args) 
    { 
     CommandEventHandler cancelled = Cancelled; 
     // Call the cancelled event. 
     if (cancelled != null) 
      cancelled(this, args); 
    } 

    protected Dispatcher callingDispatcher; 

    /// <summary> 
    /// Flag indicating that the command is executing. 
    /// </summary> 
    private bool isExecuting = false; 

    /// <summary> 
    /// Flag indicated that cancellation has been requested. 
    /// </summary> 
    private bool isCancellationRequested; 

    /// <summary> 
    /// The cancel command. 
    /// </summary> 
    private Command cancelCommand; 

    /// <summary> 
    /// The property changed event. 
    /// </summary> 
    public event PropertyChangedEventHandler PropertyChanged; 

    /// <summary> 
    /// Occurs when the command is cancelled. 
    /// </summary> 
    public event CommandEventHandler Cancelled; 

    /// <summary> 
    /// Gets or sets a value indicating whether this instance is executing. 
    /// </summary> 
    /// <value> 
    /// <c>true</c> if this instance is executing; otherwise, <c>false</c>. 
    /// </value> 
    public bool IsExecuting 
    { 
     get 
     { 
      return isExecuting; 
     } 
     set 
     { 
      if (isExecuting != value) 
      { 
       isExecuting = value; 
       NotifyPropertyChanged("IsExecuting"); 
      } 
     } 
    } 

    /// <summary> 
    /// Gets or sets a value indicating whether this instance is cancellation requested. 
    /// </summary> 
    /// <value> 
    /// <c>true</c> if this instance is cancellation requested; otherwise, <c>false</c>. 
    /// </value> 
    public bool IsCancellationRequested 
    { 
     get 
     { 
      return isCancellationRequested; 
     } 
     set 
     { 
      if (isCancellationRequested != value) 
      { 
       isCancellationRequested = value; 
       NotifyPropertyChanged("IsCancellationRequested"); 
      } 
     } 
    } 

    /// <summary> 
    /// Gets the cancel command. 
    /// </summary> 
    public Command CancelCommand 
    { 
     get { return cancelCommand; } 
    } 
} 
+0

只是我明白了:你为什么不使用异步/等待模式与异步绑定? – 2014-12-08 07:36:35

回答

0

Document类具有IMPL当您的属性更改时,界面使用户界面得到更新。

为什么当你滚动时它工作? 由于Virtualization。不明显的条目还没有被发现,所以一旦他们得到评估,他们已经获得了“新价值”。

这里是你的工作文档类:

/// <summary> 
    /// This is my Model 
    /// </summary> 
    public class Document : INotifyPropertyChanged 
    { 
     private string _name; 
     private string _data; 
     public string Name 
     { 
      get { return _name; } 
      set 
      { 
       _name = value; 
       OnPropertyChanged(); 
      } 
     } 

     public string Data 
     { 
      get { return _data; } 
      set 
      { 
       _data = value; 
       OnPropertyChanged(); 
      } 
     } 

     public Document(string name, string data) 
     { 
      Name = name; 
      Data = data; 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 

     [NotifyPropertyChangedInvocator] 
     protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 
     { 
      var handler = PropertyChanged; 
      if (handler != null) 
      { 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 
    } 
+0

非常感谢你! – berti 2014-12-08 07:59:25