【问题标题】:How to use BackgroundWorker to show/hide WPF UI element?如何使用 BackgroundWorker 显示/隐藏 WPF UI 元素?
【发布时间】:2016-09-27 17:29:37
【问题描述】:

我有一个矩形,我想让它可见,然后我想等待 500 毫秒,然后我想让它再次不可见。

最初我尝试了这段代码:

MuzzleFlash.Visibility = Visibility.Visible;
Thread.Sleep(500);
this.UpdateLayout();
this.InvalidateVisual();
MuzzleFlash.Visibility = Visibility.Collapsed;

我尝试了这两条中间线,因为它们据说会强制更新窗口,但所做的只是暂停半秒钟,而没有更改任何类型的矩形。

所以我听说了 BackgroundWorkers 以及我应该如何使用它们。经过一些简短的阅读,我想出了这个。请注意,Shoot 订阅了 Canvas 上的 MouseDown 事件,该事件以前有效:

private void Shoot(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {

        BackgroundWorker UIUpdater = new BackgroundWorker();
        UIUpdater.WorkerSupportsCancellation = true;
        UIUpdater.WorkerReportsProgress = false;

        UIUpdater.DoWork += new DoWorkEventHandler(UI_DoWork);
        UIUpdater.RunWorkerCompleted += new RunWorkerCompletedEventHandler(cancelUIUpdate);

    }

    private void UI_DoWork(object sender, DoWorkEventArgs e)
    {
        MuzzleFlash.Visibility = Visibility.Visible;
        Thread.Sleep(500);
        this.UpdateLayout();
        this.InvalidateVisual();
        MuzzleFlash.Visibility = Visibility.Collapsed;
    }

    private void cancelUIUpdate(object sender, RunWorkerCompletedEventArgs e)
    {
        BackgroundWorker bw = sender as BackgroundWorker;
        bw.CancelAsync();
    }

现在它甚至没有暂停半秒钟,向我表明工人没有做任何事情。我该如何解决这个问题,并使矩形出现/消失?

【问题讨论】:

  • 我会使用计时器。你并没有真正在工作。
  • 它不起作用,因为你在工作线程中做所有事情。主 UI 线程说 “启动一个后台工作程序,然后结束”,而 bg 工作人员说 “设置可见标志为真,等待一段时间,设置可见标志为假,然后结束回到 UI 线程,所以UI 可以更新”。请注意,bg 线程从不将通知发送回主 UI 线程以更新显示。如果您想这样做,我建议您查看 WPF 的 Dispatcher。请参阅 this answer 以获取简短摘要,该摘要使用与您在此处要执行的操作类似的示例。
  • 一个简单的“解决方案”是将您的 MuzzleFlash.Visibility = Visibility.Visible 行移动到您的主 UI 线程,以便从 Shoot(...) 方法中执行。然后代码说“设置可见标志为真,启动一个bg工作人员,然后结束并更新显示”,而bg工作人员说“等待一段时间,设置可见标志为假,然后结束返回UI线程,以便它可以更新”。此外,您可能希望将 "set visible flag false" 位放在 RunWorkerCompleted 中,因为 WPF 通常不会更新未在其上创建的线程上的对象,因此您可能会在尝试时遇到异常从 bg 工作人员更新。
  • @Rachel 谢谢你,我让它以这种方式工作。不过,也许在 XAML 中使用动画可能会更好?我不太了解 SE 礼仪,所以不确定我应该选择哪个答案
  • 您可以接受您认为最适合您的任何一个 :) 就我个人而言,我认为 Himansh's answer 是最接近我的评论并维护您当前代码的东西,但是它缺乏任何解释真的不是我们希望在答案中看到的。我更喜欢LoopedCode's answer,尽管它没有回答您关于后台工作人员使用情况的具体问题。有时,替代建议是比对所提出的确切问题的答案更好的解决方案。 :)

标签: c# wpf multithreading user-interface backgroundworker


