Как обновить свойство в пользовательском интерфейсе с помощью MVVM в подпрограмме

#c# #wpf

#c# #wpf

Вопрос:

Я хочу создать некоторый счетчик обратного отсчета. Проблема в том, что мое решение отображает только начальное значение 10 и последнее значение 1 через 10 секунд. Конечно, я реализовал интерфейс INotifyPropertyChanged. Есть предложения по этому решению?

 <Button Content="Generuj"  Command="{Binding ButtonStart}"></Button>
<TextBox  Text="{Binding Counter, Mode=OneWay}"></TextBox>

private void ButtonStartClick(object obj)
{
    for (int i = 10; i > 0; i--)
    {
         System.Threading.Thread.Sleep(1000);
         Counter = i;
    }
}
  

Комментарии:

1. Счетчик должен быть свойством, должен реализовывать INotifyPropertyChanged. Нитки. Sleep (1000); запускается из потока GUI -> это блокирует обновления в GUI. Вы заметите 10-секундный спящий режим, а затем счетчик устанавливается на 0.

2. Вы запустили запуск кода с помощью нажатия кнопки. Этот код выполняется в основном (потоке пользовательского интерфейса). Это блокирует пользовательский интерфейс до тех пор, пока код не будет выполнен. Таким образом, пользовательский интерфейс не может обновляться, пока ваш код не будет завершен. Это означает, что пользовательский интерфейс не будет показывать обновление вашего свойства, пока ваш код не завершит выполнение. Чтобы обновить пользовательский интерфейс во время выполнения вашего кода, вам необходимо запустить свой код в BackgroundWorker или в асинхронной задаче.

Ответ №1:

С потоком.Sleep вы замораживаете свой графический интерфейс. Попробуйте использовать таймер для своих целей. Таймер будет выполняться одновременно с вашим потоком GUI и, следовательно, не будет его замораживать. Также вам нужно будет реализовать событие PropertyChanged для вашего счетчика, также убедитесь, что вы установили DataContext

     //create a dependency property you can bind to (put into class)
    public int Counter
    {
        get { return (int)this.GetValue(CounterProperty); }
        set { this.SetValue(CounterProperty, value); }
    }

    public static readonly DependencyProperty CounterProperty =
        DependencyProperty.Register(nameof(Counter), typeof(int), typeof(MainWindow), new PropertyMetadata(default(int)));


    //Create a timer that runs one second and decreases CountDown when elapsed (Put into click event)
    Timer t = new Timer();
    t.Interval = 1000;
    t.Elapsed  = CountDown;
    t.Start();

    //restart countdown when value greater one (put into class)
    private void CountDown(object sender, ElapsedEventArgs e)
    {
        if (counter > 1)
        {
            (sender as Timer).Start();
        }
        Counter--;
    }
  

Ответ №2:

Вы также можете запустить счетчик в отдельном потоке

 Task.Run(() =>
             {
                 for (int i = 10; i > 0; i--)
                 {
                     Counter = i;
                     Thread.Sleep(1000);
                 }
             });
  

Ответ №3:

Вы можете использовать async await для введения незначительной задержки.

Его главное преимущество перед таймером заключается в том, что нет риска оставить делегат подключенным и запущенным.

В этой viewmodel я использую mvvmlight, но подойдет любая реализация ICommand.

  …..
using System.Threading.Tasks;
using GalaSoft.MvvmLight.CommandWpf;
namespace wpf_99
{
public class MainWindowViewModel : BaseViewModel
{
    private int counter =10;

    public int Counter
    {
        get { return counter; }
        set { counter = value; RaisePropertyChanged(); }
    }

    private RelayCommand countDownCommand;
    public RelayCommand CountDownCommand
    {
        get
        {
            return countDownCommand
            ?? (countDownCommand = new RelayCommand(
             async () =>
             {
                 for (int i = 10; i > 0; i--)
                 {
                     await Task.Delay(1000);
                     Counter = i;
                 }
             }
             ));
        }
    }
  

Не так много для представления, оно, конечно, привязывается к счетчику:

 <Grid>
    <StackPanel>
    <TextBlock Text="{Binding Counter}"/>
        <Button Content="Count" Command="{Binding CountDownCommand}"/>
    </StackPanel>
</Grid>