我正在尋找一種方法來創建“對所有人是 - 是 - 否 - 對所有人都不是”訊息框。我首先嘗試使用 MessageBox 實體,但我明白無法向其中添加自定義按鈕。所以在這里我有一個我自己創建的自定義視窗,它可以做我想要的,但它的設計遠非至少不丑陋。
所以我的問題是:如何重現與下面相同的 MessageBox 布局,但帶有額外的按鈕?

uj5u.com熱心網友回復:
有很多自定義訊息框的例子,包括 WPF 和 WinForms。
我對文字量感到非常抱歉,但這里有詳細的解釋和代碼示例。
正如@mm8 建議的那樣,最簡單的方法是Window為標題(標題)、圖示、訊息和按鈕創建簡單的構建布局。
XAML:
<Window x:Class="WPFApp.CustomMessageBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFApp"
mc:Ignorable="d"
Title=""
MinHeight="150"
Width="500"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
FontSize="14"
WindowStartupLocation="CenterScreen">
<Grid Margin="5" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28"/>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<!-- Border for our custom message box -->
<Border Grid.ColumnSpan="3"
Grid.RowSpan="3"
BorderBrush="Gray"
BorderThickness="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Border.Effect>
<DropShadowEffect BlurRadius="4"
ShadowDepth="0"
Direction="270"
Color="Black"
RenderingBias="Performance"/>
</Border.Effect>
</Border>
<!-- Header of our message box to keep Caption and to be used for window move -->
<TextBlock x:Name="CMBCaption"
HorizontalAlignment="Stretch"
Grid.Row="0"
Text="Custom Message Box Caption"
Grid.ColumnSpan="2"
Background="Gainsboro"
Foreground="Black"
FontWeight="SemiBold"
Margin="1,1,1,0"
Padding="5,2.5,0,0"
MouseLeftButtonDown="OnCaptionPress"/>
<!-- Icon for our custom message box -->
<Image x:Name="CMBIcon"
Grid.Column="0"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="36"
Height="36"/>
<!-- TextBlock for message content. Wrapped into Label because of alignment needs -->
<Label Grid.Column="1"
Grid.Row="1"
VerticalContentAlignment="Center"
Margin="2,24,4,24">
<TextBlock x:Name="CMBMessage"
TextWrapping="Wrap"
Text="Custom Message Box Message"/>
</Label>
<!-- Background for button block -->
<Rectangle Grid.Row="2"
Grid.ColumnSpan="2"
Fill="Gainsboro"
Margin="1,0,1,1"/>
<!-- Buttons block -->
<StackPanel x:Name="CMBButtons"
Grid.Row="2"
Grid.ColumnSpan="2"
Orientation="Horizontal"
FlowDirection="RightToLeft"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Margin="0,0,6,0"/>
</Grid>
</Window>
所以這里是TextBlock("CMBCaption" 為我們的標題), Image("CMBIcon" 為我們的圖示), TextBlock("CMBMessage" 為我們的訊息,Label作為內容屬性放入以進行正確對齊) 和StackPanel("CMBButtons" 為一定數量的按鈕) . “CMB”(如果不明顯)是 CustomMessageBox 的縮寫。這將為您提供簡單的小視窗,它可以是可移動的(通過MouseLeftButtonDown="OnCaptionPress"Caption TextBlock 上的處理程式),看起來簡單清新,可拉伸(取決于內容大小),并且StackPanel底部可以存盤您想要的任意數量的按鈕。

