C# 有没有办法将while语句从循环中分离出来,然后将其带回来,以便WPF可以实时更新UI?

C# 有没有办法将while语句从循环中分离出来,然后将其带回来,以便WPF可以实时更新UI?,c#,wpf,xaml,C#,Wpf,Xaml,我正在编写一个C#脚本,试图实时更新UI,以便计算到用户指定的数字。这就是我到目前为止所做的: namespace WpfApp4 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow()

我正在编写一个C#脚本,试图实时更新UI,以便计算到用户指定的数字。这就是我到目前为止所做的:

namespace WpfApp4
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public string respond
        {
            get
            {
                return response.Text;
            }
            set
            {
                response.Text = value;
            }
        }
        private void confirm_Click(object sender, RoutedEventArgs e)
        {
            int repeat1 = 0;
            int repeat = Int32.Parse(respond);
            while (repeat1 <= repeat)
            {
                this.Dispatcher.Invoke(() =>
                {
                    number1.Text = repeat1.ToString();
                });
                repeat1++;
            }
        }
    }
}
当我输入一个数字时,一切都会按它应该做的做,但由于C#脚本卡在一个循环中,WPF无法更新UI,直到它达到用户输入的数字,这不是我想要的,因为我正试图让它以与此代码段类似的方式实时更新:

static void Bat(int m)
        {
            int repeat = 0;
            do
            {
                Console.WriteLine(repeat);
                repeat++;
            }
            while (repeat <= 1000000);
            if(repeat >= 1000001)
            {
                Console.ReadLine();
            }
        }
静态无效Bat(int m)
{
int repeat=0;
做
{
控制台写入线(重复);
重复++;
}
同时(重复次数=1000001)
{
Console.ReadLine();
}
}
但是,与此代码段不同,它不是在控制台中运行的,我不希望应用程序在一行中写出每个数字,我只希望它用C#脚本计算的最新数字更新文本块。如果有人能帮忙,我们将不胜感激


编辑:简化了代码片段

简短的版本:你完全错了

