WPF:在后台不断更新 UI

Posted

技术标签:

【中文标题】WPF:在后台不断更新 UI【英文标题】:WPF: Constanly Update UI in the background 【发布时间】:2020-04-28 08:29:14 【问题描述】:

我目前正在尝试不断更改背景中按钮的颜色。我目前在与 UI 相同的线程中更新按钮。这严重减慢了 UI 元素的使用速度。我尝试过做后台工作人员,但我无法让它不断地做。它只是在开始时更新它们一次。 这是我现在正在做的伪代码。

   private void UpdateButton(object sender, EventArgs e)
        
            foreach (object obj in ButtonGrid.Children)
            
              if (obj != null && obj.HasColor)
                
                  if (obj.State.ON)
                   obj.Color = obj.color_off;
                  
                  else
                    obj.Color = new RadialGradientBrush(((SolidColorBrush)Brushes.White).Color, ((SolidColorBrush)obj.color_on).Color)
                  
                
            
        

     public MainWindow()
        
          InitializeComponent();

          if (!InitializeSkinning())
          
            ErrorAndExitNoPipe();
          
          m_timer = new DispatcherTimer();
          m_timer.Tick += UpdateButton;
          m_timer.Start();
        

我是如何尝试做后台工作者的。

private void UpdateButton(object sender, EventArgs e)
    
        foreach (object obj in ButtonGrid.Children)
        
          if (obj != null && obj.HasColor)
            
              if (obj.State.ON)
               obj.Color = obj.color_off;
              
              else
                obj.Color = new RadialGradientBrush(((SolidColorBrush)Brushes.White).Color, ((SolidColorBrush)obj.color_on).Color)
              
            
        
    

 public MainWindow()
    
      InitializeComponent();

      if (!InitializeSkinning())
      
        ErrorAndExitNoPipe();
      
     worker = new BackgroundWorker();
      worker.DoWork += (sender, e) => 
        //UpdateButton(sender, e);
      ;
      worker.RunWorkerCompleted += (sender, eventArgs) => 
        UpdateButton(sender, eventArgs);
      ;
      worker.RunWorkerAsync();
    

编辑添加 XAML 代码

它只是一个我用颜色填充并用作按钮的网格。

<!-- Button Grid-->
        <Grid x:Name="ButtonGrid" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="1" Grid.RowSpan="3">
          <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
          </Grid.RowDefinitions>
          <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
          </Grid.ColumnDefinitions>
        </Grid>

我正在解析 XML 文件以了解按钮的图像和每个按钮的功能。我只需要更改它们连续启用或禁用的边界颜色。