代碼隱藏:(檢查下面的評論和備注)
using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using Brushes = System.Windows.Media.Brushes;
namespace WPFApp
{
public partial class CustomMessageBox : Window
{
// Field that will temporarily store result before we return it and close CustomMessageBox
private static CustomMessageBoxResult result = CustomMessageBoxResult.OK;
// Buttons defined as properties, because couldn't be created (initialized) with event subscription at same time "on-the-fly".
// You can add new different buttons by adding new one as property here
// and to CustomMessageBoxButtons and CustomMessageBoxResult enums
private Button OK
{
get
{
var b = GetDefaultButton();
b.Content = nameof(OK);
b.Click = delegate { result = CustomMessageBoxResult.OK; Close(); };
return b;
}
}
private Button Cancel
{
get
{
var b = GetDefaultButton();
b.Content = nameof(Cancel);
b.Click = delegate { result = CustomMessageBoxResult.Cancel; Close(); };
return b;
}
}
private Button Yes
{
get
{
var b = GetDefaultButton();
b.Content = nameof(Yes);
b.Click = delegate { result = CustomMessageBoxResult.Yes; Close(); };
return b;
}
}
private Button No
{
get
{
var b = GetDefaultButton();
b.Content = nameof(No);
b.Click = delegate { result = CustomMessageBoxResult.No; Close(); };
return b;
}
}
// Add another if you wish
// There is no empty constructor. As least "message" should be passed to this CustomMessageBox
// Also constructor is private to prevent create its instances somewhere and force to use only static Show methods
private CustomMessageBox(string message,
string caption = "",
CustomMessageBoxButtons cmbButtons = CustomMessageBoxButtons.OKOnly,
CustomMessageBoxIcon cmbIcon = CustomMessageBoxIcon.None)
{
InitializeComponent();
// Handle Ctrl C press to copy message from CustomMessageBox
KeyDown = (sender, args) =>
{
if (Keyboard.IsKeyDown(Key.LeftCtrl) && Keyboard.IsKeyDown(Key.C))
Clipboard.SetText(CMBMessage.Text);
};
// Set message
CMBMessage.Text = message;
// Set caption
CMBCaption.Text = caption;
// Setup Buttons (depending on specified CustomMessageBoxButtons value)
// As StackPanel FlowDirection set as RightToLeft - we should add items in reverse
switch (cmbButtons)
{
case CustomMessageBoxButtons.OKOnly:
_ = CMBButtons.Children.Add(OK);
break;
case CustomMessageBoxButtons.OKCancel:
_ = CMBButtons.Children.Add(Cancel);
_ = CMBButtons.Children.Add(OK);
break;
case CustomMessageBoxButtons.YesNo:
_ = CMBButtons.Children.Add(No);
_ = CMBButtons.Children.Add(Yes);
break;
case CustomMessageBoxButtons.YesNoCancel:
_ = CMBButtons.Children.Add(Cancel);
_ = CMBButtons.Children.Add(No);
_ = CMBButtons.Children.Add(Yes);
break;
// Add another if you wish
default:
_ = CMBButtons.Children.Add(OK);
break;
}
// Set icon (depending on specified CustomMessageBoxIcon value)
// From C# 8.0 could be converted to switch-expression
switch (cmbIcon)
{
case CustomMessageBoxIcon.Information:
CMBIcon.Source = FromSystemIcon(SystemIcons.Information);
break;
case CustomMessageBoxIcon.Warning:
CMBIcon.Source = FromSystemIcon(SystemIcons.Warning);
break;
case CustomMessageBoxIcon.Question:
CMBIcon.Source = FromSystemIcon(SystemIcons.Question);
break;
case CustomMessageBoxIcon.Error:
CMBIcon.Source = FromSystemIcon(SystemIcons.Error);
break;
case CustomMessageBoxIcon.None:
default:
CMBIcon.Source = null;
break;
}
}
// Show methods create new instance of CustomMessageBox window and shows it as Dialog (blocking thread)
// Shows CustomMessageBox with specified message and default "OK" button
public static CustomMessageBoxResult Show(string message)
{
_ = new CustomMessageBox(message).ShowDialog();
return result;
}
// Shows CustomMessageBox with specified message, caption and default "OK" button
public static CustomMessageBoxResult Show(string message, string caption)
{
_ = new CustomMessageBox(message, caption).ShowDialog();
return result;
}
// Shows CustomMessageBox with specified message, caption and button(s)
public static CustomMessageBoxResult Show(string message, string caption, CustomMessageBoxButtons cmbButtons)
{
_ = new CustomMessageBox(message, caption, cmbButtons).ShowDialog();
return result;
}
// Shows CustomMessageBox with specified message, caption, button(s) and icon.
public static CustomMessageBoxResult Show(string message, string caption, CustomMessageBoxButtons cmbButtons, CustomMessageBoxIcon cmbIcon)
{
_ = new CustomMessageBox(message, caption, cmbButtons, cmbIcon).ShowDialog();
return result;
}
// Defines button(s), which should be displayed
public enum CustomMessageBoxButtons
{
// Displays only "OK" button
OKOnly,
// Displays "OK" and "Cancel" buttons
OKCancel,
// Displays "Yes" and "No" buttons
YesNo,
// Displays "Yes", "No" and "Cancel" buttons
YesNoCancel,
// Add another if you wish
}
// Defines icon, which should be displayed
public enum CustomMessageBoxIcon
{
None,
Question,
Information,
Warning,
Error
}
// Defines button, pressed by user as result
public enum CustomMessageBoxResult
{
OK,
Cancel,
Yes,
No
// Add another if you wish
}
// Returns simple Button with pre-defined properties
private static Button GetDefaultButton() => new Button
{
Width = 72,
Height = 28,
Margin = new Thickness(0, 4, 6, 4),
Background = Brushes.White,
BorderBrush = Brushes.DarkGray,
Foreground = Brushes.Black
};
// Converts system icons (like in original message box) to BitmapSource to be able to set it to Source property of Image control
private static BitmapSource FromSystemIcon(Icon icon) =>
Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
// Handler on CustomMessageBox caption-header to allow move window while left button pressed on it
private void OnCaptionPress(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
DragMove();
}
}
}
評論。
First of all, to simulate original MessageBox and call it only with CustomMessageBox().Show() (not with new CustomMessageBox().Show() as regular window) you should hide window constructor by making it private.
Enums CustomMessageBoxButtons, CustomMessageBoxIcon and CustomMessageBoxResult are replacements for MessageBoxButton, MessageBoxIcon and MessageBoxResult enums from original MessageBox.
Each Button stored as private property in CustomMessageBox class. On Buttons added to StackPanel on switch statement. They added in order to lay "from right to left" (so first added button will be at most right, next - left of first etc.).
GetDefaultButton method returns, as commented, simple button with pre-defined properties. You can customize it in any way, using gradients, styles, magic - whatever. Even you can remove it and set different style for each Button in its property (in private Button OK, private Button Cancel I mean) to make OK button green, Cancel button red, Yes button pink etc. GetDefaultButton may be rewrited to some kind of common GetButton if you want one method to create any button with specified text and click action as arguments:
private static Button GetButton(string buttonText, RoutedEventHandler clickAction)
{
Button button = new Button
{
Width = 72,
Height = 28
// and other
};
button.Content = buttonText;
button.Click = clickAction;
return button;
}
// In switch statement, when adding buttons to StackPanel, you create and add it instantly
private CustomMessageBox(...)
{
InitializeComponent();
// ...
switch (cmbButtons)
{
case CustomMessageBoxButtons.OKOnly:
_ = CMBButtons.Children.Add(GetButton("OK", delegate
{
result = CustomMessageBoxResult.OK;
Close();
}));
break;
// ...
}
}
private static BitmapSource FromSystemIcon methods, again, as commented, uses default MessageBox icon (or if correct, System Icon) to convert it to BitmapSource. It is needed, because you can't set System Icon to default WPF Image control as Source. If you want to use own icons/images you can remove it and rewrite switch statement where icons set to CMBIcon.Source with paths (URIs) to your own icons/images.
private void OnCaptionPress is handler from TextBlock which stores Caption (or is Header) of CustomMessageBox. Because in XAML WindowStyle property setted to "None" - we can't move window as it is borderless, so this handler uses header to move window until left mouse button pressed on it.
This CustomMessageBox doesn't have Close (x) button. I don't add it because each dialog button has Close() call at click action. You can add if you wish, but this have no sense.
I set window Width property to 500px, so it stretches only by heigth. You also can change it, even make resizable or return WindowStyle to default value (with minimize, maximize and close buttons).
I've also added different "Caption" TextBlock background color in switch statement where icon sets to make MessageBox be better perceived "by eye", but code is huge enough so I removed it from example.
And finally.
To add your Yes to all, No to all or other buttons, on this example, I placed comments // Add another if you wish in places, where you should add new ones:
- Add
YesToAll,NoToAllor other entries toCustomMessageBoxButtonsenum to be able specify it when call, for example, asCustomMessageBox.Show(..., ..., CustomMessageBoxButtons.YesToAllNoToAll). - Add also entries to
CustomMessageBoxResultenum to be able return them as result. - Add new Button property, named
YesToAll,NoToAllor anyway you need. Set a.Contentproperty with text, that should be displayed on this button ("Yes to all", "No to all" etc.). Add.Clickhandler, in which setresultfield withCustomMessageBoxResultenum value of this button and putClose()method to call window close.
And usage is simple, as original MessageBox:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var result1 = CustomMessageBox.Show("Some message");
var result2 = CustomMessageBox.Show("Some message", "My caption");
var result3 = CustomMessageBox.Show("Some message", "My caption", CustomMessageBoxButtons.OKOnly);
var result4 = CustomMessageBox.Show("Some message", "My caption", CustomMessageBoxButtons.OKCancel, CustomMessageBoxIcon.Warning);
// Do whatever with result
}
Few examples:
- Only message
- Message Caption
- Message Caption Buttons
- Message Caption Buttons Icon
I specially removed Yes to all, No to all button from example to allow you try create it by yourself. You have, i think, detailed guidance now.
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/339385.html
上一篇:根據用戶輸入創建多個文本框
