如何在C#静态方法中打开WPF窗口并读取TextBox值
基于MVVM的WPF模态输入窗口实现方案
核心思路
通过**模态窗口(ShowDialog)**实现调用等待,结合MVVM分离视图与逻辑,解决命令覆盖、异步提前返回、输入失效等问题。以下是可直接运行的完整实现:
1. 通用RelayCommand实现
public class RelayCommand : ICommand { private readonly Action _execute; private readonly Func<bool> _canExecute; public event EventHandler CanExecuteChanged; public RelayCommand(Action execute, Func<bool> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) { return _canExecute == null || _canExecute(); } public void Execute(object parameter) { _execute(); } public void RaiseCanExecuteChanged() { CanExecuteChanged?.Invoke(this, EventArgs.Empty); } }
2. 输入窗口ViewModel
public class TextInputViewModel : INotifyPropertyChanged { private string _inputText; private bool _isOk; public string InputText { get => _inputText; set { _inputText = value; OnPropertyChanged(); OkCommand.RaiseCanExecuteChanged(); } } public bool IsOk => _isOk; public ICommand OkCommand { get; } public ICommand CancelCommand { get; } public TextInputViewModel(string presetText = null) { InputText = presetText; OkCommand = new RelayCommand(ExecuteOk, () => !string.IsNullOrWhiteSpace(InputText)); CancelCommand = new RelayCommand(ExecuteCancel); } private void ExecuteOk() => _isOk = true; private void ExecuteCancel() => _isOk = false; public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
3. 输入窗口视图(XAML)
<Window x:Class="YourNamespace.TextInputWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="输入文本" Width="300" Height="150" WindowStartupLocation="CenterOwner"> <Grid Margin="10"> <StackPanel> <TextBox Margin="0 0 0 10" Text="{Binding InputText, UpdateSourceTrigger=PropertyChanged}"/> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right"> <Button Content="OK" Margin="0 0 10 0" Command="{Binding OkCommand}" IsDefault="True"/> <Button Content="Cancel" Command="{Binding CancelCommand}" IsCancel="True"/> </StackPanel> </StackPanel> </Grid> </Window>
4. 窗口后台代码(含静态调用方法)
public partial class TextInputWindow : Window { private readonly TextInputViewModel _viewModel; private TextInputWindow(TextInputViewModel viewModel) { InitializeComponent(); _viewModel = viewModel; DataContext = viewModel; } // 静态调用入口,支持UI/后台线程调用,返回输入内容(Cancel返回null) public static string Show(string presetText = null) { return Application.Current.Dispatcher.Invoke(() => { var viewModel = new TextInputViewModel(presetText); var window = new TextInputWindow(viewModel); window.Owner = Application.Current.Windows.OfType<Window>().FirstOrDefault(w => w.IsActive); var dialogResult = window.ShowDialog(); return dialogResult == true ? viewModel.InputText : null; }); } }
5. 使用示例
// UI线程直接调用 var userInput = TextInputWindow.Show("请输入内容"); if (userInput != null) { // 处理OK后的输入 MessageBox.Show($"输入内容:{userInput}"); } // 后台线程调用 Task.Run(() => { var input = TextInputWindow.Show("后台线程预设文本"); // 回到UI线程处理结果 Application.Current.Dispatcher.Invoke(() => { if (input != null) MessageBox.Show($"后台获取输入:{input}"); }); });
问题修复说明
- 命令覆盖:ViewModel中OkCommand和CancelCommand分别实例化独立的RelayCommand,避免复用同一实例导致覆盖。
- BeginInvoke提前返回:改用
Dispatcher.Invoke同步调用模态窗口(ShowDialog),阻塞直到用户关闭窗口后再返回结果。 - TextBox无法输入:确保窗口始终在UI线程创建显示,后台线程调用时通过Dispatcher切换到UI线程,保证输入交互正常。
内容的提问来源于stack exchange,提问作者Kojote
相关产品推荐
相关产品推荐

