C# WPF - 在按钮点击时进行长时间操作

4
我有一个按钮,点击后会调用另一个方法。这个方法执行的操作可能需要很长时间......所以我想创建一个标签,在操作开始时显示并告诉用户等待,等操作完成后,该标签将消失。唯一的问题是,因为按钮是一个UI元素(我认为这就是原因),在按钮单击后更改标签的调用只有在按钮单击完成后才会激活......(所以基本上在单击之前标签是不可见的,并且不能在单击期间更改,因此保持不变)。
以下是我的代码:
private void SearchButtonActions()
{
        UI.InvokeA(() => lstFiles.ItemsSource = FDItems);
        bool SearchAndListing = false;
        //UI.InvokeA(() => lblWait.Height = double.NaN);
        //UI.InvokeA(() => lblWait.Visibility = Visibility.Visible);
        //UI.InvokeA(() => lblWait.Content = "Search Started...");
        int index = cbTypes.SelectedIndex;
        string selecteditem = cbSearchOption.SelectedItem.ToString();
        SearchAndListing = FD.Search(index, selecteditem);
        FDItems = new ObservableCollection<Item>(FD.Items);
        //UI.InvokeA(() => lblWait.Height = 0);
        //UI.InvokeA(() => lblWait.Visibility = Visibility.Hidden);
        //UI.InvokeA(() => lblWait.Content = "Search Ended.");
        if (SearchAndListing)
        {
            UI.InvokeA(() => lstFiles.ItemsSource = FDItems);
            UI.InvokeA(() => lblCount.Content = string.Format("Items: {0}", FDItems.Count));
        }
}

我正在谈论更改lblWait的方法......

顺便提一下:UI.InvokeDispatcher.Current.InvokeAsync(Action)的快捷方式。

我已经尝试使用TasksBackGroundWorker以及将UI.Invoke更改为同步的Invoke(而不是异步的Invoke),但都没有成功......

有人能帮忙吗?


1
使用 task.run 和 progressbar。 - Gilad
2个回答

1

我之前遇到过这个问题,但一直没有仔细思考,直到现在。

你的论点是正确的,UI线程当前正在运行Click方法,因此它很忙,不会运行其他任何东西。

即使使用了调度程序,它仍然执行click方法,因此UI线程只有在完成后才会弹出下一个委托来运行。

所以这是行不通的:

   lbl.Visibility = Visibility.Visible;
   Thread.Sleep(3000);

这样是行不通的:

    Dispatcher.Invoke(() => lbl.Visibility = Visibility.Visible);
    Dispatcher.Invoke(() => Thread.Sleep(3000));

可行的方法是从后台线程调度UI操作。

XAML:

    <StackPanel>   
       <Button Click="Button_Click" Content="Click"/>
       <Label x:Name="lbl" Content="Label" Visibility="Hidden" Foreground="Red" FontSize="22" HorizontalAlignment="Center"/>
     </StackPanel>

CS :

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        await Dispatcher.InvokeAsync(() => 
        {
            Debug.WriteLine("Visibility");
            lbl.Visibility = Visibility.Visible;                
        });

        await Task.Run(() =>
        {
            return Dispatcher.InvokeAsync(() => Thread.Sleep(3000));
        });            
    }

提示:您可能想知道为什么我没有使用调度程序将可见性值传播到 UI 线程。这是因为每个 DispatcherObject(我们从 DispatcherObject 派生的 DependencyObject)都会将执行传播到其关联的 Dispatcher。


我尝试按照你告诉我的方式进行修改,但是它仍然没有起作用。我该如何向你展示新代码呢?(这里说太长了...) - David Shnayder
你能在Google文档上与我分享吗? eranotz50@gmail.com 我明天会看的,晚安。 - eran otzap
我会将文档发送到您的电子邮件。 - David Shnayder
如果有其他人想要查看,这里是链接: - David Shnayder
https://docs.google.com/document/d/1gJaT5MnNrSXc1Nl_Pj81B0fIvF_j-YfNaH_5hdmV0OI/pub - David Shnayder
@David47 对不起,我在工作场所无法访问谷歌文档,你能发封邮件给我吗? - eran otzap

0
  1. 一旦点击按钮,立即显示“请等待标签”。
  2. 在异步的Task.Run()中执行长操作:FD.Search(index, selecteditem);
  3. 使用ContinueWith()来隐藏标签,一旦长操作完成。

    private void SearchButtonActions() {
        lblWait.Visibility = Visibility.Visible;
        Task.Run(() => {
            return FD.Search(index, selecteditem);
        }).ContinueWith((Task<bool> task) => {
            Dispatcher.Invoke(() => { 
              FDItems = new ObservableCollection<Item>(FD.Items));
              lblWait.Visibility = Visibility.Hidden;
            });
        });
    }
    
如果你需要在`ContinueWith()`块中得到`FD.Search()`的结果,使用`task.Result`来获取它。

发生了类型为“System.InvalidOperationException”的异常,但在用户代码中未处理。附加信息:调用线程无法访问此对象,因为不同的线程拥有它。 - David Shnayder
那个异常只是意味着你必须使用调度程序来调用抛出异常的方法。UI线程拥有所有WPF控件。任务在不同的线程上运行。 - Zachary Yates
我解决了这个异常,但又遇到了另一个问题... 我认为由于某种原因第二个甚至第一个任务没有被执行,因为标签显示出来后就卡住了...即使在操作情况下它非常快...(它停留在应该完成操作之后)... - David Shnayder
@David47 嗯,好的。我能看一下你的项目吗?我认为这可能是一个简单的问题,只是我没有所有的上下文。 - Zachary Yates
你有Skype吗?我们可以在那里私聊,我可以发送你需要的任何内容...(加我dudushnaider) - David Shnayder
@David47 我会的,我晚上回家后会加你。 - Zachary Yates

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接