提问人:Kojote 提问时间:10/10/2023 最后编辑:Kojote 更新时间:10/11/2023 访问量:84
如何在静态 c# 窗口中打开 WPF 窗口并从文本框中读取
How open WPF Window in static c# Window and read from Textbox
问:
我想通过代码隐藏调用 WPF 窗口,并将其显示在我的应用程序中,无论我在应用程序中的哪个位置。我失败的是,在我单击按钮之前,不允许调用窗口的静态方法在代码中继续。“确定”或“取消”。我目前无法做到这一点。 然后,“确定”按钮应从文本框中读取一个值,然后在方法中处理该值。打开 WPF 窗口时,应在文本框中输入一个值(如果可用)。
我尝试了以下方法:
我的 WPF 窗口。
<Window x:Class="MyNamespace.Views.UC.TextboxAndButtons"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyNamespace.UC"
xmlns:viewModels="clr-namespace:MyNamespace.ViewModels"
mc:Ignorable="d"
Height="105" Width="200"
FontFamily="Arial"
WindowStyle="None"
Closing="WindowClosing"
WindowStartupLocation="CenterOwner"
>
<Window.DataContext>
<viewModels:TextboxAndButtonsViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<TextBlock Text="Please, input some text if you wish"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<TextBox Grid.Row="1"
Height="16"
Margin="5"
Text="{Binding UserText, Mode=TwoWay}"/>
<StackPanel Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Center">
<Button Content="OK"
Width="80"
Height="24"
Margin="10,0"
Command="{Binding OkCommand}" />
<Button Content="Cancel"
Width="80"
Height="24"
Margin="10,0"
Command="{Binding CancelCommand}" />
</StackPanel>
</Grid>
我的文本框和按钮.Xaml.cs:
public partial class TextboxAndButtons : Window
{
private readonly TextboxAndButtonsViewModel textboxAndButtonsViewModel;
public TextboxAndButtons()
{
InitializeComponent();
this.textboxAndButtonsViewModel = new TextboxAndButtonsViewModel();
this.DataContext = this.textboxAndButtonsViewModel;
if (this.textboxAndButtonsViewModel.CloseAction == null)
{
this.textboxAndButtonsViewModel.CloseAction = this.Close;
this.textboxAndButtonsViewModel.DialogResultValue = this.DialogResult;
}
}
public string UserText
{
get
{
return this.textboxAndButtonsViewModel.UserText;
}
set
{
if (!string.IsNullOrWhiteSpace(value))
{
this.textboxAndButtonsViewModel.UserText = value;
}
}
}
private void WindowClosing(object sender, CancelEventArgs e)
{
try
{
this.DialogResult = this.textboxAndButtonsViewModel.DialogResultValue;
}
catch
{
}
}
}
我的 ViewModel 类:
public class TextboxAndButtonsViewModel : ViewModelBase
{
public TextboxAndButtonsViewModel()
{
this.OkCommand = new RelayCommand(this.OkCommandExecuted);
this.OkCommand = new RelayCommand(this.CancelCommandExecuted);
}
public bool? DialogResultValue { get; set; }
private void CancelCommandExecuted(object obj)
{
this.DialogResultValue = false;
this.CloseAction();
}
private void OkCommandExecuted(object obj)
{
this.DialogResultValue = true;
this.CloseAction();
}
private string userText;
public string UserText
{
get => userText;
set
{
//userText = value;
// Prism
SetProperty(ref this.userText, value);
}
}
public Action CloseAction { get; set; }
public ICommand OkCommand { get; private set; }
public ICommand CancelCommand { get; private set; }
}
我来自任何类的静态方法:
public class TextWrapper
{
private static TextboxAndButtons textboxAndButtons;
public static string TextBoxValue { get; set; }
public static string CallTextboxWindow(string userText)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.DataBind, new Action(() =>
{
TextBoxValue = TextBoxWindow(userText);
}));
return TextBoxValue;
}
private static string TextBoxWindow(string userText)
{
string text = "";
try
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.DataBind, new Action(() =>
{
textboxAndButtons = new TextboxAndButtons();
textboxAndButtons.UserText = userText;
textboxAndButtons.Owner = Application.Current.Windows.Cast<Window>().LastOrDefault(x => x.IsActive) ?? Application.Current.MainWindow;
textboxAndButtons.ShowDialog();
if (textboxAndButtons.UserText != userText)
{
text = textboxAndButtons.UserText;
}
}));
// Check text
/// {......}
}
catch (Exception ex)
{
string error = ex.Message;
string inner = ex.InnerException.ToString();
}
return text;
}
}
使用调度程序,我更接近目标,但它还没有完全起作用。
在上面的这段代码中,我有以下行为: (它从外部调用方法“CallTextBoxWindow(string userText)”)。
当我启动我的应用程序并在类 TextWrapper --> method CallTextBoxWindow(string userText) --> at “return TextBoxValue;” 中设置断点时,它会跳到那里,这是不正确的。仅当单击 WPF 窗口中的“确定”按钮时,它才应转到该位置。如果我让代码在断点处继续,则显示窗口,我可以输入文本,单击“确定”按钮,但随后它不再在方法中。如果我现在单击“确定”按钮,窗口将关闭,仅此而已。
如果我只将 Invoke 与调度程序一起使用,那么我就会出现奇怪的行为,即我无法在文本框中输入任何内容。
我的目标是将它构建为 MVVM,但我也会对它工作的变体感到满意。
感谢您的帮助。
答:
我假设,你想要某种,但用于用户输入而不是通知。MessageBox
有很多“如何创建自定义”的例子。对于当前问题,我将使用我自己的想法。MessageBox
因此,首先,您需要创建一个单独的窗口(视图),其中包含用户输入和确定/取消按钮。TextBox
UserTextInputWindow.xaml:
<Window x:Class="YourNamespace.Views.UserTextInputWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:YourNamespace.ViewModels"
mc:Ignorable="d"
Title="UserTextInputWindow"
Name="This"
WindowStyle="None"
WindowStartupLocation="CenterScreen"
Height="200"
Width="400">
<Window.DataContext>
<viewModels:UserTextInputViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<TextBlock Text="Please, input some text if you wish"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<TextBox Grid.Row="1"
Margin="5"
Text="{Binding UserText}"/>
<StackPanel Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Center">
<Button Content="OK"
Width="100"
Height="24"
Margin="10,0"
Command="{Binding OkCommand}"
CommandParameter="{Binding ElementName=This}"/>
<Button Content="Cancel"
Width="100"
Height="24"
Margin="10,0"
Command="{Binding CancelCommand}"
CommandParameter="{Binding ElementName=This}"/>
</StackPanel>
</Grid>
</Window>
那个看起来像下面。Ofc,您可以根据需要自定义它。
从XAML中可以看出,我使用类作为此窗口的ViewModel,其代码隐藏为:UserTextInputViewModel
用户文本输入视图模型.cs:
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
using YourNamespace.Commands;
namespace YourNamespace.ViewModels
{
public class UserTextInputViewModel : INotifyPropertyChanged
{
private string _userText;
// Our binded to TextBox property, which stores user's input
public string UserText
{
get => _userText;
set
{
_userText = value;
NotifyPropertyChanged();
}
}
// A command for OK button
// ParameterizedCommand is custom implementation of ICommand interface
private ICommand _okCommand;
public ICommand OkCommand => _okCommand ??= new ParameterizedCommand(commandParameter => (commandParameter as Window)?.Close() );
// A command for Cancel button
// ParameterizedCommand is custom implementation of ICommand interface
private ICommand _cancelCommand;
public ICommand CancelCommand => _cancelCommand ??= new ParameterizedCommand(commandParameter =>
{
// On Cancel we should "reset" user input
UserText = string.Empty;
(commandParameter as Window)?.Close();
});
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ParameterizedCommand
实现是一些带有 as 参数的简单包装器,因此可以通过属性将 Action 绑定到 XAML 中。Action
object
Command
Button
参数化命令.cs:
using System;
using System.Windows.Input;
namespace YourNamespace.Commands
{
public class ParameterizedCommand : ICommand
{
private readonly Action<object> _action;
public ParameterizedCommand(Action<object> action) => _action = action;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _action(parameter);
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
}
因此,事实上,ViewModel 只是存储用户输入,并通过关闭窗口(并在取消时重置用户输入)来处理 OK/Cancel 按钮的点击。
最后,本身的代码隐藏,我隐藏了默认构造函数并用静态方法替换了它:UserTextInputWindow
Show
用户文本输入窗口.xaml.cs:
using System.Windows;
using YourNamespace.ViewModels;
namespace YourNamespace.Views
{
public partial class UserTextInputWindow
{
// Hiding default constructor by making it private
private UserTextInputWindow() => InitializeComponent();
// Introducing static method, which should display window and return
// user input string.
// Note, that this is blocking call (because of ShowDialog inside).
// Also note, that I add some useless parameter "topmost" just to avoid
// conflicts with standart Window.Show method
public static string Show(bool topmost = false)
{
// Securing ourselves by checking access from non-STA thread
return Application.Current.Dispatcher.CheckAccess()
? ShowWindowAndGetUserInput()
: Application.Current.Dispatcher.Invoke(ShowWindowAndGetUserInput);
}
private static string ShowWindowAndGetUserInput()
{
var userText = string.Empty;
var window = new UserTextInputWindow();
window.Closing += delegate
{
if (window.DataContext is UserTextInputViewModel vm)
userText = vm.UserText;
};
window.ShowDialog();
return userText;
}
}
}
用法与 非常相似。它也可以从任何线程安全地使用。MessageBox
var userInput = UserTextInputWindow.Show();
if (string.IsNullOrEmpty(userInput)) { /* User input nothing or cancel */ }
不确定这是否是纯粹且正确的 MVVM 方法,但它有效。请随时纠正我。
评论
UserTextInputWindow
UserTextInputViewModel
TextWrapper
您可以使用异步等待,直到窗口关闭,以免阻塞 UI 线程:SemaphoreSlim
public async Task OpenWindowAsync()
{
using (SemaphoreSlim semaphoreSlim = new SemaphoreSlim(0, 1))
{
string enteredText = null;
TextboxAndButtons textBoxWindow = new TextboxAndButtons();
textBoxWindow.Owner = System.Windows.Application.Current.Windows.Cast<Window>().LastOrDefault(x => x.IsActive) ?? System.Windows.Application.Current.MainWindow;
textBoxWindow.SetText(setValue);
textBoxWindow.Closed += (sender, e) =>
{
enteredText = textBoxWindow.GetText();
semaphoreSlim.Release();
};
textBoxWindow.Show();
await semaphoreSlim.WaitAsync();
if (!string.IsNullOrEmpty(enteredText))
{
string a = enteredText;
}
}
}
评论
WaitAsync
评论