WPF:创建对话框/提示

92
我需要创建一个包括文本框的对话框/提示框来获取用户输入。我的问题是,在确认对话框后如何获取文本?通常我会为此创建一个类,该类将文本保存在属性中。但是,我想使用XAML设计对话框。因此,我需要扩展XAML代码以将文本框的内容保存在属性中——但我猜纯粹使用XAML是不可能的。实现我想要做的事情的最佳方法是什么?如何构建一个可以从XAML定义但仍然可以返回输入的对话框?感谢任何提示!
4个回答

154

"负责任"的答案是建议为对话框构建ViewModel,并在TextBox上使用双向数据绑定,以便ViewModel具有某些"ResponseText"属性或类似内容。这很容易做到,但可能过度。

实用的答案是只需为文本框指定x:Name,使其成为成员,并在您的代码后台类中公开文本作为属性,如下所示:

<!-- Incredibly simplified XAML -->
<Window x:Class="MyDialog">
   <StackPanel>
       <TextBlock Text="Enter some text" />
       <TextBox x:Name="ResponseTextBox" />
       <Button Content="OK" Click="OKButton_Click" />
   </StackPanel>
</Window>

然后在你的代码后台...

partial class MyDialog : Window {

    public MyDialog() {
        InitializeComponent();
    }

    public string ResponseText {
        get { return ResponseTextBox.Text; }
        set { ResponseTextBox.Text = value; }
    }

    private void OKButton_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        DialogResult = true;
    }
}

然后使用它...

var dialog = new MyDialog();
if (dialog.ShowDialog() == true) {
    MessageBox.Show("You said: " + dialog.ResponseText);
}

非常感谢你,Josh,对于我的迟回复我很抱歉!我最初太专注于从文件加载XAML,而不是像你展示的那样创建一个类。 - stefan.at.kotlin
7
您需要处理“确定”按钮的点击事件,并设置 this.DialogResult = true;来关闭对话框并使 dialog.ShowDialog() == true。 - Erwin Mayer
这仍然是一个很棒的答案。 - tCoe
1
我找到了一个不错的简单提示对话框,可以直接使用链接 - vinsa
我只看到一个问题,这个对话框还有最大化和最小化按钮... 是否可以禁用这些按钮? - Sirop4ik
@AlekseyTimoshchenko 因为这是一个普通的Windows,您可以使用属性 ResizeModeWindowStyle 来隐藏标题栏和/或最小化/最大化按钮。不过我不确定是否可以仅禁用它们。 - Tim

46

编辑:可以使用NuGet进行安装https://www.nuget.org/packages/PromptDialog/

我只是添加了一个静态方法,使其像MessageBox一样调用:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    x:Class="utils.PromptDialog"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    WindowStartupLocation="CenterScreen" 
    SizeToContent="WidthAndHeight"
    MinWidth="300"
    MinHeight="100"
    WindowStyle="SingleBorderWindow"
    ResizeMode="CanMinimize">
<StackPanel Margin="5">
    <TextBlock Name="txtQuestion" Margin="5"/>
    <TextBox Name="txtResponse" Margin="5"/>
    <PasswordBox Name="txtPasswordResponse" />
    <StackPanel Orientation="Horizontal" Margin="5" HorizontalAlignment="Right">
        <Button Content="_Ok" IsDefault="True" Margin="5" Name="btnOk" Click="btnOk_Click" />
        <Button Content="_Cancel" IsCancel="True" Margin="5" Name="btnCancel" Click="btnCancel_Click" />
    </StackPanel>
</StackPanel>
</Window>

背后的代码:

public partial class PromptDialog : Window
{
    public enum InputType
    {
        Text,
        Password
    }

    private InputType _inputType = InputType.Text;