private void ParseButton(XmlNode button_node)
    
        if (button_node.Name == "button")
        
            int row_num = 1;
            XmlAttribute row_num_attr = button_node.Attributes["row"];
            if (row_num_attr != null)
            
                try  row_num = Convert.ToInt32(row_num_attr.Value);  finally  
            

            int col_num = 1;
            XmlAttribute col_num_attr = button_node.Attributes["column"];
            if (col_num_attr != null)
            
                try  col_num = Convert.ToInt32(col_num_attr.Value);  finally  
            

            Button Button = new Button();
            Button.HorizontalAlignment = HorizontalAlignment.Center;
            Button.VerticalAlignment = VerticalAlignment.Center;

            Button.PreviewMouseDown += PushButton;
            Button.PreviewMouseUp += ReleaseButton;

            Grid.SetRow(Button, row_num - 1);
            Grid.SetColumn(Button, col_num - 1);

            Button.BorderBrush = Brushes.Gray;
            Button.BorderThickness = new Thickness(0);
            Button.ButtonBackground = Brushes.Transparent;

            ParsePlcInfo(Button, button_node);

            string overlay_string = "";
            DirectoryInfo di = new DirectoryInfo(@"resources\\Buttons\" + button_node.InnerText);
            FileInfo[] files = di.GetFiles("*");
            foreach (FileInfo file in files)
            
                if (System.IO.Path.GetFileNameWithoutExtension(file.Name) == button_node.InnerText)
                
                    if (file.Extension == ".xml")
                    
                        continue;
                    
                    else if (file.Extension == ".xaml")
                    
                        Viewbox vb = new Viewbox();
                        Canvas canvas = XamlReader.Load(new StreamReader(file.FullName).BaseStream) as Canvas;
                        vb.Child = canvas;
                        Canvas.SetZIndex(vb, -1);
                        Button.contentGrid.Children.Add(vb);
                        break;
                    
                    else if (file.Extension == ".svg")
                    
                        overlay_string = @"resources\\Buttons\" + button_node.InnerText + @"\" + button_node.InnerText + ".svg";
                        Image image = new Image()  Source = SvgReader.Load(new StreamReader(overlay_string).BaseStream) ;
                        Canvas.SetZIndex(image, -1);
                        try  Button.contentGrid.Children.Add(image);  finally  
                        break;
                    
                
            

            ButtonGrid.Children.Add(Button);
        
    

【问题讨论】:

如果没有您的 XAML,也很难准确判断发生了什么。我建议在属性上使用数据绑定,而不是直接在控件上设置值。这将允许您从Task 设置属性值(比BackgroundWorker 更易于使用)。 【参考方案1】:

UI 元素必须在 UI 线程上更新,这是没有办法的。使用后台线程最多可以将确定新颜色的计算放入后台,然后仅调用 UI 线程进行实际更新。但是,从那里的代码来看,实际上并没有多少“计算”可言,所以我怀疑这对您有多大帮助。

在不知道ButtonGrid 是什么的情况下很难确定,但似乎使用DataTemplates、Styles 和DataTriggers 可以更好地完成您想要做的事情。您可以为您的视觉元素创建一个Style,其中DataTrigger 将根据其他属性设置颜色。这将更符合 WPF 的设计使用方式。

话虽如此,如果您坚持这样做,您也许可以使用Dispatcher.Invoke(Action, DispatcherPriority) 让事情变得更敏感。使用它,您可以告诉 WPF 框架以较低的DispatcherPriority 运行每个颜色更新,这应该为用户输入提供通行权。这是一个例子:

    private void UpdateButton(object sender, EventArgs e)
    
        foreach (object obj in ButtonGrid.Children)
        
            Application.Current.Dispatcher.Invoke(() =>
            
                if (obj != null && obj.HasColor)
                
                    if (obj.State.ON)
                    
                        obj.Color = obj.color_off;
                    
                    else
                    
                        obj.Color = new RadialGradientBrush(((SolidColorBrush)Brushes.White).Color, ((SolidColorBrush)obj.color_on).Color);
                    
                
            , System.Windows.Threading.DispatcherPriority.Background);
        
    

上面的代码将在不使用后台工作人员的情况下运行。并且应该在循环中的每个obj 之间处理用户输入。

【讨论】:

【参考方案2】:

如果我没记错的话,工人会做它的工作并完成。由于您没有在任何地方循环,它会调用一次“UpdateButton”方法并停止工作。

worker.DoWork += (sender, e) => 
    while (true)
    
        UpdateButton(sender, e);
    
;

与此类似的东西应该可以解决您的问题。也许添加一些 Thread.Sleep() 这样你就不会执行该方法太多次。

【讨论】:

以上是关于WPF:在后台不断更新 UI的主要内容,如果未能解决你的问题,请参考以下文章

WPF 精修篇 非UI进程后台更新UI进程

在后台线程内循环并不断更新 UI 时使用啥?

wpf 动态更新 UI 而不会冻结 UI

从后台进程打开窗口并在 WPF 中从用户那里获取输入

WPF 在 MVVM 模式下实现窗口后台代码与ViewModel交互

多线程任务更新 1 个进度条 - UI C# WPF