diff --git a/Version.json b/Version.json
index 07126c2..3944764 100644
--- a/Version.json
+++ b/Version.json
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
- "version": "2.0.4",
+ "version": "2.0.5",
"publicReleaseRefSpec": [
"^refs/heads/master$",
"^refs/heads/main$"
diff --git a/src/CrissCross.WPF.UI.Test/Views/Pages/DashboardPage.xaml b/src/CrissCross.WPF.UI.Test/Views/Pages/DashboardPage.xaml
index 9f70917..29524de 100644
--- a/src/CrissCross.WPF.UI.Test/Views/Pages/DashboardPage.xaml
+++ b/src/CrissCross.WPF.UI.Test/Views/Pages/DashboardPage.xaml
@@ -86,6 +86,17 @@
Grid.Row="3"
Grid.Column="2"
Height="312"
- Source="/Assets/working.gif" AnimateInDesignMode="True" />
+ AnimateInDesignMode="True"
+ Source="/Assets/working.gif" />
+
+
+
+
+
+
+
diff --git a/src/CrissCross.WPF.UI/Controls/Button/Button.xaml b/src/CrissCross.WPF.UI/Controls/Button/Button.xaml
index 501a37a..af726f3 100644
--- a/src/CrissCross.WPF.UI/Controls/Button/Button.xaml
+++ b/src/CrissCross.WPF.UI/Controls/Button/Button.xaml
@@ -16,8 +16,8 @@
-
-
+
+
@@ -85,8 +85,8 @@
-
-
+
+
diff --git a/src/CrissCross.WPF.UI/Controls/MessageBox/MessageBox.cs b/src/CrissCross.WPF.UI/Controls/MessageBox/MessageBox.cs
index 43f62b4..9a29ff4 100644
--- a/src/CrissCross.WPF.UI/Controls/MessageBox/MessageBox.cs
+++ b/src/CrissCross.WPF.UI/Controls/MessageBox/MessageBox.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for full license information.
using System.Drawing;
+using System.Runtime.CompilerServices;
using CrissCross.WPF.UI.Input;
namespace CrissCross.WPF.UI.Controls;
@@ -12,131 +13,102 @@ namespace CrissCross.WPF.UI.Controls;
///
[ToolboxItem(true)]
[ToolboxBitmap(typeof(MessageBox), "MessageBox.bmp")]
-public class MessageBox : Window
+public class MessageBox : System.Windows.Window
{
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty ShowTitleProperty = DependencyProperty.Register(
nameof(ShowTitle),
typeof(bool),
typeof(MessageBox),
new PropertyMetadata(true));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty PrimaryButtonTextProperty = DependencyProperty.Register(
nameof(PrimaryButtonText),
typeof(string),
typeof(MessageBox),
new PropertyMetadata(string.Empty));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty SecondaryButtonTextProperty = DependencyProperty.Register(
nameof(SecondaryButtonText),
typeof(string),
typeof(MessageBox),
new PropertyMetadata(string.Empty));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty CloseButtonTextProperty = DependencyProperty.Register(
nameof(CloseButtonText),
typeof(string),
typeof(MessageBox),
new PropertyMetadata("Close"));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty PrimaryButtonIconProperty = DependencyProperty.Register(
nameof(PrimaryButtonIcon),
- typeof(SymbolRegular),
+ typeof(IconElement),
typeof(MessageBox),
- new PropertyMetadata(SymbolRegular.Empty));
+ new PropertyMetadata(null));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty SecondaryButtonIconProperty = DependencyProperty.Register(
nameof(SecondaryButtonIcon),
- typeof(SymbolRegular),
+ typeof(IconElement),
typeof(MessageBox),
- new PropertyMetadata(SymbolRegular.Empty));
+ new PropertyMetadata(null));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty CloseButtonIconProperty = DependencyProperty.Register(
nameof(CloseButtonIcon),
- typeof(SymbolRegular),
+ typeof(IconElement),
typeof(MessageBox),
- new PropertyMetadata(SymbolRegular.Empty));
+ new PropertyMetadata(null));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty PrimaryButtonAppearanceProperty = DependencyProperty.Register(
nameof(PrimaryButtonAppearance),
typeof(ControlAppearance),
typeof(MessageBox),
new PropertyMetadata(ControlAppearance.Primary));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty SecondaryButtonAppearanceProperty = DependencyProperty.Register(
nameof(SecondaryButtonAppearance),
typeof(ControlAppearance),
typeof(MessageBox),
new PropertyMetadata(ControlAppearance.Secondary));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty CloseButtonAppearanceProperty = DependencyProperty.Register(
nameof(CloseButtonAppearance),
typeof(ControlAppearance),
typeof(MessageBox),
new PropertyMetadata(ControlAppearance.Secondary));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty IsPrimaryButtonEnabledProperty = DependencyProperty.Register(
nameof(IsPrimaryButtonEnabled),
typeof(bool),
typeof(MessageBox),
new PropertyMetadata(true));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty IsSecondaryButtonEnabledProperty = DependencyProperty.Register(
nameof(IsSecondaryButtonEnabled),
typeof(bool),
typeof(MessageBox),
new PropertyMetadata(true));
- ///
- /// Property for .
- ///
+ /// Identifies the dependency property.
public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register(
nameof(TemplateButtonCommand),
typeof(IRelayCommand),
typeof(MessageBox),
new PropertyMetadata(null));
- ///
- /// The TCS.
- ///
-#pragma warning disable SA1401 // Fields should be private
- protected TaskCompletionSource? Tcs;
-#pragma warning restore SA1401 // Fields should be private
+ private static readonly PropertyInfo CanCenterOverWPFOwnerPropertyInfo = typeof(System.Windows.Window).GetProperty(
+ "CanCenterOverWPFOwner",
+ BindingFlags.NonPublic | BindingFlags.Instance)!;
///
/// Initializes a new instance of the class.
@@ -156,7 +128,7 @@ public MessageBox()
}
///
- /// Gets or sets a value indicating whether gets or sets a value that determines whether to show the Title in .
+ /// Gets or sets a value indicating whether to show the in .
///
public bool ShowTitle
{
@@ -194,27 +166,27 @@ public string CloseButtonText
///
/// Gets or sets the on the primary button.
///
- public SymbolRegular PrimaryButtonIcon
+ public IconElement? PrimaryButtonIcon
{
- get => (SymbolRegular)GetValue(PrimaryButtonIconProperty);
+ get => (IconElement?)GetValue(PrimaryButtonIconProperty);
set => SetValue(PrimaryButtonIconProperty, value);
}
///
/// Gets or sets the on the secondary button.
///
- public SymbolRegular SecondaryButtonIcon
+ public IconElement? SecondaryButtonIcon
{
- get => (SymbolRegular)GetValue(SecondaryButtonIconProperty);
+ get => (IconElement?)GetValue(SecondaryButtonIconProperty);
set => SetValue(SecondaryButtonIconProperty, value);
}
///
/// Gets or sets the on the close button.
///
- public SymbolRegular CloseButtonIcon
+ public IconElement? CloseButtonIcon
{
- get => (SymbolRegular)GetValue(CloseButtonIconProperty);
+ get => (IconElement?)GetValue(CloseButtonIconProperty);
set => SetValue(CloseButtonIconProperty, value);
}
@@ -246,7 +218,7 @@ public ControlAppearance CloseButtonAppearance
}
///
- /// Gets or sets a value indicating whether gets or sets whether the primary button is enabled.
+ /// Gets or sets a value indicating whether the primary button is enabled.
///
public bool IsSecondaryButtonEnabled
{
@@ -255,7 +227,7 @@ public bool IsSecondaryButtonEnabled
}
///
- /// Gets or sets a value indicating whether gets or sets whether the secondary button is enabled.
+ /// Gets or sets a value indicating whether the secondary button is enabled.
///
public bool IsPrimaryButtonEnabled
{
@@ -264,17 +236,24 @@ public bool IsPrimaryButtonEnabled
}
///
- /// Gets command triggered after clicking the button on the Footer.
+ /// Gets the command triggered after clicking the button on the Footer.
///
public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty);
+ ///
+ /// Gets or sets the TCS.
+ ///
+ ///
+ /// The TCS.
+ ///
+ protected TaskCompletionSource? Tcs { get; set; }
+
///
/// Shows this instance.
///
/// $"Use {nameof(ShowDialogAsync)} instead.
[Obsolete($"Use {nameof(ShowDialogAsync)} instead")]
- public new void Show() =>
- throw new InvalidOperationException($"Use {nameof(ShowDialogAsync)} instead");
+ public new void Show() => throw new InvalidOperationException($"Use {nameof(ShowDialogAsync)} instead");
///
/// Shows the dialog.
@@ -282,16 +261,14 @@ public bool IsPrimaryButtonEnabled
/// A bool.
/// $"Use {nameof(ShowDialogAsync)} instead.
[Obsolete($"Use {nameof(ShowDialogAsync)} instead")]
- public new bool? ShowDialog() =>
- throw new InvalidOperationException($"Use {nameof(ShowDialogAsync)} instead");
+ public new bool? ShowDialog() => throw new InvalidOperationException($"Use {nameof(ShowDialogAsync)} instead");
///
/// Closes this instance.
///
/// $"Use {nameof(Close)} with MessageBoxResult instead.
[Obsolete($"Use {nameof(Close)} with MessageBoxResult instead")]
- public new void Close() =>
- throw new InvalidOperationException($"Use {nameof(Close)} with MessageBoxResult instead");
+ public new void Close() => throw new InvalidOperationException($"Use {nameof(Close)} with MessageBoxResult instead");
///
/// Displays a message box.
@@ -301,6 +278,7 @@ public bool IsPrimaryButtonEnabled
///
/// .
///
+ /// Thrown if the operation is canceled.
public async Task ShowDialogAsync(
bool showAsDialog = true,
CancellationToken cancellationToken = default)
@@ -343,13 +321,35 @@ protected virtual void OnLoaded()
var rootElement = (UIElement)GetVisualChild(0)!;
ResizeToContentSize(rootElement);
- CenterWindowOnScreen();
+
+ switch (WindowStartupLocation)
+ {
+ case WindowStartupLocation.Manual:
+ case WindowStartupLocation.CenterScreen:
+ CenterWindowOnScreen();
+ break;
+ case WindowStartupLocation.CenterOwner:
+ if (
+ !CanCenterOverWPFOwner()
+ || Owner.WindowState is WindowState.Maximized or WindowState.Minimized)
+ {
+ CenterWindowOnScreen();
+ }
+ else
+ {
+ CenterWindowOnOwner();
+ }
+
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
}
///
- /// Sets Width and Height.
+ /// Resizes the MessageBox to fit the content's size, including margins.
///
- /// The root element.
+ /// The root element of the MessageBox.
protected virtual void ResizeToContentSize(UIElement rootElement)
{
if (rootElement == null)
@@ -362,8 +362,8 @@ protected virtual void ResizeToContentSize(UIElement rootElement)
// left and right margin
const double margin = 12.0 * 2;
- Width = desiredSize.Width + margin;
- Height = desiredSize.Height;
+ SetCurrentValue(WidthProperty, desiredSize.Width + margin);
+ SetCurrentValue(HeightProperty, desiredSize.Height);
ResizeWidth(rootElement);
ResizeHeight(rootElement);
@@ -372,7 +372,7 @@ protected virtual void ResizeToContentSize(UIElement rootElement)
///
/// Raises the event.
///
- /// The instance containing the event data.
+ /// The instance containing the event data.
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
@@ -382,7 +382,7 @@ protected override void OnClosing(CancelEventArgs e)
return;
}
- Tcs?.TrySetResult(MessageBoxResult.None);
+ _ = Tcs?.TrySetResult(MessageBoxResult.None);
}
///
@@ -390,18 +390,17 @@ protected override void OnClosing(CancelEventArgs e)
///
protected virtual void CenterWindowOnScreen()
{
- // TODO MessageBox should be displayed on the window on which the application
var screenWidth = SystemParameters.PrimaryScreenWidth;
var screenHeight = SystemParameters.PrimaryScreenHeight;
- Left = (screenWidth / 2) - (Width / 2);
- Top = (screenHeight / 2) - (Height / 2);
+ SetCurrentValue(LeftProperty, (screenWidth / 2) - (Width / 2));
+ SetCurrentValue(TopProperty, (screenHeight / 2) - (Height / 2));
}
///
- /// Occurs after the is clicked.
+ /// Occurs after the is clicked.
///
- /// The button.
+ /// The MessageBox button.
protected virtual void OnButtonClick(MessageBoxButton button)
{
var result = button switch
@@ -411,14 +410,36 @@ protected virtual void OnButtonClick(MessageBoxButton button)
_ => MessageBoxResult.None
};
- Tcs?.TrySetResult(result);
+ _ = Tcs?.TrySetResult(result);
base.Close();
}
+#if NET8_0_OR_GREATER
+ [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_CanCenterOverWPFOwner")]
+ private static extern bool CanCenterOverWPFOwnerAccessor(System.Windows.Window w);
+#endif
+
+ // CanCenterOverWPFOwner property see https://source.dot.net/#PresentationFramework/System/Windows/Window.cs,e679e433777b21b8
+ private bool CanCenterOverWPFOwner() =>
+#if NET8_0_OR_GREATER
+ CanCenterOverWPFOwnerAccessor(this);
+#else
+ (bool)CanCenterOverWPFOwnerPropertyInfo.GetValue(this)!;
+#endif
+
+ private void CenterWindowOnOwner()
+ {
+ var left = Owner.Left + ((Owner.Width - Width) / 2);
+ var top = Owner.Top + ((Owner.Height - Height) / 2);
+
+ SetCurrentValue(LeftProperty, left);
+ SetCurrentValue(TopProperty, top);
+ }
+
private void RemoveTitleBarAndApplyMica()
{
- UnsafeNativeMethods.RemoveWindowTitlebarContents(this);
- WindowBackdrop.ApplyBackdrop(this, WindowBackdropType.Mica);
+ _ = UnsafeNativeMethods.RemoveWindowTitlebarContents(this);
+ _ = WindowBackdrop.ApplyBackdrop(this, WindowBackdropType.Mica);
}
private void ResizeWidth(UIElement element)
@@ -428,14 +449,14 @@ private void ResizeWidth(UIElement element)
return;
}
- Width = MaxWidth;
+ SetCurrentValue(WidthProperty, MaxWidth);
element.UpdateLayout();
- Height = element.DesiredSize.Height;
+ SetCurrentValue(HeightProperty, element.DesiredSize.Height);
if (Height > MaxHeight)
{
- MaxHeight = Height;
+ SetCurrentValue(MaxHeightProperty, Height);
}
}
@@ -446,14 +467,14 @@ private void ResizeHeight(UIElement element)
return;
}
- Height = MaxHeight;
+ SetCurrentValue(HeightProperty, MaxHeight);
element.UpdateLayout();
- Width = element.DesiredSize.Width;
+ SetCurrentValue(WidthProperty, element.DesiredSize.Width);
if (Width > MaxWidth)
{
- MaxWidth = Width;
+ SetCurrentValue(MaxWidthProperty, Width);
}
}
}
diff --git a/src/CrissCross.WPF.UI/Controls/PasswordBox/PasswordBox.cs b/src/CrissCross.WPF.UI/Controls/PasswordBox/PasswordBox.cs
index 8d988f4..f405315 100644
--- a/src/CrissCross.WPF.UI/Controls/PasswordBox/PasswordBox.cs
+++ b/src/CrissCross.WPF.UI/Controls/PasswordBox/PasswordBox.cs
@@ -2,6 +2,7 @@
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
+using System.Diagnostics;
using System.Windows.Controls;
namespace CrissCross.WPF.UI.Controls;
@@ -56,12 +57,17 @@ public class PasswordBox : TextBox
typeof(RoutedEventHandler),
typeof(PasswordBox));
+ private readonly PasswordHelper _passwordHelper;
private bool _lockUpdatingContents;
///
/// Initializes a new instance of the class.
///
- public PasswordBox() => _lockUpdatingContents = false;
+ public PasswordBox()
+ {
+ _lockUpdatingContents = false;
+ _passwordHelper = new(this);
+ }
///
/// Event fired from this text box when its inner content
@@ -266,54 +272,118 @@ private void UpdateTextContents(bool isTriggeredByTextInput)
}
var caretIndex = CaretIndex;
- var selectionIndex = SelectionStart;
- var currentPassword = Password ?? string.Empty;
- var newPasswordValue = currentPassword;
+ var newPasswordValue = _passwordHelper.GetPassword();
if (isTriggeredByTextInput)
{
- var currentText = Text;
- var newCharacters = currentText.Replace(PasswordChar.ToString(), string.Empty);
+ newPasswordValue = _passwordHelper.GetNewPassword();
+ }
+
+ _lockUpdatingContents = true;
- if (currentText.Length < currentPassword.Length)
+ Text = new string(PasswordChar, newPasswordValue?.Length ?? 0);
+ Password = newPasswordValue ?? string.Empty;
+ CaretIndex = caretIndex;
+
+ RaiseEvent(new RoutedEventArgs(PasswordChangedEvent));
+
+ _lockUpdatingContents = false;
+ }
+
+ private class PasswordHelper(PasswordBox passwordBox)
+ {
+ private string _currentText = string.Empty;
+ private string _newPasswordValue = string.Empty;
+ private string _currentPassword = string.Empty;
+
+ public string GetNewPassword()
+ {
+ _currentPassword = GetPassword();
+ _newPasswordValue = _currentPassword;
+ _currentText = passwordBox.Text;
+ var selectionIndex = passwordBox.SelectionStart;
+ var passwordChar = passwordBox.PasswordChar;
+ var newCharacters = _currentText.Replace(passwordChar.ToString(), string.Empty);
+ var isDeleted = false;
+
+ if (IsDeleteOption())
{
- newPasswordValue = currentPassword.Remove(selectionIndex, currentPassword.Length - currentText.Length);
+ _newPasswordValue = _currentPassword.Remove(
+ selectionIndex,
+ _currentPassword.Length - _currentText.Length);
+ isDeleted = true;
}
- if (newCharacters.Length > 1)
+ switch (newCharacters.Length)
{
- var index = currentText.IndexOf(newCharacters[0]);
+ case > 1:
+ {
+ var index = _currentText.IndexOf(newCharacters[0]);
- newPasswordValue =
- index > newPasswordValue.Length - 1
- ? newPasswordValue + newCharacters
- : newPasswordValue.Insert(index, newCharacters);
- }
- else
- {
- for (var i = 0; i < currentText.Length; i++)
- {
- if (currentText[i] == PasswordChar)
+ _newPasswordValue =
+ index > _newPasswordValue.Length - 1
+ ? _newPasswordValue + newCharacters
+ : _newPasswordValue.Insert(index, newCharacters);
+ break;
+ }
+
+ case 1:
{
- continue;
+ for (var i = 0; i < _currentText.Length; i++)
+ {
+ if (_currentText[i] == passwordChar)
+ {
+ continue;
+ }
+
+ UpdatePasswordWithInputCharacter(i, _currentText[i].ToString());
+ break;
+ }
+
+ break;
}
- newPasswordValue =
- currentText.Length == newPasswordValue.Length
- ? newPasswordValue.Remove(i, 1).Insert(i, currentText[i].ToString())
- : newPasswordValue.Insert(i, currentText[i].ToString());
- }
+ case 0 when !isDeleted:
+ {
+ // The input is a PasswordChar, which is to be inserted at the designated position.
+ var insertIndex = selectionIndex - 1;
+ UpdatePasswordWithInputCharacter(insertIndex, passwordChar.ToString());
+ break;
+ }
}
+
+ return _newPasswordValue;
}
- _lockUpdatingContents = true;
+ public string GetPassword() => passwordBox.Password ?? string.Empty;
- Text = new string(PasswordChar, newPasswordValue?.Length ?? 0);
- Password = newPasswordValue ?? string.Empty;
- CaretIndex = caretIndex;
+ private void UpdatePasswordWithInputCharacter(int insertIndex, string insertValue)
+ {
+ Debug.Assert(
+ _currentText == passwordBox.Text,
+ "_currentText == _passwordBox.Text");
- RaiseEvent(new RoutedEventArgs(PasswordChangedEvent));
+ if (_currentText.Length == _newPasswordValue.Length)
+ {
+ // If it's a direct character replacement, remove the existing one before inserting the new one.
+ _newPasswordValue = _newPasswordValue.Remove(insertIndex, 1).Insert(insertIndex, insertValue);
+ }
+ else
+ {
+ _newPasswordValue = _newPasswordValue.Insert(insertIndex, insertValue);
+ }
+ }
- _lockUpdatingContents = false;
+ private bool IsDeleteOption()
+ {
+ Debug.Assert(
+ _currentText == passwordBox.Text,
+ "_currentText == _passwordBox.Text");
+ Debug.Assert(
+ _currentPassword == passwordBox.Password,
+ "_currentPassword == _passwordBox.Password");
+
+ return _currentText.Length < _currentPassword.Length;
+ }
}
}
diff --git a/src/CrissCross.WPF.UI/Controls/TreeView/TreeView.xaml b/src/CrissCross.WPF.UI/Controls/TreeView/TreeView.xaml
index 606f661..7cef7e2 100644
--- a/src/CrissCross.WPF.UI/Controls/TreeView/TreeView.xaml
+++ b/src/CrissCross.WPF.UI/Controls/TreeView/TreeView.xaml
@@ -2,7 +2,6 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CrissCross.WPF.UI.Controls">
-
+
+
+
diff --git a/src/CrissCross.WPF.UI/Controls/TreeView/TreeViewItem.xaml b/src/CrissCross.WPF.UI/Controls/TreeView/TreeViewItem.xaml
index 37a07c4..aceda3b 100644
--- a/src/CrissCross.WPF.UI/Controls/TreeView/TreeViewItem.xaml
+++ b/src/CrissCross.WPF.UI/Controls/TreeView/TreeViewItem.xaml
@@ -4,10 +4,7 @@
xmlns:controls="clr-namespace:CrissCross.WPF.UI.Controls"
xmlns:system="clr-namespace:System;assembly=System.Runtime">
- 10
- 14
-
-