如何在后面的代码中生成一个复选框并将其作为子项发送到 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)。

  1. 切勿将 async 方法包装到 Task.Run 中!因为 TaskCheckBoxZones 是一个 async 方法,所以 Task.Run(TaskCheckBoxZones) 是一个 no-no。 TaskCheckBoxZones 或异步方法通常应使用 'Async' 后缀命名,例如TaskCheckBoxZonesAsync。这将帮助您识别异步方法以正确处理它们。
  2. 您使用的 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
  1. 不要使用 Dispatcher.BeginInvoke。它是一种古老的方法,属于前 async/await API 时代的一部分。从 .NET Framework 4.5 开始,我们使用 Dispatcher.InvokeAsync.
  2. 不要将在 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();
  1. 不要仅仅为了在 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();
}
  1. 没有理由让 private 方法 return 成为成员(例如 属性)。就让它 return void.
  2. 从不捕获基数 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>