【问题标题】:How to read textbox.Text value from another thread in WPF?如何从 WPF 中的另一个线程读取 textbox.Text 值?
【发布时间】:2009-04-02 14:53:41
【问题描述】:

在我的 WPF 表单中,我有一个文本框。
当计时器经过时,需要获取文本框的内容。
经过的计时器在与 UI 不同的线程中工作。

这个问题有两个方面:

  • 从 GUI 线程跨线程读取值的最简单、最易读的方法是什么(我发现了几个,它们看起来太冗长,不适合真正基本的东西)?
  • 我不能以非阻塞方式阅读文本吗?在这种情况下,我不关心线程安全。

--编辑--
我使用了 Dispatcher,但调用比 John 更冗长:

originalTextBox.Dispatcher.Invoke(
    DispatcherPriority.Normal, 
    (ThreadStart) delegate{text=originalTextBox.Text;}
);

不过不介意更简洁。访问文本属性应该是非常基本的。

【问题讨论】:

    标签: .net wpf multithreading


    【解决方案1】:

    Oisin 是对的,你需要看看Dispatcher。像这样的东西应该可以工作,而且不会太冗长:

    System.Windows.Application.Current.Dispatcher.Invoke(
    DispatcherPriority.Normal,
    (ThreadStart)delegate { text = MyTextBox.Text; });
    

    【讨论】:

    • 好吧,这比我拥有的更好 originalTextBox.Dispatcher.Invoke(DispatcherPriority.Normal, new Action( delegate() { text = originalTextBox.Text; }));
    【解决方案2】:

    您可以:

    • 使用Dispatcher 安排消息从后台线程在UI 线程上执行。 DispatcherPrioritySend 将为您提供最快的响应。
    • 使用DispatcherTimer 在 UI 线程上定期执行消息。
    • 使用OneWayToSource 绑定将Text 属性连接到后台组件上的属性。这样,您就不必做任何工作来获取属性值 - 它已经提供给您的组件。

    【讨论】:

      【解决方案3】:

      我使用以下扩展方法来解决这个问题:

          public static string GetTextThreadSafely(this TextBoxBase source)
          {
              if (source.InvokeRequired)
              {
                  var text = String.Empty;
                  source.Invoke((Action)(() => { text = source.GetTextThreadSafely(); }));
                  return text;
              }
              else
              {
                  return source.Text;
              }
          }
      

      当然,这个方法必须添加到单独的静态类中。

      【讨论】:

      • 太棒了!真的很棒的解决方案!我已经在我的 WinForm 项目中应用了它(虽然我把它变成了通用的)!效果很好!
      【解决方案4】:

      碰巧偶然发现了这里。不久前,我刚刚开始构建一个静态类,我可以将其添加到我的项目中,以便快速访问一些常见的控件属性。它随着时间的推移变得臃肿,但在隐藏大量调度程序代码的同时使事情变得非常容易。粗鲁但有效。可能会给你一些想法。 我基本上可以做这样的事情:

      string temp = SafeGuiWpf.GetText(originalTextBox);
      

      这是 SafeGuiWpf 最后的样子,如果您觉得它有帮助的话。 (认为​​它适用于 NET 3 及更高版本,但已经有一段时间了)

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Windows;
      using System.Windows.Controls;
      using System.Windows.Controls.Primitives;
      using System.ComponentModel;
      
      public class SafeGuiWpf
      {
          public static object GetTag(Control C)
          {
              if (C.Dispatcher.CheckAccess()) return C.Tag;
              else return C.Dispatcher.Invoke(new Func<Control, object>(GetTag), C);
          }
          public static string GetText(TextBox TB)
          {
              if (TB.Dispatcher.CheckAccess()) return TB.Text;
              else return (string)TB.Dispatcher.Invoke(new Func<TextBox,string>(GetText), TB);
          }
          public static string GetText(ComboBox TB)
          {
              if (TB.Dispatcher.CheckAccess()) return TB.Text;
              else return (string)TB.Dispatcher.Invoke(new Func<ComboBox,string>(GetText), TB);
          }
      
          public static string GetText(PasswordBox TB)
          {
              if (TB.Dispatcher.CheckAccess()) return TB.Password;
              else return (string)TB.Dispatcher.Invoke(new Func<PasswordBox, string>(GetText), TB);
          }
      
          public static void SetText(TextBlock TB, string Str)
          {
              if (TB.Dispatcher.CheckAccess()) TB.Text = Str;
              else TB.Dispatcher.Invoke(new Action<TextBlock,string>(SetText), TB, Str);
          }
          public static void SetText(TextBox TB, string Str)
          {
              if (TB.Dispatcher.CheckAccess()) TB.Text = Str;
              else TB.Dispatcher.Invoke(new Action<TextBox, string>(SetText), TB, Str);
          }
          public static void AppendText(TextBox TB, string Str)
          {
              if (TB.Dispatcher.CheckAccess())
              {
                  TB.AppendText(Str);
                  TB.ScrollToEnd(); // scroll to end?
              }
              else TB.Dispatcher.Invoke(new Action<TextBox, string>(AppendText), TB, Str);
          }
          public static bool? GetChecked(CheckBox Ck)
          {
              if (Ck.Dispatcher.CheckAccess()) return Ck.IsChecked;
              else return (bool?)Ck.Dispatcher.Invoke(new Func<CheckBox,bool?>(GetChecked), Ck);
          }
          public static void SetChecked(CheckBox Ck, bool? V)
          {
              if (Ck.Dispatcher.CheckAccess()) Ck.IsChecked = V;
              else Ck.Dispatcher.Invoke(new Action<CheckBox, bool?>(SetChecked), Ck, V);
          }
          public static bool GetChecked(MenuItem Ck)
          {
              if (Ck.Dispatcher.CheckAccess()) return Ck.IsChecked;
              else return (bool)Ck.Dispatcher.Invoke(new Func<MenuItem, bool>(GetChecked), Ck);
          }
          public static void SetChecked(MenuItem Ck, bool V)
          {
              if (Ck.Dispatcher.CheckAccess()) Ck.IsChecked = V;
              else Ck.Dispatcher.Invoke(new Action<MenuItem, bool>(SetChecked), Ck, V);
          }
          public static bool? GetChecked(RadioButton Ck)
          {
              if (Ck.Dispatcher.CheckAccess()) return Ck.IsChecked;
              else return (bool?)Ck.Dispatcher.Invoke(new Func<RadioButton, bool?>(GetChecked), Ck);
          }
          public static void SetChecked(RadioButton Ck, bool? V)
          {
              if (Ck.Dispatcher.CheckAccess()) Ck.IsChecked = V;
              else Ck.Dispatcher.Invoke(new Action<RadioButton, bool?>(SetChecked), Ck, V);
          }
      
          public static void SetVisible(UIElement Emt, Visibility V)
          {
              if (Emt.Dispatcher.CheckAccess()) Emt.Visibility = V;
              else Emt.Dispatcher.Invoke(new Action<UIElement, Visibility>(SetVisible), Emt, V);
          }
          public static Visibility GetVisible(UIElement Emt)
          {
              if (Emt.Dispatcher.CheckAccess()) return Emt.Visibility;
              else return (Visibility)Emt.Dispatcher.Invoke(new Func<UIElement, Visibility>(GetVisible), Emt);
          }
          public static bool GetEnabled(UIElement Emt)
          {
              if (Emt.Dispatcher.CheckAccess()) return Emt.IsEnabled;
              else return (bool)Emt.Dispatcher.Invoke(new Func<UIElement, bool>(GetEnabled), Emt);
          }
          public static void SetEnabled(UIElement Emt, bool V)
          {
              if (Emt.Dispatcher.CheckAccess()) Emt.IsEnabled = V;
              else Emt.Dispatcher.Invoke(new Action<UIElement, bool>(SetEnabled), Emt, V);
          }
      
          public static void SetSelectedItem(Selector Ic, object Selected)
          {
              if (Ic.Dispatcher.CheckAccess()) Ic.SelectedItem = Selected;
              else Ic.Dispatcher.Invoke(new Action<Selector, object>(SetSelectedItem), Ic, Selected);
          }
          public static object GetSelectedItem(Selector Ic)
          {
              if (Ic.Dispatcher.CheckAccess()) return Ic.SelectedItem;
              else return Ic.Dispatcher.Invoke(new Func<Selector, object>(GetSelectedItem), Ic);
          }
          public static int GetSelectedIndex(Selector Ic)
          {
              if (Ic.Dispatcher.CheckAccess()) return Ic.SelectedIndex;
              else return (int)Ic.Dispatcher.Invoke(new Func<Selector, int>(GetSelectedIndex), Ic);
          }
      
          delegate MessageBoxResult MsgBoxDelegate(Window owner, string text, string caption, MessageBoxButton button, MessageBoxImage icon);
          public static MessageBoxResult MsgBox(Window owner, string text, string caption, MessageBoxButton button, MessageBoxImage icon)
          {
              if (owner.Dispatcher.CheckAccess()) return MessageBox.Show(owner, text, caption, button, icon);
              else return (MessageBoxResult)owner.Dispatcher.Invoke(new MsgBoxDelegate(MsgBox), owner, text, caption, button, icon);
          }
      
          public static double GetRangeValue(RangeBase RngBse)
          {
              if (RngBse.Dispatcher.CheckAccess()) return RngBse.Value;
              else return (double)RngBse.Dispatcher.Invoke(new Func<RangeBase, double>(GetRangeValue), RngBse);
          }
          public static void SetRangeValue(RangeBase RngBse, double V)
          {
              if (RngBse.Dispatcher.CheckAccess()) RngBse.Value = V;
              else RngBse.Dispatcher.Invoke(new Action<RangeBase, double>(SetRangeValue), RngBse, V);
          }
      
          public static T CreateWindow<T>(Window Owner) where T : Window, new()
          {
              if (Owner.Dispatcher.CheckAccess())
              {
                  var Win = new T(); // Window created on GUI thread
                  Win.Owner = Owner;
                  return Win;
              }
              else return (T)Owner.Dispatcher.Invoke(new Func<Window, T>(CreateWindow<T>), Owner);
          }
      
          public static bool? ShowDialog(Window Dialog)
          {
              if (Dialog.Dispatcher.CheckAccess()) return Dialog.ShowDialog();
              else return (bool?)Dialog.Dispatcher.Invoke(new Func<Window, bool?>(ShowDialog), Dialog);
          }
      
          public static void SetDialogResult(Window Dialog, bool? Result)
          {
              if (Dialog.Dispatcher.CheckAccess()) Dialog.DialogResult = Result;
              else Dialog.Dispatcher.Invoke(new Action<Window, bool?>(SetDialogResult), Dialog, Result);
          }
      
          public static Window GetWindowOwner(Window window)
          {
              if (window.Dispatcher.CheckAccess()) return window.Owner;
              else return (Window)window.Dispatcher.Invoke(new Func<Window, Window>(GetWindowOwner), window);
          }
      
      } // END CLASS: SafeGuiWpf
      

      回想起来,如果我将这些作为类扩展,可能会让它们更加流畅。

      【讨论】:

        【解决方案5】:

        由于问题没有指定 C#,我想展示一个简单的 VB.NET 解决方案:

        text = originalTextBox.Dispatcher.Invoke(Function() As String
            Return originalTextBox.Text
          End Function)
        

        【讨论】:

          【解决方案6】:

          从与创建它的线程不同的线程读取 GUI 对象的值没有“快速技巧”。 WPF 只是不允许你做这一切。 Windows 窗体偶尔会抱怨,但 WPF 更严格。

          您需要了解 Dispatcher。它可能看起来很冗长,但实际上并不难理解。您将委托传递给指向您希望在 GUI 线程上调用的方法的调度程序,然后它就会执行此操作。

          这是一个很好的简单示例:

          http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher

          【讨论】:

          • 那么就没有办法调用线程不安全的值了吗?我只需要得到它,而不是设置它。
          • 我并不是在寻找快速破解,而是在寻找可读的东西。如果你愿意的话,一个短手。
          • 很抱歉打破了你的泡沫,但使用调度程序是简写。 :) 你想编写自己的调度程序/编组器吗?
          【解决方案7】:

          另一个答案是使用 Jeff Wilcox 的 SmartDispatcher 类。

          在构造函数或 Load 事件中的某处执行 SmartDispatcher.Initialize()(设置 UI 调度程序)

          然后在任何需要设置属性或调用方法的地方:

          Action a = delegate { <statements> };
          SmartDispatcher.BeginInvoke(a);
          

          这样做的好处是您不需要知道它是否在 UI 线程上(您可能需要从两者中都知道)。如有必要,SmartDispatcher 负责线程切换。

          上面是异步的,但是如果你需要同步,只需添加另一个方法来调用Invoke而不是BeginInvoke。

          【讨论】:

            【解决方案8】:

            我的解决方案... XAML:

            <Window x:Class="WpfApplication1.Window1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="Window1" Height="300" Width="300">
            <Grid>
                <TextBox Height="23" Margin="28,27,130,0" Name="textBox1" VerticalAlignment="Top" />
                <Button Height="23" HorizontalAlignment="Left" Margin="28,56,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click">Button</Button>
                <TextBox Margin="34,85,12,54" Name="textBox2" />
            </Grid>
            

            还有cs文件:

            public partial class Window1 : Window
            {
                public Window1()
                {
                    InitializeComponent();
                }
            
                private void button1_Click(object sender, RoutedEventArgs e)
                {
                    new System.Threading.Thread(this.Cuenta).Start();
                }
            
            
                private void Cuenta()
                {
                    for (int i = 0; i < 100000; i++)
                        this.SetValues(string.Format("Counting... {0} ", i));
                }
            
                private void SetValues(string str)
                {
                    System.Windows.Application.Current.Dispatcher.Invoke(
                        System.Windows.Threading.DispatcherPriority.Normal,
                        (System.Threading.ThreadStart)delegate { textBox1.Text = str; });
                }
            
            
            
            }
            

            第二个文本框用于线程运行时的类型测试

            【讨论】:

              猜你喜欢
              • 2017-03-24
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-10-28
              • 2016-10-21
              • 1970-01-01
              相关资源
              最近更新 更多