您的代码有两个主要问题:

  • 您正在UI线程中执行循环。这会阻止UI线程在其工作时执行任何操作。

    奇怪的是,您包含了对
    Dispatcher.Invoke()
    的调用,这表明您已尝试将循环移动到另一个线程,但未能完全理解您发现的内容。在您的代码中,调用
    Invoke()
    没有任何用处,因为调用它时您已经在UI线程中了。

  • 您直接访问UI元素,而不是使用视图模型数据结构将主程序逻辑与该逻辑的UI表示分离
  • 你还有几个小问题。不是致命的,但肯定是非常规的,可能会妨碍您编写良好的WPF程序:

  • 将事件处理程序直接附加到
    按钮。单击
    事件,而不是实现
    ICommand
    。这与缺少使用视图模型有关,因为人们更经常在视图模型中找到
    ICommand
    实现
  • 你不能再拖延了。无论您的实现是什么,这都可能会使UI线程被更新淹没,因为您的计算机可以以比UI更快的速度增加一个简单的计数器数量级,从而向用户显示计数器的值,并且用户自己可能无法读取计数器的值
  • 关于如何解决这些问题,已经有很多很好的例子,在堆栈溢出和其他方面。但是,我没能找到一个我认为可以很好地同时解决这两个问题的问题。因此,我将在这里尝试这样做

    上述所有问题都相对容易解决:

  • 要解决第一个问题,在过去,解决这个问题的唯一方法是在后台/工作线程中运行该操作。如果您使用的是视图模型和数据绑定,那么这种方法实际上在WPF中仍然非常有效,,因为WPF将自动处理绑定到UI元素的属性的跨线程更新。也就是说,当属性值在UI线程以外的其他线程中更改时,它将在UI线程中处理绑定目标更新。

    在现代C#中,处理类似这样的简单场景的更好方法是使用
    async
    方法对循环进行编码,使用某种机制,如
    wait Task.Delay()
    为循环提供一种产生控制并让UI更新的方法。

    在下面的示例中,我已经演示了这两种方法
  • 要解决第二个问题,只需使用预期的视图模型和数据绑定WPF范式。例如,请参阅“MVVM”,这是WPF和其他基于XAML的API中使用的一般概念的广义术语。其核心是简单地创建一个单独的数据结构,该结构保存要呈现给用户的值,并实现
    INotifyPropertyChanged
    接口,以便在这些值发生更改时,通知WPF并更新UI。WPF对绑定最常见的数据类型提供了广泛的支持,对于这种支持还不够的情况,它提供了其他机制,让您能够完全控制绑定。如果操作正确,您几乎不需要从代码隐藏直接访问UI元素的属性。请注意,在下面的示例中,我没有共享MainWindow.xaml.cs文件。那是因为我不需要对那个文件做任何事情。这只是股票模板文件,我没有做任何更改
  • 第三个问题是通过实现
    ICommand
    并在视图模型中为命令提供属性来解决的。当然,这其中的第一步是拥有一个视图模型。更简单的一个可选步骤是使用下面显示的my
    DelegateCommand
    类,以提供一个
    ICommand
    实现,该实现使用视图模型或其他数据结构中的方法来提供实际的实现细节。(我这里的示例只是这个示例所需的最小实现;实际上,我使用的是一个具有更多功能的版本。许多其他人也编写了类似的类;它们最常用的名称是
    DelegateCommand
    RelayCommand
    ,通过在web上搜索这些示例,您可以学到很多东西。)
  • 第四个问题可以通过两种方式解决。我选择了最简单的方法,即延迟实际计数,以便UI和用户有时间跟上。如果你想让计数全速进行,你需要使用一个独立于
    static void Bat(int m)
            {
                int repeat = 0;
                do
                {
                    Console.WriteLine(repeat);
                    repeat++;
                }
                while (repeat <= 1000000);
                if(repeat >= 1000001)
                {
                    Console.ReadLine();
                }
            }
    
    class DelegateCommand : ICommand
    {
        private readonly Action _action;
        private readonly Func<bool> _canExecute;
    
        public DelegateCommand(Action action, Func<bool> canExecute)
        {
            _action = action;
            _canExecute = canExecute;
        }
    
        public event EventHandler CanExecuteChanged;
    
        public bool CanExecute(object parameter)
        {
            return _canExecute();
        }
    
        public void Execute(object parameter)
        {
            _action();
        }
    
        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
    
    class ViewModel : INotifyPropertyChanged
    {
        private int _maxValue;
        private int _currentValue;
        private int? _taskThreadId;
        private bool _taskRunning;
    
        public ICommand StartInForeground { get; }
        public ICommand StartInBackground { get; }
    
        public int MaxValue
        {
            get { return _maxValue; }
            set { _UpdateField(ref _maxValue, value); }
        }
    
        public int CurrentValue
        {
            get { return _currentValue; }
            set { _UpdateField(ref _currentValue, value); }
        }
    
        public int? TaskThreadId
        {
            get { return _taskThreadId; }
            set { _UpdateField(ref _taskThreadId, value); }
        }
    
        public bool TaskRunning
        {
            get { return _taskRunning; }
            set { _UpdateField(ref _taskRunning, value, _OnTaskRunningChanged); }
        }
    
        public int UiThreadId { get; }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        public ViewModel()
        {
            UiThreadId = Thread.CurrentThread.ManagedThreadId;
            StartInForeground = new DelegateCommand(_StartInForeground, () => !TaskRunning);
            StartInBackground = new DelegateCommand(_StartInBackground, () => !TaskRunning);
        }
    
        // NOTE: generally, "async void" methods should be avoided. They are legitimate
        // for event handlers and, as in this case, invoked commands and for illustration
        // purposes.
        private async void _StartInForeground()
        {
            TaskRunning = true;
            await _CountUp();
            TaskRunning = false;
        }
    
        private async void _StartInBackground()
        {
            TaskRunning = true;
            await Task.Run(_CountUp);
            TaskRunning = false;
        }
    
        private async Task _CountUp()
        {
            TaskThreadId = Thread.CurrentThread.ManagedThreadId;
            CurrentValue = 0;
            while (CurrentValue < MaxValue)
            {
                await Task.Delay(100);
                CurrentValue++;
            }
            TaskThreadId = null;
        }
    
        private void _OnTaskRunningChanged(bool obj)
        {
            // NOTE: this method is _not_ automatically marshalled to the UI thread
            // by WPF, because its execution doesn't go through a binding. So it's
            // important that the TaskRunning property is only modified in the UI
            // thread. An alternative implementation would do additional work to
            // capture the current SynchronizatioContext when this ViewModel object
            // is created and use it to post the method calls below.
            ((DelegateCommand)StartInForeground).RaiseCanExecuteChanged();
            ((DelegateCommand)StartInBackground).RaiseCanExecuteChanged();
        }
    
        protected void _UpdateField<T>(ref T field, T newValue,
            Action<T> onChangedCallback = null,
            [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, newValue))
            {
                return;
            }
    
            T oldValue = field;
    
            field = newValue;
            onChangedCallback?.Invoke(oldValue);
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    <Window x:Class="TestSO44333293WpfCountup.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:l="clr-namespace:TestSO44333293WpfCountup"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
      <Window.DataContext>
        <l:ViewModel/>
      </Window.DataContext>
    
      <StackPanel>
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
          </Grid.ColumnDefinitions>
          <TextBlock Text="Enter maximum value: "/>
          <TextBox Text="{Binding MaxValue}" Grid.Column="1"/>
          <Button Content="Start In Foreground" Command="{Binding StartInForeground}" Grid.Column="2"/>
          <Button Content="Start In Background" Command="{Binding StartInBackground}" Grid.Column="3"/>
        </Grid>
        <UniformGrid Columns="3">
          <TextBlock Text="{Binding CurrentValue, StringFormat=Current Value: {0}}"/>
          <TextBlock Text="{Binding UiThreadId, StringFormat=UI Thread Id: {0}}"/>
          <TextBlock Text="{Binding TaskThreadId, StringFormat=Task Thread Id: {0}}"/>
        </UniformGrid>
      </StackPanel>
    </Window>