    public PromptDialog(string question, string title, string defaultValue = "", InputType inputType = InputType.Text)
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(PromptDialog_Loaded);
        txtQuestion.Text = question;
        Title = title;
        txtResponse.Text = defaultValue;
        _inputType = inputType;
        if (_inputType == InputType.Password)
            txtResponse.Visibility = Visibility.Collapsed;
        else
            txtPasswordResponse.Visibility = Visibility.Collapsed;
    }

    void PromptDialog_Loaded(object sender, RoutedEventArgs e)
    {
        if (_inputType == InputType.Password)
            txtPasswordResponse.Focus();
        else
            txtResponse.Focus();
    }

    public static string Prompt(string question, string title, string defaultValue = "", InputType inputType = InputType.Text)
    {
        PromptDialog inst = new PromptDialog(question, title, defaultValue, inputType);
        inst.ShowDialog();
        if (inst.DialogResult == true)
            return inst.ResponseText;
        return null;
    }

    public string ResponseText
    {
        get
        {
            if (_inputType == InputType.Password)
                return txtPasswordResponse.Password;
            else
                return txtResponse.Text;
        }
    }

    private void btnOk_Click(object sender, RoutedEventArgs e)
    {
        DialogResult = true;
        Close();
    }

    private void btnCancel_Click(object sender, RoutedEventArgs e)
    {
        Close();
    }
}

所以你可以这样调用它:

string repeatPassword = PromptDialog.Prompt("Repeat password", "Password confirm", inputType: PromptDialog.InputType.Password);

7
实现一个类似于“MessageBox”的静态方法,代码简洁易复用!+1 - Aaron Blenkush
2
一看到“静态”这个词,我就忽略了其他答案。谢谢! :) - maplemale
1
我制作了一个NuGet包,可供您在项目中使用:https://www.nuget.org/packages/PromptDialog/ - ManuelCanepa

17

Josh的答案很棒,所有的功劳都归于他,我稍微修改了一下:

MyDialog Xaml

    <StackPanel Margin="5,5,5,5">
        <TextBlock Name="TitleTextBox" Margin="0,0,0,10" />
        <TextBox Name="InputTextBox" Padding="3,3,3,3" />
        <Grid Margin="0,10,0,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Button Name="BtnOk" Content="OK" Grid.Column="0" Margin="0,0,5,0" Padding="8" Click="BtnOk_Click" />
            <Button Name="BtnCancel" Content="Cancel" Grid.Column="1" Margin="5,0,0,0" Padding="8" Click="BtnCancel_Click" />
        </Grid>
    </StackPanel>

我的对话框代码

    public MyDialog()
    {
        InitializeComponent();
    }

    public MyDialog(string title,string input)
    {
        InitializeComponent();
        TitleText = title;
        InputText = input;
    }

    public string TitleText
    {
        get { return TitleTextBox.Text; }
        set { TitleTextBox.Text = value; }
    }

    public string InputText
    {
        get { return InputTextBox.Text; }
        set { InputTextBox.Text = value; }
    }

    public bool Canceled { get; set; }

    private void BtnCancel_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        Canceled = true;
        Close();
    }

    private void BtnOk_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        Canceled = false;
        Close();
    }

并在其他地方调用它

var dialog = new MyDialog("test", "hello");
dialog.Show();
dialog.Closing += (sender,e) =>
{
    var d = sender as MyDialog;
    if(!d.Canceled)
        MessageBox.Show(d.InputText);
}

你应该在网格定义的 XAML 中将 50* 和 50* 替换为 * 和 *,因为不需要使用 50。 - Mafii
2
提示:在窗口上设置 WindowStyle="ToolWindow" 可以使其看起来更漂亮。同时,WindowStartupLocation="CenterOwner"dialog.Owner = this; 可以将其定位于父窗口的中心。 - solo

6
您不需要这些花哨的答案。以下是一个简单的示例,其中未在XAML中设置所有Margin、Height、Width属性,但应足以展示如何在基本水平上完成此操作。
XAML 像往常一样构建一个Window页面,并向其中添加您的字段,例如在StackPanel中添加Label和TextBox控件:
<StackPanel Orientation="Horizontal">
    <Label Name="lblUser" Content="User Name:" />
    <TextBox Name="txtUser" />
</StackPanel>

然后创建一个标准的提交按钮(“确定”或“提交”),如果需要,还可以创建一个“取消”按钮:

<StackPanel Orientation="Horizontal">
    <Button Name="btnSubmit" Click="btnSubmit_Click" Content="Submit" />
    <Button Name="btnCancel" Click="btnCancel_Click" Content="Cancel" />
</StackPanel>

代码后台
你需要在代码后台添加Click事件处理函数,但是在那之前,需要先声明一个公共变量来存储文本框的值:

public static string strUserName = String.Empty;

接下来,针对事件处理函数(右键单击按钮XAML上的Click函数,选择“转到定义”,它将为您创建该函数),您需要检查是否为空。如果不为空,则将其存储在变量中,并关闭窗口:

private void btnSubmit_Click(object sender, RoutedEventArgs e)
{        
    if (!String.IsNullOrEmpty(txtUser.Text))
    {
        strUserName = txtUser.Text;
        this.Close();
    }
    else
        MessageBox.Show("Must provide a user name in the textbox.");
}

从另一个页面调用它
你可能会想,如果我使用上面的 this.Close() 关闭窗口,我的值就会消失,对吧?错了!! 我从另一个网站上发现了这个方法:http://www.dreamincode.net/forums/topic/359208-wpf-how-to-make-simple-popup-window-for-input/

他们有一个类似的示例(我稍微整理了一下),说明如何从另一个窗口打开你的 Window 并检索值:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void btnOpenPopup_Click(object sender, RoutedEventArgs e)
    {
        MyPopupWindow popup = new MyPopupWindow();  // this is the class of your other page

        //ShowDialog means you can't focus the parent window, only the popup
        popup.ShowDialog(); //execution will block here in this method until the popup closes

        string result = popup.strUserName;
        UserNameTextBlock.Text = result;  // should show what was input on the other page
    }
}

取消按钮
您可能会想,那么取消按钮呢?因此我们只需在弹出窗口的代码后台中添加另一个公共变量:

public static bool cancelled = false;

现在我们需要包含btnCancel_Click事件处理程序,并对 btnSubmit_Click做出一些更改:

private void btnCancel_Click(object sender, RoutedEventArgs e)
{        
    cancelled = true;
    strUserName = String.Empty;
    this.Close();
}

private void btnSubmit_Click(object sender, RoutedEventArgs e)
{        
    if (!String.IsNullOrEmpty(txtUser.Text))
    {
        strUserName = txtUser.Text;
        cancelled = false;  // <-- I add this in here, just in case
        this.Close();
    }
    else
        MessageBox.Show("Must provide a user name in the textbox.");
}

然后我们只需在MainWindowbtnOpenPopup_Click事件中读取该变量:

private void btnOpenPopup_Click(object sender, RoutedEventArgs e)
{
    MyPopupWindow popup = new MyPopupWindow();  // this is the class of your other page
    //ShowDialog means you can't focus the parent window, only the popup
    popup.ShowDialog(); //execution will block here in this method until the popup closes

    // **Here we find out if we cancelled or not**
    if (popup.cancelled == true)
        return;
    else
    {
        string result = popup.strUserName;
        UserNameTextBlock.Text = result;  // should show what was input on the other page
    }
}

虽然描述较长,但我想展示使用public static变量非常简单。无需DialogResult,不需要返回值,什么都不需要。只需打开窗口,在弹出窗口的按钮事件中存储您的值,然后在主窗口函数中后续检索。


有很多方法可以改进提供的代码:1)不要使用静态存储数据,否则您会偶尔遇到几个对话框的问题;2)有DialogResult可以通过ShowDialog()“传递”'true';3)IsCancel属性使按钮成为真正的取消按钮,无需任何额外的代码... - AntonK
@AntonK 1) 使用静态对象是您可以在其他类中调用变量而无需一直实例化它们的方法。对我来说,静态变量消除了所有这些,并且更可取。从未遇到过问题,因为每次打开具有它们的对象(窗口、页面)时都会重置它们。如果您想要几个对话框,请为每个对话框创建一个 - 不要一遍又一遍地使用相同的对话框,否则会出现问题 - 但这也是糟糕的编码,因为为什么您要想要50个相同的对话框呢? - vapcguy
@AntonK 2) 在WPF中,您无法传递DialogResult,而是使用MessageBoxResult。我发现它仅适用于通过.ShowDialog()显示的自定义对话框上的标准按钮的MessageBox.Show()对话框 - 并且只能查询标准运算符MessageBoxResult.OKMessageBoxResult.Cancel,“Yes”,“No”等 - 而不是布尔值或自定义值。3)IsCancel需要将其存储在布尔值中并将其发送回来,因此这是一种一刀切的解决方案。 - vapcguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接