在我的 WPF 表单中,我有一个文本框。
当计时器到时,需要获取文本框的内容。
计时器在与 UI 不同的线程中运行。
这个问题有两个方面:
--编辑--
我使用了调度程序,但有一个比 John 更详细的调用:
originalTextBox.Dispatcher.Invoke(
DispatcherPriority.Normal,
(ThreadStart) delegate{text=originalTextBox.Text;}
);
不过不介意更简洁。访问文本属性应该是完全基本的。
Oisin 是对的,你需要看看Dispatcher。 像这样的东西应该可以工作,并且不会太冗长:
System.Windows.Application.Current.Dispatcher.Invoke(
DispatcherPriority.Normal,
(ThreadStart)delegate { text = MyTextBox.Text; });
您可以:
Dispatcher
安排消息从后台线程在 UI 线程上执行。 DispatcherPriority
或 Send
将为您提供最快的响应。DispatcherTimer
在 UI 线程上定期执行消息。OneWayToSource
绑定将 Text
属性连接到背景组件上的属性。这样,您不需要做任何工作来获取属性值 - 它已经被提供给您的组件。我使用以下扩展方法来解决这个问题:
public static string GetTextThreadSafely(this TextBoxBase source)
{
if (!source.InvokeRequired)
{
return source.Text;
}
var text = String.Empty;
source.Invoke((Action)(() => { text = source.GetTextThreadSafely(); }));
return text;
}
上述方法必须添加到单独的静态类中。
碰巧偶然来到这里。不久前,我刚刚开始构建一个静态类,我可以将其添加到我的项目中,以便快速访问一些常见的控件属性。随着时间的推移,它变得臃肿,但在隐藏大量调度程序代码的同时使事情变得非常简单。粗暴但有效。可能会给你一些想法。 我基本上可以做这样的事情:
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
回想起来,如果我将这些作为类扩展,可能会让它们变得更加光滑。
由于问题没有指定 C#,我想展示一个针对 VB.NET 的简单解决方案:
text = originalTextBox.Dispatcher.Invoke(Function() As String
Return originalTextBox.Text
End Function)
没有“快速破解”可以从与创建 GUI 对象的线程不同的线程读取 GUI 对象的值。 WPF 不会允许您完成这一切。 Windows 窗体偶尔会抱怨,但 WPF 更加严格。
您需要了解Dispatcher。它可能看起来很冗长,但实际上并不难理解。您将委托传递给调度程序,该调度程序指向您想要在 GUI 线程上调用的方法,然后它就会执行该操作。
这是一个很好的简单示例:
http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher
另一个答案是使用 Jeff Wilcox 的 SmartDispatcher 类。
在构造函数或 Load 事件中的某个位置执行 SmartDispatcher.Initialize() (以设置 UI 调度程序)
然后在任何需要设置属性或调用方法的地方:
Action a = delegate { <statements> };
SmartDispatcher.BeginInvoke(a);
这样做的好处在于,您不需要知道它是否在 UI 线程上(并且您可能需要从两个线程上执行此操作)。如果需要,SmartDispatcher 会负责线程切换。
上面是异步的,但如果需要同步,只需添加另一个方法来调用Invoke而不是BeginInvoke即可。
我的解决方案... 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; });
}
}
第二个文本框用于线程运行时的类型测试