Settings Packs
Settings Packs are the mechanism for tools to expose user-configurable options in the DBTools Settings window. This guide covers the complete settings lifecycle: from manifest declaration to runtime access and persistence.
Overview
What Are Settings Packs?
A Settings Pack bundles together:
- Settings Model - A typed class holding the configuration values
- Settings Pack Context - A ViewModel that manages UI state and data binding
- Settings Pack View - A WPF UserControl rendered in the Settings window
- Warning Definitions - Optional conditions that can disable tool functionality
Settings Packs appear as collapsible cards in the Settings window, organized by ribbon panel (Common, Structural, Testing).
Why Use Settings Packs?
- Centralized configuration: Users manage all tool settings in one place
- Automatic persistence: Settings are saved to
settings.{YEAR}.jsonwith atomic writes - Change notification:
IOptionsMonitor<T>provides live reload without restart - Feature warnings: Link settings validation to tool availability
- Consistent UX: All tools share the same settings interface pattern
Apply Timing
The Settings window should describe when saved values take effect, not show a blanket restart warning.
- Ribbon visibility and similar shell-level flags can apply immediately after save.
- Tool settings typically apply on the next command run or next view-activation hook.
RequiresRestartshould be reserved for an explicitly documented pack-specific limitation, not shown globally.
Source:
src/DBTools.Core/Settings/Features/DbtSettingsApplyBehavior.cs:1Source:src/DBTools.Core/Settings/Shell/SettingsWindowViewModel.cs:1
Architecture
The Options Pattern in DBTools
DBTools uses Microsoft.Extensions.Options with a custom persistence layer:
┌─────────────────────┐ ┌──────────────────────┐
│ settings.{YEAR}.json │────>│ IConfiguration │
│ (user data) │ │ (read from file) │
└─────────────────────┘ └──────────┬───────────┘
│
┌──────────▼───────────┐
│ IOptionsMonitor<T> │
│ (live reload) │
└──────────┬───────────┘
│
┌─────────────────────────────┼─────────────────────────────┐
│ │ │
┌────────▼────────┐ ┌──────────▼──────────┐ ┌──────────▼──────────┐
│ Tool Command │ │ ViewActivatedTask │ │ Settings Window │
│ (read current) │ │ (check auto-update) │ │ (edit & save) │
└─────────────────┘ └─────────────────────┘ └──────────┬──────────┘
│
┌──────────▼──────────┐
│ IOptionsWriter │
│ (atomic persist) │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ settings.{YEAR}.json │
│ (updated) │
└─────────────────────┘
Key Interfaces
| Interface | Purpose |
|---|---|
ISettingsProvider |
Read typed settings: Get<T>() and SaveAsync<T>() |
IOptionsMonitor<T> |
Live-reloading settings with CurrentValue |
IOptionsWriter |
Atomic write to settings file sections |
IDbtSettingsPackDefinition |
Metadata about a settings pack (title, key, view factory) |
IDbtSettingsPackContext<T> |
ViewModel interface for settings UI |
IDbtSettingsWarningDefinition |
Warning linked to settings validation |
Source:
src/DBTools.Core/Settings/ISettingsProvider.cs:3-8
Defining Settings in manifest.yml
Settings packs are declared in the tool's manifest.yml under tool.settingsPacks:
Schema
tool:
settings:
configSection: "<section-name>" # Required: JSON key in settings.{YEAR}.json
settingsPacks:
- key: "<unique-pack-key>" # Required: Globally unique identifier
title: "<display-title>" # Required: Shown in Settings window
warnings: # Optional: Associated warnings
- id: "<warning-id>" # Required: Unique warning identifier
title: "<warning-title>" # Required: Warning card title
message: "<warning-msg>" # Required: Explanation shown to user
disableTools: # Optional: Tools disabled when warning active
- "<tool-internal-name>"
- "<another-tool>"
Complete Example: FoundationTags
id: DBTools.Structural.FoundationTags
assembly: DBTools
moduleType: DBTools.Structural.FoundationTags.FoundationTagsToolModule
order: 0
tool:
settings:
configSection: Tools.FoundationTags
settingsPacks:
- key: structural.foundation_tags
title: "Combined Foundation Tags"
warnings:
- id: core.structural.combined_tags
title: "Combined Foundation Tags Disabled"
message: "Combined foundation tag updates are disabled due to a warning. Clear the warning to re-enable."
disableTools:
- DBTools.UpdateCombinedFoundationTags
- DBTools.MoveCombinedFoundationTags
- DBTools.OrganizeFoundationTypes
ribbonTools:
# ... ribbon tool definitions
Source:
src/Tools/Structural/FoundationTags/manifest.yml:1-18
Key Naming Conventions
| Property | Convention | Example |
|---|---|---|
configSection |
{Category}.{ToolName} |
Tools.FoundationTags |
key |
{category}.{feature} |
structural.foundation_tags |
warnings[].id |
core.{category}.{feature} |
core.structural.combined_tags |
Settings Model Classes
Basic Settings Model
A settings model is a POCO class with public properties:
namespace DBTools.Structural.JoistGirderWeight.Settings;
public sealed class JoistGirderWeightSettings : IAutoUpdateSettings
{
public bool AutoUpdateEnabled { get; set; } = true;
public bool HasWarning { get; set; }
}
Source:
src/Tools/Structural/JoistGirderWeight/Settings/JoistGirderWeightSettings.cs:5-10
IAutoUpdateSettings Interface
For tools with auto-update on view activation, implement IAutoUpdateSettings:
namespace DBTools.Core.Settings.Models;
public interface IAutoUpdateSettings
{
bool AutoUpdateEnabled { get; set; }
bool HasWarning { get; set; }
}
Source:
src/DBTools.Core/Settings/Models/IAutoUpdateSettings.cs:3-7
Complex Settings Model
Settings can include collections, nested objects, and custom defaults:
namespace DBTools.Structural.FoundationTags.Settings;
public sealed class FoundationTagsSettings : IAutoUpdateSettings
{
public bool AutoUpdateEnabled { get; set; } = true;
public bool HasWarning { get; set; }
/// <summary>
/// Regex patterns to match tag family names.
/// </summary>
public List<string> TagFamilyPatterns { get; set; } = new()
{
"^Combined Foundation Tag",
"^DB Foundation Tag"
};
/// <summary>
/// Regex patterns to match pier family names.
/// </summary>
public List<string> PierFamilyPatterns { get; set; } = new()
{
"^Foundation Pier"
};
/// <summary>
/// Regex patterns to match footing family names.
/// </summary>
public List<string> FootingFamilyPatterns { get; set; } = new()
{
"^Footing-Rectangular$",
"^Pile Cap-Rectangular$"
};
}
Source:
src/Tools/Structural/FoundationTags/Settings/FoundationTagsSettings.cs:1-50
JSON Representation
Settings are stored in settings.{YEAR}.json under the configSection key:
{
"Tools.FoundationTags": {
"AutoUpdateEnabled": true,
"HasWarning": false,
"TagFamilyPatterns": [
"^Combined Foundation Tag",
"^DB Foundation Tag"
],
"PierFamilyPatterns": [
"^Foundation Pier"
],
"FootingFamilyPatterns": [
"^Footing-Rectangular$",
"^Pile Cap-Rectangular$"
]
}
}
Registering Settings
Settings registration happens in two phases within your DbtToolModule:
Phase 1: RegisterSettings()
Bind the settings model to configuration using the Options pattern:
public override void RegisterSettings(
IServiceCollection services,
IConfiguration configuration,
DbtToolManifest manifest)
{
Guard.NotNull(services, nameof(services));
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(manifest, nameof(manifest));
var configSection = manifest.GetRequiredConfigSection();
services.AddOptions<FoundationTagsSettings>(configuration, configSection);
}
Source:
src/Tools/Structural/FoundationTags/FoundationTagsToolModule.cs:16-23
This registration:
- Reads the
configSectionfrom manifest (Tools.FoundationTags) - Binds
IOptions<FoundationTagsSettings>to that section - Enables
IOptionsMonitor<T>for live configuration reload
Phase 2: RegisterSettingsPacks()
Register the full settings pack definition with UI factory:
public override void RegisterSettingsPacks(IServiceCollection services, DbtToolManifest manifest)
{
Guard.NotNull(services, nameof(services));
Guard.NotNull(manifest, nameof(manifest));
var configSection = manifest.GetRequiredConfigSection();
var pack = manifest.GetSingleSettingsPack();
var warning = pack.GetSingleWarning(manifest.Id);
var panel = DbtToolPanelResolver.GetSettingsPanelName(manifest.Id);
services.AddSingleton<IDbtSettingsPackDefinition>(_ =>
{
// Define warnings
var warningDefinitions = new[]
{
new DbtSettingsWarningDefinition<FoundationTagsSettings, FoundationTagsSettingsPackContext>(
warning.Id,
pack.Key,
warning.Title,
warning.Message,
settings => settings.Get<FoundationTagsSettings>(),
(settings, options, ct) => settings.SaveAsync(configSection, options, ct),
options => options.HasWarning,
(options, active) =>
{
options.HasWarning = active;
if (active) options.AutoUpdateEnabled = false;
},
context => context.HasWarning,
(context, active) =>
{
context.HasWarning = active;
if (active) context.AutoUpdateEnabled = false;
},
warning.DisableTools?.ToArray() ?? Array.Empty<string>())
};
// Create pack definition
return new DbtSettingsPackDefinition<FoundationTagsSettings, FoundationTagsSettingsPackContext>(
pack.Key,
pack.Title,
panel,
() => new FoundationTagsSettingsPackContext(), // Context factory
ctx => new FoundationTagsSettingsPackView { DataContext = ctx }, // View factory
() => new FoundationTagsSettings(), // Defaults factory
settings => settings.Get<FoundationTagsSettings>(), // Options getter
(settings, options, ct) => settings.SaveAsync(configSection, options, ct), // Options saver
warningDefinitions);
});
}
Source:
src/Tools/Structural/FoundationTags/FoundationTagsToolModule.cs:36-81
Using AutoUpdateSettingsPackContext (Simpler Pattern)
For simple auto-update settings without custom UI, use the built-in context:
public override void RegisterSettingsPacks(IServiceCollection services, DbtToolManifest manifest)
{
var configSection = manifest.GetRequiredConfigSection();
var pack = manifest.GetSingleSettingsPack();
var warning = pack.GetSingleWarning(manifest.Id);
var panel = DbtToolPanelResolver.GetSettingsPanelName(manifest.Id);
services.AddSingleton<IDbtSettingsPackDefinition>(_ =>
{
var warningDefinitions = new[]
{
new DbtSettingsWarningDefinition<JoistGirderWeightSettings, AutoUpdateSettingsPackContext<JoistGirderWeightSettings>>(
warning.Id, pack.Key, warning.Title, warning.Message,
settings => settings.Get<JoistGirderWeightSettings>(),
(settings, options, ct) => settings.SaveAsync(configSection, options, ct),
options => options.HasWarning,
(options, active) => { options.HasWarning = active; if (active) options.AutoUpdateEnabled = false; },
context => context.HasWarning,
(context, active) => { context.HasWarning = active; if (active) context.AutoUpdateEnabled = false; },
warning.DisableTools?.ToArray() ?? Array.Empty<string>())
};
return new DbtSettingsPackDefinition<JoistGirderWeightSettings, AutoUpdateSettingsPackContext<JoistGirderWeightSettings>>(
pack.Key, pack.Title, panel,
() => new AutoUpdateSettingsPackContext<JoistGirderWeightSettings>(
"Auto Update Joist Girder Weights",
"Automatically calculate joist girder weights when opening views.",
"Enable automatic joist girder weight updates when opening views"),
ctx => new AutoUpdateSettingsPackView { DataContext = ctx },
() => new JoistGirderWeightSettings(),
settings => settings.Get<JoistGirderWeightSettings>(),
(settings, options, ct) => settings.SaveAsync(configSection, options, ct),
warningDefinitions);
});
}
Source:
src/Tools/Structural/JoistGirderWeight/JoistGirderWeightToolModule.cs:37-85
Settings Pack Context
The context is the ViewModel for your settings UI. It must implement IDbtSettingsPackContext<T>.
Required Interface Methods
public interface IDbtSettingsPackContext<TOptions> : IDbtSettingsPackContext
{
void Load(TOptions options); // Load settings into bindable properties
TOptions BuildOptionsSnapshot(); // Create settings object from current state
}
public interface IDbtSettingsPackContext
{
void Load(object options);
object BuildOptionsSnapshot();
Task<DbtSettingsValidationResult> ValidateAsync(CancellationToken ct = default);
}
Source:
src/DBTools.Core/Settings/DbtToolSettingsPack.cs:8-22
Optional Context Interfaces
| Interface | Purpose |
|---|---|
IDbtSettingsPackContextWithState |
Fire StateChanged event when properties change |
IDbtSettingsPackContextWithPendingChanges |
Track HasPendingChanges for save button state |
IDbtSettingsPackContextOwnerAware |
Receive parent window for file dialogs |
IDbtSettingsPackContextSaveAware |
Reset baseline after save via MarkSaved() |
IDbtSettingsPackContextValidateAware |
Trigger warning evaluation via WarningId |
IDbtSettingsPackContextBlockSaveOnInvalid |
Block settings save when ValidateAsync returns invalid |
Source:
src/DBTools.Core/Settings/Features/DbtSettingsPackContextContracts.cs
Use IDbtSettingsPackContextBlockSaveOnInvalid for settings where invalid values should never be persisted (for example, invalid regex patterns that would be ignored at runtime).
Built-in: AutoUpdateSettingsPackContext
For standard auto-update toggle + warning, use the built-in context:
public sealed partial class AutoUpdateSettingsPackContext<TSettings> : ObservableObject,
IDbtSettingsPackContext<TSettings>,
IDbtSettingsPackContextWithState,
IDbtSettingsPackContextWithPendingChanges
where TSettings : class, IAutoUpdateSettings, new()
{
public AutoUpdateSettingsPackContext(string title, string description, string toggleToolTip)
{
// Validates inputs and stores for binding
}
public string Title { get; }
public string Description { get; }
public string ToggleToolTip { get; }
public bool AutoUpdateEnabled { get; set; }
public bool HasWarning { get; set; }
public bool ToggleEnabled => !HasWarning;
public bool HasPendingChanges { get; }
public event EventHandler? StateChanged;
public void Load(TSettings options) { /* ... */ }
public TSettings BuildOptionsSnapshot() { /* ... */ }
public Task<DbtSettingsValidationResult> ValidateAsync(CancellationToken ct) { /* ... */ }
}
Source:
src/DBTools.Core/Settings/AutoUpdateSettingsPackContext.cs:8-130
Custom Context Example
For tools requiring additional UI beyond the toggle, create a custom context:
public sealed partial class FoundationTagsSettingsPackContext : ObservableObject,
IDbtSettingsPackContext<FoundationTagsSettings>,
IDbtSettingsPackContextWithState,
IDbtSettingsPackContextWithPendingChanges
{
private const int MaxPatterns = 3;
private FoundationTagsSettings _baseline = new();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ToggleEnabled))]
private bool _autoUpdateEnabled = true;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ToggleEnabled))]
private bool _hasWarning;
public FoundationTagsSettingsPackContext()
{
TagFamilyPatterns = new ObservableCollection<RegexPatternEntry>();
PierFamilyPatterns = new ObservableCollection<RegexPatternEntry>();
FootingFamilyPatterns = new ObservableCollection<RegexPatternEntry>();
// Subscribe to collection changes
TagFamilyPatterns.CollectionChanged += (_, _) => NotifyStateChanged();
// ... etc
}
public ObservableCollection<RegexPatternEntry> TagFamilyPatterns { get; }
public ObservableCollection<RegexPatternEntry> PierFamilyPatterns { get; }
public ObservableCollection<RegexPatternEntry> FootingFamilyPatterns { get; }
public void Load(FoundationTagsSettings options)
{
AutoUpdateEnabled = options.AutoUpdateEnabled;
HasWarning = options.HasWarning;
LoadPatternCollection(TagFamilyPatterns, options.TagFamilyPatterns, "^Combined Foundation Tag");
// ... load other collections
_baseline = CloneOptions(options);
NotifyStateChanged();
}
public FoundationTagsSettings BuildOptionsSnapshot()
{
return new FoundationTagsSettings
{
AutoUpdateEnabled = HasWarning ? false : AutoUpdateEnabled,
HasWarning = HasWarning,
TagFamilyPatterns = TagFamilyPatterns.Select(p => p.Pattern).Where(p => !string.IsNullOrWhiteSpace(p)).ToList(),
// ... etc
};
}
public Task<DbtSettingsValidationResult> ValidateAsync(CancellationToken ct = default)
{
var errors = new List<string>();
ValidatePatternGroup(errors, TagFamilyPatterns, "tag family");
// ... validate other groups
return errors.Count == 0
? Task.FromResult(DbtSettingsValidationResult.Success)
: Task.FromResult(DbtSettingsValidationResult.FromErrors(errors.ToArray()));
}
[RelayCommand]
private void AddTagPattern() => AddPattern(TagFamilyPatterns, nameof(CanAddTagPattern), nameof(CanRemoveTagPattern));
[RelayCommand]
private void ClearWarning()
{
HasWarning = false;
NotifyStateChanged();
}
}
Source:
src/Tools/Structural/FoundationTags/Settings/FoundationTagsSettingsPackContext.cs:18-272
Accessing Settings at Runtime
In Commands (One-time Read)
Use ISettingsProvider.Get<T>() for a snapshot of current settings:
public class MyCommand : DbtToolCommand
{
protected override Result ExecuteCore(ExternalCommandData commandData, ref string message, ElementSet elements)
{
var settings = AppRuntime.Settings.Get<MyToolSettings>();
if (!settings.SomeFeatureEnabled)
{
message = "Feature is disabled in settings.";
return Result.Cancelled;
}
// Use settings.SomeValue, settings.SomeList, etc.
}
}
In Services (Live Reload)
Inject IOptionsMonitor<T> for settings that update without restart:
public class MyService
{
private readonly IOptionsMonitor<MyToolSettings> _options;
public MyService(IOptionsMonitor<MyToolSettings> options)
{
_options = options;
}
public void DoWork()
{
// Always reflects current settings.{YEAR}.json values
var current = _options.CurrentValue;
if (current.AutoUpdateEnabled && !current.HasWarning)
{
// Perform auto-update logic
}
}
}
In ViewActivatedTask (Auto-update Pattern)
The ViewActivatedTaskBase<T> abstracts settings access for view-activated tools:
public sealed class CombinedTagsViewActivatedTask : ViewActivatedTaskBase<FoundationTagsSettings>
{
public CombinedTagsViewActivatedTask(
ISettingsProvider settingsProvider,
ILogger<CombinedTagsViewActivatedTask> logger,
string[] warningIds)
: base(settingsProvider, logger, warningIds)
{
}
protected override FoundationTagsSettings GetSettings(ISettingsProvider settingsProvider)
=> settingsProvider.Get<FoundationTagsSettings>();
protected override void ExecuteTask(Document document, View view)
{
// Called only when AutoUpdateEnabled && !HasWarning
}
}
Source:
src/DBTools.Core/Tools/ViewActivatedTaskBase.cs:16-80
Persisting Settings
Automatic Save (Settings Window)
When the user clicks Save in the Settings window, SettingsViewModel.Save() handles persistence:
// Inside SettingsViewModel.Save()
foreach (var pack in _packLookup.Values)
{
var snapshot = pack.Pack.BuildOptionsSnapshot();
await pack.Definition.SaveOptionsAsync(_settings, snapshot, CancellationToken.None);
_publisher.PublishPackChanged(pack.Key, pack.Definition.OptionsType, snapshot);
}
Source:
src/DBTools.Core/Settings/ViewModels/SettingsViewModel.cs:259-292
Manual Save (Programmatic)
Save settings directly using ISettingsProvider:
var settings = AppRuntime.Settings.Get<MyToolSettings>();
settings.SomeValue = newValue;
await AppRuntime.Settings.SaveAsync("Tools.MyTool", settings);
IOptionsWriter (Low-level)
For direct section writes, use IOptionsWriter:
public interface IOptionsWriter
{
/// <summary>
/// Save a typed settings object under a named section in the settings file.
/// The write must be atomic (write temp + replace).
/// </summary>
Task SaveSectionAsync<T>(string section, T data, CancellationToken ct = default) where T : class;
}
Source:
src/DBTools.Core/Settings/Contracts/IOptionsWriter.cs:1-10
The OptionsWriter implementation ensures atomic writes:
- Write to
settings.{YEAR}.json.tmp - Use
File.Replace()for atomic swap - Retry on
IOException(file in use)
Source:
src/DBTools.Core/Settings/OptionsWriter.cs:27-110
Settings UI Integration
View Location
Settings pack views are rendered in the Settings window under their assigned panel. The panel is determined by DbtToolPanelResolver.GetSettingsPanelName():
| Tool ID Pattern | Panel |
|---|---|
DBTools.Settings |
DB Tools Settings |
DBTools.Structural.* or DBTools.SGT |
DB Tools Structural |
DBTools.VTC or DBTools.Testing.* |
DB Tools Testing |
| Everything else | DB Tools Common |
Source:
src/DBTools.Core/Tools/DbtToolPanelResolver.cs:27-44
Built-in AutoUpdateSettingsPackView
For simple toggle + description, use the provided view:
<UserControl x:Class="DBTools.Core.Settings.Views.AutoUpdateSettingsPackView">
<Border Style="{DynamicResource FeatureCard}">
<Grid>
<ToggleButton
IsChecked="{Binding AutoUpdateEnabled, Mode=TwoWay}"
IsEnabled="{Binding ToggleEnabled}"
Style="{DynamicResource SwitchToggleButton}"
ToolTip="{Binding ToggleToolTip}" />
<TextBlock Text="{Binding Title}" />
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" />
</Grid>
</Border>
</UserControl>
Source:
src/DBTools.Core/Settings/Views/AutoUpdateSettingsPackView.xaml:1-54
Custom Views
For complex settings, create a custom view in your tool's Settings/ folder:
<UserControl x:Class="DBTools.Structural.FoundationTags.Settings.FoundationTagsSettingsPackView">
<StackPanel>
<!-- Auto-update toggle -->
<CheckBox IsChecked="{Binding AutoUpdateEnabled}"
IsEnabled="{Binding ToggleEnabled}" />
<!-- Tag pattern list -->
<ItemsControl ItemsSource="{Binding TagFamilyPatterns}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Pattern, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Add/Remove buttons -->
<Button Command="{Binding AddTagPatternCommand}"
IsEnabled="{Binding CanAddTagPattern}" />
</StackPanel>
</UserControl>
Warning Integration
How Warnings Work
Warnings are conditions that disable tool functionality:
- Warning activates: Validation fails (e.g., invalid path)
- Tools disabled: Ribbon buttons grayed out, commands rejected
- User notified: Warning card appears in Settings window
- User fixes: Corrects settings, clicks "Clear Warning"
- Tools re-enabled: Ribbon buttons active again
Warning Definition
In RegisterSettingsPacks(), create a DbtSettingsWarningDefinition:
new DbtSettingsWarningDefinition<TSettings, TContext>(
id: "core.structural.combined_tags", // Unique warning ID
packKey: "structural.foundation_tags", // Parent pack key
title: "Combined Foundation Tags Disabled",
message: "Combined foundation tag updates are disabled due to a warning...",
optionsGetter: settings => settings.Get<TSettings>(),
optionsSaver: (settings, options, ct) => settings.SaveAsync(section, options, ct),
optionsFlagGetter: options => options.HasWarning,
optionsFlagSetter: (options, active) => { options.HasWarning = active; if (active) options.AutoUpdateEnabled = false; },
contextFlagGetter: context => context.HasWarning,
contextFlagSetter: (context, active) => { context.HasWarning = active; if (active) context.AutoUpdateEnabled = false; },
toolInternalNamesToDisable: new[] { "DBTools.MyCommand" })
Source:
src/DBTools.Core/Settings/DbtSettingsWarningDefinition.cs:22-99
Warning Service
IDbtSettingsWarningService manages warning state across the application:
public interface IDbtSettingsWarningService
{
IReadOnlyCollection<IDbtSettingsWarningDefinition> Definitions { get; }
Task<bool> IsWarningActiveAsync(string warningId, CancellationToken ct = default);
Task SetWarningAsync(string warningId, bool active, object? context = null, CancellationToken ct = default);
Task PublishWarningStateAsync(CancellationToken ct = default);
}
Source:
src/DBTools.Core/Settings/DbtSettingsWarningService.cs:11-18
Validation-Triggered Warnings
Implement IDbtSettingsPackContextValidateAware to automatically trigger warnings on validation:
public sealed partial class LibrarySettingsPackContext : ObservableObject,
IDbtSettingsPackContext<MasterLibrarySettings>,
IDbtSettingsPackContextValidateAware // <-- Key interface
{
public string WarningId => _warningId; // From constructor
public async Task<DbtSettingsValidationResult> ValidateAsync(CancellationToken ct = default)
{
var options = BuildOptionsSnapshot();
var invalidEntries = options.Files.Where(f => !IsValid(f)).ToList();
HasWarnings = invalidEntries.Count > 0;
if (HasWarnings)
return new DbtSettingsValidationResult(false, new[] { "Invalid library path" });
return DbtSettingsValidationResult.Success;
}
}
Source:
src/Tools/Common/TDV/Settings/LibrarySettingsPackContext.cs:19-169
Startup Evaluation (Validate-Aware Packs)
Validate-aware settings packs are evaluated during addin startup (after ribbon composition and before the initial warning publish) so ribbon tools are disabled immediately when underlying configuration is invalid, without requiring the user to open Settings.
The startup evaluator:
- Creates pack contexts without constructing WPF views
- Loads options from
ISettingsProvider - Runs
ValidateAsynconly forIDbtSettingsPackContextValidateAwarecontexts - Activates warnings on invalid results
- Does not activate warnings on validation exceptions (exceptions are treated as runtime errors and should surface via normal error flows/logs)
- Does not proactively clear warnings on startup
Source:
src/DBTools.App/Addin/AddinEntry.cs:324Source:src/DBTools.Core/Settings/Features/DbtSettingsStartupWarningEvaluator.cs:36
Real Examples
Example 1: JoistGirderWeight (Auto-Update + Regex Matching)
Use case: Toggle auto-calculation and configure regex matching for family/load resolution.
Settings model:
public sealed class JoistGirderWeightSettings : IAutoUpdateSettings
{
public bool AutoUpdateEnabled { get; set; } = true;
public bool HasWarning { get; set; }
public List<string> FamilyNamePatterns { get; set; } = new()
{
"^(?:(?:[BV]?G)|Vulcraft) Joist Girder$"
};
public List<string> LoadParameterPatterns { get; set; } = new()
{
"^(?:Panel Point Load|Panel Load - User Defined|Panel Load|Point Load - User Defined|Point Load|Total Load - User Defined|Total Load|Joist Load|BG Joist Girder)$"
};
}
Source:
src/Tools/Structural/JoistGirderWeight/Settings/JoistGirderWeightSettings.cs
Registration: Uses custom JoistGirderWeightSettingsPackContext and JoistGirderWeightSettingsPackView with +/- regex row controls (max 3) and save-time validation.
Manifest:
settingsPacks:
- key: structural.joist_girder_weight
title: "Joist Girder Weights"
warnings:
- id: core.structural.joist_girder
title: "Joist Girder Weights Disabled"
disableTools:
- DBTools.UpdateJoistGirderWeights
Source:
src/Tools/Structural/JoistGirderWeight/manifest.yml:8-16
Example 2: FoundationTags (Custom Patterns)
Use case: Configure regex patterns for family matching.
Settings model: Includes List<string> for patterns.
Source:
src/Tools/Structural/FoundationTags/Settings/FoundationTagsSettings.cs:1-50
Context: Custom FoundationTagsSettingsPackContext with pattern editing commands.
Source:
src/Tools/Structural/FoundationTags/Settings/FoundationTagsSettingsPackContext.cs:18-272
View: Custom FoundationTagsSettingsPackView.xaml with pattern lists.
Example 3: TDV Library (Complex Validation)
Use case: Configure library file paths with validation warnings.
Settings model:
public sealed class MasterLibrarySettings
{
public IList<LibraryFileEntry> Files { get; set; } = new List<LibraryFileEntry>();
public bool HasWarnings { get; set; }
}
Source:
src/Tools/Common/TDV/Settings/MasterLibrarySettings.cs:1-7
Context: LibrarySettingsPackContext with file picker, tree view, and validation.
Source:
src/Tools/Common/TDV/Settings/LibrarySettingsPackContext.cs:19-352
Key features:
IDbtSettingsPackContextOwnerAwarefor file dialogsIDbtSettingsPackContextSaveAwarefor baseline resetIDbtSettingsPackContextValidateAwarefor automatic warning activation
Best Practices
Do
- Use
IAutoUpdateSettingsfor tools with view-activated auto-update - Clone settings in context to detect
HasPendingChanges - Fire
StateChangedwhen any bindable property changes - Implement validation that returns meaningful error messages
- Use existing patterns - check JoistGirderWeight for simple, FoundationTags for complex
Don't
- Don't save directly from context - let
SettingsViewModel.Save()handle persistence - Don't bypass warnings - respect
HasWarningin command availability - Don't block UI thread - use
asyncfor file I/O in validation - Don't ignore
CancellationToken- validation can be cancelled
Testing Settings
- Build artifacts tests verify settings structure
- Integration tests can mock
ISettingsProvider - Manual testing in Revit validates UI binding
Related Documentation
- Creating New Tools - Complete tool creation guide
- Feature Warnings - Deep dive on the warning system
- Core Infrastructure - DBTools.Core project reference
Summary
Settings Packs provide a unified way to expose tool configuration:
- Declare in
manifest.ymlwithsettingsPacks - Model settings as a typed class (optionally implementing
IAutoUpdateSettings) - Register via
RegisterSettings()andRegisterSettingsPacks()in your tool module - Access at runtime with
ISettingsProvider.Get<T>()orIOptionsMonitor<T> - Persist automatically through the Settings window or manually via
IOptionsWriter - Warn users when validation fails, disabling affected tools until resolved
The system provides live-reload, atomic persistence, and consistent UX across all DBTools tools.