WPF: Utwórz okno dialogowe / monit

84

Muszę utworzyć okno dialogowe / monit zawierające pole tekstowe do wprowadzania danych przez użytkownika. Mój problem polega na tym, jak uzyskać tekst po potwierdzeniu dialogu? Zwykle tworzyłbym dla tego klasę, która zapisywałaby tekst we właściwości. Jednak chcę zaprojektować okno dialogowe przy użyciu XAML. Musiałbym więc w jakiś sposób rozszerzyć kod XAML, aby zapisać zawartość TextBox we właściwości - ale myślę, że nie jest to możliwe w przypadku czystego XAML. Jaki byłby najlepszy sposób na zrealizowanie tego, co chciałbym zrobić? Jak zbudować okno dialogowe, które można zdefiniować z XAML, ale nadal może w jakiś sposób zwrócić dane wejściowe? Dzięki za podpowiedź!

stefan.at.wpf
źródło

Odpowiedzi:

143

Odpowiedzialną odpowiedzią byłoby zasugerowanie mi zbudowania ViewModel dla okna dialogowego i użycie dwukierunkowego wiązania danych w TextBox, tak aby ViewModel miał pewną właściwość „ResponseText” lub nie. Jest to dość łatwe, ale prawdopodobnie przesada.

Pragmatyczną odpowiedzią byłoby po prostu nadanie twojemu polu tekstowemu x: Name, aby stało się elementem członkowskim i ujawnienie tekstu jako właściwości w kodzie za klasą, tak jak poniżej:

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

Następnie w twoim kodzie za ...

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

Następnie, aby go użyć ...

var dialog = new MyDialog();
if (dialog.ShowDialog() == true) {
    MessageBox.Show("You said: " + dialog.ResponseText);
}
Josh
źródło
Bardzo dziękuję Josh i przepraszam za moją spóźnioną odpowiedź! Początkowo zbytnio skupiałem się na ładowaniu XAML z pliku, zamiast po prostu tworzyć klasę, jak pokazałeś.
stefan.at.wpf
7
Musisz obsłużyć zdarzenie kliknięcia przycisku OK i ustawić this.DialogResult = true; aby zamknąć okno dialogowe i mieć dialog.ShowDialog () == true.
Erwin Mayer
to wciąż świetna odpowiedź.
tCoe
Znalazłem ładne proste okno wiersza gotowy do użycia łącza
vinsa
Tylko jeden problem widzę tutaj, że to okno dialogowe ma również przycisk maksymalizacji i minimalizacji ... Czy można wyłączyć te przyciski?
Aleksey Timoshchenko
35

Po prostu dodaję metodę statyczną, aby nazwać ją jak 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>

A kod za:

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

Więc możesz to nazwać tak:

string repeatPassword = PromptDialog.Prompt("Repeat password", "Password confirm", inputType: PromptDialog.InputType.Password);
Pythonizo
źródło
6
+1 Do implementacji MessageBoxstatycznej metody w stylu a . Zwięzły kod wielokrotnego użytku!
Aaron Blenkush,
2
Gdy tylko zobaczyłem słowo „statyczne”, zignorowałem pozostałe odpowiedzi. Dziękuję Ci! :)
maplemale
16

Świetna odpowiedź Josha, cała zasługa jego, jednak nieznacznie zmodyfikowałem ją do tego:

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>

Kod MyDialog za

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

I nazwij to gdzie indziej

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
źródło
Powinieneś zamienić (w definicji sieci xaml) 50 * i 50 * na * i *, ponieważ nie ma potrzeby 50.
Mafii
Wskazówka: ustawienie WindowStyle="ToolWindow"w oknie sprawia, że ​​wygląda jeszcze ładniej. Również WindowStartupLocation="CenterOwner"i dialog.Owner = this;umieszcza go na środku okna nadrzędnego
solo
2

Nie potrzebujesz ŻADNEJ z tych innych wymyślnych odpowiedzi. Poniżej znajduje się uproszczony przykład, który nie ma wszystkich Margin, Height, Widthwłaściwości określone w XAML, ale powinien być wystarczający, aby pokazać, w jaki sposób, aby to zrobić na poziomie podstawowym.

