如何在静态C# Window中打开WPF Window并从Textbox中读取

h9a6wy2h  于 2023-10-22  发布在  C#
关注(0)|答案(2)|浏览(127)

我想通过代码隐藏调用WPF窗口,并在应用程序中显示它,无论我在应用程序中的哪个位置。我失败的地方是,调用窗口的静态方法不允许在代码中继续,直到我单击了一个按钮。确定或取消。我现在办不到。然后,OK按钮应该从文本框中读取一个值,然后在方法中处理它。打开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>

我的TextboxAndroid.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 -->方法CallTextBoxWindow(string userText)--> at“return TextBoxValue;“它跳到那里,这是不正确的。只有当单击WPF窗口中的“确定”按钮时,它才应该出现在那里。如果我让代码在断点处继续,那么窗口会显示,我可以输入文本,单击OK按钮,但随后它不再在方法中。如果我现在点击OK按钮,窗口关闭,就这样。
如果我只在调度程序中使用fixke,那么我会有一个奇怪的行为,我不能在文本框中输入任何东西。
我的目标是将它构建为MVVM,但我也很高兴它能在其中工作。
谢谢你的帮忙。

icomxhvb

icomxhvb1#

我假设,您需要某种MessageBox,但用于用户输入而不是通知。
有很多关于“如何创建自定义MessageBox 'es”的例子。对于目前的问题,我将使用我自己的想法。
因此,首先,您需要创建一个单独的窗口(视图),其中包含TextBox用于用户输入和OK/Cancel按钮。

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中可以看到,我使用UserTextInputViewModel类作为此窗口的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实现是对一些Action的简单 Package ,并以object作为参数,因此Action可以通过Command属性绑定到XAML中的Button

parameterizedCommand.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

UserTextInputWindow.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方法,但它的工作。你可以纠正我。

rkkpypqq

rkkpypqq2#

您可以使用SemaphoreSlim异步等待,直到窗口关闭,以不阻塞UI线程:

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;
        }
    }
}

相关问题