WPF: создать диалоговое окно / приглашение

84

Мне нужно создать диалоговое окно / приглашение, включая текстовое поле для ввода пользователем. Моя проблема в том, как получить текст после подтверждения диалога? Обычно я делал для этого класс, который сохранял бы текст в свойстве. Однако я хочу создать диалог с использованием XAML. Поэтому мне как-то пришлось бы расширить код XAML, чтобы сохранить содержимое TextBox в свойстве, но я думаю, что это невозможно с чистым XAML. Как лучше всего понять, чем я хочу заниматься? Как создать диалог, который может быть определен из XAML, но может каким-то образом возвращать ввод? Спасибо за подсказку!

stefan.at.wpf
источник

Ответы:

143

«Ответственным» ответом для меня было бы предложить создать 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);
}
Джош
источник
Большое спасибо, Джош, и извини за мой поздний ответ! Изначально я был слишком сосредоточен на загрузке XAML из файла, вместо того, чтобы просто создать класс, как показано вами.
stefan.at.wpf 05
7
Вам необходимо обработать событие нажатия кнопки OK и установить this.DialogResult = true; чтобы закрыть диалог и получить dialog.ShowDialog () == true.
Эрвин Майер,
это по-прежнему отличный ответ.
tCoe
Я нашел красивый простой диалог, готовый к использованию ссылка
Винса
Здесь я вижу только одну проблему: в этом диалоговом окне также есть кнопка разворачивания и кнопка сворачивания .... Можно ли отключить эти кнопки?
Алексей Тимощенко
35

Я просто добавляю статический метод, чтобы вызвать его как 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);
Pythonizo
источник
6
+1 За реализацию MessageBoxстатического метода в стиле. Краткий, многоразовый код!
Аарон Бленкуш
2
Как только я увидел слово «статика», я проигнорировал другие ответы. Спасибо! :)
maplemale 04
16

Отличный ответ Джоша, вся его заслуга, однако я немного изменил его:

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>

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

И назовите это где-нибудь еще

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
источник
Вы должны заменить (в определении вашей сетки xaml) 50 * и 50 * на * и *, потому что в 50 нет необходимости.
Mafii
Совет: установка WindowStyle="ToolWindow"окна делает его еще красивее. Также WindowStartupLocation="CenterOwner"и dialog.Owner = this;позиционирует его в центре родительского окна
соло
2

Вам не нужны никакие другие причудливые ответы. Ниже приведен упрощенный пример , который не имеет все Margin, Height,Width свойства , установленные в XAML, но должно быть достаточно , чтобы показать , как получить это сделано на базовом уровне.

XAML
Создайте Windowстраницу, как обычно, и добавьте к ней свои поля, скажем, Labelи TextBoxэлемент управления внутри StackPanel:

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

Затем создайте стандарт Buttonдля отправки («ОК» или «Отправить») и кнопку «Отменить», если хотите:

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

Code-Behind
Вы добавите Clickфункции обработчика событий в код программной части, но когда вы перейдете туда, сначала объявите общедоступную переменную, в которой вы будете хранить значение вашего текстового поля:

public static string strUserName = String.Empty;

Затем для функций обработчика событий (щелкните правой кнопкой мыши Clickфункцию на кнопке XAML, выберите «Перейти к определению», он создаст его для вас) вам потребуется проверить, пуст ли ваше поле. Вы сохраняете его в своей переменной, если это не так, и закрываете окно:

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

А затем мы просто читаем эту переменную в нашем MainWindow btnOpenPopup_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, никаких возвращаемых значений, ничего. Просто откройте окно, сохраните свои значения с помощью событий кнопок во всплывающем окне, а затем извлеките их в функции главного окна.

vapcguy
источник
Есть много способов улучшить предоставленный код: 1) не используйте static для хранения данных, иначе вы иногда будете сталкиваться с проблемами с несколькими диалогами; 2) есть DialogResult для "передачи" true через ShowDialog (); 3) Атрибут IsCancel для кнопки делает ее настоящей кнопкой Отмена без какого-либо дополнительного кода ...
AntonK
@AntonK 1) Использование статических объектов - это то, как вы можете вызывать переменные в других классах, не создавая их все время. На мой взгляд, статические переменные исключают все это и намного предпочтительнее. С ними никогда не было проблем, потому что они будут сброшены каждый раз, когда объект (окно, страница), в котором они есть, будет открыт. Если вам нужно несколько диалогов, создайте диалог для каждого из них - не используйте одно и то же снова и снова или да, это проблематично - но также и плохой код, потому что зачем вам один и тот же диалог 50 раз?
vapcguy
@AntonK 2) Вы не можете передать обратно DialogResultв WPF, это MessageBoxResult, что я нашел работает только от стандартных кнопок на MessageBox.Show()диалог - не один из диалогового окна пользовательского показанного через .ShowDialog()- и может только запрос для стандартных операторов MessageBoxResult.OK, MessageBoxResult.Cancel«Да» , «Нет» и т. Д. - не логические или пользовательские значения. 3) IsCancelпотребовалось бы сохранить его в логическом значении и отправить обратно, так что это было универсальное решение.
vapcguy