XAML
Zbuduj Windowstronę tak jak zwykle i dodaj do niej swoje pola, powiedz a Labeli TextBoxkontroluj wewnątrz a StackPanel:

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

Następnie utwórz standard Buttonprzesyłania („OK” lub „Prześlij”) i przycisk „Anuluj”, jeśli chcesz:

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

Code-Behind
Będziesz dodać Clickfunkcje obsługi zdarzeń w kodzie opóźnieniem, ale kiedy tam najpierw zadeklarować zmienną publiczną, gdzie można przechowywać swoją wartość pola tekstowego:

public static string strUserName = String.Empty;

Następnie dla funkcji obsługi zdarzeń (kliknij prawym przyciskiem myszy Clickfunkcję na przycisku XAML, wybierz „Przejdź do definicji”, utworzy ją za Ciebie), musisz sprawdzić, czy twoje pole jest puste. Przechowujesz go w swojej zmiennej, jeśli tak nie jest, i zamknij okno:

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

Wywołanie tego z innej strony
Myślisz, że jeśli zamknę okno z tym this.Close(), moja wartość zniknie, prawda? NIE!! Dowiedziałem się tego z innej strony: http://www.dreamincode.net/forums/topic/359208-wpf-how-to-make-simple-popup-window-for-input/

Mieli podobny przykład do tego (trochę go wyczyściłem), jak otworzyć twój Windowz innego i pobrać wartości:

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

Przycisk Anuluj Zastanawiasz
się, a co z tym przyciskiem Anuluj? Więc po prostu dodajemy kolejną zmienną publiczną z powrotem w naszym kodzie wyskakującego okna:

public static bool cancelled = false;

Dołączmy nasz btnCancel_Clickprogram obsługi zdarzeń i wprowadźmy jedną zmianę w 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.");
}

A potem właśnie odczytaliśmy tę zmienną w naszym MainWindow btnOpenPopup_Clickzdarzeniu:

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

Długa odpowiedź, ale chciałem pokazać, jakie to łatwe przy użyciu public staticzmiennych. Nie DialogResult, żadnych wartości zwracanych, nic. Po prostu otwórz okno, zapisz wartości za pomocą przycisków zdarzeń w wyskakującym oknie, a następnie pobierz je w funkcji okna głównego.

vapcguy
źródło
Istnieje wiele sposobów na ulepszenie dostarczonego kodu: 1) nie używaj statycznego do przechowywania danych, w przeciwnym razie czasami napotkasz problemy z kilkoma oknami dialogowymi; 2) istnieje DialogResult, aby „przekazać” wartość „true” za pośrednictwem ShowDialog (); 3) Atrybut IsCancel dla przycisku sprawia, że ​​jest to prawdziwy przycisk Anuluj bez dodatkowego kodu ...
AntonK
@AntonK 1) Używanie obiektów statycznych to sposób, w jaki możesz wywoływać zmienne z innych klas bez konieczności ciągłego ich tworzenia. Dla mnie zmienne statyczne eliminują to wszystko i są o wiele lepsze. Nigdy nie miałem z nimi problemu, ponieważ zostaną zresetowane za każdym razem, gdy obiekt (Okno, Strona), który je ma, zostanie otwarty. Jeśli chcesz mieć kilka okien dialogowych, utwórz okno dialogowe dla każdego z nich - nie używaj w kółko tego samego lub tak, to jest problematyczne - ale także złe kodowanie, bo po co miałbyś chcieć tego samego okna dialogowego 50 razy?
vapcguy
@AntonK 2) Nie można przejść z powrotem DialogResultw WPF, to MessageBoxResult, co znalazłem działa tylko ze standardowych przycisków na MessageBox.Show()oknie - nie jednego z niestandardowego okna dialogowego przedstawionego przez .ShowDialog()- a może tylko zapytania dla standardowych operatorów MessageBoxResult.OK, MessageBoxResult.Cancel„Tak” , „Nie” itp. - nie wartości logiczne ani niestandardowe. 3) i IsCanceltak wymagałoby przechowywania go w wartości logicznej i odesłania go z powrotem, więc było to uniwersalne rozwiązanie.
vapcguy