如何在后面的代码中生成一个复选框并将其作为子项发送到 MainWindow 上的边框?
How do I generate a checkbox in the code behind and send it as a child to the border on the MainWindow?
这张图是我关于STA线程的错误。
- 项目正在等待传入数据。其实我不想只是等待申请。我尝试使用调度程序或任务,但出现此错误。
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
ToggleStart();
var stackPanel = await Task.Run(TaskCheckBoxZones).ConfigureAwait(false);
ZonesBorder.Child = stackPanel;
//await Dispatcher.BeginInvoke((Action)async delegate
//{
// var findCheckBoxes = new FindCheckBoxes
// {
// CheckBoxItemTab = CheckBoxPanel,
// CheckBoxItemTab2 = CheckBoxPanel2,
// Token = token,
// };
// await findCheckBoxes.LoadPaymentsMethods();
//});
}
async Task<StackPanel> TaskCheckBoxZones()
{
var stackPanel = await new CreateCheckBoxZones()
{
Token = token
}.LoadZonesData();
return stackPanel;
}
上面的代码块在Settings.xaml.cs
public class CreateCheckBoxZones
{
private List<OrderZones> OrderZonesList;
public string Token = "";
public CreateCheckBoxZones()
{
OrderZonesList = new List<OrderZones>();
_innerStack = new StackPanel();
}
//public Border Border;
private StackPanel _innerStack;
public StackPanel InnerStack
{
get { return _innerStack; }
set { _innerStack = value; }
}
public async Task<StackPanel> LoadZonesData()
{
OrderZonesList = await MapOrderZones();
foreach (var zone in OrderZonesList)
{
CheckBox cb = new CheckBox();
cb.Tag = zone.Id;
cb.Content = zone.Name;
cb.IsChecked = zone.Status == 100;
cb.Click += ZonesCheckBox_Click;
_innerStack.Children.Add(cb);
}
return _innerStack;
}
private async Task<List<OrderZones>> MapOrderZones()
{
var zoneModels = await TakeZones();
if (zoneModels[0]._id == null)
throw new Exception("Zones is can not null.");
foreach (var zone in zoneModels)
{
OrderZonesList.Add(new OrderZones(
zone._id,
zone.placement,
zone.name.tr,
zone.minBasketSize,
zone.status,
zone.restaurant));
}
return OrderZonesList;
}
public async Task<List<ZoneModel>> TakeZones()
{
try
{
string url = Constants.GET_ZONES;
var response = await new WebClient<List<ZoneModel>>()
{
Endpoint = url,
Token = Token,
Method = Method.GET
}.SendToAPIAsync();
if (response.ErrorException != null)
{
MessageBox.Show("Bölgeleri alırken, bir hata aldın.", "Bölgeler", MessageBoxButton.OK, MessageBoxImage.Error);
}
return response.Data;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return null;
}
上面的代码块是关于我的 LoadCheckbox class
class 获取一些 API 端点信息,但我获取了数据。在 UI 上选中 Define 复选框时,我的应用程序冻结。我正在寻找一些解决办法。
您应该使用 ListBox
(而不是 StackPanel
)和 DataTemplate
来生成项目(包括 CheckBox
)。
- 切勿将
async
方法包装到 Task.Run
中!因为 TaskCheckBoxZones
是一个 async
方法,所以 Task.Run(TaskCheckBoxZones)
是一个 no-no。 TaskCheckBoxZones
或异步方法通常应使用 'Async' 后缀命名,例如TaskCheckBoxZonesAsync
。这将帮助您识别异步方法以正确处理它们。
- 您使用的
ConfigureAwait(false)
有误。 ConfigureAwait(false)
旨在通过允许继续在线程池线程上执行来避免上下文切换回捕获的 SynchronizationContext
。例如,当 Task
捕获主线程的当前 SynchronizationContext
时,然后使用 ConfigureAwait(false)
配置它可能会强制继续执行,即 await
之后的代码执行在非 UI 线程上。
在 UI 上下文中使用 ConfigureAwait(false)
很可能会抛出 InvalidOperationException
以指示非法 cross-thread 操作:
private void OnLoaded(object sender, EventArgs e)
{
/** UI thread context **/
// Task captures the current SynchronizationContext which is the UI thread.
var stackPanel = await Task.Run(TaskCheckBoxZones) // Never wrap an async method into a Task.Run
.ConfigureAwait(false); // ConfigureAwait(false) will force continuation on a non UI thread.
/** Worker thread context (non UI) **/
ZonesBorder.Child = stackPanel; // Will throw InvalidOperationException (cross-thread)
要解决此问题,您通常不应在 UI 上下文中使用 ConfigureAwait(false)
。它适用于非 UI 库代码。在 UI 级别或应用程序级别上下文中,您很可能希望在 async
操作完成后 return 到 UI 线程:
private void OnLoaded(object sender, EventArgs e)
{
/** UI thread context **/
// Task captures the current SynchronizationContext which is the UI thread.
// Since ConfigureAwait(true) is the default behavior, the Task will return to the UI thread.
var stackPanel = await TaskCheckBoxZonesAsync();
/** UI thread context **/
ZonesBorder.Child = stackPanel; // Access allowed
- 不要使用
Dispatcher.BeginInvoke
。它是一种古老的方法,属于前 async/await API 时代的一部分。从 .NET Framework 4.5 开始,我们使用 Dispatcher.InvokeAsync
.
- 不要将在 UI 线程上执行的代码排队到
Dispatcher
。此外,在这种情况下等待 Dispatcher
绝对是多余的。规则 1) 也适用于 Dispatcher
:避免将 async
方法包装到 Dispatcher
调用中:
你的版本不好
await Dispatcher.BeginInvoke((Action)async delegate
{
var findCheckBoxes = new FindCheckBoxes
{
CheckBoxItemTab = CheckBoxPanel,
CheckBoxItemTab2 = CheckBoxPanel2,
Token = token,
};
await findCheckBoxes.LoadPaymentsMethods();
});
正确版本
var findCheckBoxes = new FindCheckBoxes
{
CheckBoxItemTab = CheckBoxPanel,
CheckBoxItemTab2 = CheckBoxPanel2,
Token = token,
};
await findCheckBoxes.LoadPaymentsMethodsAsync();
- 不要仅仅为了在
Dispatcher
上创建控件而创建后台线程。这绝对是毫无意义且昂贵的。如果控件的实例化成本很高,请考虑使用 async
初始化例程。通常在后台线程上调用构造函数是一种严重的代码异味,也是编写不当的指标 class。构造函数必须 return 立即 。构造函数永远不应该是 long-running:
你的版本不好
private asnyc void OnButton_Click(object sender, EventArgs e)
{
await Task.Run(() => Dispatcher.InvokeAsync(new MyDialogWindow().ShowDialog));
}
正确版本
private asnyc void OnButton_Click(object sender, EventArgs e)
{
var dialog = new MyDialogWindow();
await dialog.InitializeAsync(); // Perform longrunning and CPU intensive or IO initialization in a non blocking manner
dialog.ShowDialog();
}
- 没有理由让
private
方法 return 成为成员(例如 属性)。就让它 return void
.
- 从不捕获基数 class
Exception
并且从不向用户显示异常消息。如果您可以从特定异常中恢复,那么只捕获这个异常。让应用程序因任何其他异常而崩溃。
解决方案
MainWindow.xaml.cs
partial class MainWIndow : Window
{
// Use Clear, Add and Remove to modify this collection during runtime.
// If you need to replace the complete collection (not recommended), then make this property into a dependency property.
public ObservableCollection<OrderZone> OrderZones { get; }
public MainWindow()
{
InitializeComponent();
this.ZoneModels = new ObservableCollection<OrderZone>();
this.Loaded += OnLoaded;
}
private async void OnLoaded(object sender, EventArgs e)
{
var zoneCreator = new OrderZoneCreator();
IAsyncEnumerable<OrderZone> orderZones = zoneCreator.EnumerateOrderZonesAsync();
await foreach (OrderZone orderZone in orderZones)
{
this.OrderZones.Add(orderZone);
}
}
}
ZoneModelCreator.cs
public class OrderZoneCreator
{
public async IAsyncEnumerable<OrderZone> EnumerateOrderZonesAsync()
{
List<ZoneModel> zoneModels = await GetZonesAsync();
foreach (var zone in zoneModels)
{
var newOrderZone = new OrderZone(
zone._id,
zone.placement,
zone.name.tr,
zone.minBasketSize,
zone.status,
zone.restaurant);
yield return newOrderZone;
}
}
private async Task<List<ZoneModel>> GetZonesAsync()
{
string url = Constants.GET_ZONES;
var webClient = new WebClient<List<ZoneModel>>()
{
Endpoint = url,
Token = Token,
Method = Method.GET
};
var response = await webClient.SendToAPIAsync();
if (response.ErrorException != null)
{
// This is an internal operation and shouldn't generate user messages
// as the user can't do anything to make this operation succeed.
// Throw an exception if this error is not acceptable
// or do nothing and return an empty collection.
return new List<ZoneModel>();
}
return response.Data;
}
}
StatusToBooleanConverter.cs
class StatusToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is OrderZone orderZone
? orderZone.Status == 100
: Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
MainWindow.xaml
<Window>
<Window.Resources>
<local:StatusToBooleanConverter x:Key="StatusToBooleanConverter" />
</Window.Resources>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=OrderZones}"
BorderBrush="Black"
BorderThickness=2>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:OrderZone}">
<CheckBox Content="{Binding Name, Mode OneTime}"
Tag="{Binding Id, Mode OneTime}"
Click="ZonesCheckBox_Click"
IsChecked="{Binding Status, Converter={StaticResource StatusToBooleanConverter}, Mode OneTime}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
- 项目正在等待传入数据。其实我不想只是等待申请。我尝试使用调度程序或任务,但出现此错误。
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
ToggleStart();
var stackPanel = await Task.Run(TaskCheckBoxZones).ConfigureAwait(false);
ZonesBorder.Child = stackPanel;
//await Dispatcher.BeginInvoke((Action)async delegate
//{
// var findCheckBoxes = new FindCheckBoxes
// {
// CheckBoxItemTab = CheckBoxPanel,
// CheckBoxItemTab2 = CheckBoxPanel2,
// Token = token,
// };
// await findCheckBoxes.LoadPaymentsMethods();
//});
}
async Task<StackPanel> TaskCheckBoxZones()
{
var stackPanel = await new CreateCheckBoxZones()
{
Token = token
}.LoadZonesData();
return stackPanel;
}
上面的代码块在Settings.xaml.cs
public class CreateCheckBoxZones
{
private List<OrderZones> OrderZonesList;
public string Token = "";
public CreateCheckBoxZones()
{
OrderZonesList = new List<OrderZones>();
_innerStack = new StackPanel();
}
//public Border Border;
private StackPanel _innerStack;
public StackPanel InnerStack
{
get { return _innerStack; }
set { _innerStack = value; }
}
public async Task<StackPanel> LoadZonesData()
{
OrderZonesList = await MapOrderZones();
foreach (var zone in OrderZonesList)
{
CheckBox cb = new CheckBox();
cb.Tag = zone.Id;
cb.Content = zone.Name;
cb.IsChecked = zone.Status == 100;
cb.Click += ZonesCheckBox_Click;
_innerStack.Children.Add(cb);
}
return _innerStack;
}
private async Task<List<OrderZones>> MapOrderZones()
{
var zoneModels = await TakeZones();
if (zoneModels[0]._id == null)
throw new Exception("Zones is can not null.");
foreach (var zone in zoneModels)
{
OrderZonesList.Add(new OrderZones(
zone._id,
zone.placement,
zone.name.tr,
zone.minBasketSize,
zone.status,
zone.restaurant));
}
return OrderZonesList;
}
public async Task<List<ZoneModel>> TakeZones()
{
try
{
string url = Constants.GET_ZONES;
var response = await new WebClient<List<ZoneModel>>()
{
Endpoint = url,
Token = Token,
Method = Method.GET
}.SendToAPIAsync();
if (response.ErrorException != null)
{
MessageBox.Show("Bölgeleri alırken, bir hata aldın.", "Bölgeler", MessageBoxButton.OK, MessageBoxImage.Error);
}
return response.Data;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return null;
}
上面的代码块是关于我的 LoadCheckbox class class 获取一些 API 端点信息,但我获取了数据。在 UI 上选中 Define 复选框时,我的应用程序冻结。我正在寻找一些解决办法。
您应该使用 ListBox
(而不是 StackPanel
)和 DataTemplate
来生成项目(包括 CheckBox
)。
- 切勿将
async
方法包装到Task.Run
中!因为TaskCheckBoxZones
是一个async
方法,所以Task.Run(TaskCheckBoxZones)
是一个 no-no。TaskCheckBoxZones
或异步方法通常应使用 'Async' 后缀命名,例如TaskCheckBoxZonesAsync
。这将帮助您识别异步方法以正确处理它们。 - 您使用的
ConfigureAwait(false)
有误。ConfigureAwait(false)
旨在通过允许继续在线程池线程上执行来避免上下文切换回捕获的SynchronizationContext
。例如,当Task
捕获主线程的当前SynchronizationContext
时,然后使用ConfigureAwait(false)
配置它可能会强制继续执行,即await
之后的代码执行在非 UI 线程上。
在 UI 上下文中使用ConfigureAwait(false)
很可能会抛出InvalidOperationException
以指示非法 cross-thread 操作:
private void OnLoaded(object sender, EventArgs e)
{
/** UI thread context **/
// Task captures the current SynchronizationContext which is the UI thread.
var stackPanel = await Task.Run(TaskCheckBoxZones) // Never wrap an async method into a Task.Run
.ConfigureAwait(false); // ConfigureAwait(false) will force continuation on a non UI thread.
/** Worker thread context (non UI) **/
ZonesBorder.Child = stackPanel; // Will throw InvalidOperationException (cross-thread)
要解决此问题,您通常不应在 UI 上下文中使用 ConfigureAwait(false)
。它适用于非 UI 库代码。在 UI 级别或应用程序级别上下文中,您很可能希望在 async
操作完成后 return 到 UI 线程:
private void OnLoaded(object sender, EventArgs e)
{
/** UI thread context **/
// Task captures the current SynchronizationContext which is the UI thread.
// Since ConfigureAwait(true) is the default behavior, the Task will return to the UI thread.
var stackPanel = await TaskCheckBoxZonesAsync();
/** UI thread context **/
ZonesBorder.Child = stackPanel; // Access allowed
- 不要使用
Dispatcher.BeginInvoke
。它是一种古老的方法,属于前 async/await API 时代的一部分。从 .NET Framework 4.5 开始,我们使用Dispatcher.InvokeAsync
. - 不要将在 UI 线程上执行的代码排队到
Dispatcher
。此外,在这种情况下等待Dispatcher
绝对是多余的。规则 1) 也适用于Dispatcher
:避免将async
方法包装到Dispatcher
调用中:
你的版本不好
await Dispatcher.BeginInvoke((Action)async delegate
{
var findCheckBoxes = new FindCheckBoxes
{
CheckBoxItemTab = CheckBoxPanel,
CheckBoxItemTab2 = CheckBoxPanel2,
Token = token,
};
await findCheckBoxes.LoadPaymentsMethods();
});
正确版本
var findCheckBoxes = new FindCheckBoxes
{
CheckBoxItemTab = CheckBoxPanel,
CheckBoxItemTab2 = CheckBoxPanel2,
Token = token,
};
await findCheckBoxes.LoadPaymentsMethodsAsync();
- 不要仅仅为了在
Dispatcher
上创建控件而创建后台线程。这绝对是毫无意义且昂贵的。如果控件的实例化成本很高,请考虑使用async
初始化例程。通常在后台线程上调用构造函数是一种严重的代码异味,也是编写不当的指标 class。构造函数必须 return 立即 。构造函数永远不应该是 long-running:
你的版本不好
private asnyc void OnButton_Click(object sender, EventArgs e)
{
await Task.Run(() => Dispatcher.InvokeAsync(new MyDialogWindow().ShowDialog));
}
正确版本
private asnyc void OnButton_Click(object sender, EventArgs e)
{
var dialog = new MyDialogWindow();
await dialog.InitializeAsync(); // Perform longrunning and CPU intensive or IO initialization in a non blocking manner
dialog.ShowDialog();
}
- 没有理由让
private
方法 return 成为成员(例如 属性)。就让它 returnvoid
. - 从不捕获基数 class
Exception
并且从不向用户显示异常消息。如果您可以从特定异常中恢复,那么只捕获这个异常。让应用程序因任何其他异常而崩溃。
解决方案
MainWindow.xaml.cs
partial class MainWIndow : Window
{
// Use Clear, Add and Remove to modify this collection during runtime.
// If you need to replace the complete collection (not recommended), then make this property into a dependency property.
public ObservableCollection<OrderZone> OrderZones { get; }
public MainWindow()
{
InitializeComponent();
this.ZoneModels = new ObservableCollection<OrderZone>();
this.Loaded += OnLoaded;
}
private async void OnLoaded(object sender, EventArgs e)
{
var zoneCreator = new OrderZoneCreator();
IAsyncEnumerable<OrderZone> orderZones = zoneCreator.EnumerateOrderZonesAsync();
await foreach (OrderZone orderZone in orderZones)
{
this.OrderZones.Add(orderZone);
}
}
}
ZoneModelCreator.cs
public class OrderZoneCreator
{
public async IAsyncEnumerable<OrderZone> EnumerateOrderZonesAsync()
{
List<ZoneModel> zoneModels = await GetZonesAsync();
foreach (var zone in zoneModels)
{
var newOrderZone = new OrderZone(
zone._id,
zone.placement,
zone.name.tr,
zone.minBasketSize,
zone.status,
zone.restaurant);
yield return newOrderZone;
}
}
private async Task<List<ZoneModel>> GetZonesAsync()
{
string url = Constants.GET_ZONES;
var webClient = new WebClient<List<ZoneModel>>()
{
Endpoint = url,
Token = Token,
Method = Method.GET
};
var response = await webClient.SendToAPIAsync();
if (response.ErrorException != null)
{
// This is an internal operation and shouldn't generate user messages
// as the user can't do anything to make this operation succeed.
// Throw an exception if this error is not acceptable
// or do nothing and return an empty collection.
return new List<ZoneModel>();
}
return response.Data;
}
}
StatusToBooleanConverter.cs
class StatusToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is OrderZone orderZone
? orderZone.Status == 100
: Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
MainWindow.xaml
<Window>
<Window.Resources>
<local:StatusToBooleanConverter x:Key="StatusToBooleanConverter" />
</Window.Resources>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=OrderZones}"
BorderBrush="Black"
BorderThickness=2>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:OrderZone}">
<CheckBox Content="{Binding Name, Mode OneTime}"
Tag="{Binding Id, Mode OneTime}"
Click="ZonesCheckBox_Click"
IsChecked="{Binding Status, Converter={StaticResource StatusToBooleanConverter}, Mode OneTime}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>