WPF: Tạo hộp thoại / lời nhắc


84

Tôi cần tạo Hộp thoại / Lời nhắc bao gồm TextBox để người dùng nhập. Vấn đề của tôi là, làm thế nào để lấy văn bản sau khi đã xác nhận hộp thoại? Thông thường, tôi sẽ tạo một lớp cho lớp này sẽ lưu văn bản trong một thuộc tính. Tuy nhiên, tôi muốn thiết kế Hộp thoại bằng XAML. Vì vậy, bằng cách nào đó tôi sẽ phải mở rộng Mã XAML để lưu nội dung của TextBox trong một thuộc tính - nhưng tôi đoán điều đó là không thể với XAML thuần túy. Cách tốt nhất để nhận ra những gì tôi muốn làm là gì? Làm thế nào để xây dựng một hộp thoại có thể được định nghĩa từ XAML nhưng bằng cách nào đó vẫn có thể trả về đầu vào? Cảm ơn cho bất kỳ gợi ý!

Câu trả lời:


143

Câu trả lời "có trách nhiệm" dành cho tôi là đề xuất xây dựng một ViewModel cho hộp thoại và sử dụng liên kết dữ liệu hai chiều trên TextBox để ViewModel có một số thuộc tính "ResponseText" hoặc không. Điều này đủ dễ để thực hiện nhưng có lẽ quá mức cần thiết.

Câu trả lời thực dụng sẽ là chỉ đặt cho hộp văn bản của bạn một dấu x: Name để nó trở thành một thành viên và hiển thị văn bản dưới dạng thuộc tính trong mã của bạn đằng sau lớp như vậy:

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

Sau đó, trong mã của bạn đằng sau ...

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

Sau đó, để sử dụng nó ...

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

Cảm ơn Josh rất nhiều và xin lỗi vì trả lời muộn của tôi! Ban đầu, tôi tập trung quá nhiều vào việc tải XAML từ một tệp thay vì chỉ tạo một lớp như bạn trình bày.
stefan.at.wpf

7
Bạn cần xử lý sự kiện nhấp vào nút OK và đặt this.DialogResult = true; để đóng hộp thoại và có hộp thoại.ShowDialog () == true.
Erwin Mayer

đây vẫn là một câu trả lời tuyệt vời.
tCoe

Tôi tìm thấy một hộp thoại nhắc đơn giản đẹp mắt đã sẵn sàng để sử dụng liên kết
vinsa

Chỉ có một vấn đề tôi có thể thấy ở đây, đó là hộp thoại này cũng có nút tối đa và nút thu nhỏ .... Có thể tắt các nút này không?
Aleksey Timoshchenko

34

Tôi chỉ thêm một phương thức tĩnh để gọi nó giống như một 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>

Và mã đằng sau:

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

Vì vậy, bạn có thể gọi nó như:

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

6
+1 Để triển khai MessageBoxphương thức tĩnh a -style. Mã ngắn gọn, có thể sử dụng lại!
Aaron Blenkush

2
Ngay khi tôi nhìn thấy từ "tĩnh", tôi đã bỏ qua các câu trả lời khác. Cảm ơn bạn! :)
maplemale

16

Câu trả lời tuyệt vời của Josh, tất cả đều ghi công cho anh ấy, tuy nhiên, tôi đã sửa đổi một chút thành câu này:

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>

Mã MyDialog Phía sau

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

Và gọi nó ở một nơi khác

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

Bạn nên thay thế (trong định nghĩa XAML lưới của bạn) 50 * 50 * với * và * vì không có nhu cầu 50.
Mafii

Mẹo: cài đặt WindowStyle="ToolWindow"trên Window làm cho nó trông đẹp hơn. Ngoài ra WindowStartupLocation="CenterOwner"dialog.Owner = this;vị trí nó ở trung tâm của cửa sổ cha mẹ
độc tấu

2

Bạn không cần BẤT KỲ câu trả lời cầu kỳ nào khác. Dưới đây là một ví dụ đơn giản mà không có tất cả các Margin, Height, Widththuộc tính thiết lập trong XAML, nhưng phải đủ để cho thấy làm thế nào để có được điều này được thực hiện ở mức cơ bản.

XAML
Xây dựng một Windowtrang như bình thường và thêm các trường của bạn vào đó, nói a LabelTextBoxđiều khiển bên trong StackPanel:

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

