C# 有没有办法将while语句从循环中分离出来,然后将其带回来,以便WPF可以实时更新UI?
我正在编写一个C#脚本,试图实时更新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()
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#脚本计算的最新数字更新文本块。如果有人能帮忙,我们将不胜感激
编辑:简化了代码片段简短的版本:你完全错了 您的代码有两个主要问题:
奇怪的是,您包含了对
Dispatcher.Invoke()
的调用,这表明您已尝试将循环移动到另一个线程,但未能完全理解您发现的内容。在您的代码中,调用Invoke()
没有任何用处,因为调用它时您已经在UI线程中了。按钮。单击事件,而不是实现ICommand
。这与缺少使用视图模型有关,因为人们更经常在视图模型中找到ICommand
实现
在现代C#中,处理类似这样的简单场景的更好方法是使用
async
方法对循环进行编码,使用某种机制,如wait Task.Delay()
为循环提供一种产生控制并让UI更新的方法。在下面的示例中,我已经演示了这两种方法
INotifyPropertyChanged
接口,以便在这些值发生更改时,通知WPF并更新UI。WPF对绑定最常见的数据类型提供了广泛的支持,对于这种支持还不够的情况,它提供了其他机制,让您能够完全控制绑定。如果操作正确,您几乎不需要从代码隐藏直接访问UI元素的属性。请注意,在下面的示例中,我没有共享MainWindow.xaml.cs文件。那是因为我不需要对那个文件做任何事情。这只是股票模板文件,我没有做任何更改ICommand
并在视图模型中为命令提供属性来解决的。当然,这其中的第一步是拥有一个视图模型。更简单的一个可选步骤是使用下面显示的myDelegateCommand
类,以提供一个ICommand
实现,该实现使用视图模型或其他数据结构中的方法来提供实际的实现细节。(我这里的示例只是这个示例所需的最小实现;实际上,我使用的是一个具有更多功能的版本。许多其他人也编写了类似的类;它们最常用的名称是DelegateCommand
或RelayCommand
,通过在web上搜索这些示例,您可以学到很多东西。)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>