JD2022-TU1/main/tools/framework/JD.MessageService/TaskDialog.cs

1083 lines
36 KiB
C#

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using JD.Kernel.Classes;
using JD.Kernel.StaticHelpers;
using JD.Kernel.Structs;
namespace JD.MessageService
{
/// <summary>
/// A wrapper around Vista's TaskDialog API
/// </summary>
internal static partial class TaskDialog
{
#region Constants
private static readonly Version RequiredOSVersion = new Version(6, 0, 5243);
#endregion
#region Properties
public static bool IsSupported
{
get
{
OperatingSystem os = Environment.OSVersion;
if (os.Platform != PlatformID.Win32NT)
{
return false;
}
return os.Version >= RequiredOSVersion;
}
}
#endregion
#region Methods
public static MessageDialog.Button Show(IWin32Window owner, MessageDialog dialog)
{
return Show(owner == null ? IntPtr.Zero : owner.Handle, dialog);
}
public static MessageDialog.Button Show(IntPtr hwnd, MessageDialog dialog)
{
Throw.InvalidOperationIf(!IsSupported, "TaskDialog API is only supported on Windows Vista and later");
using (new EnableThemingInScope())
using (var instance = new Instance(hwnd, dialog))
{
return instance.Show();
}
}
private static TaskDialogIcon ConvertIcon(MessageDialogIcon icon)
{
switch (icon)
{
case MessageDialogIcon.None:
return TaskDialogIcon.None;
case MessageDialogIcon.Information:
return TaskDialogIcon.Information;
case MessageDialogIcon.Warning:
return TaskDialogIcon.Warning;
case MessageDialogIcon.Error:
return TaskDialogIcon.Error;
case MessageDialogIcon.Shield:
return TaskDialogIcon.Shield;
default:
throw new NotImplementedException();
}
}
private static TaskDialogFlags ConvertFlags(Flags<MessageDialogFlags> flags, uint numButtons)
{
TaskDialogFlags result = TaskDialogFlags.None;
if (flags.Contains(MessageDialogFlags.UseCommandLinks) && numButtons > 0)
{
result |= TaskDialogFlags.TDF_USE_COMMAND_LINKS;
}
return result;
}
private static TaskDialogCommonButtons ConvertCommonButtons(MessageDialog dialog)
{
TaskDialogCommonButtons result = TaskDialogCommonButtons.None;
foreach (var button in dialog.Buttons)
{
if (button.IsCommonButton)
{
// Id corresponds directly
result |= ConvertCommonButton((MessageDialogCommonButton)button.Id);
}
}
return result;
}
private static int LocateDefaultButton(MessageDialog dialog)
{
foreach (var button in dialog.Buttons)
{
if (button.IsDefault)
{
return ToTaskDialogButtonId(button);
}
}
return 0;
}
private static int ToTaskDialogButtonId(MessageDialog.Button button)
{
if (button.IsCommonButton)
{
switch ((MessageDialogCommonButton)button.Id)
{
case MessageDialogCommonButton.Ok:
return 1;
case MessageDialogCommonButton.Cancel:
return 2;
case MessageDialogCommonButton.Retry:
return 4;
case MessageDialogCommonButton.Yes:
return 6;
case MessageDialogCommonButton.No:
return 7;
case MessageDialogCommonButton.Close:
return 8;
default:
throw new NotImplementedException();
}
}
return button.Id;
}
private static TaskDialogCommonButtons ConvertCommonButton(MessageDialogCommonButton button)
{
switch (button)
{
case MessageDialogCommonButton.Ok:
return TaskDialogCommonButtons.Ok;
case MessageDialogCommonButton.Yes:
return TaskDialogCommonButtons.Yes;
case MessageDialogCommonButton.No:
return TaskDialogCommonButtons.No;
case MessageDialogCommonButton.Retry:
return TaskDialogCommonButtons.Retry;
case MessageDialogCommonButton.Cancel:
return TaskDialogCommonButtons.Cancel;
case MessageDialogCommonButton.Close:
return TaskDialogCommonButtons.Cancel;
default:
throw new NotImplementedException();
}
}
private static void ConvertButtons(ref TaskDialogConfig config, MessageDialog dialog)
{
var buttons = dialog.Buttons.Where(b => !b.IsCommonButton).ToList();
if (buttons.Count > 0)
{
int elementSize = Marshal.SizeOf(typeof(TaskDialogButton));
config.pButtons = Marshal.AllocHGlobal(elementSize * buttons.Count);
config.cButtons = (uint)buttons.Count;
IntPtr currentPtr = config.pButtons;
foreach (var button in buttons)
{
var marshalButton = ConvertButton(button);
Marshal.StructureToPtr(marshalButton, currentPtr, false);
currentPtr = (IntPtr)(currentPtr.ToInt64() + elementSize);
}
}
}
private static TaskDialogButton ConvertButton(MessageDialog.Button button)
{
var result = new TaskDialogButton
{
buttonId = button.Id,
buttonText = button.Text
};
if (!string.IsNullOrEmpty(button.Content))
{
result.buttonText += "\n" + button.Content;
}
return result;
}
private static int ConvertResult(int result)
{
if (result >= MessageDialog.CustomButtonStartId)
{
return result;
}
if (result == 8)
{
// Not part of DialogResult..
return (int)MessageDialogCommonButton.Close;
}
switch ((DialogResult)result)
{
case DialogResult.OK:
return (int)MessageDialogCommonButton.Ok;
case DialogResult.Yes:
return (int)MessageDialogCommonButton.Yes;
case DialogResult.No:
return (int)MessageDialogCommonButton.No;
case DialogResult.Retry:
return (int)MessageDialogCommonButton.Retry;
case DialogResult.Cancel:
return (int)MessageDialogCommonButton.Cancel;
default:
throw new NotImplementedException();
}
}
private static void CreateConfig(IntPtr hwnd, MessageDialog dialog, ref TaskDialogConfig config)
{
config.cbSize = (uint)Marshal.SizeOf(typeof(TaskDialogConfig));
config.hwndParent = hwnd;
config.dwCommonButtons = ConvertCommonButtons(dialog);
if (!string.IsNullOrEmpty(dialog.Caption))
{
config.pszWindowTitle = dialog.Caption;
}
config.MainIcon = new IconUnion((int)ConvertIcon(dialog.MainIcon));
if (!string.IsNullOrEmpty(dialog.MainMessage))
{
config.pszMainInstruction = dialog.MainMessage;
}
if (!string.IsNullOrEmpty(dialog.Content))
{
config.pszContent = dialog.Content;
}
ConvertButtons(ref config, dialog);
if (!string.IsNullOrEmpty(dialog.CheckBoxText))
{
config.pszVerificationText = dialog.CheckBoxText;
}
config.nDefaultButton = LocateDefaultButton(dialog);
config.dwFlags = ConvertFlags(dialog.Flags, config.cButtons);
if (dialog.EscapeKeyReturnsNull)
{
if (dialog.Buttons.FirstOrDefault(button => button.IsCommonButton && button.Id == (int)MessageDialogCommonButton.Cancel) != null)
{
throw new ApplicationException("The MessageDialog.EscapeKeyReturnsNull should never be true when a cancel button is available since the underlying API will necessarily return the cancel button when the user presses the escape key");
}
config.dwFlags |= TaskDialogFlags.TDF_ALLOW_DIALOG_CANCELLATION;
}
if (dialog.IsChecked)
{
config.dwFlags |= TaskDialogFlags.TDF_VERIFICATION_FLAG_CHECKED;
}
if (dialog.ProgressBarMaximum > 0)
{
config.dwFlags |= TaskDialogFlags.TDF_SHOW_PROGRESS_BAR;
}
if (dialog.ShouldTick)
{
config.dwFlags |= TaskDialogFlags.TDF_CALLBACK_TIMER;
}
if (dialog.PositionRelativeToParent)
{
config.dwFlags |= TaskDialogFlags.TDF_POSITION_RELATIVE_TO_WINDOW;
}
}
private static void DestroyConfig(ref TaskDialogConfig config)
{
if (config.pButtons != IntPtr.Zero)
{
IntPtr currentPtr = config.pButtons;
for (int i = 0; i < config.cButtons; i++)
{
Marshal.DestroyStructure(currentPtr, typeof(TaskDialogButton));
currentPtr = (IntPtr)(currentPtr.ToInt64() + Marshal.SizeOf(typeof(TaskDialogButton)));
}
Marshal.FreeHGlobal(config.pButtons);
}
}
#endregion
#region Dll Interop
[DllImport("ComCtl32.dll", CharSet = CharSet.Auto, PreserveSig = false)]
private static extern void TaskDialogIndirect([In] ref TaskDialogConfig pTaskConfig, [Out] out int pnButton, [Out] out int pnRadioButton, [Out] out bool pfVerificationFlagChecked);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, uint Msg, IntPtr wparam, IntPtr lparam);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
[GeneratedCode("PInvoke.net", "1.0")] // To shut up gendarme about large struct size
private struct TaskDialogConfig
{
/// <summary>
/// Size of the structure in bytes.
/// </summary>
public uint cbSize;
/// <summary>
/// Parent window handle.
/// </summary>
public IntPtr hwndParent;
/// <summary>
/// Module instance handle for resources.
/// </summary>
public IntPtr hInstance;
/// <summary>
/// Flags.
/// </summary>
public TaskDialogFlags dwFlags; // TASKDIALOG_FLAGS (TDF_XXX) flags
/// <summary>
/// Bit flags for commonly used buttons.
/// </summary>
public TaskDialogCommonButtons dwCommonButtons; // TASKDIALOG_COMMON_BUTTON (TDCBF_XXX) flags
/// <summary>
/// Window title.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string pszWindowTitle; // string or MAKEINTRESOURCE()
/// <summary>
/// The Main icon. Overloaded member. Can be string, a handle, a special value or a resource ID.
/// </summary>
public IconUnion MainIcon;
/// <summary>
/// Main Instruction.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string pszMainInstruction;
/// <summary>
/// Content.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string pszContent;
/// <summary>
/// Count of custom Buttons.
/// </summary>
public uint cButtons;
/// <summary>
/// Array of custom buttons.
/// </summary>
public IntPtr pButtons;
/// <summary>
/// ID of default button.
/// </summary>
public int nDefaultButton;
/// <summary>
/// Count of radio Buttons.
/// </summary>
public uint cRadioButtons;
/// <summary>
/// Array of radio buttons.
/// </summary>
public IntPtr pRadioButtons;
/// <summary>
/// ID of default radio button.
/// </summary>
public int nDefaultRadioButton;
/// <summary>
/// Text for verification check box. often "Don't ask be again".
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string pszVerificationText;
/// <summary>
/// Expanded Information.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string pszExpandedInformation;
/// <summary>
/// Text for expanded control.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string pszExpandedControlText;
/// <summary>
/// Text for expanded control.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string pszCollapsedControlText;
/// <summary>
/// Icon for the footer. An overloaded member link MainIcon.
/// </summary>
public IconUnion FooterIcon;
/// <summary>
/// Footer Text.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string pszFooter;
/// <summary>
/// Function pointer for callback.
/// </summary>
public TaskDialogCallback pfCallback;
/// <summary>
/// Data that will be passed to the call back.
/// </summary>
public IntPtr lpCallbackData;
/// <summary>
/// Width of the Task Dialog's area in DLU's.
/// </summary>
public uint cxWidth; // width of the Task Dialog's client area in DLU's. If 0, Task Dialog will calculate the ideal width.
}
// Packing must be set to 4 to make this work on 64-bit platforms.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
private struct TaskDialogButton
{
/// <summary>
/// The ID of the button. This value is returned by TaskDialog.Show when the button is clicked.
/// </summary>
public int buttonId;
/// <summary>
/// The string that appears on the button.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string buttonText;
}
[Flags]
private enum TaskDialogFlags
{
None = 0,
/// <summary>
/// Enable hyperlinks.
/// </summary>
TDF_ENABLE_HYPERLINKS = 0x0001,
/// <summary>
/// Use icon handle for main icon.
/// </summary>
TDF_USE_HICON_MAIN = 0x0002,
/// <summary>
/// Use icon handle for footer icon.
/// </summary>
TDF_USE_HICON_FOOTER = 0x0004,
/// <summary>
/// Allow dialog to be cancelled, even if there is no cancel button.
/// </summary>
TDF_ALLOW_DIALOG_CANCELLATION = 0x0008,
/// <summary>
/// Use command links rather than buttons.
/// </summary>
TDF_USE_COMMAND_LINKS = 0x0010,
/// <summary>
/// Use command links with no icons rather than buttons.
/// </summary>
TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020,
/// <summary>
/// Show expanded info in the footer area.
/// </summary>
TDF_EXPAND_FOOTER_AREA = 0x0040,
/// <summary>
/// Expand by default.
/// </summary>
TDF_EXPANDED_BY_DEFAULT = 0x0080,
/// <summary>
/// Start with verification flag already checked.
/// </summary>
TDF_VERIFICATION_FLAG_CHECKED = 0x0100,
/// <summary>
/// Show a progress bar.
/// </summary>
TDF_SHOW_PROGRESS_BAR = 0x0200,
/// <summary>
/// Show a marquee progress bar.
/// </summary>
TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400,
/// <summary>
/// Callback every 200 milliseconds.
/// </summary>
TDF_CALLBACK_TIMER = 0x0800,
/// <summary>
/// Center the dialog on the owner window rather than the monitor.
/// </summary>
TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000,
/// <summary>
/// Right to Left Layout.
/// </summary>
TDF_RTL_LAYOUT = 0x2000,
/// <summary>
/// No default radio button.
/// </summary>
TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000,
/// <summary>
/// Task Dialog can be minimized.
/// </summary>
TDF_CAN_BE_MINIMIZED = 0x8000
}
[Flags]
private enum TaskDialogCommonButtons
{
/// <summary>
/// No common buttons.
/// </summary>
None = 0,
/// <summary>
/// OK common button. If selected Task Dialog will return DialogResult.OK.
/// </summary>
Ok = 0x0001,
/// <summary>
/// Yes common button. If selected Task Dialog will return DialogResult.Yes.
/// </summary>
Yes = 0x0002,
/// <summary>
/// No common button. If selected Task Dialog will return DialogResult.No.
/// </summary>
No = 0x0004,
/// <summary>
/// Cancel common button. If selected Task Dialog will return DialogResult.Cancel.
/// If this button is specified, the dialog box will respond to typical cancel actions (Alt-F4 and Escape).
/// </summary>
Cancel = 0x0008,
/// <summary>
/// Retry common button. If selected Task Dialog will return DialogResult.Retry.
/// </summary>
Retry = 0x0010,
/// <summary>
/// Close common button. If selected Task Dialog will return this value.
/// </summary>
Close = 0x0020,
}
private enum TaskDialogIcon
{
/// <summary>
/// No Icon.
/// </summary>
None = 0,
/// <summary>
/// System warning icon.
/// </summary>
Warning = 0xFFFF, // MAKEINTRESOURCEW(-1)
/// <summary>
/// System Error icon.
/// </summary>
Error = 0xFFFE, // MAKEINTRESOURCEW(-2)
/// <summary>
/// System Information icon.
/// </summary>
Information = 0xFFFD, // MAKEINTRESOURCEW(-3)
/// <summary>
/// Shield icon.
/// </summary>
Shield = 0xFFFC, // MAKEINTRESOURCEW(-4)
}
// NOTE: We include a "spacer" so that the struct size varies on 64-bit architectures.
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
private struct IconUnion
{
internal IconUnion(int i)
{
spacer = IntPtr.Zero;
mainIcon = i;
}
[FieldOffset(0)]
private int mainIcon;
// This field is used to adjust the length of the structure on 32/64bit OS.
[FieldOffset(0)]
private IntPtr spacer;
}
private delegate int TaskDialogCallback([In] IntPtr hwnd, [In] uint msg, [In] UIntPtr wParam, [In] IntPtr lParam, [In] IntPtr refData);
/// <summary>Notifications returned by Task Dialogs to the callback.</summary>
/// <remarks>From CommCtrl.h.</remarks>
private enum TaskDialogNotification : uint
{
TDN_CREATED = 0,
TDN_NAVIGATED = 1,
TDN_BUTTON_CLICKED = 2, // wParam = Button ID
TDN_HYPERLINK_CLICKED = 3, // lParam = (LPCWSTR)pszHREF
TDN_TIMER = 4, // wParam = Milliseconds since dialog created or timer reset
TDN_DESTROYED = 5,
TDN_RADIO_BUTTON_CLICKED = 6, // wParam = Radio Button ID
TDN_DIALOG_CONSTRUCTED = 7,
TDN_VERIFICATION_CLICKED = 8, // wParam = 1 if checkbox checked, 0 if not, lParam is unused and always 0
TDN_HELP = 9,
TDN_EXPANDO_BUTTON_CLICKED = 10 // wParam = 0 (dialog is now collapsed), wParam != 0 (dialog is now expanded)
}
/// <summary>Messages that can be sent to Task Dialogs.</summary>
/// <remarks>From CommCtrl.h.</remarks>
private enum TaskDialogMessages : uint
{
TDM_NAVIGATE_PAGE = 0x0400 + 101,
TDM_CLICK_BUTTON = 0x0400 + 102, // wParam = Button ID
TDM_SET_MARQUEE_PROGRESS_BAR = 0x0400 + 103, // wParam = 0 (nonMarque) wParam != 0 (Marquee)
TDM_SET_PROGRESS_BAR_STATE = 0x0400 + 104, // wParam = new progress state
TDM_SET_PROGRESS_BAR_RANGE = 0x0400 + 105, // lParam = MAKELPARAM(nMinRange, nMaxRange)
TDM_SET_PROGRESS_BAR_POS = 0x0400 + 106, // wParam = new position
TDM_SET_PROGRESS_BAR_MARQUEE = 0x0400 + 107, // wParam = 0 (stop marquee), wParam != 0 (start marquee), lparam = speed (milliseconds between repaints)
TDM_SET_ELEMENT_TEXT = 0x0400 + 108, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR)
TDM_CLICK_RADIO_BUTTON = 0x0400 + 110, // wParam = Radio Button ID
TDM_ENABLE_BUTTON = 0x0400 + 111, // lParam = 0 (disable), lParam != 0 (enable), wParam = Button ID
TDM_ENABLE_RADIO_BUTTON = 0x0400 + 112, // lParam = 0 (disable), lParam != 0 (enable), wParam = Radio Button ID
TDM_CLICK_VERIFICATION = 0x0400 + 113, // wParam = 0 (unchecked), 1 (checked), lParam = 1 (set key focus)
TDM_UPDATE_ELEMENT_TEXT = 0x0400 + 114, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR)
TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE = 0x0400 + 115, // wParam = Button ID, lParam = 0 (elevation not required), lParam != 0 (elevation required)
TDM_UPDATE_ICON = 0x0400 + 116 // wParam = icon element (TASKDIALOG_ICON_ELEMENTS), lParam = new icon (hIcon if TDF_USE_HICON_* was set, PCWSTR otherwise)
}
private enum ProgressBarState : uint
{
PBST_NORMAL = 1,
PBST_ERROR = 2,
PBST_PAUSE = 3
}
private enum TaskDialogElement : uint
{
TDE_CONTENT,
TDE_EXPANDED_INFORMATION,
TDE_FOOTER,
TDE_MAIN_INSTRUCTION
}
#endregion
private class InstanceData : IDisposable
{
#region Variables
private readonly Instance m_owner;
private readonly IntPtr m_hwndParent;
private readonly MessageDialog m_dialog;
private TaskDialogConfig m_config;
private IntPtr m_customConfigPtr;
#endregion
#region Constructor
public InstanceData(Instance owner, IntPtr hwndParent, MessageDialog dialog)
{
m_owner = owner;
m_hwndParent = hwndParent;
m_dialog = dialog;
m_config = new TaskDialogConfig();
CreateConfig(hwndParent, dialog, ref m_config);
m_config.pfCallback = owner.Callback;
m_dialog.PropertyChanged += m_owner.WhenDialogPropertyChanged;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
void Dispose(bool disposing)
{
if (disposing)
{
m_dialog.PropertyChanged -= m_owner.WhenDialogPropertyChanged;
}
if (m_customConfigPtr != IntPtr.Zero)
{
Marshal.FreeHGlobal(m_customConfigPtr);
m_customConfigPtr = IntPtr.Zero;
}
DestroyConfig(ref m_config);
}
~InstanceData()
{
Dispose(false);
}
#endregion
#region Properties
public MessageDialog Dialog
{
get { return m_dialog; }
}
public IntPtr HwndParent
{
get { return m_hwndParent; }
}
public TaskDialogConfig Configuration
{
get { return m_config; }
}
public IntPtr ConfigurationPtr
{
get
{
if (m_customConfigPtr == IntPtr.Zero)
{
m_customConfigPtr = Marshal.AllocHGlobal(Marshal.SizeOf(m_config));
Marshal.StructureToPtr(m_config, m_customConfigPtr, false);
}
return m_customConfigPtr;
}
}
#endregion
}
private class Instance : IDisposable
{
#region Variables
private static readonly PropertyChangedHandlers ms_propertyChangedHandlers = new PropertyChangedHandlers();
private readonly ElementStrings m_elementStrings = new ElementStrings();
private InstanceData m_data;
private IntPtr m_hwnd;
#endregion
#region Constructor
static Instance()
{
ms_propertyChangedHandlers.Register(d => d.MainMessage, i => i.WhenMainMessageChanged);
ms_propertyChangedHandlers.Register(d => d.Content, i => i.WhenContentChanged);
}
public Instance(IntPtr hwndParent, MessageDialog dialog)
{
m_data = new InstanceData(this, hwndParent, dialog);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposing)
{
DisposableHelper.SafeDispose(ref m_data);
}
m_elementStrings.Clear();
}
~Instance()
{
Dispose(false);
}
#endregion
#region Properties
private MessageDialog Dialog
{
get { return m_data.Dialog; }
}
#endregion
#region Methods
public MessageDialog.Button Show()
{
var configuration = m_data.Configuration;
bool checkedResult;
int buttonIdResult;
int radioButtonIdResult;
TaskDialogIndirect(ref configuration, out buttonIdResult, out radioButtonIdResult, out checkedResult);
buttonIdResult = ConvertResult(buttonIdResult);
m_data.Dialog.IsChecked = checkedResult;
// The following expression will be unable to locate the button and will return null only in this very specific case:
// - No common button with MessageDialogCommonButton.Cancel has been specifically created in the dialog AND
// - The developer set MessageDialog.EscapeKeyReturnsNull on the MessageDialog AND
// - The user closed the dialog with the escape key
return m_data.Dialog.Buttons.FirstOrDefault(b => b.Id == buttonIdResult);
}
internal int Callback(IntPtr hwnd, uint msg, UIntPtr wParam, IntPtr lParam, IntPtr refData)
{
m_hwnd = hwnd;
switch ((TaskDialogNotification)msg)
{
case TaskDialogNotification.TDN_CREATED:
OnCreated(hwnd);
break;
case TaskDialogNotification.TDN_NAVIGATED:
OnCreated(hwnd);
CenterWindow.Center(hwnd);
break;
case TaskDialogNotification.TDN_TIMER:
OnTimer(hwnd, TimeSpan.FromMilliseconds(wParam.ToUInt64()));
return 1; // Reset ms count
}
return 0;
}
private void OnCreated(IntPtr hwnd)
{
// Disable buttons that asked for it
foreach (var button in Dialog.Buttons)
{
if (!button.Enabled)
{
SendMessageHelper(hwnd, TaskDialogMessages.TDM_ENABLE_BUTTON, ToTaskDialogButtonId(button), 0);
}
}
// Set progress bar maximum
if (Dialog.ProgressBarMaximum > 0)
{
var progressBarRange = MakeLParam(0, Dialog.ProgressBarMaximum);
SendMessageHelper(hwnd, TaskDialogMessages.TDM_SET_PROGRESS_BAR_RANGE, 0, progressBarRange);
SendMessageHelper(hwnd, TaskDialogMessages.TDM_SET_PROGRESS_BAR_STATE, (int)ProgressBarState.PBST_NORMAL, 0);
}
}
private void OnTimer(IntPtr hwnd, TimeSpan deltaTime)
{
MessageDialog.TickEventArgs tickEventArgs = new MessageDialog.TickEventArgs(deltaTime);
Dialog.OnTick(tickEventArgs);
if (Dialog.ProgressBarMaximum > 0 && tickEventArgs.CurrentProgress >= 0)
{
SendMessageHelper(hwnd, TaskDialogMessages.TDM_SET_PROGRESS_BAR_POS, tickEventArgs.CurrentProgress, 0);
}
if (tickEventArgs.NewDialog != null)
{
NavigateTo(hwnd, tickEventArgs.NewDialog);
}
}
private static long MakeLParam(int wLow, int wHigh)
{
return (((short)wHigh << 16) | (wLow & 0xffff));
}
private static void SendMessageHelper(IntPtr hwnd, TaskDialogMessages message, int wParam, long lParam)
{
SendMessage(hwnd, (uint)message, (IntPtr)wParam, new IntPtr(lParam));
}
internal void WhenDialogPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
Debug.Assert(m_hwnd != IntPtr.Zero);
ms_propertyChangedHandlers.Handle(e.PropertyName, this, m_hwnd);
}
private void SetText(IntPtr hwnd, TaskDialogElement element, string text)
{
IntPtr stringPtr = m_elementStrings.Update(element, text);
SendMessage(hwnd, (uint)TaskDialogMessages.TDM_SET_ELEMENT_TEXT, (IntPtr)element, stringPtr);
}
private void WhenMainMessageChanged(IntPtr hwnd)
{
SetText(hwnd, TaskDialogElement.TDE_MAIN_INSTRUCTION, Dialog.MainMessage);
}
private void WhenContentChanged(IntPtr hwnd)
{
SetText(hwnd, TaskDialogElement.TDE_CONTENT, Dialog.Content);
}
private void NavigateTo(IntPtr hwnd, MessageDialog newDialog)
{
m_hwnd = IntPtr.Zero;
IntPtr hwndParent = m_data.HwndParent;
DisposableHelper.SafeDispose(m_data);
m_data = new InstanceData(this, hwndParent, newDialog);
SendMessage(hwnd, (uint)TaskDialogMessages.TDM_NAVIGATE_PAGE, IntPtr.Zero, m_data.ConfigurationPtr);
}
#endregion
}
private class PropertyChangedHandlers
{
private readonly Dictionary<string, Func<Instance, Action<IntPtr>>> m_propertyChangedHandlers = new Dictionary<string, Func<Instance, Action<IntPtr>>>();
public void Register(Expression<Func<MessageDialog, object>> expression, Func<Instance, Action<IntPtr>> action)
{
m_propertyChangedHandlers.Add(MemberNameResolver.ResolveMemberName(expression), action);
}
public void Handle(string name, Instance instance, IntPtr hwnd)
{
Func<Instance, Action<IntPtr>> action;
if (m_propertyChangedHandlers.TryGetValue(name, out action))
{
action(instance)(hwnd);
}
}
}
private class ElementStrings
{
private readonly IntPtr[] m_elementStrings = new IntPtr[Enum.GetNames(typeof(TaskDialogElement)).Length];
public IntPtr Update(TaskDialogElement element, string text)
{
int elementIndex = (int)element;
Delete(elementIndex);
IntPtr newStringPtr = Marshal.StringToHGlobalUni(text);
m_elementStrings[elementIndex] = newStringPtr;
return newStringPtr;
}
public void Clear()
{
for (int i = 0; i < m_elementStrings.Length; i++)
{
Delete(i);
}
}
private void Delete(int i)
{
if (m_elementStrings[i] != IntPtr.Zero)
{
Marshal.FreeHGlobal(m_elementStrings[i]);
m_elementStrings[i] = IntPtr.Zero;
}
}
}
private static class CenterWindow
{
public static void Center(IntPtr hwnd)
{
Rect rect;
GetClientRect(hwnd, out rect);
var screen = Screen.FromHandle(hwnd);
int x = screen.WorkingArea.Width / 2 - rect.Width / 2;
int y = screen.WorkingArea.Height / 2 - rect.Height / 2;
SetWindowPos(hwnd, IntPtr.Zero, x, y, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
private struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
public int Width
{
get { return right - left; }
}
public int Height
{
get { return bottom - top; }
}
}
private const int SWP_NOACTIVATE = 0x0010;
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOZORDER = 0x0004;
[DllImport("user32.dll")]
private static extern bool GetClientRect(IntPtr hWnd, out Rect lpRect);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
}
}
}