WPF: crear un cuadro de diálogo / solicitud

84

Necesito crear un diálogo / indicador que incluya TextBox para la entrada del usuario. Mi problema es, ¿cómo obtener el texto después de haber confirmado el diálogo? Por lo general, haría una clase para esto que guardaría el texto en una propiedad. Sin embargo, quiero diseñar el cuadro de diálogo usando XAML. Entonces, de alguna manera tendría que extender el código XAML para guardar el contenido del TextBox en una propiedad, pero supongo que eso no es posible con XAML puro. ¿Cuál sería la mejor manera de darme cuenta de lo que me gustaría hacer? ¿Cómo crear un cuadro de diálogo que se pueda definir desde XAML pero que de alguna manera pueda devolver la entrada? ¡Gracias por cualquier pista!

stefan.at.wpf
fuente

Respuestas:

143

La respuesta "responsable" sería sugerir la construcción de un ViewModel para el cuadro de diálogo y usar un enlace de datos bidireccional en el TextBox para que el ViewModel tuviera alguna propiedad "ResponseText" o lo que no. Esto es bastante fácil de hacer, pero probablemente exagerado.

La respuesta pragmática sería simplemente darle a su cuadro de texto una x: Name para que se convierta en miembro y exponer el texto como una propiedad en su código detrás de la clase así:

<!-- 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>

Luego, en su código detrás ...

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

Entonces para usarlo ...

var dialog = new MyDialog();
if (dialog.ShowDialog() == true) {
    MessageBox.Show("You said: " + dialog.ResponseText);
}
Josh
fuente
¡Muchas gracias Josh y perdón por mi respuesta tardía! Inicialmente me concentré demasiado en cargar XAML desde un archivo en lugar de simplemente crear una clase como la que mostraste.
stefan.at.wpf
7
Necesita manejar el evento de clic del botón OK y establecer this.DialogResult = true; para cerrar el diálogo y tener dialog.ShowDialog () == true.
Erwin Mayer
esta sigue siendo una gran respuesta.
tCoe
Encontré un cuadro de diálogo sencillo y agradable listo para usar en el enlace
vinsa
Solo puedo ver un problema aquí, que este diálogo también tiene un botón de maximaze y un botón de minimaze ... ¿Es posible deshabilitar estos botones?
Aleksey Timoshchenko
34

Solo agrego un método estático para llamarlo como un 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>

Y el código detrás:

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

Entonces puedes llamarlo así:

string repeatPassword = PromptDialog.Prompt("Repeat password", "Password confirm", inputType: PromptDialog.InputType.Password);
Pythonizo
fuente
6
+1 Para implementar un MessageBoxmétodo estático de estilo. ¡Código conciso y reutilizable!
Aaron Blenkush
2
Tan pronto como vi la palabra "estática" ignoré las otras respuestas. ¡Gracias! :)
maplemale
16

Gran respuesta de Josh, todo el mérito para él, sin embargo, lo modifiqué ligeramente a esto:

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>

Código subyacente de MyDialog

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

Y llámalo en otro lugar

var dialog = new MyDialog("test", "hello");
dialog.Show();
dialog.Closing += (sender,e) =>
{
    var d = sender as MyDialog;
    if(!d.Canceled)
        MessageBox.Show(d.InputText);
}
xtds
fuente
Debe reemplazar (en su definición de cuadrícula xaml) 50 * y 50 * con * y * porque no hay necesidad de 50.
Mafii
Consejo: la configuración WindowStyle="ToolWindow"en la ventana hace que se vea aún mejor. También WindowStartupLocation="CenterOwner"y lo dialog.Owner = this;coloca en el centro de la ventana principal
solo
2

No necesita NINGUNA de estas otras respuestas sofisticadas. A continuación se muestra un ejemplo simplista que no tiene todo el Margin, Height, Widthpropiedades establecidas en el XAML, pero debe ser suficiente para mostrar cómo hacer esto a un nivel básico.

XAML
Construir una Windowpágina como lo haría normalmente y añadir sus campos para que, digamos una Labely TextBoxde control dentro de una StackPanel:

<StackPanel Orientation="Horizontal">
    <Label Name="lblUser" Content="User Name:" />
    <TextBox Name="txtUser" />
</StackPanel>

Luego cree un estándar Buttonpara Envío ("Aceptar" o "Enviar") y un botón "Cancelar" si lo desea:

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

Código subyacente
Agregará las Clickfunciones del controlador de eventos en el código subyacente, pero cuando vaya allí, primero declare una variable pública donde almacenará el valor del cuadro de texto:

public static string strUserName = String.Empty;

Luego, para las funciones del controlador de eventos (haga clic Clickcon el botón derecho en la función en el botón XAML, seleccione "Ir a la definición", la creará para usted), necesita una verificación para ver si su casilla está vacía. Lo almacena en su variable si no es así, y cierra su ventana:

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.");
}

Llamarlo desde otra página
Estás pensando, si cierro mi ventana con esothis.Close() ahí arriba, mi valor se ha ido, ¿verdad? ¡¡NO!! Encontré esto en otro sitio: http://www.dreamincode.net/forums/topic/359208-wpf-how-to-make-simple-popup-window-for-input/

Tenían un ejemplo similar a este (lo limpié un poco) de cómo abrir su Window de otro y recuperar los valores:

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

Botón Cancelar
Estás pensando, bueno, ¿qué pasa con el botón Cancelar? Así que simplemente agregamos otra variable pública en nuestro código subyacente de la ventana emergente:

public static bool cancelled = false;

E incluyamos nuestro btnCancel_Click controlador de eventos y hagamos un cambio en 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.");
}

Y luego leemos esa variable en nuestro MainWindow btnOpenPopup_Click evento:

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

Respuesta larga, pero quería mostrar lo fácil que es usar public staticvariables. No DialogResult, sin valores devueltos, nada. Simplemente abra la ventana, almacene sus valores con los eventos de botón en la ventana emergente, luego recupérelos en la función de la ventana principal.

vapcguy
fuente
Hay muchas formas de mejorar el código proporcionado: 1) no utilice estática para almacenar datos, de lo contrario, ocasionalmente encontrará problemas con varios diálogos; 2) hay DialogResult para "pasar" 'verdadero' a través de ShowDialog (); 3) El atributo IsCancel para un botón lo convierte en un verdadero botón Cancelar sin ningún código adicional ...
AntonK
@AntonK 1) El uso de objetos estáticos es la forma de llamar variables en otras clases sin tener que instanciarlas todo el tiempo. Para mí, las variables estáticas eliminan todo eso y son mucho más preferibles. Nunca tuve un problema con ellos, porque se restablecerán cada vez que se abra el objeto (Ventana, Página) que los tiene. Si desea varios cuadros de diálogo, cree un cuadro de diálogo para cada uno, no use el mismo una y otra vez o sí, eso es problemático, pero también una mala codificación, porque ¿por qué querría el mismo cuadro de diálogo 50 veces?
vapcguy
@AntonK 2) No se puede pasar de nuevo DialogResulten WPF, es MessageBoxResultque he encontrado sólo funciona de botones estándar en un MessageBox.Show()diálogo - no uno de un diálogo personalizado se muestra a través de .ShowDialog()- y sólo puede consulta de los operadores de la norma, MessageBoxResult.OK, MessageBoxResult.Cancel, "Sí" , "No", etc., no booleanos ni valores personalizados. 3) IsCancelrequeriría almacenarlo en un booleano y enviarlo de vuelta, de todos modos, por lo que esta era una solución única para todos.
vapcguy