【解决方案1】:

在 wpf 中使用后台工作人员管理动画是个坏主意。动画应该从 Storyboard 运行。它可以通过 xaml 或在代码中指定。

使用 xaml 动画矩形的问题非常简单:

<Rectangle x:Name="MuzzleFlash" Height="100" Width="100" Fill="Red">
    <Rectangle.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames BeginTime="0:0:0.5"  Storyboard.TargetProperty="Visibility">
                        <DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}" />
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Rectangle.Triggers>
</Rectangle>

对于更复杂的动画,您可以使用 Blend 进行交互设计。

在 cmets 后更新响应: 如果你想让它出现和消失,你必须从父容器中操作它,因为一旦矩形隐藏,它就不会响应鼠标事件。

您可以将矩形放在网格中并通过名称引用它以更改其在动画中的可见性。

<Grid Background="Gray">
    <Grid.Triggers>
        <EventTrigger RoutedEvent="MouseDown">
            <BeginStoryboard>
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames BeginTime="0:0:0.0"  Storyboard.TargetProperty="Visibility" Storyboard.TargetName="MuzzleFlash">
                        <DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" />
                        <DiscreteObjectKeyFrame KeyTime="0:0:0.5" Value="{x:Static Visibility.Hidden}" />
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Grid.Triggers>
    <Rectangle x:Name="MuzzleFlash" Height="100" Width="100" Fill="Red">

    </Rectangle>
</Grid>

【讨论】:

  • 那我该如何触发呢?加载的字面意思是“加载中”还是我可以从代码中调用它?
  • 你想怎么触发呢?这里的示例使用 Loaded 作为示例。您可以为任何事件触发它。为什么你认为你需要它从代码中触发它?
  • 好吧,我本质上想要它,所以当我单击矩形时会显示半秒钟,然后再次消失。我将事件设置为 MouseDown,默认情况下将 Visibility 设置为 Hidden(所以 x:Static Visibility.Visible 在情节提要中),但它不起作用(没有响应)。我想我之前得到了一些响应,当我将它设置为 MouseDown 但默认情况下可见,因此隐藏起来。
  • 如果设置为隐藏,则不是 HitTestVisible,因此不会响应鼠标事件。您想通过父容器来操作它。查看更新的示例。
  • 对了。谢谢您的帮助。我已经有了一个解决方案,出于质量原因,我认为没有任何改变它的意义(我喜欢 c# 课程 3 周) - 但是,对于任何搜索这个主题的人来说,这是最好的答案,所以我会标记为这样。
【解决方案2】:

如果你使用 MVVm 并且你正在绑定到你的视图模型,那么试试这个

将可见性转换器绑定到矩形并仅在值类似于真假时显示它?

然后在后台工作人员中设置时间前后的值

https://www.rhyous.com/2011/02/22/binding-visibility-to-a-bool-value-in-wpf/

【讨论】:

    【解决方案3】:
        private BackgroundWorker bw = new BackgroundWorker();
    
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //Background Worker code///
            bw.WorkerReportsProgress = true;
            bw.DoWork += bw_DoWork;
            bw.ProgressChanged += bw_ProgressChanged;
            bw.RunWorkerCompleted += bw_RunWorkerCompleted;
            bw.RunWorkerAsync();
    
            //Progress Bar Window
            grdProgress.Visibility = Visibility.Visible;
        }
        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            //something to do
        }
    
        private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //Progress Bar Window close
            grdProgress.Visibility = Visibility.Hidden;
        }
    
        private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            prg.Value = e.ProgressPercentage;
        }
    
    
      bw.ReportProgress(value);
    

    【讨论】:

    • 除了提供代码之外,请解释为什么您的答案有效。
    猜你喜欢
    • 2018-12-22
    • 2013-05-04
    • 1970-01-01
    • 2018-01-05
    • 2022-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-11
    相关资源
    最近更新 更多