Sau đó, tạo tiêu chuẩn Buttoncho Gửi ("OK" hoặc "Gửi") và nút "Hủy" nếu bạn muốn:

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

Code-Behind
Bạn sẽ thêm các Clickhàm xử lý sự kiện trong đoạn mã phía sau, nhưng khi bạn đến đó, trước tiên, hãy khai báo một biến công khai nơi bạn sẽ lưu trữ giá trị hộp văn bản của mình:

public static string strUserName = String.Empty;

Sau đó, đối với các chức năng xử lý sự kiện (nhấp chuột phải vào Clickchức năng trên nút XAML, chọn "Go To Definition", nó sẽ tạo cho bạn), bạn cần kiểm tra xem hộp của bạn có trống không. Bạn lưu trữ nó trong biến nếu không, và đóng cửa sổ của bạn:

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

Gọi nó từ một trang khác
Bạn đang nghĩ, nếu tôi đóng cửa sổ của tôi với cái this.Close()đó ở đó, giá trị của tôi sẽ biến mất, phải không? KHÔNG!! Tôi tìm thấy điều này từ một trang web khác: http://www.dreamincode.net/forums/topic/359208-wpf-how-to-make-simple-popup-window-for-input/

Họ đã có một ví dụ tương tự như thế này (tôi đã làm sạch nó một chút) về cách mở của bạn Windowtừ người khác và truy xuất các giá trị:

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

Nút Hủy
Bạn đang nghĩ, còn nút Hủy đó thì sao? Vì vậy, chúng tôi chỉ cần thêm một biến công khai khác trở lại trong mã cửa sổ bật lên của chúng tôi phía sau:

public static bool cancelled = false;

Và hãy bao gồm btnCancel_Clicktrình xử lý sự kiện của chúng tôi và thực hiện một thay đổi đối với 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.");
}

Và sau đó chúng tôi chỉ đọc biến đó trong MainWindow btnOpenPopup_Clicksự kiện của chúng tôi :

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

Phản hồi dài, nhưng tôi muốn cho thấy điều này dễ dàng như thế nào bằng cách sử dụng public staticcác biến. Không DialogResult, không có giá trị trả về, không có gì. Chỉ cần mở cửa sổ, lưu trữ các giá trị của bạn với các sự kiện nút trong cửa sổ bật lên, sau đó truy xuất chúng sau đó trong chức năng cửa sổ chính.


Có rất nhiều cách để cải thiện mã được cung cấp: 1) không sử dụng tĩnh để lưu trữ dữ liệu, nếu không, đôi khi bạn sẽ gặp sự cố với một số hộp thoại; 2) có DialogResult để "vượt qua" 'true' thông qua ShowDialog (); 3) IsCancel thuộc tính cho một nút làm cho nó nút một sự thật Hủy bỏ mà không cần bất kỳ mã thêm ...
AntonK

@AntonK 1) Sử dụng các đối tượng tĩnh là cách bạn có thể gọi các biến trong các lớp khác mà không cần phải khởi tạo chúng mọi lúc. Đối với tôi, các biến tĩnh loại bỏ tất cả những điều đó và rất thích hợp. Không bao giờ gặp sự cố với chúng, vì chúng sẽ được đặt lại bất cứ lúc nào đối tượng (Cửa sổ, Trang) có chúng được mở. Nếu bạn muốn có nhiều hộp thoại, hãy tạo một hộp thoại cho mỗi hộp thoại - không sử dụng lặp đi lặp lại cùng một hộp thoại hoặc có, điều đó có vấn đề - nhưng mã hóa cũng không tốt, vì tại sao bạn muốn cùng một hộp thoại đến 50 lần?
vapcguy

@AntonK 2) Bạn không thể vượt qua trở lại DialogResulttrong WPF, nó MessageBoxResult, mà tôi tìm thấy chỉ hoạt động từ nút tiêu chuẩn trên một MessageBox.Show()hộp thoại - không phải là một từ một hộp thoại tùy chỉnh hiện qua .ShowDialog()- và chỉ có thể truy vấn cho các nhà khai thác tiêu chuẩn, MessageBoxResult.OK, MessageBoxResult.Cancel, "Yes" , "Không", v.v. - không phải Boolean hoặc giá trị tùy chỉnh. 3) IsCancelsẽ yêu cầu lưu trữ nó trong Boolean và gửi nó trở lại, vì vậy đây là một giải pháp phù hợp với tất cả.
vapcguy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.