From a21b27ad5d8ec272e37501b95f5b157e6d4c94ff Mon Sep 17 00:00:00 2001 From: Bill Henning Date: Thu, 2 Nov 2023 10:51:43 -0400 Subject: [PATCH] Updates for v23.1.1. --- Documentation/api-filter.yml | 8 + Documentation/docfx.json | 2 +- .../topics/conversion/.toc-generator.yml | 2 + .../topics/conversion/converting-to-v23-1.md | 24 ++ Documentation/topics/conversion/index.md | 12 + .../fundamentals/user-prompt/appearance.md | 72 +++++- .../user-prompt/builder-pattern.md | 122 +++++++++- .../fundamentals/user-prompt/clipboard.md | 17 +- .../user-prompt/display-and-results.md | 135 ++++++++++- .../user-prompt/extension-methods.md | 27 ++- .../topics/fundamentals/user-prompt/index.md | 31 ++- .../fundamentals/user-prompt/localization.md | 45 +++- .../fundamentals/user-prompt/message-box.md | 93 +++++++- .../user-prompt/user-prompt-buttons.md | 132 ++++++++++- .../user-prompt/user-prompt-content.md | 118 +++++++++- .../topics/themes/getting-started.md | 2 +- .../images/user-interface-density-compact.png | Bin 0 -> 3948 bytes .../user-interface-density-spacious.png | Bin 0 -> 5122 bytes .../themes/images/user-interface-density.png | Bin 0 -> 4497 bytes Documentation/topics/themes/index.md | 7 + .../topics/themes/theme-definitions.md | 20 +- .../topics/themes/user-interface-density.md | 219 ++++++++++++++++++ Readme.md | 10 + Samples/SampleBrowser/Directory.Build.props | 4 +- .../ActiproSoftware.References.props | 2 +- .../MessageBoxIntro/MainControl.axaml | 1 - .../UserPromptIntro/MainControl.axaml | 1 - .../FundamentalsSamples/Overview.axaml | 6 +- .../SharedSamples/Overview.axaml | 20 +- .../Demos/ProfileForm/MainControl.axaml | 56 +++-- .../ThemesSamples/Overview.axaml | 58 +++-- .../Styling/UserInterfaceDensitySamples.axaml | 103 ++++++++ .../UserInterfaceDensitySamples.axaml.cs | 59 +++++ .../CategorizedSampleItemsControl.axaml | 4 +- .../Controls/Themes/ControlExample.axaml | 28 +-- .../Themes/ControlExampleItemsControl.axaml | 6 +- .../Controls/Themes/DocumentViewer.axaml | 2 + .../Themes/FeaturedSampleItemsControl.axaml | 4 +- .../Themes/ProductFamilyItemsControl.axaml | 4 +- .../Themes/ProductOverviewHeader.axaml | 6 +- .../Themes/WhatsNewItemsControl.axaml | 4 +- .../Documents/ReleaseHistories/v23.1.md | 17 +- .../SampleBrowser/Models/ProductData.axaml | 1 + .../ViewModels/ApplicationViewModel.cs | 23 +- .../Views/ApplicationDrawerView.axaml | 14 +- .../Views/ApplicationStyles.axaml | 6 +- .../SampleBrowser/Views/HomeControl.axaml | 8 +- .../SampleBrowser/Views/RootView.axaml | 27 ++- .../SampleBrowser/Views/RootView.axaml.cs | 15 +- 49 files changed, 1451 insertions(+), 126 deletions(-) create mode 100644 Documentation/topics/conversion/.toc-generator.yml create mode 100644 Documentation/topics/conversion/converting-to-v23-1.md create mode 100644 Documentation/topics/conversion/index.md create mode 100644 Documentation/topics/themes/images/user-interface-density-compact.png create mode 100644 Documentation/topics/themes/images/user-interface-density-spacious.png create mode 100644 Documentation/topics/themes/images/user-interface-density.png create mode 100644 Documentation/topics/themes/user-interface-density.md create mode 100644 Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/ThemesSamples/Styling/UserInterfaceDensitySamples.axaml create mode 100644 Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/ThemesSamples/Styling/UserInterfaceDensitySamples.axaml.cs diff --git a/Documentation/api-filter.yml b/Documentation/api-filter.yml index 36ad7e6..41aa629 100644 --- a/Documentation/api-filter.yml +++ b/Documentation/api-filter.yml @@ -1,5 +1,13 @@ apiRules: +# Include EditorBrowsableState.Never in Actipro namespaces (excluded by default) +- include: + uidRegex: ^ActiproSoftware\. + hasAttribute: + uid: System.ComponentModel.EditorBrowsableAttribute + ctorArguments: + - System.ComponentModel.EditorBrowsableState.Never + # Exclude compatibility and core product code - exclude: uidRegex: ^ActiproSoftware\.Compatibility$ diff --git a/Documentation/docfx.json b/Documentation/docfx.json index 12e6e46..59d1319 100644 --- a/Documentation/docfx.json +++ b/Documentation/docfx.json @@ -3,7 +3,7 @@ { "src": [ { - "src": "../../Source/bin/Release/net6.0", + "src": "../../Deploy/Build/AssembliesDocFX", "files": [ // Actipro assemblies "ActiproSoftware.*.dll" diff --git a/Documentation/topics/conversion/.toc-generator.yml b/Documentation/topics/conversion/.toc-generator.yml new file mode 100644 index 0000000..c45d7bb --- /dev/null +++ b/Documentation/topics/conversion/.toc-generator.yml @@ -0,0 +1,2 @@ +title: "Conversion Notes" +order: 42 diff --git a/Documentation/topics/conversion/converting-to-v23-1.md b/Documentation/topics/conversion/converting-to-v23-1.md new file mode 100644 index 0000000..76a424f --- /dev/null +++ b/Documentation/topics/conversion/converting-to-v23-1.md @@ -0,0 +1,24 @@ +--- +title: "Converting to v23.1" +page-title: "Converting to v23.1 - Conversion Notes" +order: 10 +--- +# Converting to v23.1 + +While v23.1.0 was the first public release version, some updates were made in maintenance releases that are described below. + +## User Interface Density Changes + +v23.1.1 introduced the concept of user interface density, a configurable way to set how spacious a user interface layout should appear. v23.1.0 shipped with what is effectively now the `Spacious` density option, which is very touch friendly. v23.1.1 has updated the default to be the `Normal` density option, resulting in a slightly more compact layout out of the box. + +The [User Interface Density](../themes/user-interface-density.md) topic discusses the density feature in detail and how to change it both initially and at any point later at run-time. Use code in that topic to adjust the density if you prefer the old `Spacious` density option or the even more dense desktop-oriented `Compact` density option for your application. + +## Theme Definition Changes + +### ScrollBar Thumbs + +v23.1.0 used to have a `ScrollBarThumbMargin` property on [ThemeDefinition](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition) that would keep the thumb ascent relative to the containing scrollbar track size. Scrollbar thumb sizing has been refactored in v23.1.1 and is much more flexible. The new design allows for fixed thumb thicknesses, or alternatively having the thumb size relative to the containing track size. Thumbs now default to a slightly thinner fixed `4.0` thickness instead of the previous effective value of `6.0`. Set both the [ScrollBarThumbMinAscent](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ScrollBarThumbMinAscent) and [ScrollBarThumbMaxAscent](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ScrollBarThumbMaxAscent) properties to `6.0` if the previous appearance is desired. + +### MenuItem Padding + +v23.1.0 used to have a `MenuItemPadding` property on [ThemeDefinition](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition), but that property was removed in v23.1.1 since the padding applied to menu items now automatically scales based on UI density. \ No newline at end of file diff --git a/Documentation/topics/conversion/index.md b/Documentation/topics/conversion/index.md new file mode 100644 index 0000000..f15b8a8 --- /dev/null +++ b/Documentation/topics/conversion/index.md @@ -0,0 +1,12 @@ +--- +title: "Overview" +page-title: "Conversion Notes" +order: 1 +--- +# Overview + +Occasionally during large updates to a product, some breaking changes are necessary to either add enhanced functionality or improve the overall design of the various products. + +Read the following topics that are appropriate for your scenario if you are converting from an older version to the latest version. + +- [Converting to v23.1](converting-to-v23-1.md) diff --git a/Documentation/topics/fundamentals/user-prompt/appearance.md b/Documentation/topics/fundamentals/user-prompt/appearance.md index dd3c088..08c38d3 100644 --- a/Documentation/topics/fundamentals/user-prompt/appearance.md +++ b/Documentation/topics/fundamentals/user-prompt/appearance.md @@ -17,9 +17,16 @@ When either the [HeaderBackground](xref:@ActiproUIRoot.Controls.UserPromptContro ## Images +@if (avalonia) { Any `IImage` can be set to the [StatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StatusImage) or [FooterImage](xref:@ActiproUIRoot.Controls.UserPromptControl.FooterImage) properties for a custom look. When using the [builder pattern](builder-pattern.md), set the `IImage` using [WithStatusImage](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithStatusImage*) or [WithFooterImage](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithFooterImage*). The images used by [MessageBoxImage](xref:@ActiproUIRoot.Controls.MessageBoxImage) can also be customized by assigning a custom [ImageProvider](xref:@ActiproUIRoot.Media.ImageProvider) to the [ImageProvider](xref:@ActiproUIRoot.Media.ImageProvider).[Default](xref:@ActiproUIRoot.Media.ImageProvider.Default) property. Each value for [MessageBoxImage](xref:@ActiproUIRoot.Controls.MessageBoxImage) corresponds to a key of the same name defined by [SharedImageKeys](xref:@ActiproUIRoot.Media.SharedImageKeys). For example, the image [MessageBoxImage](xref:@ActiproUIRoot.Controls.MessageBoxImage).[Warning](xref:@ActiproUIRoot.Controls.MessageBoxImage.Warning) corresponds to the key [SharedImageKeys](xref:@ActiproUIRoot.Media.SharedImageKeys).[Warning](xref:@ActiproUIRoot.Media.SharedImageKeys.Warning). A custom class which derives from [ImageProvider](xref:@ActiproUIRoot.Media.ImageProvider) can override the [ImageProvider](xref:@ActiproUIRoot.Media.ImageProvider).[GetImageSource](xref:@ActiproUIRoot.Media.ImageProvider.GetImageSource*) method to return a custom `IImage` for one or more of those keys. +} +@if (wpf) { +Any `ImageSource` can be set to the [StatusImageSource](xref:@ActiproUIRoot.Controls.UserPromptControl.StatusImageSource) or [FooterImageSource](xref:@ActiproUIRoot.Controls.UserPromptControl.FooterImageSource) properties for a custom look. When using the [builder pattern](builder-pattern.md), set the `ImageSource` using [WithStatusImage](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithStatusImage*) or [WithFooterImage](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithFooterImage*). + +The images used by [UserPromptStandardImage](xref:@ActiproUIRoot.Controls.UserPromptStandardImage) can also be customized by assigning a custom [ImageProvider](xref:@ActiproUIRoot.Media.ImageProvider) to the [ImageProvider](xref:@ActiproUIRoot.Media.ImageProvider).[Default](xref:@ActiproUIRoot.Media.ImageProvider.Default) property. Each value for [UserPromptStandardImage](xref:@ActiproUIRoot.Controls.UserPromptStandardImage) corresponds to a key of the same name defined by [SharedImageKeys](xref:@ActiproUIRoot.Media.SharedImageSourceKeys). For example, the image [UserPromptStandardImage](xref:@ActiproUIRoot.Controls.UserPromptStandardImage).[Warning](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.Warning) corresponds to the key [SharedImageSourceKeys](xref:@ActiproUIRoot.Media.SharedImageSourceKeys).[Warning](xref:@ActiproUIRoot.Media.SharedImageSourceKeys.Warning). A custom class which derives from [ImageProvider](xref:@ActiproUIRoot.Media.ImageProvider) can override the [ImageProvider](xref:@ActiproUIRoot.Media.ImageProvider).[GetImageSource](xref:@ActiproUIRoot.Media.ImageProvider.GetImageSource*) method to return a custom `ImageSource` for one or more of those keys. +} ## Customize UserPromptWindow @@ -27,6 +34,7 @@ The [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) does not h The following code demonstrates using the callback to customize the flow direction of the window: +@if (avalonia) { ```csharp var userPromptControl = new UserPromptControl() { ... }; Window owner = null; // Use default @@ -39,9 +47,25 @@ await UserPromptWindow.ShowDialog( window.FlowDirection = FlowDirection.RightToLeft; }); ``` +} +@if (wpf) { +```csharp +var userPromptControl = new UserPromptControl() { ... }; +Window owner = null; // Use default + +UserPromptWindow.ShowDialog( + userPromptControl, + "Window Title", + owner, + window => { + window.FlowDirection = FlowDirection.RightToLeft; + }); +``` +} The [builder pattern](builder-pattern.md) exposes the same customization using the [AfterInitializeWindow](xref:@ActiproUIRoot.Controls.UserPromptBuilder.AfterInitializeWindow*) callback as shown in the following example: +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here @@ -50,7 +74,19 @@ await UserPromptBuilder.Configure() }) .Show(); ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .AfterInitializeWindow(window => { + window.FlowDirection = FlowDirection.RightToLeft; + }) + .Show(); +``` +} +@if (avalonia) { Finally, an advanced configuration of [MessageBox](message-box.md) also allows access to the [builder pattern](builder-pattern.md) as shown below: ```csharp @@ -61,4 +97,38 @@ await MessageBox.Show( window.FlowDirection = FlowDirection.RightToLeft; }) ); -``` \ No newline at end of file +``` +} +@if (wpf) { +Finally, an advanced configuration of [ThemedMessageBox](message-box.md) also allows access to the [builder pattern](builder-pattern.md) as shown below: + +```csharp +ThemedMessageBox.Show( + "Use the optional 'configure' parameter to access the UserPromptBuilder." + configure: builder => builder + .AfterInitializeWindow(window => { + window.FlowDirection = FlowDirection.RightToLeft; + }) + ); +``` +} + +@if (wpf) { +## Theme Assets + +See the [Theme Reusable Assets](../../../themes/reusable-assets.md) topic for more details on using and customizing theme assets. The following reusable assets are used by [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl): + +| Asset Resource Key | Description | +|-----|-----| +| [ContainerForegroundLowestNormalBrushKey](xref:@ActiproUIRoot.Themes.AssetResourceKeys.ContainerForegroundLowestNormalBrushKey) | Assigned to the following properties: `Foreground`. | +| [ContainerBackgroundLowestBrushKey](xref:@ActiproUIRoot.Themes.AssetResourceKeys.ContainerBackgroundLowestBrushKey) | Assigned to the following properties: `Background`. | +| [ContainerForegroundLowNormalBrushKey](xref:@ActiproUIRoot.Themes.AssetResourceKeys.ContainerForegroundLowNormalBrushKey) | Assigned to the following properties: [TrayForeground](xref:@ActiproUIRoot.Controls.UserPromptControl.TrayForeground). | +| [ContainerBackgroundLowBrushKey](xref:@ActiproUIRoot.Themes.AssetResourceKeys.ContainerBackgroundLowBrushKey) | Assigned to the following properties: [TrayBackground](xref:@ActiproUIRoot.Controls.UserPromptControl.TrayBackground). | +| [ContainerBorderLowBrushKey](xref:@ActiproUIRoot.Themes.AssetResourceKeys.ContainerBorderLowBrushKey) | Assigned to the following properties: `BorderBrush`. | +| [PrimaryAccentForegroundLowestNormalBrushKey](xref:@ActiproUIRoot.Themes.AssetResourceKeys.PrimaryAccentForegroundLowestNormalBrushKey) | Assigned to the following properties: [HeaderForeground](xref:@ActiproUIRoot.Controls.UserPromptControl.HeaderForeground). | +| [ExtraLargeFontSizeDoubleKey](xref:@ActiproUIRoot.Themes.AssetResourceKeys.ExtraLargeFontSizeDoubleKey) | Assigned to the following properties: [HeaderFontSize](xref:@ActiproUIRoot.Controls.UserPromptControl.HeaderFontSize). | +| [ContainerForegroundLowDisabledBrushKey](xref:@ActiproUIRoot.Themes.AssetResourceKeys.ContainerForegroundLowDisabledBrushKey) | Used for the focus rectangle of the **Expanded Information** toggle. | +| [ButtonForegroundHoverBrushKey](xref:@ActiproUIRoot.Themes.AssetResourceKeys.ButtonForegroundHoverBrushKey) | Assigned to the following properties of the **Expanded Information** toggle when the mouse is over the control: `Foreground`. | +| [ButtonForegroundPressedBrushKey](xref:@ActiproUIRoot.Themes.AssetResourceKeys.ButtonForegroundPressedBrushKey) | Assigned to the following properties of the **Expanded Information** toggle when pressed: `Foreground`. | +| [ContainerForegroundLowDisabledBrushKey](xref:@ActiproUIRoot.Themes.AssetResourceKeys.ContainerForegroundLowDisabledBrushKey) | Assigned to the following properties of the **Expanded Information** toggle when disabled: `Foreground`. | +} \ No newline at end of file diff --git a/Documentation/topics/fundamentals/user-prompt/builder-pattern.md b/Documentation/topics/fundamentals/user-prompt/builder-pattern.md index 39a1fc0..776e834 100644 --- a/Documentation/topics/fundamentals/user-prompt/builder-pattern.md +++ b/Documentation/topics/fundamentals/user-prompt/builder-pattern.md @@ -48,6 +48,7 @@ UserPromptBuilder.RegisterGlobalConfigureCallback(_ => _ ); ``` +@if (avalonia) { ##### MessageBox Only The [MessageBox](xref:@ActiproUIRoot.Controls.MessageBox).[RegisterGlobalBuilderConfigureCallback](xref:@ActiproUIRoot.Controls.MessageBox.RegisterGlobalBuilderConfigureCallback*) method is used to register a callback specifically for the builder used by [MessageBox](xref:@ActiproUIRoot.Controls.MessageBox), and this callback is invoked after the previously mentioned global callback for all user prompts. Use this method to specifically define application-wide configurations for the prompts displayed by [MessageBox](xref:@ActiproUIRoot.Controls.MessageBox). @@ -65,6 +66,26 @@ MessageBox.RegisterGlobalBuilderConfigureCallback(_ => _ }) ); ``` +} +@if (wpf) { +##### ThemedMessageBox Only + +The [ThemedMessageBox](xref:@ActiproUIRoot.Controls.ThemedMessageBox).[RegisterGlobalBuilderConfigureCallback](xref:@ActiproUIRoot.Controls.ThemedMessageBox.RegisterGlobalBuilderConfigureCallback*) method is used to register a callback specifically for the builder used by [ThemedMessageBox](xref:@ActiproUIRoot.Controls.ThemedMessageBox), and this callback is invoked after the previously mentioned global callback for all user prompts. Use this method to specifically define application-wide configurations for the prompts displayed by [ThemedMessageBox](xref:@ActiproUIRoot.Controls.ThemedMessageBox). + +The following example demonstrates how a global callback can be registered that alters the default behavior by displaying a message box title in the header of the prompt: + +```csharp +ThemedMessageBox.RegisterGlobalBuilderConfigureCallback(_ => _ + .AfterBuild(builder => { + // Configure UserPromptControl.Header with the Title + builder.Instance!.Header = builder.Title; + + // Clear the Title configuration to avoid it appearing elsewhere + builder.WithTitle(null); + }) +); +``` +} ### Show Prompt @@ -74,6 +95,7 @@ Finally, call the [Show](xref:@ActiproUIRoot.Controls.UserPromptBuilder.Show*) m The following code sample demonstrates how [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder) could be used to display a prompt to the user with response buttons of **Yes** or **No**. +@if (avalonia) { ```csharp var result = await UserPromptBuilder.Configure() .WithHeaderContent("Overwrite existing file?") @@ -82,7 +104,17 @@ var result = await UserPromptBuilder.Configure() .WithStatusImage(MessageBoxImage.Question) .Show(); ``` - +} +@if (wpf) { +```csharp +var result = UserPromptBuilder.Configure() + .WithHeaderContent("Overwrite existing file?") + .WithContent("The specified file already exists. Do you want to overwrite the file?") + .WithStandardButtons(UserPromptStandardButtons.YesNo) + .WithStatusImage(UserPromptStandardImage.Question) + .Show(); +``` +} The fluent API allows the entire configuration to be defined as a single statement. See the [User Prompt Content](user-prompt-content.md) and [User Prompt Buttons](user-prompt-buttons.md) topics for more details and examples. @@ -91,6 +123,7 @@ See the [User Prompt Content](user-prompt-content.md) and [User Prompt Buttons]( Nothing happens with [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder) until the [Show](xref:@ActiproUIRoot.Controls.UserPromptBuilder.Show*) method is called. At that point, the following sequence of events will occur: +@if (avalonia) { - Create a [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) and assign to the [Instance](xref:@ActiproUIRoot.Controls.UserPromptBuilder.Instance) property. - Invoke all [AfterInitialize](xref:@ActiproUIRoot.Controls.UserPromptBuilder.AfterInitialize*) callbacks. - Assign properties on [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl). After this point, the [Instance](xref:@ActiproUIRoot.Controls.UserPromptBuilder.Instance) property is fully initialized any changes to builder properties that affect the configuration of the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) *will not* be applied. @@ -107,6 +140,20 @@ Nothing happens with [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPrompt - Invoke all [OnResponding](xref:@ActiproUIRoot.Controls.UserPromptBuilder.OnResponding*) callbacks when a response is indicated. - Invoke all [AfterShow](xref:@ActiproUIRoot.Controls.UserPromptBuilder.AfterShow*) callbacks with the indicated result. - Return the result. +} +@if (wpf) { +- Create a [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) and assign to the [Instance](xref:@ActiproUIRoot.Controls.UserPromptBuilder.Instance) property. +- Invoke all [AfterInitialize](xref:@ActiproUIRoot.Controls.UserPromptBuilder.AfterInitialize*) callbacks. +- Assign properties on [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl). After this point, the [Instance](xref:@ActiproUIRoot.Controls.UserPromptBuilder.Instance) property is fully initialized any changes to builder properties that affect the configuration of the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) *will not* be applied. +- Invoke all [AfterBuild](xref:@ActiproUIRoot.Controls.UserPromptBuilder.AfterBuild*) callbacks. +- Invoke all [BeforeShow](xref:@ActiproUIRoot.Controls.UserPromptBuilder.BeforeShow*) callbacks. +- Configure [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) to host the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl). +- Invoke all [AfterInitializeWindow](xref:@ActiproUIRoot.Controls.UserPromptBuilder.AfterInitializeWindow*) callbacks. +- Show the [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) as a dialog and await a response. +- Invoke all [OnResponding](xref:@ActiproUIRoot.Controls.UserPromptBuilder.OnResponding*) callbacks when a response is indicated. +- Invoke all [AfterShow](xref:@ActiproUIRoot.Controls.UserPromptBuilder.AfterShow*) callbacks with the indicated result. +- Return the result. +} Callbacks are available at different stages of the build process to support extensibility. @@ -124,6 +171,7 @@ The [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder).[Instanc This callback is invoked immediately after an instance of [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) is created and can be used to initialize the control or further customize the builder before the builder configuration settings are applied. +@if (avalonia) { ```csharp var result = await UserPromptBuilder.Configure() // ... other configuration options here @@ -132,11 +180,23 @@ var result = await UserPromptBuilder.Configure() }) .Show(); ``` +} +@if (wpf) { +```csharp +var result = UserPromptBuilder.Configure() + // ... other configuration options here + .AfterInitialize(builder => { + // Define logic here + }) + .Show(); +``` +} ### AfterBuild Callback This callback is invoked after all builder configuration settings have been applied and can be used to finalize the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) before it is shown. +@if (avalonia) { ```csharp var result = await UserPromptBuilder.Configure() // ... other configuration options here @@ -145,14 +205,31 @@ var result = await UserPromptBuilder.Configure() }) .Show(); ``` +} +@if (wpf) { +```csharp +var result = UserPromptBuilder.Configure() + // ... other configuration options here + .AfterBuild(builder => { + // Define logic here + }) + .Show(); +``` +} > [!IMPORTANT] > At this stage in the lifecycle, the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) defined by [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder).[Instance](xref:@ActiproUIRoot.Controls.UserPromptBuilder.Instance) is fully configured by the builder. Any changes made to the builder configuration at this point *will not* be applied to the control. ### BeforeShow Callback -This callback is invoked before attempting to show the prompt. At this point, the [RequestedDisplayMode](xref:@ActiproUIRoot.Controls.UserPromptBuilder.RequestedDisplayMode) has been evaluated and the [ActualDisplayMode](xref:@ActiproUIRoot.Controls.UserPromptBuilder.ActualDisplayMode) is assigned. This callback could be used to further customize the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) based on how it will be displayed. +@if (avalonia) { +This callback is invoked before attempting to show the prompt. At this point, the [RequestedDisplayMode](xref:@ActiproUIRoot.Controls.UserPromptBuilder.RequestedDisplayMode) has been evaluated and the [ActualDisplayMode](xref:@ActiproUIRoot.Controls.UserPromptBuilder.ActualDisplayMode) is assigned. This callback could be used to further customize the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) based on how it will be displayed. +} +@if (wpf) { +This callback is invoked before attempting to show the prompt. This callback could be used to further customize the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) before it is displayed. +} +@if (avalonia) { ### AfterInitializeWindow Callback (Dialogs Only) When displayed as a [Dialog](xref:@ActiproUIRoot.Controls.UserPromptDisplayMode.Dialog), this callback is invoked to finalize the [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) that will host the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) as a modal dialog. The callback is invoked after the window is initially configured. @@ -165,11 +242,27 @@ var result = await UserPromptBuilder.Configure() }) .Show(); ``` +} +@if (wpf) { +### AfterInitializeWindow Callback + +This callback is invoked to finalize the [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) that will host the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) as a modal dialog. The callback is invoked after the window is initially configured. + +```csharp +var result = UserPromptBuilder.Configure() + // ... other configuration options here + .AfterInitializeWindow(window => { + // Define logic here + }) + .Show(); +``` +} ### OnResponding Callback This callback is invoked when the user indicates a response and can be used to confirm and/or cancel the response using the [UserPromptResponseEventArgs](xref:@ActiproUIRoot.Controls.UserPromptResponseEventArgs) that are passed. +@if (avalonia) { ```csharp var result = await UserPromptBuilder.Configure() // ... other configuration options here @@ -178,9 +271,21 @@ var result = await UserPromptBuilder.Configure() }) .Show(); ``` +} +@if (wpf) { +```csharp +var result = UserPromptBuilder.Configure() + // ... other configuration options here + .OnResponding((builder, args) => { + // Define logic here + }) + .Show(); +``` +} ### AfterShow Callback +@if (avalonia) { This callback is invoked after the prompt is closed and passes the [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult). ```csharp @@ -191,3 +296,16 @@ var result = await UserPromptBuilder.Configure() }) .Show(); ``` +} +@if (wpf) { +This callback is invoked after the prompt is closed and passes the [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult). + +```csharp +var result = UserPromptBuilder.Configure() + // ... other configuration options here + .AfterShow((builder, result) => { + // Define logic here + }) + .Show(); +``` +} \ No newline at end of file diff --git a/Documentation/topics/fundamentals/user-prompt/clipboard.md b/Documentation/topics/fundamentals/user-prompt/clipboard.md index ffe2c82..98b128c 100644 --- a/Documentation/topics/fundamentals/user-prompt/clipboard.md +++ b/Documentation/topics/fundamentals/user-prompt/clipboard.md @@ -5,7 +5,7 @@ order: 25 --- # Clipboard Support -[UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) supports copying text to the clipboard when the default clipboard keyboard shortcut (e.g., Ctrl+C on Windows) is invoked. Since [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) also supports rich content of potentially complex control structures, it may not be possible to accurately translate the various properties to textual clipboard content. +[UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) supports copying text to the clipboard when @if (avalonia) { the default clipboard keyboard shortcut (e.g., Ctrl+C on Windows) is invoked.}@if (wpf) { Ctrl+C is pressed}. Since [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) also supports rich content of potentially complex control structures, it may not be possible to accurately translate the various properties to textual clipboard content. The properties in the following table have a corresponding string-based property that can be used to explicitly define the text to be placed on the system clipboard for that object when the copy command is invoked. @@ -26,6 +26,7 @@ When [ButtonItemsClipboardText](xref:@ActiproUIRoot.Controls.UserPromptControl.B When using the [builder pattern](builder-pattern.md), the attached property can be set using the [WithContentClipboardText](xref:@ActiproUIRoot.Controls.UserPromptButtonBuilder.WithContentClipboardText*) method of the button's builder as shown in the following example: +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here @@ -34,4 +35,16 @@ await UserPromptBuilder.Configure() .WithContentClipboardText("Continue") ) .Show(); -``` \ No newline at end of file +``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithButton(_ => _ + .WithResult(UserPromptStandardResult.Ignore) + .WithContentClipboardText("Continue") + ) + .Show(); +``` +} \ No newline at end of file diff --git a/Documentation/topics/fundamentals/user-prompt/display-and-results.md b/Documentation/topics/fundamentals/user-prompt/display-and-results.md index 17e5520..0040787 100644 --- a/Documentation/topics/fundamentals/user-prompt/display-and-results.md +++ b/Documentation/topics/fundamentals/user-prompt/display-and-results.md @@ -7,6 +7,7 @@ order: 6 Once the [User Prompt Content](user-prompt-content.md) and [User Prompt Buttons](user-prompt-buttons.md) have been defined, it is time to show the prompt and collect feedback from the user. +@if (avalonia) { ## Display Mode When using the [builder pattern](builder-pattern.md), the following options are available for displaying a user prompt (as defined by [UserPromptDisplayMode](xref:@ActiproUIRoot.Controls.UserPromptDisplayMode)): @@ -25,13 +26,15 @@ The [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder).[WithDis > Requesting [Dialog](xref:@ActiproUIRoot.Controls.UserPromptDisplayMode.Dialog) mode on unsupported platforms will throw `PlatformNotSupportedException`. Generally, [DialogPreferred](xref:@ActiproUIRoot.Controls.UserPromptDisplayMode.DialogPreferred) should be requested instead. The actual display mode used may differ from the display mode requested! For example, if an existing user prompt is currently displayed as an overlay, additional user prompts will also display as an overlay even if dialogs are supported and requested. Before the prompt is shown, the [ActualDisplayMode](xref:@ActiproUIRoot.Controls.UserPromptBuilder.ActualDisplayMode) property will be set to either [Dialog](xref:@ActiproUIRoot.Controls.UserPromptDisplayMode.Dialog) or [Overlay](xref:@ActiproUIRoot.Controls.UserPromptDisplayMode.Overlay) based on the display mode being used. +} ## Showing a User Prompt Dialog -The [builder pattern](builder-pattern.md) is recommended for showing user prompts, but is not required. On supported platforms, a user prompt is typically displayed as a modal dialog. The [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow).[ShowDialog](xref:@ActiproUIRoot.Controls.UserPromptWindow.ShowDialog*) method can be used to display any [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) in a modal dialog that will automatically close and return a [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) when the user responds. +The [builder pattern](builder-pattern.md) is recommended for showing user prompts, but is not required. @if (avalonia) { On supported platforms, a user prompt is typically displayed as a modal dialog. } The [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow).[ShowDialog](xref:@ActiproUIRoot.Controls.UserPromptWindow.ShowDialog*) method can be used to display any [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) in a modal dialog that will automatically close and return a @if (avalonia) { [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) }@if (wpf) { [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult) } when the user responds. The following code demonstrates showing a [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) as a modal dialog using [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) and evaluating the result: +@if (avalonia) { ```csharp var userPromptControl = new UserPromptControl() { Content = "This file has been modified. Do you want to save your changes before closing?", @@ -42,9 +45,23 @@ if (result == MessageBoxResult.Yes) { // Insert code to save changes here } ``` +} +@if (wpf) { +```csharp +var userPromptControl = new UserPromptControl() { + Content = "This file has been modified. Do you want to save your changes before closing?", + StandardButtons = UserPromptStandardButtons.YesNo, +}; +var result = UserPromptWindow.ShowDialog(userPromptControl, "Save Changes?"); +if (result == UserPromptStandardResult.Yes) { + // Insert code to save changes here +} +``` +} When using the [builder pattern](builder-pattern.md), the [Show](xref:@ActiproUIRoot.Controls.UserPromptBuilder.Show*) method is used to display a prompt based on the configuration and return the result as shown in the sample blow: +@if (avalonia) { ```csharp var result = await UserPromptBuilder.Configure() .WithContent("This file has been modified. Do you want to save your changes before closing?") @@ -54,6 +71,18 @@ if (result == MessageBoxResult.Yes) { // Insert code to save changes here } ``` +} +@if (wpf) { +```csharp +var result = UserPromptBuilder.Configure() + .WithContent("This file has been modified. Do you want to save your changes before closing?") + .WithStandardButtons(UserPromptStandardButtons.YesNo) + .Show(); +if (result == UserPromptStandardResult.Yes) { + // Insert code to save changes here +} +``` +} > [!NOTE] > See the "Configuring and Evaluating Results" section below for more details on working with the result of a prompt. @@ -64,13 +93,24 @@ All `Window` instances should have a title, so the [UserPromptWindow](xref:@Acti When using the [builder pattern](builder-pattern.md), the [WithTitle](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithTitle*) method is used to set the title associated with the user prompt: +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here .WithTitle("Actipro Avalonia UI Controls") .Show(); ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithTitle("Actipro WPF Controls") + .Show(); +``` +} +@if (avalonia) { #### Overlay Title Mode When using the [builder pattern](builder-pattern.md) and displaying a prompt as an [Overlay](xref:@ActiproUIRoot.Controls.UserPromptDisplayMode.Overlay), the following modes are available for displaying titles (as defined by [UserPromptOverlayTitleMode](xref:@ActiproUIRoot.Controls.UserPromptOverlayTitleMode)): @@ -84,10 +124,11 @@ When using the [builder pattern](builder-pattern.md) and displaying a prompt as The [ShowWhenNoHeader](xref:@ActiproUIRoot.Controls.UserPromptOverlayTitleMode.ShowWhenNoHeader) mode is the default, but the static [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder).[DefaultOverlayTitleMode](xref:@ActiproUIRoot.Controls.UserPromptBuilder.DefaultOverlayTitleMode) property can be used to change the default to a different value. The [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder).[WithOverlayTitleMode](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithOverlayTitleMode*) method can be used to configure the [OverlayTitleMode](xref:@ActiproUIRoot.Controls.UserPromptBuilder.OverlayTitleMode) of an individual builder instance to a non-default value. +} #### Inferred and Fallback Titles -When a title is undefined or `null`, the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[StandardStatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardStatusImage) property is used to infer a contextually appropriate title. For example, the title is set to `"Warning"` when the status image is [MessageBoxImage](xref:@ActiproUIRoot.Controls.MessageBoxImage).[Warning](xref:@ActiproUIRoot.Controls.MessageBoxImage.Warning). +When a title is undefined or `null`, the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[StandardStatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardStatusImage) property is used to infer a contextually appropriate title. For example, the title is set to `"Warning"` when the status image is @if (avalonia) { [MessageBoxImage](xref:@ActiproUIRoot.Controls.MessageBoxImage).[Warning](xref:@ActiproUIRoot.Controls.MessageBoxImage.Warning) }@if (wpf) { [UserPromptStandardImage](xref:@ActiproUIRoot.Controls.UserPromptStandardImage).[Warning](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.Warning) }. If a title cannot be inferred, the static [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder).[FallbackTitle](xref:@ActiproUIRoot.Controls.UserPromptBuilder.FallbackTitle) value will be used. @@ -98,20 +139,39 @@ If a title is still undefined, the `TitleAttribute` metadata of the entry assemb ### Close Caption Button +@if (avalonia) { A [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) will only display a **Close** caption button in the title bar if a response can be associated with closing the window. The [CloseResult](xref:@ActiproUIRoot.Controls.UserPromptControl.CloseResult) property is used to define the result associated with closing the window instead of invoking an available button. When set to [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult).[None](xref:@ActiproUIRoot.Controls.MessageBoxResult.None), the **Close** caption button will not be displayed. When [CloseResult](xref:@ActiproUIRoot.Controls.UserPromptControl.CloseResult) is set to `null` and the [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons) property is used to define the available buttons, an appropriate [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) will be assigned to [CloseResult](xref:@ActiproUIRoot.Controls.UserPromptControl.CloseResult) based on the current value of [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons). For example, using [MessageBoxButtons](xref:@ActiproUIRoot.Controls.MessageBoxButtons).[YesNoCancel](xref:@ActiproUIRoot.Controls.MessageBoxButtons.YesNoCancel) will automatically assign [MessageBoxButtons](xref:@ActiproUIRoot.Controls.MessageBoxButtons).[Cancel](xref:@ActiproUIRoot.Controls.MessageBoxButtons.Cancel) as the [CloseResult](xref:@ActiproUIRoot.Controls.UserPromptControl.CloseResult). Explicitly set the [CloseResult](xref:@ActiproUIRoot.Controls.UserPromptControl.CloseResult) to [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult).[None](xref:@ActiproUIRoot.Controls.MessageBoxResult.None) to prevent the **Close** caption button from automatically displaying. +} +@if (wpf) { +A [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) will only display a **Close** caption button in the title bar if a response can be associated with closing the window. The [CloseResult](xref:@ActiproUIRoot.Controls.UserPromptControl.CloseResult) property is used to define the result associated with closing the window instead of invoking an available button. When set to [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult).[None](xref:@ActiproUIRoot.Controls.UserPromptStandardResult.None), the **Close** caption button will not be displayed. + +When [CloseResult](xref:@ActiproUIRoot.Controls.UserPromptControl.CloseResult) is set to `null` and the [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons) property is used to define the available buttons, an appropriate [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult) will be assigned to [CloseResult](xref:@ActiproUIRoot.Controls.UserPromptControl.CloseResult) based on the current value of [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons). + +For example, using [UserPromptStandardButtons](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons).[YesNoCancel](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.YesNoCancel) will automatically assign [UserPromptStandardButtons](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons).[Cancel](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.Cancel) as the [CloseResult](xref:@ActiproUIRoot.Controls.UserPromptControl.CloseResult). Explicitly set the [CloseResult](xref:@ActiproUIRoot.Controls.UserPromptControl.CloseResult) to [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult).[None](xref:@ActiproUIRoot.Controls.UserPromptStandardResult.None) to prevent the **Close** caption button from automatically displaying. +} When using the [builder pattern](builder-pattern.md), the [WithCloseResult](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithCloseResult*) method is used to set the close result: +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here .WithCloseResult(MessageBoxResult.Ignore) .Show(); ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithCloseResult(UserPromptStandardResult.Ignore) + .Show(); +``` +} ## Configuring and Evaluating Results @@ -127,7 +187,7 @@ When using the [builder pattern](builder-pattern.md), the [Show](xref:@ActiproUI Typically, the first available button will be the default button that receives initial focus. There are two ways to change this behavior to explicitly indicate which button should be the default: [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[DefaultResult](xref:@ActiproUIRoot.Controls.UserPromptControl.DefaultResult) and `Button.IsDefault`. -When standard buttons are configured using the [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons) property, the default button is defined by setting the [DefaultResult](xref:@ActiproUIRoot.Controls.UserPromptControl.DefaultResult) property to the [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) that corresponds to the standard button. +When standard buttons are configured using the [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons) property, the default button is defined by setting the [DefaultResult](xref:@ActiproUIRoot.Controls.UserPromptControl.DefaultResult) property to the @if (avalonia) { [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) }@if (wpf) { [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult) } that corresponds to the standard button. When custom buttons are configured using the [ButtonItems](xref:@ActiproUIRoot.Controls.UserPromptControl.ButtonItems) property, the first `Button` control whose `IsDefault` property is set to `true` will be configured as the default. @@ -144,7 +204,7 @@ await UserPromptBuilder.Configure() ``` > [!TIP] -> Custom buttons can also use the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[DefaultResult](xref:@ActiproUIRoot.Controls.UserPromptControl.DefaultResult) property to define the default button if the desired `Button` has set the `UserPromptControl.ButtonResult` attached property to a [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult). +> Custom buttons can also use the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[DefaultResult](xref:@ActiproUIRoot.Controls.UserPromptControl.DefaultResult) property to define the default button if the desired `Button` has set the `UserPromptControl.ButtonResult` attached property to a @if (avalonia) { [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) }@if (wpf) { [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult) }. > [!WARNING] > The [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[DefaultResult](xref:@ActiproUIRoot.Controls.UserPromptControl.DefaultResult) property is ignored if any button has set `Button.IsDefault` to `true`. @@ -155,6 +215,7 @@ The [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[Respond When using the [builder pattern](builder-pattern.md), the [OnResponding](xref:@ActiproUIRoot.Controls.UserPromptBuilder.OnResponding*) method is used to define a callback that will receive the [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder) and [UserPromptResponseEventArgs](xref:@ActiproUIRoot.Controls.UserPromptResponseEventArgs) of the event. The following demonstrates how the callback can be used to cancel a response: +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here @@ -167,6 +228,21 @@ await UserPromptBuilder.Configure() }) .Show(); ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .OnResponding((builder, args) => { + if ((builder?.Instance is UserPromptControl userPromptControl) && (userPromptControl.IsChecked)) { + // Cancel the response + ThemedMessageBox.Show($"Cancelling response of '{args.Response}'", "Result Canceled"); + args.Cancel = true; + } + }) + .Show(); +``` +} > [!WARNING] > The [Responding](xref:@ActiproUIRoot.Controls.UserPromptControl.Responding) event handler or [OnResponding](xref:@ActiproUIRoot.Controls.UserPromptBuilder.OnResponding*) callback must be executed synchronously so the thread will be blocked waiting for the response. Otherwise, the prompt will close before the event/callback has completed executing. @@ -183,6 +259,7 @@ By default, a [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow). The [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow).[ShowDialog](xref:@ActiproUIRoot.Controls.UserPromptWindow.ShowDialog*) method accepts a callback that can be used to configure the [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) before it is displayed. The following sample demonstrates changing the startup location to `CenterOwner`: +@if (avalonia) { ```csharp var userPromptControl = new UserPromptControl() { ... }; Window owner = null; // Use default @@ -195,17 +272,42 @@ await UserPromptWindow.ShowDialog( window.WindowStartupLocation = WindowStartupLocation.CenterOwner; }); ``` +} +@if (wpf) { +```csharp +var userPromptControl = new UserPromptControl() { ... }; +Window owner = null; // Use default + +UserPromptWindow.ShowDialog( + userPromptControl, + "Window Title", + owner, + window => { + window.WindowStartupLocation = WindowStartupLocation.CenterOwner; + }); +``` +} See the "Customize UserPromptWindow" section of the [Customizing Appearance](appearance.md) topic for more examples of how to set properties like `WindowStartupLocation` on the [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow). When using the [builder pattern](builder-pattern.md), the [WithWindowStartupLocation](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithWindowStartupLocation*) method is used to specify a desired startup location. +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here .WithWindowStartupLocation(WindowStartupLocation.CenterOwner) .Show(); ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithWindowStartupLocation(WindowStartupLocation.CenterOwner) + .Show(); +``` +} > [!TIP] > When using the [builder pattern](builder-pattern.md) with auto-sizing, prompts that can be sized within the bounds of their owner `Window` will be configured with `WindowStartupLocation.CenterOwner` and only larger prompts will default to `WindowStartupLocation.CenterScreen`. This dynamic centering, which is the default behavior, is only available if an explicit startup location is not specified. @@ -222,5 +324,28 @@ The minimum width, accessible by [AutoSizeMinimumWidth](xref:@ActiproUIRoot.Cont When [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder).[WindowStartupLocation](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WindowStartupLocation) is `null` (the default), the auto-size logic will first attempt to size the prompt to a width and height that do not exceed the bounds of the owner. This is important to allow `WindowStartupLocation.CenterOwner` to be used without some parts of the prompt potentially displayed off screen. Otherwise, if it is not displayed as an overlay, the prompt will be sized within the working area of the screen so that `WindowStartupLocation.CenterScreen` can be used to fully display the prompt. +@if (avalonia) { +> [!TIP] +> Auto-size logic is also applied to [MessageBox](message-box.md) since it uses the [builder pattern](builder-pattern.md) to create prompts. This mean that, by default, [MessageBox](xref:@ActiproUIRoot.Controls.MessageBox).[Show](xref:@ActiproUIRoot.Controls.MessageBox.Show*) will always attempt to center messages over the owner, but will fall back to center screen if the message is too big. +} +@if (wpf) { > [!TIP] -> Auto-size logic is also applied to [MessageBox](message-box.md) since it uses the [builder pattern](builder-pattern.md) to create prompts. This mean that, by default, [MessageBox](xref:@ActiproUIRoot.Controls.MessageBox).[Show](xref:@ActiproUIRoot.Controls.MessageBox.Show*) will always attempt to center messages over the owner, but will fall back to center screen if the message is too big. \ No newline at end of file +> Auto-size logic is also applied to [ThemedMessageBox](message-box.md) since it uses the [builder pattern](builder-pattern.md) to create prompts. This mean that, by default, [ThemedMessageBox](xref:@ActiproUIRoot.Controls.ThemedMessageBox).[Show](xref:@ActiproUIRoot.Controls.ThemedMessageBox.Show*) will always attempt to center messages over the owner, but will fall back to center screen if the message is too big. +} + +@if (wpf) { +## Playing a SystemSound + +When a native `MessageBox` is displayed, an associated `System.Media.SystemSound` may be played based on the value of `MessageBoxImage` used. Set [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[SystemSound](xref:@ActiproUIRoot.Controls.UserPromptControl.SystemSound) to any `SystemSound` and it will be played when the [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) is shown. + +When [SystemSound](xref:@ActiproUIRoot.Controls.UserPromptControl.SystemSound) is set to `null` and the [StandardStatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardStatusImage) property is used to define the status image, an appropriate `SystemSound` will be automatically assigned based on the image as shown in the following table: + +| StandardStatusImage | SystemSound | +|-----|-----| +| [None](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.None) | None | +| [Error](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.Error) | `System.Media.SystemSounds.Hand` | +| [Information](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.Information) | `System.Media.SystemSounds.Asterisk` | +| [Question](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.Question) | `System.Media.SystemSounds.Question` | +| [Warning](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.Warning) | `System.Media.SystemSounds.Exclamation` | + +} \ No newline at end of file diff --git a/Documentation/topics/fundamentals/user-prompt/extension-methods.md b/Documentation/topics/fundamentals/user-prompt/extension-methods.md index 27a7d0a..4c01dbe 100644 --- a/Documentation/topics/fundamentals/user-prompt/extension-methods.md +++ b/Documentation/topics/fundamentals/user-prompt/extension-methods.md @@ -7,21 +7,33 @@ order: 15 One of the benefits of the [builder pattern](builder-pattern.md) is that it's easy to extend the builder class with extension methods. +@if (avalonia) { > [!IMPORTANT] > Import the `ActiproSoftware.UI.Avalonia.Controls` namespace to include the built-in extension methods. +} +@if (wpf) { +> [!IMPORTANT] +> Import the `ActiproSoftware.Windows.Extensions` namespace to include the built-in extension methods. +} ## Exception Prompt A common scenario in application development is to catch an `Exception` and prompt the user that an error occurred. +@if (avalonia) { ![Screenshot](../images/user-prompt-for-exception.png) +} +@if (wpf) { +![Screenshot](../../images/user-prompt-for-exception.png) +} *UserPromptControl configured to display details of an exception with stack trace expanded* -The [ForException](xref:@ActiproUIRoot.Controls.UserPromptExtensions.ForException*) extension method can be used to pre-configure the [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder) to display an exception. This includes the error status image, a summary of the error, and a detailed stack trace in the expanded information with a link to copy the stack trace to the clipboard. +The @if (avalonia) { [ForException](xref:@ActiproUIRoot.Controls.UserPromptExtensions.ForException*) }@if (wpf) { [ForException](xref:@ActiproUIRoot.Extensions.UserPromptExtensions.ForException*) } extension method can be used to pre-configure the [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder) to display an exception. This includes the error status image, a summary of the error, and a detailed stack trace in the expanded information with a link to copy the stack trace to the clipboard. The following sample demonstrates how easily a rich exception prompt can be created and shown to the user: +@if (avalonia) { ```csharp try { // @@ -32,6 +44,19 @@ catch (Exception ex) { .Show(); } ``` +} +@if (wpf) { +```csharp +try { + // +} +catch (Exception ex) { + UserPromptBuilder.Configure() + .ForException(ex, "Custom header text.") + .Show(); +} +``` +} > [!TIP] > See the [Localization](localization.md) topic for details on how to customize the string resources used for exception prompt. \ No newline at end of file diff --git a/Documentation/topics/fundamentals/user-prompt/index.md b/Documentation/topics/fundamentals/user-prompt/index.md index c468131..cef707f 100644 --- a/Documentation/topics/fundamentals/user-prompt/index.md +++ b/Documentation/topics/fundamentals/user-prompt/index.md @@ -7,7 +7,12 @@ order: 1 **User Prompt** provides a modern replacement for traditional **MessageBox** or **Task Dialog** functionality. +@if (avalonia) { ![Screenshot](../images/user-prompt.png) +} +@if (wpf) { +![Screenshot](../../images/user-prompt.png) +} *UserPromptControl with optional content areas labeled, OK/Cancel buttons, Information status image, and optional footer image* @@ -25,7 +30,7 @@ Key features of User Prompt include: - A simple and familiar [MessageBox](message-box.md) API for common prompts. - A convenient [UserPromptBuilder](builder-pattern.md) that uses a fluent API to easily configure and show complex prompts. - Use [extension methods](extension-methods.md) to easily configure prompts like showing an `Exception` message with a stack trace. -- Full support for [Actipro Themes](../../themes/index.md). +- Full support for @if (avalonia) { [Actipro Themes](../../themes/index.md) } @if (wpf) { [Actipro Themes](../../../themes/index.md) }. ## Controls @@ -43,6 +48,7 @@ The [MessageBox](message-box.md) class is intentionally designed to be consisten The following code demonstrates using [MessageBox](message-box.md) to prompt the user with a question: +@if (avalonia) { ```csharp var result = await MessageBox.Show( "The specified file already exists. Do you want to overwrite the file?", @@ -50,6 +56,16 @@ var result = await MessageBox.Show( MessageBoxButtons.YesNo, MessageBoxImage.Question); ``` +} +@if (wpf) { +```csharp +var result = ThemedMessageBox.Show( + "The specified file already exists. Do you want to overwrite the file?", + "Overwrite existing file?", + MessageBoxButton.YesNo, + MessageBoxImage.Question); +``` +} See the [MessageBox](message-box.md) topic for more information. @@ -61,6 +77,7 @@ The [builder pattern](builder-pattern.md) can be used to easily configure and sh The following code demonstrates creating a [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) with a header, message, **Yes** and **No** buttons, a **Question** status image, and a checkbox: +@if (avalonia) { ```csharp var result = await UserPromptBuilder.Configure() .WithHeaderContent("Overwrite existing file?") @@ -70,5 +87,17 @@ var result = await UserPromptBuilder.Configure() .WithCheckBoxContent("_Always overwrite files") .Show(); ``` +} +@if (wpf) { +```csharp +var result = UserPromptBuilder.Configure() + .WithHeaderContent("Overwrite existing file?") + .WithContent("The specified file already exists. Do you want to overwrite the file?") + .WithStandardButtons(UserPromptStandardButtons.YesNo) + .WithStatusImage(UserPromptStandardImage.Question) + .WithCheckBoxContent("_Always overwrite files") + .Show(); +``` +} See the [Builder Pattern](builder-pattern.md) topic for more information. diff --git a/Documentation/topics/fundamentals/user-prompt/localization.md b/Documentation/topics/fundamentals/user-prompt/localization.md index 699695d..8f17d08 100644 --- a/Documentation/topics/fundamentals/user-prompt/localization.md +++ b/Documentation/topics/fundamentals/user-prompt/localization.md @@ -7,8 +7,14 @@ order: 30 The string resources below are available to localize or customize built-in strings utilized by user prompt. Each string resource is defined in a specific assembly and corresponding namespace. - See the [Customizing String Resources](../../customizing-string-resources.md) topic for additional details. +@if (avalonia) { +See the [Customizing String Resources](../../customizing-string-resources.md) topic for additional details. +} +@if (wpf) { +See the [Customizing String Resources](../../../customizing-string-resources.md) topic for additional details. +} +@if (avalonia) { ## Shared Library String Resources The following string resources are defined in the Shared Library assembly and are accessible through types defined in the `ActiproSoftware.Properties.Shared` namespace: @@ -39,7 +45,9 @@ This code shows how to set custom values for string resources. ActiproSoftware.Properties.Shared.SR.SetCustomString(ActiproSoftware.Properties.Shared.SRName.UIButtonRetryText, "T_ry Again"); ActiproSoftware.Properties.Shared.SR.SetCustomString(ActiproSoftware.Properties.Shared.SRName.UIButtonIgnoreText, "Cont_inue"); ``` +} +@if (avalonia) { ## Fundamentals String Resources The following string resources are defined in the Fundamentals assembly and are accessible through types defined in the `ActiproSoftware.Properties.Fundamentals` namespace: @@ -56,4 +64,39 @@ This code shows how to set custom values for string resources. ```csharp ActiproSoftware.Properties.Fundamentals.SR.SetCustomString(ActiproSoftware.Properties.Fundamentals.SRName.UIExceptionPromptCopyButtonText, "Copy Stack Trace"); ``` +} +@if (wpf) { +## Customize String Resources + +The following string resources are available to localize or customize built-in strings: + +| Resource key | Description | +|-----|-----| +| `UICommandCloseWindowText` | The text displayed as the tooltip for the **Close** button used by [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow). The default value is `"Close"`. | +| `UIDialogButtonAbortText` | The label of the standard [Abort](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.Abort) button. The default value is `"_Abort"`. | +| `UIDialogButtonCancelText` | The label of the standard [Cancel](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.Cancel) button. The default value is `"Cancel"`. | +| `UIDialogButtonCloseText` | The label of the standard [Close](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.Close) button. The default value is `"C_lose"`. | +| `UIDialogButtonHelpText` | The label of the standard [Help](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.Help) button. The default value is `"Help"`. | +| `UIDialogButtonIgnoreText` | The label of the standard [Ignore](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.Ignore) button. The default value is `"_Ignore"`. | +| `UIDialogButtonNoText` | The label of the standard [No](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.No) button. The default value is `"_No"`. | +| `UIDialogButtonOKText` | The label of the standard [OK](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.OK) button. The default value is `"OK"`. | +| `UIDialogButtonRetryText` | The label of the standard [Retry](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.Retry) button. The default value is `"_Retry"`. | +| `UIDialogButtonYesText` | The label of the standard [Yes](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.Yes) button. The default value is `"_Yes"`. | +| `UIDialogTitleErrorText` | The default title used by [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) when an explicit title is not defined and [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[StandardStatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardStatusImage) is set to [Error](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.Error). The default value is `"Error"`. | +| `UIDialogTitleIndeterminateText` | The default title used by [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) when an explicit title is not defined and [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[StandardStatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardStatusImage) is set to [None](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.None) or [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[StatusImageSource](xref:@ActiproUIRoot.Controls.UserPromptControl.StatusImageSource) is populated. The default value is `String.Empty`. | +| `UIDialogTitleInformationText` | The default title used by [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) when an explicit title is not defined and [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[StandardStatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardStatusImage) is set to [Information](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.Information). The default value is `"Information"`. | +| `UIDialogTitleQuestionText` | The default title used by [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) when an explicit title is not defined and [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[StandardStatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardStatusImage) is set to [Question](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.Question). The default value is `"Question"`. | +| `UIDialogTitleWarningText` | The default title used by [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) when an explicit title is not defined and [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[StandardStatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardStatusImage) is set to [Warning](xref:@ActiproUIRoot.Controls.UserPromptStandardImage.Warning). The default value is `"Warning"`. | +| `UIExceptionPromptCopyElementText` | The label of the `UIElement` displayed on the [exception prompt](extension-methods.md) to copy the stack trace. The default value is `"Copy to Clipboard"`. | +| `UIExceptionPromptDetailsLabelText` | The text of the label displayed on the [exception prompt](extension-methods.md) above the stack trace details. The default value is `"_Stack Trace:"`. | +| `UIExceptionPromptToggleDetailsCollapsedText` | The text of the expanded information toggle on the [exception prompt](extension-methods.md) when the stack trace is collapsed. The default value is `"Show _details"`. | +| `UIExceptionPromptToggleDetailsExpandedText` | The text of the expanded information toggle on the [exception prompt](extension-methods.md) when the stack trace is expanded. The default value is `"Hide _details"`. | + +This code shows how to set custom values for string resources. + +```csharp +ActiproSoftware.Products.Shared.SR.SetCustomString(ActiproSoftware.Products.Shared.SRName.UIDialogButtonRetryText.ToString(), "T_ry Again"); +ActiproSoftware.Products.Shared.SR.SetCustomString(ActiproSoftware.Products.Shared.SRName.UIDialogButtonIgnoreText.ToString(), "Cont_inue"); +``` +} diff --git a/Documentation/topics/fundamentals/user-prompt/message-box.md b/Documentation/topics/fundamentals/user-prompt/message-box.md index 85a3466..02bec0a 100644 --- a/Documentation/topics/fundamentals/user-prompt/message-box.md +++ b/Documentation/topics/fundamentals/user-prompt/message-box.md @@ -7,12 +7,15 @@ order: 2 The [MessageBox](xref:@ActiproUIRoot.Controls.MessageBox) class is intentionally designed to be consistent with the native WPF `MessageBox` API and can be used to quickly display the most common prompts. +@if (avalonia) { ![Screenshot](../images/messagebox.png) - *MessageBox dialog with optional status image* +} +@if (wpf) { +![Screenshot](../../images/messagebox.png) -> [!WARNING] -> This functionality should only be used on desktop platforms that support windowing. +*ThemedMessageBox dialog with optional status image* +} > [!TIP] > See the [Builder Pattern](builder-pattern.md) topic for additional details on showing more advanced user prompts. @@ -21,10 +24,18 @@ The [MessageBox](xref:@ActiproUIRoot.Controls.MessageBox) class is intentionally The [MessageBox](xref:@ActiproUIRoot.Controls.MessageBox).[Show](xref:@ActiproUIRoot.Controls.MessageBox.Show*) method is used to show a basic prompt based on the arguments passed. The only required argument is the text to be displayed, so the simplest prompt can be shown as follows: +@if (avalonia) { ```csharp await MessageBox.Show("Operation complete."); ``` +} +@if (wpf) { +```csharp +ThemedMessageBox.Show("Operation complete."); +``` +} +@if (avalonia) { There are two overloads of the [Show](xref:@ActiproUIRoot.Controls.MessageBox.Show*) method where one allows an owner `TopLevel` (typically a `Window`) to be passed, and the other doesn't. All other arguments are identical. The following basic arguments are available: | Argument | Description | @@ -45,10 +56,38 @@ var result = await MessageBox.Show( MessageBoxImage.Question MessageBoxResult.No); ``` +} +@if (wpf) { +There are several overloads of the [Show](xref:@ActiproUIRoot.Controls.ThemedMessageBox.Show*) method where each accepts a combination of one or more of the following basic arguments: + +| Argument | Description | +|-----|-----| +| `messageBoxText` | The primary message text to be displayed. | +| `caption` | The title bar caption of the window. | +| `button` | One or more of the `MessageBoxButton` values for each button to display. | +| `icon` | One of the `MessageBoxImage` values indicating a status image to display. | +| `defaultResult` | The default `MessageBoxResult`, which must correspond to one of the given `button` values. | + +The following code demonstrates prompting the user with a question and storing the result: + +```csharp +result = ThemedMessageBox.Show( + "The specified file already exists. Do you want to overwrite the file?", + "Overwrite existing file?", + MessageBoxButton.YesNo, + MessageBoxImage.Question + MessageBoxResult.No); +``` +} ## Owner +@if (avalonia) { The user prompt must have an owner `TopLevel` (typically a `Window`). One of the [MessageBox](xref:@ActiproUIRoot.Controls.MessageBox).[Show](xref:@ActiproUIRoot.Controls.MessageBox.Show*) overloads allows an owner to be specified. If one is not specified, a default owner will be determined. With desktop applications, the default is the currently active `Window`. On single-view applications, the default is the `TopLevel` of the current view. +} +@if (wpf) { +The user prompt must have an owner `Window`. Several of the [ThemedMessageBox](xref:@ActiproUIRoot.Controls.ThemedMessageBox).[Show](xref:@ActiproUIRoot.Controls.ThemedMessageBox.Show*) overloads allow an owner to be specified. If one is not specified, a default owner will be determined by the currently active `Window`. +} ## Advanced Configuration @@ -62,9 +101,55 @@ See the [Builder Pattern](builder-pattern.md) topic for more details on configur The following demonstrates defining a `configure` callback to add a header message: +@if (avalonia) { ```csharp await MessageBox.Show( "The project was successfully compiled and deployed to the remote server." configure: builder => builder.WithHeaderContent("Deploy successful!") ); -``` \ No newline at end of file +``` +} +@if (wpf) { +```csharp +ThemedMessageBox.Show( + "The project was successfully compiled and deployed to the remote server." + configure: builder => builder.WithHeaderContent("Deploy successful!") + ); +``` +} + +@if (wpf) { +## Replace MessageBox with ThemedMessageBox + +The native WPF `MessageBox` is a convenient and effective component for prompting the user of an application, but the lack of theme support creates an inconsistent experience. This is especially true with applications utilizing a dark theme only to have a `MessageBox` dialog appear as a bright, white box on top of an otherwise dark canvas. + +[ThemedMessageBox](xref:@ActiproUIRoot.Controls.ThemedMessageBox) is a fully themed drop-in replacement for `MessageBox` that intentionally uses the same arguments for the [ThemedMessageBox](xref:@ActiproUIRoot.Controls.ThemedMessageBox).[Show](xref:@ActiproUIRoot.Controls.ThemedMessageBox.Show*) method. This allows most applications to convert to fully themed prompts by replacing `MessageBox` with `ThemedMessageBox` in their existing code. + +> [!NOTE] +> The `MessageBox.Show` overloads which accept `MessageBoxOptions` are not implemented by [ThemedMessageBox](xref:@ActiproUIRoot.Controls.ThemedMessageBox) since the functionality enabled by those options is not necessary for WPF windows. When converting, exclude any arguments for `MessageBoxOptions` and test your application for expected behavior. + +### Alias-based Conversion + +Since the API is the same between `MessageBox` and [ThemedMessageBox](xref:@ActiproUIRoot.Controls.ThemedMessageBox), an application can easily redirect all `MessageBox` usage to [ThemedMessageBox](xref:@ActiproUIRoot.Controls.ThemedMessageBox) by using an alias. + +> [!TIP] +> Using an alias to convert an application to [ThemedMessageBox](xref:@ActiproUIRoot.Controls.ThemedMessageBox) will help ensure the native `MessageBox` is not accidentally used out of habit or by unknowing contributors. + +The following code demonstrates using an alias in the same file: + +```csharp +using MessageBox = ActiproSoftware.Windows.Controls.ThemedMessageBox; +... +MessageBox.Show("This message will be displayed by ThemedMessageBox without any other code change."); +``` + +Starting with C# v10, a global alias can be used to affect all files: + +```csharp +global using MessageBox = ActiproSoftware.Windows.Controls.ThemedMessageBox; +``` + +> [!WARNING] +> The alias will not apply to any usage of `MessageBox` written with a fully or partially qualified domain name; e.g., `System.Windows.MessageBox.Show`. + +} diff --git a/Documentation/topics/fundamentals/user-prompt/user-prompt-buttons.md b/Documentation/topics/fundamentals/user-prompt/user-prompt-buttons.md index 8ac187c..d025c55 100644 --- a/Documentation/topics/fundamentals/user-prompt/user-prompt-buttons.md +++ b/Documentation/topics/fundamentals/user-prompt/user-prompt-buttons.md @@ -9,17 +9,31 @@ A user typically responds to a prompt by invoking a button that corresponds to t 1. Use a pre-defined set of one or more standard buttons. 1. Explicitly define each button. +@if (avalonia) { By default, an [OK](xref:@ActiproUIRoot.Controls.MessageBoxButtons.OK) button will be displayed if no other button configuration is assigned. +} +@if (wpf) { +By default, an [OK](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.OK) button will be displayed if no other button configuration is assigned. +} ## Standard Buttons +@if (avalonia) { Most prompts can be built using one or more of the commonly used buttons defined by the [MessageBoxButtons](xref:@ActiproUIRoot.Controls.MessageBoxButtons) enumeration. These flags can easily be combined to display a fixed set of buttons to the user and are assigned to the [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons) property. > [!TIP] > Several popular combinations of buttons are predefined for convenience. For instance, the value [MessageBoxButtons](xref:@ActiproUIRoot.Controls.MessageBoxButtons).[YesNoCancel](xref:@ActiproUIRoot.Controls.MessageBoxButtons.YesNoCancel) can be used instead of `MessageBoxButtons.Yes | MessageBoxButtons.No | MessageBoxButtons.Cancel`. +} +@if (wpf) { +Most prompts can be built using one or more of the commonly used buttons defined by the [UserPromptStandardButtons](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons) enumeration. These flags can easily be combined to display a fixed set of buttons to the user and are assigned to the [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons) property. + +> [!TIP] +> Several popular combinations of buttons are predefined for convenience. For instance, the value [UserPromptStandardButtons](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons).[YesNoCancel](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.YesNoCancel) can be used instead of `UserPromptStandardButtons.Yes | UserPromptStandardButtons.No | UserPromptStandardButtons.Cancel`. +} When using the [builder pattern](builder-pattern.md), the [WithStandardButtons](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithStandardButtons*) method is used to configure the [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons) property. +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here @@ -27,6 +41,16 @@ await UserPromptBuilder.Configure() .WithStandardButtons(MessageBoxButtons.YesNo) .Show(); ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithContent("Error processing file. How do you want to proceed?") + .WithStandardButtons(UserPromptStandardButtons.YesNo) + .Show(); +``` +} > [!WARNING] > The [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons) property is ignored if the [ButtonItems](xref:@ActiproUIRoot.Controls.UserPromptControl.ButtonItems) property is explicitly populated as discussed below. @@ -34,14 +58,15 @@ await UserPromptBuilder.Configure() ## Explicit Button Definitions While the [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons) can be used for most prompts, some scenarios may require explicitly defining the individual buttons. Examples include: -- Adding a button that is not available as one of the [MessageBoxButtons](xref:@ActiproUIRoot.Controls.MessageBoxButtons) values. +- Adding a button that is not available as one of the @if (avalonia) { [MessageBoxButtons](xref:@ActiproUIRoot.Controls.MessageBoxButtons) }@if (wpf) { [UserPromptStandardButtons](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons) } values. - Customizing the content of a button (e.g., changing the label or adding an icon). - Assigning a custom command to the button. Collections of explicitly defined buttons are assigned to the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[ButtonItems](xref:@ActiproUIRoot.Controls.UserPromptControl.ButtonItems) property (which can be set to any `IEnumerable`). Each available object will be displayed as a `Button` by the [UserPromptButtonItemsControl](xref:@ActiproUIRoot.Controls.Primitives.UserPromptButtonItemsControl). -When using the [builder pattern](builder-pattern.md), the [WithButton](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithButton*) method is used to configure individual buttons that will be added to the [ButtonItems](xref:@ActiproUIRoot.Controls.UserPromptControl.ButtonItems) property. The simplest overload to [WithButton](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithButton*) accepts a [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) that associates a response with the button. The overload also has optional arguments to define an `ICommand` or label. +When using the [builder pattern](builder-pattern.md), the [WithButton](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithButton*) method is used to configure individual buttons that will be added to the [ButtonItems](xref:@ActiproUIRoot.Controls.UserPromptControl.ButtonItems) property. The simplest overload to [WithButton](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithButton*) accepts a @if (avalonia) { [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) }@if (wpf) { [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult) } that associates a response with the button. The overload also has optional arguments to define an `ICommand` or label. +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here @@ -49,11 +74,22 @@ await UserPromptBuilder.Configure() .WithButton(MessageBoxResult.Ignore, label: "Continue") .Show(); ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithButton(UserPromptStandardResult.Cancel) + .WithButton(UserPromptStandardResult.Ignore, label: "Continue") + .Show(); +``` +} ### Button Builder For more advanced button definitions, a special [UserPromptButtonBuilder](xref:@ActiproUIRoot.Controls.UserPromptButtonBuilder) is utilized to build each button. Similar to the [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptBuilder), the button builder is used to create and configure a `Button` control that is suitable for use on a user prompt. +@if (avalonia) { The following example demonstrates using the button builder to create a button with a custom label and an alternate theme using Avalonia `Classes`: ```csharp @@ -66,26 +102,50 @@ await UserPromptBuilder.Configure() ) .Show(); ``` +} +@if (wpf) { +The following example demonstrates using the button builder to create a button with a custom label: + +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithButton(_ => _ + .WithResult(UserPromptStandardResult.Ignore) + .WithContent("Continue") + ) + .Show(); +``` +} ## Help Button -A special [Help](xref:@ActiproUIRoot.Controls.MessageBoxButtons.Help) button can be included in [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons). Invoking the **Help** button will execute the command designated by the [HelpCommand](xref:@ActiproUIRoot.Controls.UserPromptControl.HelpCommand) property. An additional [HelpCommandParameter](xref:@ActiproUIRoot.Controls.UserPromptControl.HelpCommandParameter) can also be populated that will be passed to the [HelpCommand](xref:@ActiproUIRoot.Controls.UserPromptControl.HelpCommand) when invoked. +A special @if (avalonia) { [Help](xref:@ActiproUIRoot.Controls.MessageBoxButtons.Help) }@if (wpf) { [Help](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.Help) } button can be included in [StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons). Invoking the **Help** button will execute the command designated by the [HelpCommand](xref:@ActiproUIRoot.Controls.UserPromptControl.HelpCommand) property. An additional [HelpCommandParameter](xref:@ActiproUIRoot.Controls.UserPromptControl.HelpCommandParameter) can also be populated that will be passed to the [HelpCommand](xref:@ActiproUIRoot.Controls.UserPromptControl.HelpCommand) when invoked. When using the [builder pattern](builder-pattern.md), the [WithHelpCommand](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithHelpCommand*) method is used to automatically configure an additional **Help** button and can be used with standard buttons or explicitly defined buttons This method allows the [HelpCommand](xref:@ActiproUIRoot.Controls.UserPromptControl.HelpCommand) to be directly assigned, or a callback `Action` can be defined instead. The following shows how to configure a **Help** button using a callback that will invoke a custom method for displaying help: +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here .WithHelpCommand(() => OpenHelp()) .Show(); ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithHelpCommand(() => OpenHelp()) + .Show(); +``` +} > [!IMPORTANT] -> When an `ICommand` or callback `Action` is defined for the **Help** button, the prompt will *not* close when the **Help** button is clicked. Otherwise, the prompt will close and [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult).[Help](xref:@ActiproUIRoot.Controls.MessageBoxResult.Help) will be returned as the user response just like clicking any other button. +> When an `ICommand` or callback `Action` is defined for the **Help** button, the prompt will *not* close when the **Help** button is clicked. Otherwise, the prompt will close and @if (avalonia) { [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult).[Help](xref:@ActiproUIRoot.Controls.MessageBoxResult.Help) }@if (wpf) { [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult).[Help](xref:@ActiproUIRoot.Controls.UserPromptStandardResult.Help) } will be returned as the user response just like clicking any other button. ## ButtonResult Attached Property -The `UserPromptControl.ButtonResult` attached property is used to associate a `Button` with a specific [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult). Any `Button` without an explicitly defined `Button.Command` will be automatically assigned a special `ICommand` that, when invoked, will read the `UserPromptControl.ButtonResult` attached property of the `Button` and assign that value to [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[Result](xref:@ActiproUIRoot.Controls.UserPromptControl.Result). +The `UserPromptControl.ButtonResult` attached property is used to associate a `Button` with a specific @if (avalonia) { [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) }@if (wpf) { [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult) }. Any `Button` without an explicitly defined `Button.Command` will be automatically assigned a special `ICommand` that, when invoked, will read the `UserPromptControl.ButtonResult` attached property of the `Button` and assign that value to [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[Result](xref:@ActiproUIRoot.Controls.UserPromptControl.Result). The `UserPromptControl.ButtonResult` attached property is also used to identify which `Button` corresponds to [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[DefaultResult](xref:@ActiproUIRoot.Controls.UserPromptControl.DefaultResult) and will receive keyboard focus when shown in a [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow). @@ -94,6 +154,7 @@ The `UserPromptControl.ButtonResult` attached property is also used to identify When using the [builder pattern](builder-pattern.md), the button builder's [WithResult](xref:@ActiproUIRoot.Controls.UserPromptButtonBuilder.WithResult*) method is used to define the result. +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here @@ -102,21 +163,41 @@ await UserPromptBuilder.Configure() ) .Show(); ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithButton(_ => _ + .WithResult(UserPromptStandardResult.Ignore) + ) + .Show(); +``` +} ### Standard Button Result +@if (avalonia) { Each individual value for [MessageBoxButtons](xref:@ActiproUIRoot.Controls.MessageBoxButtons) has a corresponding value in [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult). For example, the [MessageBoxButtons](xref:@ActiproUIRoot.Controls.MessageBoxButtons).[OK](xref:@ActiproUIRoot.Controls.MessageBoxButtons.OK) button corresponds to the [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult).[OK](xref:@ActiproUIRoot.Controls.MessageBoxResult.OK) result. When using the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons) property to define buttons, the `UserPromptControl.ButtonResult` attached property is automatically populated with the corresponding [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult). Invoking one of the buttons will assign the value to [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[Result](xref:@ActiproUIRoot.Controls.UserPromptControl.Result). > [!NOTE] > The [MessageBoxButtons](xref:@ActiproUIRoot.Controls.MessageBoxButtons).[Help](xref:@ActiproUIRoot.Controls.MessageBoxButtons.Help) button is primarily used to invoke the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[HelpCommand](xref:@ActiproUIRoot.Controls.UserPromptControl.HelpCommand). If a [HelpCommand](xref:@ActiproUIRoot.Controls.UserPromptControl.HelpCommand) is defined, it does not assign a [Result](xref:@ActiproUIRoot.Controls.UserPromptControl.Result) and thus will not cause the [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) to close when invoked. +} +@if (wpf) { +Each individual value for [UserPromptStandardButtons](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons) has a corresponding value in [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult). For example, the [UserPromptStandardButtons](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons).[OK](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.OK) button corresponds to the [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult).[OK](xref:@ActiproUIRoot.Controls.UserPromptStandardResult.OK) result. When using the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[StandardButtons](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardButtons) property to define buttons, the `UserPromptControl.ButtonResult` attached property is automatically populated with the corresponding [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult). Invoking one of the buttons will assign the value to [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[Result](xref:@ActiproUIRoot.Controls.UserPromptControl.Result). + +> [!NOTE] +> The [UserPromptStandardButtons](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons).[Help](xref:@ActiproUIRoot.Controls.UserPromptStandardButtons.Help) button is primarily used to invoke the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[HelpCommand](xref:@ActiproUIRoot.Controls.UserPromptControl.HelpCommand). If a [HelpCommand](xref:@ActiproUIRoot.Controls.UserPromptControl.HelpCommand) is defined, it does not assign a [Result](xref:@ActiproUIRoot.Controls.UserPromptControl.Result) and thus will not cause the [UserPromptWindow](xref:@ActiproUIRoot.Controls.UserPromptWindow) to close when invoked. +} ## Custom Button Result -When populating [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[ButtonItems](xref:@ActiproUIRoot.Controls.UserPromptControl.ButtonItems) with custom `Button` instances, each button can optionally be associated with a specific [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) using the `UserPromptControl.ButtonResult` attached property. Any `Button` without an explicitly defined `Button.Command` will be automatically assigned a special `ICommand` that, when invoked, will read the `UserPromptControl.ButtonResult` attached property of the `Button` and assign that value to [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[Result](xref:@ActiproUIRoot.Controls.UserPromptControl.Result). +When populating [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[ButtonItems](xref:@ActiproUIRoot.Controls.UserPromptControl.ButtonItems) with custom `Button` instances, each button can optionally be associated with a specific @if (avalonia) { [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) }@if (wpf) { [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult) } using the `UserPromptControl.ButtonResult` attached property. Any `Button` without an explicitly defined `Button.Command` will be automatically assigned a special `ICommand` that, when invoked, will read the `UserPromptControl.ButtonResult` attached property of the `Button` and assign that value to [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[Result](xref:@ActiproUIRoot.Controls.UserPromptControl.Result). > [!IMPORTANT] > Any `Button` which defines its own `Button.Command` must manually assign an appropriate value to [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[Result](xref:@ActiproUIRoot.Controls.UserPromptControl.Result) when invoked. +@if (avalonia) { The following code demonstrates how to assign a [MessageBoxResult](xref:@ActiproUIRoot.Controls.MessageBoxResult) to a custom button when building a [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl): ```csharp @@ -150,4 +231,41 @@ if (result == (MessageBoxResult.CustomButton + 1)) { else if (result == (MessageBoxResult.CustomButton + 2)) { // Custom 2 was selected } -``` \ No newline at end of file +``` +} +@if (wpf) { +The following code demonstrates how to assign a [UserPromptStandardResult](xref:@ActiproUIRoot.Controls.UserPromptStandardResult) to a custom button when building a [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl): + +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithButton(_ => _ + .WithResult(UserPromptStandardResult.CustomButton) + .WithContent("Custom") + ) + .Show(); +``` + +If multiple custom buttons are required, a value can be added to [CustomButton](xref:@ActiproUIRoot.Controls.UserPromptStandardResult.CustomButton) to distinguish one custom button from another as shown below: + +```csharp +var result = UserPromptBuilder.Configure() + // ... other configuration options here + .WithButton(_ => _ + .WithResult(UserPromptStandardResult.CustomButton + 1) + .WithContent("Custom 1") + ) + .WithButton(_ => _ + .WithResult(UserPromptStandardResult.CustomButton + 2) + .WithContent("Custom 2") + ) + .Show(); + +if (result == (UserPromptStandardResult.CustomButton + 1)) { + // Custom 1 was selected +} +else if (result == (UserPromptStandardResult.CustomButton + 2)) { + // Custom 2 was selected +} +``` +} \ No newline at end of file diff --git a/Documentation/topics/fundamentals/user-prompt/user-prompt-content.md b/Documentation/topics/fundamentals/user-prompt/user-prompt-content.md index a23d5dc..afa2723 100644 --- a/Documentation/topics/fundamentals/user-prompt/user-prompt-content.md +++ b/Documentation/topics/fundamentals/user-prompt/user-prompt-content.md @@ -7,7 +7,12 @@ order: 4 To build a user prompt, you can create a new instance of [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) and set the appropriate properties just like any other `Control`. The control can be defined in XAML, but prompts often have varying content that is composed, as needed, in code. +@if (avalonia) { ![Screenshot](../images/user-prompt.png) +} +@if (wpf) { +![Screenshot](../../images/user-prompt.png) +} *UserPromptControl with optional content areas labeled, OK/Cancel buttons, Information status image, and optional footer image* @@ -29,7 +34,7 @@ When using the [builder pattern](builder-pattern.md), various `With*` methods ar 1. `With*(Func? object)` - Define a factory method that, when invoked, will return the content for the area. 1. `With*(Func? object)` - Define a factory method that, when invoked, is passed the instance of [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) being configured and returns the content for the area. -The last overload is particularly useful when the content needs to reference the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl). For example, custom content might only be visible when the prompt is expanded and would need to bind it's `IsVisible` property to the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[IsExpanded](xref:@ActiproUIRoot.Controls.UserPromptControl.IsExpanded) property. +The last overload is particularly useful when the content needs to reference the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl). For example, custom content might only be visible when the prompt is expanded and would need to bind it's @if (avalonia) { `IsVisible` } @if (wpf) { `Visibility` } property to the [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl).[IsExpanded](xref:@ActiproUIRoot.Controls.UserPromptControl.IsExpanded) property. > [!NOTE] > Each `With*` content method overload supports a `null` reference, and passing `null` will effectively clear any previously defined content. @@ -42,6 +47,7 @@ Any `TextBlock` controls used within `Content` will wrap text by default. When using the [builder pattern](builder-pattern.md), the [WithContent](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithContent*) method is used to configure the `Content` property. +@if (avalonia) { ```csharp // Simple message await UserPromptBuilder.Configure() @@ -62,8 +68,32 @@ await UserPromptBuilder.Configure() ) .Show(); ``` +} +@if (wpf) { +```csharp +// Simple message +UserPromptBuilder.Configure() + // ... other configuration options here + .WithContent("Simple string-based content") + .Show(); + +// Complex content +UserPromptBuilder.Configure() + // ... other configuration options here + .WithContent( + new StackPanel() { + Children = { + new TextBlock() { Text = "Copying..." }, + new ProgressBar() { Minimum = 0, Maximum = 100, Value = 25 } + } + } + ) + .Show(); +``` +} #### Scrollbars + The default [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) template defines `Content` within a `ScrollViewer`. Use the [HorizontalScrollBarVisibility](xref:@ActiproUIRoot.Controls.UserPromptControl.HorizontalScrollBarVisibility) and [VerticalScrollBarVisibility](xref:@ActiproUIRoot.Controls.UserPromptControl.VerticalScrollBarVisibility) properties to manage scroll bar visibility. When using the [builder pattern](builder-pattern.md), the [WithScrollBarVisibility](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithScrollBarVisibility*) method is used to configure both scrollbars. @@ -79,14 +109,24 @@ Any `TextBlock` controls used within `Header` will wrap text by default. Any `Te When using the [builder pattern](builder-pattern.md), the [WithHeaderContent](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithHeaderContent*) method is used to configure the `Header` property. +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here .WithHeaderContent("Export complete") .WithContent("Your project has been successfully exported.") .Show(); -}; ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithHeaderContent("Export complete") + .WithContent("Your project has been successfully exported.") + .Show(); +``` +} ### Footer @@ -94,6 +134,7 @@ The `Footer` property can be defined to display content at the bottom of the pro Any `TextBlock` controls used within `Footer` will wrap text by default. +@if (avalonia) { Use the [FooterImage](xref:@ActiproUIRoot.Controls.UserPromptControl.FooterImage) property to optionally display a 16x16 image next to the `Footer` content. Any `IImage` is supported. When using the [builder pattern](builder-pattern.md), the [WithFooterContent](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithFooterContent*) method is used to configure the [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptControl.Footer) property and the [WithFooterImage](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithFooterImage*) method configures the [FooterImage](xref:@ActiproUIRoot.Controls.UserPromptControl.FooterImage) property. @@ -105,6 +146,20 @@ await UserPromptBuilder.Configure() .WithFooterImage(GetImage("/Images/Icons/Help16.png")) .Show(); ``` +} +@if (wpf) { +Use the [FooterImageSource](xref:@ActiproUIRoot.Controls.UserPromptControl.FooterImageSource) property to optionally display a 16x16 image next to the `Footer` content. Any `ImageSource` is supported. + +When using the [builder pattern](builder-pattern.md), the [WithFooterContent](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithFooterContent*) method is used to configure the [UserPromptBuilder](xref:@ActiproUIRoot.Controls.UserPromptControl.Footer) property and the [WithFooterImage](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithFooterImage*) method configures the [FooterImageSource](xref:@ActiproUIRoot.Controls.UserPromptControl.FooterImageSource) property. + +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithFooterContent("Click 'Help' button for more information") + .WithFooterImageSource(new BitmapImage(new Uri("pack://application:,,,/SampleBrowser;component/Images/Icons/Help16.png"))) + .Show(); +``` +} ### CheckBox @@ -114,6 +169,7 @@ The [IsChecked](xref:@ActiproUIRoot.Controls.UserPromptControl.IsChecked) proper When using the [builder pattern](builder-pattern.md), the [WithCheckBoxContent](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithCheckBoxContent*) method is used to configure the [CheckBoxContent](xref:@ActiproUIRoot.Controls.UserPromptControl.CheckBoxContent) property and the [WithIsChecked](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithIsChecked*) method is used to configure the [IsChecked](xref:@ActiproUIRoot.Controls.UserPromptControl.IsChecked) property: +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here @@ -121,12 +177,23 @@ await UserPromptBuilder.Configure() .WithIsChecked(true) .Show(); ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithCheckBoxContent("_Stop showing messages like this") + .WithIsChecked(true) + .Show(); +``` +} > [!NOTE] > The [UserPromptControl](xref:@ActiproUIRoot.Controls.UserPromptControl) only displays the `CheckBox` and manages the state. The developer is responsible for persisting settings and suppressing any prompts the user has requested not to be displayed. When using the [builder pattern](builder-pattern.md), an overload of [WithIsChecked](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithIsChecked*) allows one delegate to be passed as the "getter" of the initial checked state, and another as the "setter" of the final checked state. This can be used to easily synchronize the checked state of the prompt with a local variable as shown below: +@if (avalonia) { ```csharp var isChecked = false; var result = await UserPromptBuilder.Configure() @@ -141,6 +208,23 @@ if (isChecked && (result == MessageBoxResult.OK)) { // Persist user request to stop prompting } ``` +} +@if (wpf) { +```csharp +var isChecked = false; +var result = UserPromptBuilder.Configure() + // ... other configuration options here + .WithCheckBoxContent("_Stop showing messages like this") + .WithIsChecked( + getter: () => isChecked, + setter: (value) => isChecked = value + ) + .Show(); +if (isChecked && (result == UserPromptStandardResult.OK)) { + // Persist user request to stop prompting +} +``` +} ### Expanded Information @@ -152,6 +236,7 @@ When [ExpandedInformationContent](xref:@ActiproUIRoot.Controls.UserPromptControl When using the [builder pattern](builder-pattern.md), the [WithExpandedInformationContent](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithExpandedInformationContent*) method is used to set the [ExpandedInformationContent](xref:@ActiproUIRoot.Controls.UserPromptControl.ExpandedInformationContent) property, the [WithExpandedInformationHeaderText](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithExpandedInformationHeaderText*) method is used to set both the [ExpandedInformationCollapsedHeaderText](xref:@ActiproUIRoot.Controls.UserPromptControl.ExpandedInformationCollapsedHeaderText) and [ExpandedInformationExpandedHeaderText](xref:@ActiproUIRoot.Controls.UserPromptControl.ExpandedInformationExpandedHeaderText) properties, and the [WithIsExpanded](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithIsExpanded*) method is used to configure the initial value of the [IsExpanded](xref:@ActiproUIRoot.Controls.UserPromptControl.IsExpanded) property: +@if (avalonia) { ```csharp await UserPromptBuilder.Configure() // ... other configuration options here @@ -159,11 +244,22 @@ await UserPromptBuilder.Configure() .WithExpandedInformationContent("This content will be hidden until the user clicks 'Show more'.") .Show(); ``` +} +@if (wpf) { +```csharp +UserPromptBuilder.Configure() + // ... other configuration options here + .WithExpandedInformationHeaderText("Show more", "Show less") + .WithExpandedInformationContent("This content will be hidden until the user clicks 'Show more'.") + .Show(); +``` +} ## Status Image An image can be used to visually communicate the status or classification of the prompt displayed. Users can quickly differentiate an error from a warning based on the image displayed with the prompt. +@if (avalonia) { The [StatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StatusImage) property can be set to any 32x32 `IImage`. Several common images, like those for an error or warning, are defined by the [MessageBoxImage](xref:@ActiproUIRoot.Controls.MessageBoxImage) enumeration. Instead of populating the [StatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StatusImage) property, set the [StandardStatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardStatusImage) property to one of the [MessageBoxImage](xref:@ActiproUIRoot.Controls.MessageBoxImage) values. @@ -178,6 +274,22 @@ var result = await UserPromptBuilder.Configure() // ... other configuration options here .WithStatusImage(MessageBoxImage.Information) .Show(); -}; ``` +} +@if (wpf) { +The [StatusImageSource](xref:@ActiproUIRoot.Controls.UserPromptControl.StatusImageSource) property can be set to any 32x32 `ImageSource`. + +Several common images, like those for an error or warning, are defined by the [UserPromptStandardImage](xref:@ActiproUIRoot.Controls.UserPromptStandardImage) enumeration. Instead of populating the [StatusImageSource](xref:@ActiproUIRoot.Controls.UserPromptControl.StatusImageSource) property, set the [StandardStatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardStatusImage) property to one of the [UserPromptStandardImage](xref:@ActiproUIRoot.Controls.UserPromptStandardImage) values. + +> [!WARNING] +> The [StandardStatusImage](xref:@ActiproUIRoot.Controls.UserPromptControl.StandardStatusImage) property is ignored if the [StatusImageSource](xref:@ActiproUIRoot.Controls.UserPromptControl.StatusImageSource) property is explicitly populated. + +When using the [builder pattern](builder-pattern.md), the [WithStatusImage](xref:@ActiproUIRoot.Controls.UserPromptBuilder.WithStatusImage*) has one overload that accepts a custom `ImageSource` and another for a standard [UserPromptStandardImage](xref:@ActiproUIRoot.Controls.UserPromptStandardImage): +```csharp +var result = UserPromptBuilder.Configure() + // ... other configuration options here + .WithStatusImage(UserPromptStandardImage.Information) + .Show(); +``` +} \ No newline at end of file diff --git a/Documentation/topics/themes/getting-started.md b/Documentation/topics/themes/getting-started.md index 9b01c76..37c4043 100644 --- a/Documentation/topics/themes/getting-started.md +++ b/Documentation/topics/themes/getting-started.md @@ -265,7 +265,7 @@ The [ModernTheme](xref:@ActiproUIRoot.Themes.ModernTheme) should ideally still b ``` -Then in the `Application.Initialize()` method, lookup the [ModernTheme](xref:@ActiproUIRoot.Themes.ModernTheme) instance, modify the appropriate properties, and refresh resources to apply the change. +Then in the `Application.Initialize()` method, look up the [ModernTheme](xref:@ActiproUIRoot.Themes.ModernTheme) instance, modify the appropriate properties, and refresh resources to apply the change. ```csharp public partial class App : Application { diff --git a/Documentation/topics/themes/images/user-interface-density-compact.png b/Documentation/topics/themes/images/user-interface-density-compact.png new file mode 100644 index 0000000000000000000000000000000000000000..165baec6d6deba686dc9713d3f6274ad76abb095 GIT binary patch literal 3948 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 DG7f$$ literal 0 HcmV?d00001 diff --git a/Documentation/topics/themes/images/user-interface-density-spacious.png b/Documentation/topics/themes/images/user-interface-density-spacious.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c55cfcf00b38771f4566d9f931293dc6392ce5 GIT binary patch literal 5122 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 R&c8i?nX$D|wc*e4{{p)j7?1z} literal 0 HcmV?d00001 diff --git a/Documentation/topics/themes/images/user-interface-density.png b/Documentation/topics/themes/images/user-interface-density.png new file mode 100644 index 0000000000000000000000000000000000000000..d3a73e44cfe911317c4358c5eb26d9980f09b16e GIT binary patch literal 4497 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 literal 0 HcmV?d00001 diff --git a/Documentation/topics/themes/index.md b/Documentation/topics/themes/index.md index eefeb9c..fbba9b8 100644 --- a/Documentation/topics/themes/index.md +++ b/Documentation/topics/themes/index.md @@ -12,6 +12,7 @@ Actipro Themes is a complete framework for managing the themes of Actipro and na - Themes contain resources for both `Light` and `Dark` theme variants, allowing for instant toggling between the two. - Theme definitions with many options for determining how dynamically created theme resources are generated. - Ability to swap to alternate theme definitions at run-time. +- Facilities for controlling user interface density (spacing) throughout an application. - Theme assets, such as control themes, brushes, thicknesses, glyphs, and more that can be reused anywhere in an application. - Extensibility points to customize and/or override theme assets, or even append custom assets. @@ -59,6 +60,12 @@ While the Actipro control themes rely heavily on these theme assets, assets can See the [Theme Assets](theme-assets.md) topic for details on browsing theme resources, reusing various kinds of assets, and externally overriding assets. +## User Interface Density + +User interface density describes how tightly controls are packed together. A simple setting on a theme definition sets the application-wide user interface density, which can adjust appearance features such as margins, paddings, corner radii, lengths, and more. + +See the [User Interface Density](user-interface-density.md) topic to discover how to set an application-wide user interface density and adjust elements based on the current density setting. + ## Containers Themes generate many brushes that are intended for use in building out the chrome of containers, which are elements such as `Border` that provide backgrounds for various views and host other controls inside. diff --git a/Documentation/topics/themes/theme-definitions.md b/Documentation/topics/themes/theme-definitions.md index 6471aaf..ce6bb4b 100644 --- a/Documentation/topics/themes/theme-definitions.md +++ b/Documentation/topics/themes/theme-definitions.md @@ -19,6 +19,12 @@ A theme definition contains many options that guide a theme generator on how to Theme definitions are represented by the [ThemeDefinition](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition) class, which has a wide variety of properties for directing theme generator output. +### General Options + +| Property | Description | +|-----|-----| +| [UserInterfaceDensity](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.UserInterfaceDensity) | The density of the user interface, which generally describes the amount of spacing to apply. See the [User Interface Density](user-interface-density.md) topic for extensive detail on how user interface density works. | + ### Font Options | Property | Description | @@ -46,7 +52,6 @@ Color ramp name properties should be set to [Hue](xref:@ActiproUIRoot.Themes.Gen | Property | Description | |-----|-----| | [MenuItemIconColumnWidth](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.MenuItemIconColumnWidth) | The width for menu item icon columns. | -| [MenuItemPadding](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.MenuItemPadding) | The padding around menu item text. | | [MenuItemPopupColumnWidth](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.MenuItemPopupColumnWidth) | The width for menu item popup columns. | ### Switch Control Options @@ -62,13 +67,24 @@ Color ramp name properties should be set to [Hue](xref:@ActiproUIRoot.Themes.Gen | [SwitchScale](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.SwitchScale) | The scale factor for checkbox and radio button controls. | | [ToggleSwitchAppearanceKind](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ToggleSwitchAppearanceKind) | The [SwitchAppearanceKind](xref:@ActiproUIRoot.Themes.Generation.SwitchAppearanceKind) that indicates the default appearance for `ToggleSwitch` controls. | +### ScrollBar Options + +| Property | Description | +|-----|-----| +| [ScrollBarThumbMarginAscent](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ScrollBarThumbMarginAscent) | The ascent for scroll bar thumb margins, which is the margin space around the short end of the thumb. | +| [ScrollBarThumbMarginExtent](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ScrollBarThumbMarginExtent) | The extent for scroll bar thumb margins, which is the margin space around the long end of the thumb. | +| [ScrollBarThumbMaxAscent](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ScrollBarThumbMaxAscent) | The maximum ascent for scroll bar thumbs, which is the maximum thickness of the thumb given the current thumb margin settings. | +| [ScrollBarThumbMinAscent](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ScrollBarThumbMinAscent) | The minimum ascent for scroll bar thumbs, which is the minimum thickness of the thumb given the current thumb margin settings. | + +> [!TIP] +> Set the [ScrollBarThumbMinAscent](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ScrollBarThumbMinAscent) and [ScrollBarThumbMaxAscent](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ScrollBarThumbMaxAscent) properties to the same value, such as `4.0`, to have a fixed thumb thickness. Or increase the [ScrollBarThumbMaxAscent](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ScrollBarThumbMaxAscent) property to something larger and use the [ScrollBarThumbMarginAscent](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ScrollBarThumbMarginAscent) property to indicate how much to indent the thumb from the track's current size. + ### Other Control Options | Property | Description | |-----|-----| | [ButtonAppearanceKind](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ButtonAppearanceKind) | The [ButtonAppearanceKind](xref:@ActiproUIRoot.Themes.Generation.ButtonAppearanceKind) that indicates the default appearance for various button controls (e.g., `Button`, `SplitButton`). | | [EditAppearanceKind](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.EditAppearanceKind) | The [EditAppearanceKind](xref:@ActiproUIRoot.Themes.Generation.EditAppearanceKind) that indicates the default appearance for various edit controls (e.g., `ComboBox`, `TextBox`). | -| [ScrollBarThumbMargin](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.ScrollBarThumbMargin) | The margin for scroll bar thumbs. | | [TabAppearanceKind](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.TabAppearanceKind) | The [TabAppearanceKind](xref:@ActiproUIRoot.Themes.Generation.TabAppearanceKind) that indicates the default appearance for various tab controls (e.g., `TabControl`). | ## Custom Theme Generators diff --git a/Documentation/topics/themes/user-interface-density.md b/Documentation/topics/themes/user-interface-density.md new file mode 100644 index 0000000..06a0d03 --- /dev/null +++ b/Documentation/topics/themes/user-interface-density.md @@ -0,0 +1,219 @@ +--- +title: "User Interface Density" +page-title: "User Interface Density - Themes Reference" +order: 30 +--- +# User Interface Density + +User interface density describes how tightly controls are packed together. A simple setting on a [theme definition](theme-definitions.md) sets the application-wide user interface density, which can adjust appearance features such as: + +- Margin +- Padding +- Corner radius +- Width, height, and related minimum and maximums +- Panel spacing +- Grid length +- Other various scalar values + +These appearance features can be controlled via the use of theme resources and special XAML markup extensions. + +![Screenshot](../themes/images/user-interface-density-spacious.png) +![Screenshot](../themes/images/user-interface-density.png) +![Screenshot](../themes/images/user-interface-density-compact.png) + +*Sample controls showing spacious, normal, and compact densities* + +## Crisper Than Universal Scaling + +One option for controlling how large UI controls appear is to universally scale the user interface, such as via the use of a layout transform. While this can make the user interface appear larger or smaller, it has its drawbacks. For instance, borders and backgrounds will likely become anti-aliased due to being non-integer values. The entire UI may make much less effective use of the layout space available, especially in certain key areas. + +The second option, involving the concept of user interface density, is often a better way to go. Actipro themes provide several options for the UI density setting. The density setting directly affects a number of [theme resources](theme-assets.md) like paddings, corner radii, etc. that are generated by the [theme generator](theme-generator.md). As an example, when a [Compact](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Compact) density is used, the padding theme resources for controls such as buttons and textboxes will be much smaller than when a [Spacious](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Spacious) density is used. Likewise, corner radii theme resources will have larger values with more spacious densities. + +In addition to density controlling how certain theme resources are generated, which are often only used in UI control themes, it's also possible to apply custom scaling for user interface elements throughout an application. This is extremely important since it means that all the views that contain UI controls can also be fully adjusted based on the UI density setting. This feature is implemented through the use of several utility XAML markup extensions that can scale any `Thickness`, `Double`, or `CornerRadius` value for the current density. Through proper use of these markup extensions, all margins, paddings, etc. within views can intelligently grow with more spacious densities, while borders can continue to remain crisp integer values with no anti-aliasing. You have full control over which UI elements are scaled and how, allowing for full control over creating an optimized layout for multiple densities. + +## Setting the Application UI Density + +The application-wide UI density is set via the [ThemeDefinition](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition).[UserInterfaceDensity](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.UserInterfaceDensity) property. The property can be set to any of these [UserInterfaceDensity](xref:@ActiproUIRoot.Themes.UserInterfaceDensity) values: + +- [Compact](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Compact) - A more compact density that reduces overall spacing, allowing for more UI controls to fit in an area. This option may be preferred for desktop applications with a lot of UI controls. +- [Normal](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Normal) - A balance between the compact and spacious options, where a good number of UI controls fit into an area, and controls are large enough to be interacted with via touch. This option is the default density. +- [Spacious](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Spacious) - A more spacious density with even larger spacing that is ideal for touch-based interfaces. + +### Initial Density + +The following example shows how to set the initial UI density for an application to [Spacious](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Spacious). + +```xaml + + + + + + + + + + + +``` + +> [!NOTE] +> The code above is not necessary if the intended density is [Normal](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Normal), which is the default density. + +### Changing at Run-time + +An application may choose to allow the user interface density to be altered at run-time. Once an application is initialized, look up the [ModernTheme](xref:@ActiproUIRoot.Themes.ModernTheme) instance, modify the appropriate definition properties, and refresh resources to apply the change. + +```csharp +if (ModernTheme.TryGetCurrent(out var modernTheme) && (modernTheme.Definition is not null)) { + // Optionally update the base font size based on the density + modernTheme.Definition.BaseFontSize = newDensity switch { + UserInterfaceDensity.Compact => 13.0, + _ => 14.0, // Normal, Spacious + }; + + // Set the new UI density + modernTheme.Definition.UserInterfaceDensity = newDensity; + + // Must manually refresh resources after changing definition properties + modernTheme.RefreshResources(); +} +``` + +## Scaling Infrastructure + +The scaling infrastructure consists of two main areas: + +- Generated theme resources like [ThemeResourceKind](xref:@ActiproUIRoot.Themes.ThemeResourceKind).[ButtonPadding](xref:@ActiproUIRoot.Themes.ThemeResourceKind.ButtonPadding), which are used throughout UI control templates. See the [Theme Assets](theme-assets.md) topic's section on how to reuse resources for information on using these resources in your own control themes or views. + +- XAML markup extensions that can dynamically scale `Thickness`, `CornerRadius`, etc. values, intended to be used throughout application views. + +Through proper use of both areas, an application's entire user interface can effectively support multiple density appearances. + +### XAML Markup Extension Calculations + +Scaling with the XAML markup extensions is a calculation performed using up to two kinds of numbers for each value: + +- **Factor** - A required number that is multiplied by the current UI density's scale unit. Density scale units are as follows: + - [Compact](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Compact) = 4 + - [Normal](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Normal) = 6 + - [Spacious](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Spacious) = 8 +- **Adjustment** - An optional unscaled number that is added to the result of the factor calculation. + +The XAML markup extensions all use a string expression as the input. The string expression should generally be delimited by single quotes; however the single quotes are unnecessary when only a single value is passed as the expression. Multiple values, possibly used in `Thickness` and `CornerRadius` markup extensions, are delimited by commas. + +Each value's sub-expression is in the syntax `factor [+/- adjustment]` where the square braces section is optional and only used when an adjustment is made. The `+/-` portion means either the `+` or `-` operator. Here are some examples: + +- `4` - A factor of `4` with no adjustment, meaning a [Normal](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Normal) density (scale unit of `6`) would result in `6 * 4 = 24`. +- `2 + 3` - A factor of `2` and an adjustment of `5`, meaning a [Normal](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Normal) density (scale unit of `6`) would result in `(6 * 2) + 3 = 15`. +- `5 - 10` - A factor of `5` and an adjustment of `-10`, meaning a [Normal](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Normal) density (scale unit of `6`) would result in `(6 * 5) - 10 = 20`. + +> [!TIP] +> Some scaled values (like `Thickness`) can accept multiple sub-expressions. If you don't want to scale all of the values, a factor of `0` can used to effectively disable scaling. For example, the sub-expression `0 + 10` will multiply the scale unit by `0` (which is always `0`) and then add `10` as an adjustment for a value of `10` for all densities. + +The following table shows the same sub-expression calculated at different UI densities: + +| Expression | Compact (4) | Normal (6) | Spacious (8) | +| ----- | ----- | ----- | ----- | +| `10` | `(4 * 10) = 40` | `(6 * 10) = 60` | `(8 * 10) = 80` | +| `10 + 5` | `(4 * 10) + 5 = 45` | `(6 * 10) + 5 = 65` | `(8 * 10) + 5 = 85` | +| `10 - 5` | `(4 * 10) - 5 = 35` | `(6 * 10) - 5 = 55` | `(8 * 10) - 5 = 75` | +| `0 + 10` | `(4 * 0) + 10 = 10` | `(6 * 0) + 10 = 10` | `(8 * 0) + 10 = 10` | + +> [!IMPORTANT] +> Since most value scaling is used for layout, the final value of all sub-expressions will be rounded to the nearest whole number away from zero. + +See the sections below for example usages of each kind of markup extension. + +## Scaled Double + +A scaled `Double` is the easiest to understand since the `Double` type represents a single number. The [ScaledDoubleExtension](xref:@ActiproUIRoot.Markup.Xaml.ScaledDoubleExtension) markup extension class is used to achieve scaling of `Double` values, which are commonly used in `Width`, `Height`, `Spacing`, etc. properties. + +The following example shows a couple usage scenarios of this markup extension: + +```xaml + + + + + ... + + +``` + +## Scaled Grid Length + +A scaled `GridLength` also represents a single number. The [ScaledGridLengthExtension](xref:@ActiproUIRoot.Markup.Xaml.ScaledGridLengthExtension) markup extension class is used to achieve scaling of `GridLength` values, which are commonly used in a `Grid`'s `RowDefinition` and `ColumnDefinition` properties. + +The following example shows usage scenario of this markup extension: + +```xaml + + + + + + + + ... + +``` + +A `*` character can optionally be appended to the end of the markup extension's string expression to indicate it should use star sizing. + +## Scaled Thickness + +The [ScaledThicknessExtension](xref:@ActiproUIRoot.Markup.Xaml.ScaledThicknessExtension) markup extension class is used to achieve scaling of `Thickness` values, which are commonly used in `Padding`, `Margin`, etc. properties. + +The string expression for the markup extension can contain one or more sub-expressions: + +- 1 value - A uniform thickness length. +- 2 values - The first sub-expression is for horizontal (left/right) and the second sub-expression is for vertical (top/bottom). +- 4 values - Left, top, right, and bottom sub-expressions. + +The following example shows a couple usage scenarios of this markup extension: + +```xaml + + + ... + +``` + +## Scaled Corner Radius + +The [ScaledCornerRadiusExtension](xref:@ActiproUIRoot.Markup.Xaml.ScaledCornerRadiusExtension) markup extension class is used to achieve scaling of `CornerRadius` values, which are commonly used in border corner radius properties. + +The string expression for the markup extension can contain one or more sub-expressions: + +- 1 value - A uniform corner radius. +- 2 values - The first sub-expression is for top (top-left/top-right) and the second sub-expression is for bottom (bottom-left/bottom-right). +- 4 values - Top-left, top-right, bottom-right, and bottom-left sub-expressions. + +The following example shows a couple usage scenarios of this markup extension: + +```xaml + + + ... + +``` + +## Recommended Font Sizes + +Font sizes can be subjective, based on the needs of your application. The default font size set in the [ThemeDefinition](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition).[BaseFontSize](xref:@ActiproUIRoot.Themes.Generation.ThemeDefinition.BaseFontSize) property is `14.0`. + +It is recommended to use that font size for [Normal](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Normal) and [Spacious](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Spacious) densities, while possibly reducing the font size to `13.0` for [Compact](xref:@ActiproUIRoot.Themes.UserInterfaceDensity.Compact) density. + +See the "Changing at Run-time" section above for an example of how to alter the base font size when a new UI density is being set. diff --git a/Readme.md b/Readme.md index 8d5a8cf..226ba69 100644 --- a/Readme.md +++ b/Readme.md @@ -21,6 +21,7 @@ Samples, documentation, and other related open-source projects for [Actipro Aval - [Review Product Documentation](#review-product-documentation) - [Evaluate in Your Apps](#evaluate-in-your-apps) - [Supported Technologies](#supported-technologies) +- [Branches](#branches) - [Contributing](#contributing) - [Support](#support) - [Licensing](#licensing) @@ -96,6 +97,15 @@ These two NuGet packages can be referenced if you wish to use Actipro's themes f \* *Some features or functionality may be limited or unavailable on non-desktop platforms.* +## Branches + +This repository has two primary branches: + +- [main](https://github.com/Actipro/Avalonia-Controls/tree/main) - The codebase for official production-ready releases. +- [develop](https://github.com/Actipro/Avalonia-Controls/tree/develop) - Contains work-in-progress code, which may include pre-release logic that is not ready for production usage. + +When cloning this repository for samples of our official releases, it's best to use the [main](https://github.com/Actipro/Avalonia-Controls/tree/main) branch. + ## Contributing We welcome contributions to our open-source repository. If you want to submit a pull request, please first open a [GitHub issue](https://github.com/Actipro/Avalonia-Controls/issues) or [contact us](https://www.actiprosoftware.com/company/contact) to discuss. diff --git a/Samples/SampleBrowser/Directory.Build.props b/Samples/SampleBrowser/Directory.Build.props index be1fbbf..d736827 100644 --- a/Samples/SampleBrowser/Directory.Build.props +++ b/Samples/SampleBrowser/Directory.Build.props @@ -7,8 +7,8 @@ enable true - 23.1.0.0 - 23.1.0.0 - 20231017 + 23.1.1.0 + 23.1.0.0 - 20231102 Actipro Avalonia UI Controls Sample Browser $(Product) diff --git a/Samples/SampleBrowser/References/ActiproSoftware.References.props b/Samples/SampleBrowser/References/ActiproSoftware.References.props index 60e2d3f..2fae7f6 100644 --- a/Samples/SampleBrowser/References/ActiproSoftware.References.props +++ b/Samples/SampleBrowser/References/ActiproSoftware.References.props @@ -2,7 +2,7 @@ - 23.1.0 + 23.1.1-beta.1 diff --git a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Controls/MessageBoxIntro/MainControl.axaml b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Controls/MessageBoxIntro/MainControl.axaml index 3d2b3f3..9f46561 100644 --- a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Controls/MessageBoxIntro/MainControl.axaml +++ b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Controls/MessageBoxIntro/MainControl.axaml @@ -12,7 +12,6 @@ > diff --git a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Controls/UserPromptIntro/MainControl.axaml b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Controls/UserPromptIntro/MainControl.axaml index e253d36..7def41c 100644 --- a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Controls/UserPromptIntro/MainControl.axaml +++ b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Controls/UserPromptIntro/MainControl.axaml @@ -65,7 +65,6 @@ diff --git a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Overview.axaml b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Overview.axaml index 9b4884a..cfd9f43 100644 --- a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Overview.axaml +++ b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/FundamentalsSamples/Overview.axaml @@ -15,14 +15,14 @@ The library will be grown over time, with new controls coming soon. - - - + diff --git a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/SharedSamples/Overview.axaml b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/SharedSamples/Overview.axaml index 2f18279..d7e2ba1 100644 --- a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/SharedSamples/Overview.axaml +++ b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/SharedSamples/Overview.axaml @@ -23,11 +23,11 @@ Several helpful value converters for use in bindings are also included. - + - - - + + + @@ -38,7 +38,7 @@ - + @@ -50,7 +50,7 @@ - @@ -63,7 +63,7 @@ - @@ -79,7 +79,7 @@ - + @@ -87,11 +87,11 @@ - - + diff --git a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/ThemesSamples/Demos/ProfileForm/MainControl.axaml b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/ThemesSamples/Demos/ProfileForm/MainControl.axaml index 8bc9303..e10c13f 100644 --- a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/ThemesSamples/Demos/ProfileForm/MainControl.axaml +++ b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/ThemesSamples/Demos/ProfileForm/MainControl.axaml @@ -8,7 +8,7 @@ @@ -65,7 +65,7 @@ - + @@ -90,7 +90,13 @@ - + + + + + + + @@ -102,7 +108,15 @@ - + + + + + + + + + @@ -124,7 +138,13 @@ - + + + + + + + @@ -140,7 +160,15 @@ - + + + + + + + + + diff --git a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/ThemesSamples/Overview.axaml b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/ThemesSamples/Overview.axaml index bbaa8e1..148e48f 100644 --- a/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/ThemesSamples/Overview.axaml +++ b/Samples/SampleBrowser/SampleBrowser.Common/ProductSamples/ThemesSamples/Overview.axaml @@ -18,28 +18,34 @@ A handful of native control examples using Actipro themes are presented below. - + + BorderBrush="{actipro:ThemeResource Container2BorderBrush}" BorderThickness="1" CornerRadius="{actipro:ScaledCornerRadius 1.5}" Padding="{actipro:ScaledThickness 4}"> - + - + - +