Message boxes are themed
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
|
||||
5.4.9.5 Beta (2021-??-??)
|
||||
|
||||
- Message boxes are themed.
|
||||
|
||||
|
||||
5.4.9.4 Beta (2021-08-24)
|
||||
|
||||
- Fix of command palette crash on Windows 7.
|
||||
|
||||
@@ -5,3 +5,15 @@ dotnet_diagnostic.IDE0058.severity = none
|
||||
|
||||
# IDE0055: Fix formatting
|
||||
dotnet_diagnostic.IDE0055.severity = none
|
||||
|
||||
# IDE0022: Use block body for methods
|
||||
dotnet_diagnostic.IDE0022.severity = none
|
||||
|
||||
# IDE0040: Add accessibility modifiers
|
||||
dotnet_diagnostic.IDE0040.severity = none
|
||||
|
||||
# IDE0011: Add braces
|
||||
dotnet_diagnostic.IDE0011.severity = none
|
||||
|
||||
# IDE0010: Add missing cases
|
||||
dotnet_diagnostic.IDE0010.severity = none
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace mpvnet
|
||||
else
|
||||
{
|
||||
if (Msg.ShowQuestion("Create history.txt file in config folder?",
|
||||
"mpv.net will write the date, time and filename of opened files to it.") == DialogResult.OK)
|
||||
"mpv.net will write the date, time and filename of opened files to it.") == MessageBoxResult.OK)
|
||||
|
||||
File.WriteAllText(Core.ConfigFolder + "history.txt", "");
|
||||
}
|
||||
|
||||
@@ -1161,18 +1161,19 @@ namespace mpvnet
|
||||
|
||||
if (gb < 10)
|
||||
{
|
||||
DialogResult result = Msg.ShowQuestion("Click Yes for Blu-ray and No for DVD.",
|
||||
null, MessageBoxButtons.YesNoCancel);
|
||||
System.Windows.MessageBoxResult result =
|
||||
Msg.ShowQuestion("Click Yes for Blu-ray and No for DVD.",
|
||||
null, System.Windows.MessageBoxButton.YesNoCancel);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case DialogResult.Yes:
|
||||
case System.Windows.MessageBoxResult.Yes:
|
||||
Command("stop");
|
||||
Thread.Sleep(500);
|
||||
SetPropertyString("bluray-device", path);
|
||||
LoadFiles(new[] { @"bd://" }, false, false);
|
||||
break;
|
||||
case DialogResult.No:
|
||||
case System.Windows.MessageBoxResult.No:
|
||||
Command("stop");
|
||||
Thread.Sleep(500);
|
||||
SetPropertyString("dvd-device", path);
|
||||
|
||||
@@ -1,41 +1,50 @@
|
||||
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows;
|
||||
|
||||
using WinForms = System.Windows.Forms;
|
||||
|
||||
using static mpvnet.Global;
|
||||
|
||||
using MsgBoxEx;
|
||||
|
||||
public class Msg
|
||||
{
|
||||
private static readonly string WindowTitle = WinForms.Application.ProductName;
|
||||
|
||||
public static void ShowInfo(object title, object content = null)
|
||||
{
|
||||
Show(title, content, MessageBoxIcon.Information);
|
||||
Show(title, content, MessageBoxImage.Information);
|
||||
}
|
||||
|
||||
public static void ShowError(object title, object content = null)
|
||||
{
|
||||
Show(title, content, MessageBoxIcon.Error);
|
||||
Show(title, content, MessageBoxImage.Error);
|
||||
}
|
||||
|
||||
public static void ShowWarning(object title, object content = null)
|
||||
{
|
||||
Show(title, content, MessageBoxIcon.Warning);
|
||||
Show(title, content, MessageBoxImage.Warning);
|
||||
}
|
||||
|
||||
public static DialogResult ShowQuestion(object title, object content = null,
|
||||
MessageBoxButtons buttons = MessageBoxButtons.OKCancel)
|
||||
public static MessageBoxResult ShowQuestion(object title, object content = null,
|
||||
MessageBoxButton buttons = MessageBoxButton.OKCancel)
|
||||
{
|
||||
return Show(title, content, MessageBoxIcon.Question, buttons);
|
||||
return Show(title, content, MessageBoxImage.Question, buttons);
|
||||
}
|
||||
|
||||
public static void ShowException(Exception exception)
|
||||
{
|
||||
Show(exception, null, MessageBoxIcon.Error);
|
||||
Show(exception, null, MessageBoxImage.Error);
|
||||
}
|
||||
|
||||
public static DialogResult Show(object title, object content, MessageBoxIcon icon,
|
||||
MessageBoxButtons buttons = MessageBoxButtons.OK)
|
||||
public static MessageBoxResult Show(
|
||||
object title,
|
||||
object content,
|
||||
MessageBoxImage img,
|
||||
MessageBoxButton buttons = MessageBoxButton.OK)
|
||||
{
|
||||
string msg = (title?.ToString().TrimEx() + BR2 + content?.ToString().TrimEx()).Trim();
|
||||
return MessageBox.Show(msg, Application.ProductName, buttons, icon);
|
||||
return MessageBoxEx.OpenMessageBox(null, msg, WindowTitle, buttons, img);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ namespace mpvnet
|
||||
|
||||
if ((App.Settings.UpdateCheckVersion != onlineVersion.ToString() ||
|
||||
showUpToDateMessage) && Msg.ShowQuestion(
|
||||
$"New version {onlineVersion} is available, update now?") == DialogResult.OK)
|
||||
$"New version {onlineVersion} is available, update now?") ==
|
||||
System.Windows.MessageBoxResult.OK)
|
||||
{
|
||||
string url = $"https://github.com/stax76/mpv.net/releases/download/{onlineVersion}/mpv.net-{onlineVersion}-portable.zip";
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace mpvnet
|
||||
App.UpdateWpfColors();
|
||||
|
||||
if (ThemeConf != GetThemeConf())
|
||||
Msg.ShowInfo("Changed theme settings require mpv.net being restarted.");
|
||||
MessageBox.Show("Changed theme settings require mpv.net being restarted.", "Info");
|
||||
}
|
||||
|
||||
string GetCompareString()
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace mpvnet
|
||||
DataGrid grid = (DataGrid)sender;
|
||||
|
||||
if (e.Command == DataGrid.DeleteCommand)
|
||||
if (Msg.ShowQuestion($"Confirm to delete: {(grid.SelectedItem as CommandItem).Input} ({(grid.SelectedItem as CommandItem).Path})") != System.Windows.Forms.DialogResult.OK)
|
||||
if (Msg.ShowQuestion($"Confirm to delete: {(grid.SelectedItem as CommandItem).Input} ({(grid.SelectedItem as CommandItem).Path})") != MessageBoxResult.OK)
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
224
src/WPF/MsgBox/MessageBoxEx.xaml
Normal file
224
src/WPF/MsgBox/MessageBoxEx.xaml
Normal file
@@ -0,0 +1,224 @@
|
||||
<Window x:Class="MsgBoxEx.MessageBoxEx"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="{Binding Path=MessageTitle}"
|
||||
MinHeight="100" MaxHeight="{Binding Path=ScreenHeight}"
|
||||
MinWidth="200" MaxWidth="{Binding Path=MaxFormWidth}"
|
||||
Background="{Binding Path=MessageBackground}"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ShowInTaskbar="False"
|
||||
WindowStyle="SingleBorderWindow"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="WidthAndHeight"
|
||||
SizeChanged="NotifiableWindow_SizeChanged"
|
||||
Loaded="Window_Loaded"
|
||||
Closing="Window_Closing"
|
||||
Height="Auto"
|
||||
Width="Auto"
|
||||
SourceInitialized="Window_SourceInitialized"
|
||||
>
|
||||
|
||||
<Window.Resources>
|
||||
<Style x:Key="GroupBoxExpanderToggleButtonStyle" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Grid SnapsToDevicePixels="False" Background="Transparent">
|
||||
<Ellipse HorizontalAlignment="Center" x:Name="circle" VerticalAlignment="Center" Width="15" Height="15"
|
||||
Fill="{DynamicResource ButtonNormalBackgroundFill}" Stroke="{Binding Path=MessageForeground}"/>
|
||||
<Ellipse Visibility="Hidden" HorizontalAlignment="Center" x:Name="shadow" VerticalAlignment="Center" Width="13" Height="13"
|
||||
Fill="{DynamicResource ExpanderShadowFill}"/>
|
||||
<Path SnapsToDevicePixels="false" x:Name="arrow" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
Stroke="{Binding Path=MessageForeground}" StrokeThickness="2" Data="M1,1 L4,4 7,1" />
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="true">
|
||||
<Setter Property="Data" TargetName="arrow" Value="M 1,4 L 4,1 L 7,4"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<SolidColorBrush x:Key="GroupBoxBorderBrush" Color="#D0D0BF"/>
|
||||
<SolidColorBrush x:Key="GroupBoxHeaderBrush" Color="White"/>
|
||||
<BorderGapMaskConverter x:Key="BorderGapMaskConverter"/>
|
||||
|
||||
<Style x:Key="linkCursor" TargetType="{x:Type TextBlock}" >
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True" >
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
</Window.Resources>
|
||||
|
||||
<Grid >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid x:Name="gridMsg" Grid.Row="0" Margin="10,10,0,10" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid x:Name="imgGrid" Grid.Column="0">
|
||||
<Image x:Name="imgMsgBoxIcon"
|
||||
Source="{Binding Path=MessageIcon}"
|
||||
Visibility="{Binding Path=ShowIcon}"
|
||||
Width="32" Height="32"
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Top"
|
||||
MouseLeftButtonUp="ImgMsgBoxIcon_MouseLeftButtonUp"
|
||||
>
|
||||
</Image>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" VerticalAlignment="Center" >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox Grid.Row="0" x:Name="textboxMessage"
|
||||
TextWrapping="Wrap" BorderThickness="0" IsReadOnly="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Padding="0,0,10,0"
|
||||
Background="Transparent"
|
||||
Foreground="{Binding Path=MessageForeground}"
|
||||
Text="{Binding Path=Message}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="1" x:Name="tbUrl" Margin="0,0,0,10"
|
||||
Visibility="{Binding Path=ShowUrl}"
|
||||
Foreground="{Binding Path=UrlForeground}"
|
||||
TextWrapping="Wrap"
|
||||
TextDecorations="Underline"
|
||||
HorizontalAlignment="Center"
|
||||
MouseLeftButtonUp="TbUrl_MouseLeftButtonUp"
|
||||
Style="{StaticResource linkCursor}"
|
||||
/>
|
||||
|
||||
<CheckBox Grid.Row="2" Margin="0,0,0,10" HorizontalAlignment="Center"
|
||||
Visibility="{Binding Path=ShowCheckBox}"
|
||||
Content="{Binding Path=CheckBoxData.CheckBoxText}"
|
||||
IsChecked="{Binding Path=CheckBoxData.CheckBoxIsChecked}"
|
||||
Foreground="{Binding Path=MessageForeground}"></CheckBox>
|
||||
|
||||
<Border x:Name="stackButtons" Grid.Row="3" Padding="10"
|
||||
Background="{Binding Path=ButtonBackground}" >
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" >
|
||||
<Button x:Name="btnOK" Content=" OK " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Height="25"
|
||||
Visibility="{Binding Path=ShowOk}"
|
||||
IsDefault="{Binding Path=IsDefaultOK}"
|
||||
Click="BtnOK_Click" />
|
||||
<Button x:Name="btnYes" Content=" Yes " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowYes}"
|
||||
IsDefault="{Binding Path=IsDefaultYes}"
|
||||
Click="BtnYes_Click"/>
|
||||
<Button x:Name="btnNo" Content=" No " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowNo}"
|
||||
IsDefault="{Binding Path=IsDefaultNo}"
|
||||
Click="BtnNo_Click"/>
|
||||
<Button x:Name="btnAbort" Content=" Abort " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowAbort}"
|
||||
IsDefault="{Binding Path=IsDefaultAbort}"
|
||||
Click="BtnAbort_Click"/>
|
||||
<Button x:Name="btnRetry" Content=" Retry " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowRetry}"
|
||||
IsDefault="{Binding Path=IsDefaultRetry}"
|
||||
Click="BtnRetry_Click"/>
|
||||
<Button x:Name="btnIgnore" Content=" Ignore " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowIgnore}"
|
||||
IsDefault="{Binding Path=IsDefaultIgnore}"
|
||||
Click="BtnIgnore_Click"/>
|
||||
<Button x:Name="btnCancel" Content=" Cancel " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowCancel}"
|
||||
IsDefault="{Binding Path=IsDefaultCancel}"
|
||||
Click="BtnCancel_Click"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Expander Grid.Row="4" Header="Details"
|
||||
IsExpanded="{Binding Path=Expanded}"
|
||||
Visibility="{Binding Path=ShowDetailsBtn}"
|
||||
>
|
||||
<Expander.Template>
|
||||
<ControlTemplate TargetType="{x:Type Expander}">
|
||||
<Grid SnapsToDevicePixels="true">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="6" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="6" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="6" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid SnapsToDevicePixels="False" Background="Transparent" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton Grid.Column="0" MinHeight="0" MinWidth="0" Name="HeaderToggle"
|
||||
Style="{StaticResource GroupBoxExpanderToggleButtonStyle}"
|
||||
IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
<ContentPresenter ContentSource="Header" RecognizesAccessKey="true"
|
||||
TextElement.Foreground="{Binding ElementName=textboxMessage, Path=Foreground}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Margin="3,0,0,0" />
|
||||
</Grid>
|
||||
<ContentPresenter x:Name="ExpandSite" Visibility="Collapsed" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsExpanded" Value="true">
|
||||
<Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="false">
|
||||
<Setter Property="Foreground" Value="{Binding Path=MessageForeground}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="true">
|
||||
<Setter Property="Foreground" Value="{Binding Path=MessageForeground}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Expander.Template>
|
||||
<TextBox x:Name="textboxDetails" Grid.Row="2" Margin="0,10,0,0" Text="{Binding Path=DetailsText}" Height="100"
|
||||
TextWrapping="Wrap" BorderThickness="0" IsReadOnly="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Padding="0,0,10,0"
|
||||
Background="Transparent"
|
||||
Foreground="{Binding Path=MessageForeground}"
|
||||
/>
|
||||
</Expander>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
926
src/WPF/MsgBox/MessageBoxEx.xaml.cs
Normal file
926
src/WPF/MsgBox/MessageBoxEx.xaml.cs
Normal file
@@ -0,0 +1,926 @@
|
||||
|
||||
using mpvnet;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Media;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace MsgBoxEx
|
||||
{
|
||||
public partial class MessageBoxEx : Window, INotifyPropertyChanged
|
||||
{
|
||||
#region INotifyPropertyChanged
|
||||
|
||||
private bool isModified = false;
|
||||
|
||||
public bool IsModified {
|
||||
get { return isModified; }
|
||||
set {
|
||||
if (value != isModified)
|
||||
{
|
||||
isModified = true;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
if (PropertyChanged != null)
|
||||
{
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
if (propertyName != "IsModified")
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion INotifyPropertyChanged
|
||||
|
||||
private const string _DEFAULT_CAPTION = "Application Message";
|
||||
|
||||
#region fields
|
||||
|
||||
private double screenHeight;
|
||||
private string message;
|
||||
private string messageTitle;
|
||||
private MessageBoxButton? buttons;
|
||||
private MessageBoxResult messageResult;
|
||||
private MessageBoxButtonEx? buttonsEx;
|
||||
private MessageBoxResultEx messageResultEx;
|
||||
private ImageSource messageIcon;
|
||||
private MessageBoxImage msgBoxImage;
|
||||
private double buttonWidth = 0d;
|
||||
private bool expanded = false;
|
||||
private bool isDefaultOK;
|
||||
private bool isDefaultCancel;
|
||||
private bool isDefaultYes;
|
||||
private bool isDefaultNo;
|
||||
private bool isDefaultAbort;
|
||||
private bool isDefaultRetry;
|
||||
private bool isDefaultIgnore;
|
||||
|
||||
private bool usingExButtons = false;
|
||||
|
||||
#endregion fields
|
||||
|
||||
#region properties
|
||||
|
||||
public double ScreenHeight {
|
||||
get { return screenHeight; }
|
||||
set {
|
||||
if (value != screenHeight)
|
||||
{
|
||||
screenHeight = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Message {
|
||||
get { return message; }
|
||||
set {
|
||||
if (value != message)
|
||||
{
|
||||
message = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string MessageTitle {
|
||||
get { return messageTitle; }
|
||||
set {
|
||||
if (value != messageTitle)
|
||||
{
|
||||
messageTitle = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MessageBoxResult MessageResult { get { return this.messageResult; } set { this.messageResult = value; } }
|
||||
public MessageBoxResultEx MessageResultEx { get { return this.messageResultEx; } set { this.messageResultEx = value; } }
|
||||
|
||||
public MessageBoxButton? Buttons {
|
||||
get { return buttons; }
|
||||
set {
|
||||
if (value != buttons)
|
||||
{
|
||||
buttons = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged("ShowOk");
|
||||
NotifyPropertyChanged("ShowCancel");
|
||||
NotifyPropertyChanged("ShowYes");
|
||||
NotifyPropertyChanged("ShowNo");
|
||||
}
|
||||
}
|
||||
}
|
||||
public MessageBoxButtonEx? ButtonsEx {
|
||||
get { return buttonsEx; }
|
||||
set {
|
||||
if (value != buttonsEx)
|
||||
{
|
||||
buttonsEx = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged("ShowOk");
|
||||
NotifyPropertyChanged("ShowCancel");
|
||||
NotifyPropertyChanged("ShowYes");
|
||||
NotifyPropertyChanged("ShowNo");
|
||||
NotifyPropertyChanged("ShowAbort");
|
||||
NotifyPropertyChanged("ShowRetry");
|
||||
NotifyPropertyChanged("ShowIgnore");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Visibility ShowOk => (!usingExButtons && Buttons == MessageBoxButton.OK ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.OKCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.OK ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.OKCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowCancel => (!usingExButtons && Buttons == MessageBoxButton.OKCancel ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.OKCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.RetryCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowYes => (!usingExButtons && Buttons == MessageBoxButton.YesNo ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNo ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNoCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowNo => (!usingExButtons && Buttons == MessageBoxButton.YesNo ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNo ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNoCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowRetry => (usingExButtons && ButtonsEx == MessageBoxButtonEx.AbortRetryIgnore ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.RetryCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowAbort => (usingExButtons && ButtonsEx == MessageBoxButtonEx.AbortRetryIgnore)
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowIgnore => (usingExButtons && ButtonsEx == MessageBoxButtonEx.AbortRetryIgnore)
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowIcon => (MessageIcon != null) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public ImageSource MessageIcon {
|
||||
get => messageIcon;
|
||||
set {
|
||||
if (value != messageIcon)
|
||||
{
|
||||
messageIcon = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double ButtonWidth {
|
||||
get => buttonWidth;
|
||||
set {
|
||||
if (value != buttonWidth)
|
||||
{
|
||||
buttonWidth = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Expanded {
|
||||
get => expanded;
|
||||
set {
|
||||
if (value != expanded)
|
||||
{
|
||||
expanded = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultOK {
|
||||
get => isDefaultOK;
|
||||
set {
|
||||
if (value != isDefaultOK)
|
||||
{
|
||||
isDefaultOK = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultCancel {
|
||||
get => isDefaultCancel;
|
||||
set {
|
||||
if (value != isDefaultCancel)
|
||||
{
|
||||
isDefaultCancel = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultYes {
|
||||
get => isDefaultYes;
|
||||
set {
|
||||
if (value != isDefaultYes)
|
||||
{
|
||||
isDefaultYes = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultNo {
|
||||
get => isDefaultNo;
|
||||
set {
|
||||
if (value != isDefaultNo)
|
||||
{
|
||||
isDefaultNo = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultAbort {
|
||||
get => isDefaultAbort;
|
||||
set {
|
||||
if (value != isDefaultAbort)
|
||||
{
|
||||
isDefaultAbort = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultRetry {
|
||||
get => isDefaultRetry;
|
||||
set {
|
||||
if (value != isDefaultRetry)
|
||||
{ isDefaultRetry = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultIgnore {
|
||||
get => isDefaultIgnore;
|
||||
set {
|
||||
if (value != isDefaultIgnore)
|
||||
{
|
||||
isDefaultIgnore = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion properties
|
||||
|
||||
#region constructors
|
||||
|
||||
private MessageBoxEx()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
LargestButtonWidth();
|
||||
}
|
||||
|
||||
public MessageBoxEx(string msg, string title, MessageBoxButton buttons = MessageBoxButton.OK,
|
||||
MessageBoxImage image = MessageBoxImage.None)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
Init(msg, title, buttons, image);
|
||||
}
|
||||
|
||||
public MessageBoxEx(string msg, string title, MessageBoxButtonEx buttons = MessageBoxButtonEx.OK,
|
||||
MessageBoxImage image = MessageBoxImage.None)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
Init(msg, title, buttons, image);
|
||||
}
|
||||
|
||||
#endregion constructors
|
||||
|
||||
#region non-static methods
|
||||
|
||||
protected virtual void Init(string msg, string title, MessageBoxButton buttons, MessageBoxImage image)
|
||||
{
|
||||
if (Theme.Current != null)
|
||||
{
|
||||
MessageForeground = Theme.Current.GetBrush("heading");
|
||||
MessageBackground = Theme.Current.GetBrush("background");
|
||||
ButtonBackground = Theme.Current.GetBrush("highlight");
|
||||
}
|
||||
|
||||
InitTop(msg, title);
|
||||
usingExButtons = false;
|
||||
ButtonsEx = null;
|
||||
Buttons = buttons;
|
||||
SetButtonTemplates();
|
||||
InitBottom(image);
|
||||
FindDefaultButton(staticButtonDefault);
|
||||
}
|
||||
|
||||
protected virtual void Init(string msg, string title, MessageBoxButtonEx buttons, MessageBoxImage image)
|
||||
{
|
||||
InitTop(msg, title);
|
||||
usingExButtons = true;
|
||||
Buttons = null;
|
||||
ButtonsEx = buttons;
|
||||
SetButtonTemplates();
|
||||
InitBottom(image);
|
||||
FindDefaultButtonEx(staticButtonDefault);
|
||||
}
|
||||
|
||||
void InitTop(string msg, string title)
|
||||
{
|
||||
// determine whether or not to show the details pane and checkbox
|
||||
ShowDetailsBtn = (string.IsNullOrEmpty(DetailsText)) ? Visibility.Collapsed : Visibility.Visible;
|
||||
ShowCheckBox = (CheckBoxData == null) ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
// Well, the binding for family/size don't appear to be working, so I have to set them
|
||||
// manually. Weird...
|
||||
FontFamily = MsgFontFamily;
|
||||
FontSize = MsgFontSize;
|
||||
LargestButtonWidth();
|
||||
|
||||
// determine the screen area height, and the height of the textblock
|
||||
ScreenHeight = SystemParameters.WorkArea.Height - 150;
|
||||
|
||||
// configure the form based on specified criteria
|
||||
Message = msg;
|
||||
MessageTitle = (string.IsNullOrEmpty(title.Trim())) ? _DEFAULT_CAPTION : title;
|
||||
|
||||
// url (if specified)
|
||||
if (Url != null)
|
||||
{
|
||||
tbUrl.Text = (string.IsNullOrEmpty(UrlDisplayName)) ? Url.ToString() : UrlDisplayName;
|
||||
tbUrl.ToolTip = new ToolTip() { Content = Url.ToString() };
|
||||
}
|
||||
}
|
||||
|
||||
private void InitBottom(MessageBoxImage image)
|
||||
{
|
||||
// set the form's colors (you can also set these colors in your program's startup code
|
||||
// (either in app.xaml.cs or MainWindow.cs) before you use the MessageBox for the
|
||||
// first time
|
||||
MessageBackground = (MessageBackground == null) ? new SolidColorBrush(Colors.White) : MessageBackground;
|
||||
MessageForeground = (MessageForeground == null) ? new SolidColorBrush(Colors.Black) : MessageForeground;
|
||||
ButtonBackground = (ButtonBackground == null) ? new SolidColorBrush(ColorFromString("#cdcdcd")) : ButtonBackground;
|
||||
|
||||
MessageIcon = null;
|
||||
|
||||
msgBoxImage = image;
|
||||
|
||||
if (DelegateObj != null)
|
||||
{
|
||||
Style style = (Style)(this.FindResource("ImageOpacityChanger"));
|
||||
|
||||
if (style != null)
|
||||
{
|
||||
imgMsgBoxIcon.Style = style;
|
||||
|
||||
if (!string.IsNullOrEmpty(DelegateToolTip))
|
||||
{
|
||||
ToolTip tooltip = new ToolTip() { Content = DelegateToolTip };
|
||||
// for some reason, Image elements can't do tooltips, so I assign the tootip
|
||||
// to the parent grid. This seems to work fine.
|
||||
imgGrid.ToolTip = tooltip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// multiple images have the same ordinal value, and are indicated in the comments below.
|
||||
// WTF Microsoft?
|
||||
switch ((int)image)
|
||||
{
|
||||
case 16: // MessageBoxImage.Error, MessageBoxImage.Stop, MessageBox.Image.Hand
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Error);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Hand.Play();
|
||||
}
|
||||
break;
|
||||
|
||||
case 64: // MessageBoxImage.Information, MessageBoxImage.Asterisk
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Information);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Asterisk.Play();
|
||||
}
|
||||
break;
|
||||
|
||||
case 32: // MessageBoxImage.Question
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Question);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Question.Play();
|
||||
}
|
||||
break;
|
||||
|
||||
case 48: // MessageBoxImage.Warning, MessageBoxImage.Exclamation
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Warning);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Exclamation.Play();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
MessageIcon = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public ImageSource GetIcon(Icon icon)
|
||||
{
|
||||
BitmapSource image = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
|
||||
return image;
|
||||
}
|
||||
|
||||
protected virtual void CenterInScreen()
|
||||
{
|
||||
double width = ActualWidth;
|
||||
double height = ActualHeight;
|
||||
Left = (SystemParameters.WorkArea.Width - width) / 2 + SystemParameters.WorkArea.Left;
|
||||
Top = (SystemParameters.WorkArea.Height - height) / 2 + SystemParameters.WorkArea.Top;
|
||||
}
|
||||
|
||||
protected void LargestButtonWidth()
|
||||
{
|
||||
Typeface typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
|
||||
|
||||
StackPanel panel = (StackPanel)stackButtons.Child;
|
||||
double width = 0;
|
||||
string largestName = string.Empty;
|
||||
foreach (Button button in panel.Children)
|
||||
{
|
||||
// Using the FormattedText object
|
||||
// will strip whitespace before measuring the text, so we convert spaces to double
|
||||
// hyphens to compensate (I like to pad button Content with a leading and trailing
|
||||
// space) so that the button is wide enough to present a more padded appearance.
|
||||
FormattedText formattedText = new FormattedText(
|
||||
(button.Name == "btnDetails") ? "--Details--" : ((string)(button.Content)).Replace(" ", "--"),
|
||||
CultureInfo.CurrentUICulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
FontSize = FontSize,
|
||||
System.Windows.Media.Brushes.Black,
|
||||
VisualTreeHelper.GetDpi(this).PixelsPerDip);
|
||||
|
||||
if (width < formattedText.Width)
|
||||
largestName = button.Name;
|
||||
|
||||
width = Math.Max(width, formattedText.Width);
|
||||
}
|
||||
ButtonWidth = Math.Ceiling(width/*width + polyArrow.Width+polyArrow.Margin.Right+Margin.Left*/);
|
||||
}
|
||||
|
||||
void SetButtonTemplates()
|
||||
{
|
||||
// set the button template (if specified)
|
||||
if (!string.IsNullOrEmpty(ButtonTemplateName))
|
||||
{
|
||||
bool foundResource = true;
|
||||
|
||||
try
|
||||
{
|
||||
FindResource(ButtonTemplateName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
foundResource = false;
|
||||
}
|
||||
|
||||
if (foundResource)
|
||||
{
|
||||
btnOK.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnYes.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnNo.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnCancel.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnAbort.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnRetry.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnIgnore.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FindDefaultButtonEx(MessageBoxButtonDefault buttonDefault)
|
||||
{
|
||||
// determine default button
|
||||
IsDefaultOK = false;
|
||||
IsDefaultCancel = false;
|
||||
IsDefaultYes = false;
|
||||
IsDefaultNo = false;
|
||||
IsDefaultAbort = false;
|
||||
IsDefaultRetry = false;
|
||||
IsDefaultIgnore = false;
|
||||
|
||||
if (buttonDefault != MessageBoxButtonDefault.None)
|
||||
{
|
||||
switch (ButtonsEx)
|
||||
{
|
||||
case MessageBoxButtonEx.OK:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButtonEx.OKCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.OK:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNoCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
break;
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button3:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNo:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.RetryCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Retry:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultRetry = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultRetry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.AbortRetryIgnore:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Abort:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultAbort = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Retry:
|
||||
IsDefaultRetry = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button3:
|
||||
case MessageBoxButtonDefault.Ignore:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultIgnore = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultAbort = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FindDefaultButton(MessageBoxButtonDefault buttonDefault)
|
||||
{
|
||||
// determine default button
|
||||
IsDefaultOK = false;
|
||||
IsDefaultCancel = false;
|
||||
IsDefaultYes = false;
|
||||
IsDefaultNo = false;
|
||||
IsDefaultAbort = false;
|
||||
IsDefaultRetry = false;
|
||||
IsDefaultIgnore = false;
|
||||
|
||||
if (buttonDefault != MessageBoxButtonDefault.None)
|
||||
{
|
||||
switch (Buttons)
|
||||
{
|
||||
case MessageBoxButton.OK:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButton.OKCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.OK:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
break;
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button3:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButton.YesNo:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion non-static methods
|
||||
|
||||
#region event handlers
|
||||
|
||||
#region buttons
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the OK button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnOK_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.OK;
|
||||
this.MessageResultEx = MessageBoxResultEx.OK;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the Yes button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnYes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.Yes;
|
||||
this.MessageResultEx = MessageBoxResultEx.Yes;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the No button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnNo_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.No;
|
||||
this.MessageResultEx = MessageBoxResultEx.No;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
private void BtnAbort_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.None;
|
||||
this.MessageResultEx = MessageBoxResultEx.Abort;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
private void BtnRetry_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.None;
|
||||
this.MessageResultEx = MessageBoxResultEx.Retry;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
private void BtnIgnore_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.None;
|
||||
this.MessageResultEx = MessageBoxResultEx.Ignore;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the Cancel button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnCancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.Cancel;
|
||||
this.MessageResultEx = MessageBoxResultEx.Cancel;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
#endregion buttons
|
||||
|
||||
void NotifiableWindow_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
CenterInScreen();
|
||||
}
|
||||
|
||||
void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
imgMsgBoxIcon.ToolTip = (msgBoxImage == MessageBoxImage.Error) ? MsgBoxIconToolTip : null;
|
||||
}
|
||||
|
||||
void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
DetailsText = null;
|
||||
CheckBoxData = null;
|
||||
staticButtonDefault = MessageBoxButtonDefault.Forms;
|
||||
|
||||
if (MessageResult == MessageBoxResult.None)
|
||||
{
|
||||
if (usingExButtons)
|
||||
{
|
||||
switch (ButtonsEx)
|
||||
{
|
||||
case MessageBoxButtonEx.OK:
|
||||
MessageResultEx = MessageBoxResultEx.OK;
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNoCancel:
|
||||
case MessageBoxButtonEx.OKCancel:
|
||||
case MessageBoxButtonEx.RetryCancel:
|
||||
case MessageBoxButtonEx.AbortRetryIgnore:
|
||||
MessageResultEx = MessageBoxResultEx.Cancel;
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNo:
|
||||
MessageResultEx = MessageBoxResultEx.No;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (Buttons)
|
||||
{
|
||||
case MessageBoxButton.OK:
|
||||
MessageResult = MessageBoxResult.OK;
|
||||
break;
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
case MessageBoxButton.OKCancel:
|
||||
MessageResult = MessageBoxResult.Cancel;
|
||||
break;
|
||||
case MessageBoxButton.YesNo:
|
||||
MessageResult = MessageBoxResult.No;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImgMsgBoxIcon_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (DelegateObj != null && msgBoxImage == MessageBoxImage.Error && Buttons == MessageBoxButton.OK)
|
||||
{
|
||||
DelegateObj.PerformAction(Message);
|
||||
|
||||
if (ExitAfterErrorAction)
|
||||
{
|
||||
MessageResult = MessageBoxResult.None;
|
||||
DialogResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TbUrl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ProcessHelp.ShellExecute(Url.ToString());
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
|
||||
|
||||
private const uint MF_BYCOMMAND = 0x00000000;
|
||||
private const uint MF_GRAYED = 0x00000001;
|
||||
private const uint SC_CLOSE = 0xF060;
|
||||
|
||||
void Window_SourceInitialized(object sender, EventArgs e)
|
||||
{
|
||||
if (!enableCloseButton)
|
||||
{
|
||||
var hWnd = new WindowInteropHelper(this);
|
||||
var sysMenu = GetSystemMenu(hWnd.Handle, false);
|
||||
EnableMenuItem(sysMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion event handlers
|
||||
}
|
||||
}
|
||||
233
src/WPF/MsgBox/MessageBoxExStatic.cs
Normal file
233
src/WPF/MsgBox/MessageBoxExStatic.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing.Text;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MsgBoxEx
|
||||
{
|
||||
public partial class MessageBoxEx : Window, INotifyPropertyChanged
|
||||
{
|
||||
#region static fields
|
||||
|
||||
private static double screenWidth = SystemParameters.WorkArea.Width - 100;
|
||||
|
||||
private static bool enableCloseButton = true;
|
||||
private static bool isSilent = false;
|
||||
private static List<string> installedFonts = new List<string>();
|
||||
public static MessageBoxButtonDefault staticButtonDefault;
|
||||
|
||||
#endregion static fields
|
||||
|
||||
#region static properties
|
||||
|
||||
public static Color DefaultUrlForegroundColor => Colors.Blue;
|
||||
|
||||
private static string MsgBoxIconToolTip { get; set; }
|
||||
|
||||
protected static MsgBoxExDelegate DelegateObj { get; set; }
|
||||
protected static bool ExitAfterErrorAction { get; set; }
|
||||
|
||||
public static ContentControl ParentWindow { get; set; }
|
||||
|
||||
public static string ButtonTemplateName { get; set; }
|
||||
|
||||
public static Brush MessageBackground { get; set; }
|
||||
|
||||
public static Brush MessageForeground { get; set; }
|
||||
|
||||
public static Brush ButtonBackground { get; set; }
|
||||
|
||||
public static double MaxFormWidth { get; set; } = screenWidth;
|
||||
|
||||
public static Visibility ShowDetailsBtn { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public static string DetailsText { get; set; }
|
||||
|
||||
public static Visibility ShowCheckBox { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public static MsgBoxExCheckBoxData CheckBoxData { get; set; } = null;
|
||||
|
||||
public static FontFamily MsgFontFamily { get; set; } = new FontFamily("Segoe UI");
|
||||
|
||||
public static double MsgFontSize { get; set; } = 12;
|
||||
|
||||
public static Uri Url { get; set; } = null;
|
||||
|
||||
public static Visibility ShowUrl { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public static string UrlDisplayName { get; set; } = null;
|
||||
|
||||
public static SolidColorBrush UrlForeground { get; set; } = new SolidColorBrush(DefaultUrlForegroundColor);
|
||||
|
||||
public static string DelegateToolTip { get; set; }
|
||||
|
||||
#endregion static properties
|
||||
|
||||
#region Show and ShowEx
|
||||
|
||||
public static MessageBoxResult OpenMessageBox(
|
||||
Window owner, string msg, string title, MessageBoxButton buttons, MessageBoxImage image)
|
||||
{
|
||||
//if (owner == null)
|
||||
//{
|
||||
// owner = (Application.Current.MainWindow.Visibility == Visibility.Visible) ? Application.Current.MainWindow : null;
|
||||
//}
|
||||
|
||||
MessageBoxEx form = new MessageBoxEx(msg, title, buttons, image) /*{ Owner = owner }*/;
|
||||
|
||||
form.ShowDialog();
|
||||
return form.MessageResult;
|
||||
}
|
||||
|
||||
public static MessageBoxResultEx OpenMessageBox(Window owner, string msg, string title, MessageBoxButtonEx buttons, MessageBoxImage image)
|
||||
{
|
||||
//if (owner == null)
|
||||
//{
|
||||
// owner = (Application.Current.MainWindow.Visibility == Visibility.Visible) ? Application.Current.MainWindow : null;
|
||||
//}
|
||||
MessageBoxEx form = new MessageBoxEx(msg, title, buttons, image) /*{ Owner = owner }*/;
|
||||
form.ShowDialog();
|
||||
return form.MessageResultEx;
|
||||
}
|
||||
|
||||
#endregion Show and ShowEx
|
||||
|
||||
#region static configuration methods
|
||||
|
||||
public static void SetMessageBackground(Color color)
|
||||
{
|
||||
try
|
||||
{
|
||||
MessageBackground = new SolidColorBrush(color);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetMessageForeground(Color color)
|
||||
{
|
||||
try
|
||||
{
|
||||
MessageForeground = new SolidColorBrush(color);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetButtonBackground(System.Windows.Media.Color color)
|
||||
{
|
||||
try
|
||||
{
|
||||
ButtonBackground = new SolidColorBrush(color);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static Color ColorFromString(string colorString)
|
||||
{
|
||||
Color wpfColor = System.Windows.Media.Colors.Black;
|
||||
|
||||
try
|
||||
{
|
||||
wpfColor = (Color)ColorConverter.ConvertFromString(colorString);
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
return wpfColor;
|
||||
}
|
||||
|
||||
public static void SetFont()
|
||||
{
|
||||
MsgFontFamily = Application.Current.MainWindow.FontFamily;
|
||||
MsgFontSize = Application.Current.MainWindow.FontSize;
|
||||
}
|
||||
|
||||
public static void SetFont(ContentControl parent)
|
||||
{
|
||||
MsgFontFamily = parent.FontFamily;
|
||||
MsgFontSize = parent.FontSize;
|
||||
}
|
||||
|
||||
public static void SetFont(string familyName, double size)
|
||||
{
|
||||
if (!IsFontFamilyValid(familyName))
|
||||
if (!string.IsNullOrEmpty(familyName))
|
||||
MsgFontFamily = new System.Windows.Media.FontFamily(familyName);
|
||||
MsgFontSize = Math.Max(1.0, size);
|
||||
}
|
||||
|
||||
private static bool IsFontFamilyValid(string name)
|
||||
{
|
||||
if (installedFonts.Count == 0)
|
||||
using (InstalledFontCollection fontsCollection = new InstalledFontCollection())
|
||||
installedFonts = (from x in fontsCollection.Families select x.Name).ToList();
|
||||
return installedFonts.Contains(name);
|
||||
}
|
||||
|
||||
public static void SetButtonTemplateName(string name)
|
||||
{
|
||||
ButtonTemplateName = name;
|
||||
}
|
||||
|
||||
public static void SetMaxFormWidth(double value)
|
||||
{
|
||||
MaxFormWidth = Math.Max(value, 300);
|
||||
double minWidth = 300;
|
||||
MaxFormWidth = Math.Max(minWidth, Math.Min(value, screenWidth));
|
||||
}
|
||||
|
||||
public static void ResetToDefaults()
|
||||
{
|
||||
MsgFontSize = 12d;
|
||||
MsgFontFamily = new System.Windows.Media.FontFamily("Segoe UI");
|
||||
DelegateObj = null;
|
||||
DetailsText = null;
|
||||
MessageForeground = null;
|
||||
MessageBackground = null;
|
||||
ButtonBackground = null;
|
||||
ParentWindow = null;
|
||||
isSilent = false;
|
||||
enableCloseButton = true;
|
||||
ButtonTemplateName = null;
|
||||
MsgBoxIconToolTip = null;
|
||||
ShowCheckBox = Visibility.Collapsed;
|
||||
CheckBoxData = null;
|
||||
ExitAfterErrorAction = false;
|
||||
MaxFormWidth = 800;
|
||||
Url = null;
|
||||
ShowUrl = Visibility.Collapsed;
|
||||
UrlDisplayName = null;
|
||||
UrlForeground = new SolidColorBrush(DefaultUrlForegroundColor);
|
||||
staticButtonDefault = MessageBoxButtonDefault.Forms;
|
||||
}
|
||||
|
||||
public static void EnableCloseButton(bool enable)
|
||||
{
|
||||
enableCloseButton = enable;
|
||||
}
|
||||
|
||||
public static void SetAsSilent(bool quiet)
|
||||
{
|
||||
isSilent = quiet;
|
||||
}
|
||||
|
||||
public static void SetDefaultButton(MessageBoxButtonDefault buttonDefault)
|
||||
{
|
||||
staticButtonDefault = buttonDefault;
|
||||
}
|
||||
|
||||
#endregion static configuration methods
|
||||
}
|
||||
}
|
||||
57
src/WPF/MsgBox/MsgBoxExCheckBoxData.cs
Normal file
57
src/WPF/MsgBox/MsgBoxExCheckBoxData.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MsgBoxEx
|
||||
{
|
||||
public class MsgBoxExCheckBoxData : INotifyPropertyChanged
|
||||
{
|
||||
private bool isModified = false;
|
||||
|
||||
public bool IsModified {
|
||||
get => isModified;
|
||||
set {
|
||||
if (value != isModified)
|
||||
{
|
||||
isModified = true;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
if (PropertyChanged != null)
|
||||
{
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
if (propertyName != "IsModified")
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
private string checkBoxText;
|
||||
private bool checkBoxIsChecked;
|
||||
|
||||
public string CheckBoxText {
|
||||
get => checkBoxText;
|
||||
set {
|
||||
if (value != checkBoxText)
|
||||
checkBoxText = value; NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckBoxIsChecked {
|
||||
get => checkBoxIsChecked;
|
||||
set {
|
||||
if (value != checkBoxIsChecked)
|
||||
{
|
||||
checkBoxIsChecked = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/WPF/MsgBox/MsgBoxExDelegate.cs
Normal file
18
src/WPF/MsgBox/MsgBoxExDelegate.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace MsgBoxEx
|
||||
{
|
||||
public abstract class MsgBoxExDelegate
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public string Details { get; set; }
|
||||
public DateTime MessageDate { get; set; }
|
||||
|
||||
public virtual MessageBoxResult PerformAction(string message, string details = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/WPF/MsgBox/MsgBoxExtendedFunctionality.cs
Normal file
25
src/WPF/MsgBox/MsgBoxExtendedFunctionality.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
namespace MsgBoxEx
|
||||
{
|
||||
public class MsgBoxExtendedFunctionality
|
||||
{
|
||||
public MessageBoxButtonDefault ButtonDefault { get; set; }
|
||||
public string DetailsText { get; set; }
|
||||
public MsgBoxExCheckBoxData CheckBoxData { get; set; }
|
||||
public MsgBoxExDelegate MessageDelegate { get; set; }
|
||||
public bool ExitAfterAction { get; set; }
|
||||
public string DelegateToolTip { get; set; }
|
||||
|
||||
public MsgBoxUrl URL { get; set; }
|
||||
|
||||
public MsgBoxExtendedFunctionality()
|
||||
{
|
||||
ButtonDefault = MessageBoxButtonDefault.Forms;
|
||||
DetailsText = null;
|
||||
CheckBoxData = null;
|
||||
MessageDelegate = null;
|
||||
URL = null;
|
||||
DelegateToolTip = "Click this icon for additional info/actions.";
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/WPF/MsgBox/MsgBoxUrl.cs
Normal file
18
src/WPF/MsgBox/MsgBoxUrl.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MsgBoxEx
|
||||
{
|
||||
public class MsgBoxUrl
|
||||
{
|
||||
public Uri URL { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public Color Foreground { get; set; }
|
||||
|
||||
public MsgBoxUrl()
|
||||
{
|
||||
Foreground = MessageBoxEx.DefaultUrlForegroundColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/WPF/MsgBox/MsgEnumerators.cs
Normal file
16
src/WPF/MsgBox/MsgEnumerators.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
namespace MsgBoxEx
|
||||
{
|
||||
public enum MessageBoxButtonEx { OK = 0, OKCancel, AbortRetryIgnore, YesNoCancel, YesNo, RetryCancel }
|
||||
|
||||
public enum MessageBoxResultEx { None = 0, OK, Cancel, Abort, Retry, Ignore, Yes, No }
|
||||
|
||||
public enum MessageBoxButtonDefault
|
||||
{
|
||||
OK, Cancel, Yes, No, Abort, Retry, Ignore, // specific button
|
||||
Button1, Button2, Button3, // button by ordinal left-to-right position
|
||||
MostPositive, LeastPositive, // button by positivity
|
||||
Forms, // button according to the Windows.Forms standard messagebox
|
||||
None // no default button
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,15 @@
|
||||
<Compile Include="WPF\HandyControl\Tools\AnimationHelper.cs" />
|
||||
<Compile Include="WPF\HandyControl\Tools\Extension\StringExtension.cs" />
|
||||
<Compile Include="WPF\HandyControl\Tools\RegexPatterns.cs" />
|
||||
<Compile Include="WPF\MsgBox\MessageBoxEx.xaml.cs">
|
||||
<DependentUpon>MessageBoxEx.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="WPF\MsgBox\MessageBoxExStatic.cs" />
|
||||
<Compile Include="WPF\MsgBox\MsgBoxExCheckBoxData.cs" />
|
||||
<Compile Include="WPF\MsgBox\MsgBoxExDelegate.cs" />
|
||||
<Compile Include="WPF\MsgBox\MsgBoxExtendedFunctionality.cs" />
|
||||
<Compile Include="WPF\MsgBox\MsgBoxUrl.cs" />
|
||||
<Compile Include="WPF\MsgBox\MsgEnumerators.cs" />
|
||||
<Compile Include="WPF\RelayCommand.cs" />
|
||||
<Compile Include="Misc\CSharpScriptHost.cs" />
|
||||
<Compile Include="Misc\Extension.cs" />
|
||||
@@ -117,6 +126,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="WPF\MsgBox\MessageBoxEx.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="WPF\SearchTextBoxUserControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
|
||||
Reference in New Issue
Block a user