У меня есть список и кнопка в каждом столбце. Когда пользователь нажимает кнопку, он запускает асинхронное действие в модели представления, где я отключаю все кнопки и выполняю большое действие. Как только действие будет завершено, я снова включу их.
Однако если действие занимает слишком много времени, кнопки автоматически не включаются повторно, хотя я устанавливаю для свойства bound значение true и уведомляю о представлении. Если пользователь выполнит ЛЮБОЕ действие GUI после его завершения, кнопки снова включатся.
Еще одна странная вещь: если я делаю await Task.Delay
вместо Thread.Sleep
(Примечание: я делаю настоящую работу в полном приложении), он работает правильно.
Что что происходит здесь?
Я упростил здесь код, исключив модель (все логики c живет в ВМ).
Код модели представления:
namespace WpfTestApp
{
public class viewmodel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public RelayAsyncCommand<object> RunCommand { get; private set; }
private ObservableCollection<subVM> _subVMs;
public ObservableCollection<subVM> SubVMs
{
get => _subVMs; set
{
_subVMs = value;
NotifyPropertyChanged();
}
}
public viewmodel()
{
RunCommand = new RelayAsyncCommand<object>(OnRun);
SubVMs = new ObservableCollection<subVM>
{
new subVM("ItemA"),
new subVM("ItemB"),
};
}
private async void OnRun(object o)
{
subVM vm = o as subVM;
if (vm != null)
{
ChangeRunMode(false);
Thread.Sleep(500);
}
ChangeRunMode(true);
}
private void ChangeRunMode(bool on)
{
foreach (subVM vm in SubVMs)
{
vm.ButtonEnabled = on;
}
}
}
public class subVM : INotifyPropertyChanged
{
private string name = "";
public string Name
{
get => name;
set
{
if (value != name)
{
name = value;
}
}
}
public subVM(string name)
{
Name = name;
}
private bool tsk = true;
public bool ButtonEnabled
{
get => tsk;
set
{
if (tsk != value)
{
tsk = value;
NotifyPropertyChanged("ButtonEnabled");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Просмотр XAML:
<Window x:Class="WpfTestApp.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"
xmlns:local="clr-namespace:WpfTestApp"
mc:Ignorable="d"
Title="MainWindow" Height="130" Width="350">
<Window.DataContext>
<local:viewmodel/>
</Window.DataContext>
<ListView Margin="5"
BorderBrush="DarkSlateGray" BorderThickness="1"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding SubVMs}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
Width="200" DisplayMemberBinding ="{Binding Name}"/>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
Content="Load"
IsEnabled="{Binding ButtonEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0"
VerticalAlignment="Center"
Command="{Binding Path=DataContext.RunCommand, IsAsync=True, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding}"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Window>
Вещи, которые я пробовал: Я пытался await Task.Run
на своей реальной проблеме, и это все еще происходит.
RelayCommand и Asyn c версия (Я думал, что это были стандартные шаблоны, но здесь вы go):
public class RelayAsyncCommand<T> : RelayCommand<T>
{
private bool isExecuting = false;
public event EventHandler Started;
public event EventHandler Ended;
public bool IsExecuting
{
get { return this.isExecuting; }
}
public RelayAsyncCommand(Action<T> execute, Predicate<T> canExecute)
: base(execute, canExecute)
{
}
public RelayAsyncCommand(Action<T> execute)
: base(execute)
{
}
public override Boolean CanExecute(Object parameter)
{
return ((base.CanExecute(parameter)) && (!this.isExecuting));
}
public override void Execute(object parameter)
{
try
{
this.isExecuting = true;
if (this.Started != null)
{
this.Started(this, EventArgs.Empty);
}
Task task = Task.Factory.StartNew(() =>
{
this._execute((T)parameter);
});
task.ContinueWith(t =>
{
this.OnRunWorkerCompleted(EventArgs.Empty);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception ex)
{
this.OnRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true));
}
}
private void OnRunWorkerCompleted(EventArgs e)
{
this.isExecuting = false;
if (this.Ended != null)
{
this.Ended(this, e);
}
}
}
public class RelayCommand<T> : ICommand
{
#region Fields
readonly protected Action<T> _execute;
readonly protected Predicate<T> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public virtual bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public virtual void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion // ICommand Members
}