如何在静态 c# 窗口中打开 WPF 窗口并从文本框中读取

How open WPF Window in static c# Window and read from Textbox

提问人:Kojote 提问时间:10/10/2023 最后编辑:Kojote 更新时间:10/11/2023 访问量:84

问:

我想通过代码隐藏调用 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,但我也会对它工作的变体感到满意。

感谢您的帮助。

C# WPF 文本框 窗口 静态方法

评论


答:

0赞 Auditive 10/10/2023 #1

我假设,你想要某种,但用于用户输入而不是通知。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,您可以根据需要自定义它。

enter image description here

从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 中。ActionobjectCommandButton

参数化命令.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 按钮的点击。

最后,本身的代码隐藏,我隐藏了默认构造函数并用静态方法替换了它:UserTextInputWindowShow

用户文本输入窗口.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;
        }
    }
}

我此示例的项目结构如下所示(与此示例无关,绘制了项目):enter image description here

用法与 非常相似。它也可以从任何线程安全地使用。MessageBox

var userInput = UserTextInputWindow.Show();
if (string.IsNullOrEmpty(userInput)) { /* User input nothing or cancel */ }

不确定这是否是纯粹且正确的 MVVM 方法,但它有效。请随时纠正我。

评论

0赞 Kojote 10/10/2023
谢谢 Auditive,我已经实现了它,但它不起作用。与我的代码相同的行为。出现 WPF 窗口,但我无法在文本框中输入任何文本,并且按钮没有反应。由于按钮的原因,我从您的代码中改编了一些东西。对于属性 IComannd,我省略了 parameterizedCommand,因为我无法对它执行任何操作。因此,我只使用了“ICommand OKCommand {get;私有集;}”。
0赞 Auditive 10/10/2023
重新编辑您的问题,并提供您使用过的新代码示例。此外,我的示例是可复制粘贴的,因此您只需将 Window 添加到您的项目中,即可将 XAML 从 answer 粘贴到其中(带有命名空间更正),粘贴 Window 的代码隐藏部分,并创建一个带有 name 的类,同时粘贴来自 answer 的内容。我已经编辑了 ViewModel 示例并添加了 ParameterizedCommand 实现,因此您可以完全“按原样”使用它。UserTextInputWindowUserTextInputViewModel
0赞 Kojote 10/11/2023
Auditive,感谢您的帮助,但不幸的是我有同样的效果。窗口打开,但我无法在文本框中输入任何内容,并且按钮没有反应。我已经修改了我的帖子,现在将我的当前状态与完整的代码放在一起。
0赞 Auditive 10/11/2023
你想达到什么目的?我无法遵循你的逻辑。我的示例显示窗口,用户可以在其中输入一些文本,该文本在窗口关闭后返回。你的 ?为什么需要字段中的存储窗口?您需要定义用户按下的“确定”或“取消”按钮吗?还是只是接收输入的文本?或者提供一些文本以在该窗口中编辑并接收编辑的文本?您只需要 1 个这样的窗口,或者可以一次打开尽可能多的窗口?TextWrapper
0赞 Kojote 10/12/2023
您的代码可以正常工作并完全执行应有的操作,但前提是我将其作为新项目进行测试。一旦我将您的示例合并到我的软件中。我无法在文本框中输入任何内容。光标在文本框中闪烁,但我无法输入任何内容。我可以将某些内容复制到文本框中,但前提是我使用鼠标。鼠标右键 -- >粘贴。问题一定是软件,它非常古老和复杂。这就是为什么我尝试使用调度程序,但它并没有给我带来预期的成功。我仍然感到绝望,我必须自己找到解决这种奇怪行为的方法。
0赞 mm8 10/11/2023 #2

您可以使用异步等待,直到窗口关闭,以免阻塞 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;
        }
    }
}

评论

0赞 Auditive 10/11/2023
为什么出于任何原因应该有信号量?为什么?
0赞 mm8 10/17/2023
@Auditive:异步方法,可让您异步等待。再读一遍答案?WaitAsync
0赞 mm8 10/17/2023
@Kojote:你试过这个吗?