WPF: crea una finestra di dialogo / prompt


84

Ho bisogno di creare una finestra di dialogo / prompt che includa TextBox per l'input dell'utente. Il mio problema è come ottenere il testo dopo aver confermato la finestra di dialogo? Di solito creerei una classe per questo che salverebbe il testo in una proprietà. Tuttavia voglio progettare la finestra di dialogo utilizzando XAML. Quindi dovrei in qualche modo estendere il codice XAML per salvare il contenuto del TextBox in una proprietà, ma immagino che non sia possibile con XAML puro. Quale sarebbe il modo migliore per realizzare cosa mi piacerebbe fare? Come costruire una finestra di dialogo che può essere definita da XAML ma può comunque restituire in qualche modo l'input? Grazie per qualsiasi suggerimento!

Risposte:


143

La risposta "responsabile" sarebbe per me suggerire di costruire un ViewModel per la finestra di dialogo e utilizzare l'associazione dati bidirezionale sul TextBox in modo che il ViewModel avesse una proprietà "ResponseText" o cosa no. Questo è abbastanza facile da fare ma probabilmente eccessivo.

La risposta pragmatica sarebbe semplicemente dare alla tua casella di testo una x: Name in modo che diventi un membro ed esponga il testo come una proprietà nel codice dietro la classe in questo modo:

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

Quindi nel tuo codice dietro ...

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

Quindi per usarlo ...

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

Grazie mille Josh e scusa per la mia risposta in ritardo! Inizialmente ero concentrato troppo sul caricamento di XAML da un file invece di creare semplicemente una classe come mostrato da te.
stefan.at.wpf

7
È necessario gestire l'evento clic del pulsante OK e impostare this.DialogResult = true; per chiudere la finestra di dialogo e avere dialog.ShowDialog () == true.
Erwin Mayer

questa è ancora un'ottima risposta.
tCoe

Ho trovato una bella finestra di dialogo semplice pronta per l'uso del collegamento
vinsa

Solo un problema che posso vedere qui, che questa finestra di dialogo ha anche il pulsante massimizza e il pulsante minimizza .... È possibile disabilitare questi pulsanti?
Aleksey Timoshchenko

35

Aggiungo solo un metodo statico per chiamarlo come 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>

E il codice dietro:

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

Quindi puoi chiamarlo come:

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

6
+1 Per implementare un MessageBoxmetodo statico in stile. Codice conciso e riutilizzabile!
Aaron Blenkush

2
Non appena ho visto la parola "statico" ho ignorato le altre risposte. Grazie! :)
maplemale

16

Ottima risposta di Josh, tutto merito di lui, l'ho leggermente modificata in questo però:

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>

Codice MyDialog dietro

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

E chiamalo altrove

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

Dovresti sostituire (nella definizione della griglia xaml) 50 * e 50 * con * e * perché non è necessario 50.
Mafii

Suggerimento: l'impostazione WindowStyle="ToolWindow"sulla finestra lo rende ancora più bello. Inoltre WindowStartupLocation="CenterOwner"e lo dialog.Owner = this;posiziona al centro della finestra genitore
solo

2

Non hai bisogno di NESSUNA di queste altre risposte fantasiose. Di seguito è riportato un esempio semplicistico che non ha tutto il Margin, Height, Widthproprietà impostate nel XAML, ma dovrebbe essere sufficiente per mostrare come ottenere questo fatto a un livello di base.

XAML
Crea una Windowpagina come faresti normalmente e aggiungi i tuoi campi, ad esempio a Labele TextBoxcontrolla all'interno di a StackPanel:

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

Quindi crea uno standard Buttonper l'invio ("OK" o "Invia") e un pulsante "Annulla" se lo desideri:

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

Code-Behind
Aggiungerai le Clickfunzioni del gestore di eventi nel code-behind, ma quando vai lì, prima dichiara una variabile pubblica in cui memorizzerai il valore della casella di testo:

public static string strUserName = String.Empty;

Quindi, per le funzioni del gestore eventi (fai clic Clickcon il pulsante destro del mouse sulla funzione sul pulsante XAML, seleziona "Vai a definizione", la creerà per te), è necessario un controllo per vedere se la casella è vuota. Lo memorizzi nella tua variabile se non lo è e chiudi la finestra:

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

Chiamalo da un'altra pagina
Stai pensando, se chiudo la mia finestra con quello this.Close()lassù, il mio valore è andato, giusto? NO!! L'ho scoperto da un altro sito: http://www.dreamincode.net/forums/topic/359208-wpf-how-to-make-simple-popup-window-for-input/

Avevano un esempio simile a questo (l'ho ripulito un po ') di come aprire il tuo Windowda un altro e recuperare i valori:

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

Pulsante Annulla
Stai pensando, beh, che dire del pulsante Annulla, però? Quindi aggiungiamo di nuovo un'altra variabile pubblica nella nostra finestra pop-up code-behind:

public static bool cancelled = false;

E includiamo il nostro btnCancel_Clickgestore di eventi e apportiamo una modifica a 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.");
}

E poi leggiamo quella variabile nel nostro MainWindow btnOpenPopup_Clickevento:

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

Risposta lunga, ma volevo mostrare quanto sia facile usare le public staticvariabili. No DialogResult, nessun valore di ritorno, niente. Basta aprire la finestra, memorizzare i valori con i pulsanti eventi nella finestra a comparsa, quindi recuperarli in seguito nella funzione della finestra principale.


Ci sono molti modi per migliorare il codice fornito: 1) non usare statico per memorizzare i dati, altrimenti occasionalmente incontrerai problemi con diverse finestre di dialogo; 2) c'è DialogResult per "passare" 'true' tramite ShowDialog (); 3) L'attributo IsCancel per un pulsante lo rende un vero pulsante Annulla senza alcun codice aggiuntivo ...
AntonK

@AntonK 1) L'uso di oggetti statici è il modo in cui puoi chiamare variabili in altre classi senza doverle istanziare tutto il tempo. Per me, le variabili statiche eliminano tutto questo e sono di gran lunga preferibili. Non ho mai avuto problemi con loro, perché verranno ripristinati ogni volta che l'oggetto (Finestra, Pagina) che li ha viene aperto. Se vuoi più finestre di dialogo, crea una finestra di dialogo per ciascuna - non usare la stessa più e più volte o sì, è problematico - ma anche una cattiva codifica, perché perché dovresti volere la stessa finestra di dialogo 50 volte?
vapcguy

@AntonK 2) Non è possibile passare di nuovo DialogResultin WPF, è MessageBoxResult, che ho trovato funziona solo da pulsanti standard su una MessageBox.Show()finestra - non è uno da una finestra di dialogo personalizzato mostrato attraverso .ShowDialog()- e può solo query per gli operatori standard, MessageBoxResult.OK, MessageBoxResult.Cancel, "Sì" , "No" e così via - non valori booleani o personalizzati. 3) IsCancelrichiederebbe di memorizzarlo in un booleano e di rispedirlo, quindi questa era una soluzione valida per tutti.
vapcguy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.