|
| 1 | +--- |
| 2 | +title: InfoBars and Notifications |
| 3 | +description: Learn how to display non-modal informational messages using InfoBars. |
| 4 | +category: fundamentals |
| 5 | +order: 11 |
| 6 | +--- |
| 7 | + |
| 8 | +import Callout from '@components/Callout.astro'; |
| 9 | + |
| 10 | +InfoBars are non-modal notification banners that appear at the top of documents, tool windows, or the main VS window. They're ideal for suggestions, warnings, or actions that don't require immediate attention. |
| 11 | + |
| 12 | +## Quick Start with Community Toolkit |
| 13 | + |
| 14 | +```csharp |
| 15 | +// Simple InfoBar in a document |
| 16 | +var docView = await VS.Documents.GetActiveDocumentViewAsync(); |
| 17 | +var infoBar = await VS.InfoBar.CreateAsync(docView, "NuGet packages need to be restored."); |
| 18 | +await infoBar.TryShowInfoBarUIAsync(); |
| 19 | +``` |
| 20 | + |
| 21 | +## InfoBar with Actions |
| 22 | + |
| 23 | +```csharp |
| 24 | +var model = new InfoBarModel( |
| 25 | + new[] |
| 26 | + { |
| 27 | + new InfoBarTextSpan("This file has been modified outside the editor. "), |
| 28 | + new InfoBarHyperlink("Reload", "reload"), |
| 29 | + new InfoBarTextSpan(" | "), |
| 30 | + new InfoBarHyperlink("Ignore", "ignore") |
| 31 | + }, |
| 32 | + KnownMonikers.StatusWarning, |
| 33 | + isCloseButtonVisible: true); |
| 34 | + |
| 35 | +var infoBar = await VS.InfoBar.CreateAsync(docView, model); |
| 36 | +infoBar.ActionItemClicked += (s, e) => |
| 37 | +{ |
| 38 | + switch (e.ActionItem.ActionContext) |
| 39 | + { |
| 40 | + case "reload": |
| 41 | + ReloadDocument(); |
| 42 | + break; |
| 43 | + case "ignore": |
| 44 | + // Do nothing |
| 45 | + break; |
| 46 | + } |
| 47 | + e.InfoBarUIElement.Close(); |
| 48 | +}; |
| 49 | + |
| 50 | +await infoBar.TryShowInfoBarUIAsync(); |
| 51 | +``` |
| 52 | + |
| 53 | +## InfoBarModel |
| 54 | + |
| 55 | +The `InfoBarModel` class defines the content and appearance: |
| 56 | + |
| 57 | +```csharp |
| 58 | +var model = new InfoBarModel( |
| 59 | + textSpans: new[] |
| 60 | + { |
| 61 | + new InfoBarTextSpan("Your message here"), |
| 62 | + new InfoBarHyperlink("Click Me", "action1") |
| 63 | + }, |
| 64 | + image: KnownMonikers.StatusInformation, |
| 65 | + isCloseButtonVisible: true); |
| 66 | +``` |
| 67 | + |
| 68 | +### Text Spans |
| 69 | + |
| 70 | +| Type | Description | |
| 71 | +|------|-------------| |
| 72 | +| `InfoBarTextSpan` | Plain text | |
| 73 | +| `InfoBarHyperlink` | Clickable link with action context | |
| 74 | +| `InfoBarButton` | Button-styled action (VS 2019+) | |
| 75 | + |
| 76 | +```csharp |
| 77 | +new InfoBarTextSpan("Regular text. "), |
| 78 | +new InfoBarHyperlink("Link text", actionContext: "myAction"), |
| 79 | +new InfoBarButton("Button", actionContext: "buttonAction") |
| 80 | +``` |
| 81 | + |
| 82 | +### Icons |
| 83 | + |
| 84 | +Use `KnownMonikers` for standard icons: |
| 85 | + |
| 86 | +| Icon | Usage | |
| 87 | +|------|-------| |
| 88 | +| `KnownMonikers.StatusInformation` | General information | |
| 89 | +| `KnownMonikers.StatusWarning` | Warnings | |
| 90 | +| `KnownMonikers.StatusError` | Errors | |
| 91 | +| `KnownMonikers.StatusOK` | Success/confirmation | |
| 92 | +| `KnownMonikers.StatusHelp` | Help/tips | |
| 93 | + |
| 94 | +## Showing InfoBars in Different Locations |
| 95 | + |
| 96 | +### In a Document |
| 97 | + |
| 98 | +```csharp |
| 99 | +// Get the active document |
| 100 | +var docView = await VS.Documents.GetActiveDocumentViewAsync(); |
| 101 | +var infoBar = await VS.InfoBar.CreateAsync(docView, "Message for this document"); |
| 102 | +await infoBar.TryShowInfoBarUIAsync(); |
| 103 | +``` |
| 104 | + |
| 105 | +### In a Tool Window |
| 106 | + |
| 107 | +```csharp |
| 108 | +// In your tool window class |
| 109 | +var model = new InfoBarModel("Update available for this extension."); |
| 110 | +var infoBar = await VS.InfoBar.CreateAsync(MyToolWindow, model); |
| 111 | +await infoBar.TryShowInfoBarUIAsync(); |
| 112 | +``` |
| 113 | + |
| 114 | +### In the Main Window (Global) |
| 115 | + |
| 116 | +```csharp |
| 117 | +var model = new InfoBarModel( |
| 118 | + new[] { new InfoBarTextSpan("A new version of this extension is available.") }, |
| 119 | + KnownMonikers.StatusInformation); |
| 120 | + |
| 121 | +var infoBar = await VS.InfoBar.CreateAsync(model); |
| 122 | +await infoBar.TryShowInfoBarUIAsync(); |
| 123 | +``` |
| 124 | + |
| 125 | +## Handling Actions |
| 126 | + |
| 127 | +```csharp |
| 128 | +var model = new InfoBarModel( |
| 129 | + new[] |
| 130 | + { |
| 131 | + new InfoBarTextSpan("Configuration file is missing. "), |
| 132 | + new InfoBarHyperlink("Create", "create"), |
| 133 | + new InfoBarHyperlink("Learn More", "help") |
| 134 | + }, |
| 135 | + KnownMonikers.StatusWarning); |
| 136 | + |
| 137 | +var infoBar = await VS.InfoBar.CreateAsync(model); |
| 138 | + |
| 139 | +infoBar.ActionItemClicked += async (sender, args) => |
| 140 | +{ |
| 141 | + var action = args.ActionItem.ActionContext as string; |
| 142 | + |
| 143 | + switch (action) |
| 144 | + { |
| 145 | + case "create": |
| 146 | + await CreateConfigFileAsync(); |
| 147 | + args.InfoBarUIElement.Close(); |
| 148 | + break; |
| 149 | + |
| 150 | + case "help": |
| 151 | + System.Diagnostics.Process.Start("https://docs.example.com/config"); |
| 152 | + // Don't close - user might want to create after reading |
| 153 | + break; |
| 154 | + } |
| 155 | +}; |
| 156 | + |
| 157 | +await infoBar.TryShowInfoBarUIAsync(); |
| 158 | +``` |
| 159 | + |
| 160 | +## Closing an InfoBar |
| 161 | + |
| 162 | +```csharp |
| 163 | +// User clicks close button (automatic) |
| 164 | +// Or programmatically: |
| 165 | +infoBar.ActionItemClicked += (s, e) => |
| 166 | +{ |
| 167 | + e.InfoBarUIElement.Close(); |
| 168 | +}; |
| 169 | + |
| 170 | +// Or track the UI element |
| 171 | +IVsInfoBarUIElement _infoBarUI; |
| 172 | + |
| 173 | +var infoBar = await VS.InfoBar.CreateAsync(model); |
| 174 | +infoBar.ActionItemClicked += (s, e) => _infoBarUI = e.InfoBarUIElement; |
| 175 | +await infoBar.TryShowInfoBarUIAsync(); |
| 176 | + |
| 177 | +// Later... |
| 178 | +_infoBarUI?.Close(); |
| 179 | +``` |
| 180 | + |
| 181 | +## Traditional Approach |
| 182 | + |
| 183 | +Using `IVsInfoBarUIFactory` directly: |
| 184 | + |
| 185 | +```csharp |
| 186 | +public class InfoBarService |
| 187 | +{ |
| 188 | + private readonly IServiceProvider _serviceProvider; |
| 189 | + |
| 190 | + public InfoBarService(IServiceProvider serviceProvider) |
| 191 | + { |
| 192 | + _serviceProvider = serviceProvider; |
| 193 | + } |
| 194 | + |
| 195 | + public async Task ShowInfoBarAsync(IVsWindowFrame frame, string message) |
| 196 | + { |
| 197 | + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); |
| 198 | + |
| 199 | + var factory = (IVsInfoBarUIFactory)_serviceProvider.GetService(typeof(SVsInfoBarUIFactory)); |
| 200 | + var host = frame as IVsInfoBarHost; |
| 201 | + |
| 202 | + if (factory == null || host == null) return; |
| 203 | + |
| 204 | + var model = new InfoBarModel( |
| 205 | + new[] { new InfoBarTextSpan(message) }, |
| 206 | + KnownMonikers.StatusInformation, |
| 207 | + isCloseButtonVisible: true); |
| 208 | + |
| 209 | + var uiElement = factory.CreateInfoBar(model); |
| 210 | + uiElement.Advise(new InfoBarEvents(), out _); |
| 211 | + host.AddInfoBar(uiElement); |
| 212 | + } |
| 213 | +} |
| 214 | + |
| 215 | +public class InfoBarEvents : IVsInfoBarUIEvents |
| 216 | +{ |
| 217 | + public void OnClosed(IVsInfoBarUIElement infoBarUIElement) |
| 218 | + { |
| 219 | + // InfoBar was closed |
| 220 | + } |
| 221 | + |
| 222 | + public void OnActionItemClicked(IVsInfoBarUIElement infoBarUIElement, IVsInfoBarActionItem actionItem) |
| 223 | + { |
| 224 | + ThreadHelper.ThrowIfNotOnUIThread(); |
| 225 | + |
| 226 | + var context = actionItem.ActionContext as string; |
| 227 | + // Handle action... |
| 228 | + } |
| 229 | +} |
| 230 | +``` |
| 231 | + |
| 232 | +## Complete Example |
| 233 | + |
| 234 | +An extension that suggests enabling a feature: |
| 235 | + |
| 236 | +```csharp |
| 237 | +public class FeatureSuggestionService |
| 238 | +{ |
| 239 | + private IVsInfoBarUIElement _currentInfoBar; |
| 240 | + private const string SettingKey = "FeatureXEnabled"; |
| 241 | + |
| 242 | + public async Task SuggestFeatureAsync() |
| 243 | + { |
| 244 | + // Don't show if already enabled or dismissed |
| 245 | + if (await IsFeatureEnabledAsync() || await WasDismissedAsync()) |
| 246 | + return; |
| 247 | + |
| 248 | + var model = new InfoBarModel( |
| 249 | + new IVsInfoBarTextSpan[] |
| 250 | + { |
| 251 | + new InfoBarTextSpan("Feature X can improve your productivity. "), |
| 252 | + new InfoBarHyperlink("Enable", "enable"), |
| 253 | + new InfoBarTextSpan(" | "), |
| 254 | + new InfoBarHyperlink("Learn More", "learn"), |
| 255 | + new InfoBarTextSpan(" | "), |
| 256 | + new InfoBarHyperlink("Don't show again", "dismiss") |
| 257 | + }, |
| 258 | + KnownMonikers.StatusInformation, |
| 259 | + isCloseButtonVisible: true); |
| 260 | + |
| 261 | + var infoBar = await VS.InfoBar.CreateAsync(model); |
| 262 | + |
| 263 | + infoBar.ActionItemClicked += async (sender, args) => |
| 264 | + { |
| 265 | + var action = args.ActionItem.ActionContext as string; |
| 266 | + |
| 267 | + switch (action) |
| 268 | + { |
| 269 | + case "enable": |
| 270 | + await EnableFeatureAsync(); |
| 271 | + await VS.StatusBar.ShowMessageAsync("Feature X enabled!"); |
| 272 | + args.InfoBarUIElement.Close(); |
| 273 | + break; |
| 274 | + |
| 275 | + case "learn": |
| 276 | + await VS.Commands.ExecuteAsync( |
| 277 | + "View.WebBrowser", |
| 278 | + "https://example.com/feature-x"); |
| 279 | + break; |
| 280 | + |
| 281 | + case "dismiss": |
| 282 | + await SaveDismissedAsync(); |
| 283 | + args.InfoBarUIElement.Close(); |
| 284 | + break; |
| 285 | + } |
| 286 | + }; |
| 287 | + |
| 288 | + if (await infoBar.TryShowInfoBarUIAsync()) |
| 289 | + { |
| 290 | + // Track so we can close programmatically if needed |
| 291 | + _currentInfoBar = null; // Get from event if needed |
| 292 | + } |
| 293 | + } |
| 294 | + |
| 295 | + private Task<bool> IsFeatureEnabledAsync() => Task.FromResult(false); |
| 296 | + private Task<bool> WasDismissedAsync() => Task.FromResult(false); |
| 297 | + private Task EnableFeatureAsync() => Task.CompletedTask; |
| 298 | + private Task SaveDismissedAsync() => Task.CompletedTask; |
| 299 | +} |
| 300 | +``` |
| 301 | + |
| 302 | +## InfoBar in Solution Explorer |
| 303 | + |
| 304 | +Show an InfoBar for the entire solution: |
| 305 | + |
| 306 | +```csharp |
| 307 | +public async Task ShowSolutionInfoBarAsync(string message) |
| 308 | +{ |
| 309 | + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); |
| 310 | + |
| 311 | + var shell = (IVsShell)await VS.Services.GetShellAsync(); |
| 312 | + shell.GetProperty((int)__VSSPROPID7.VSSPROPID_MainWindowInfoBarHost, out var hostObj); |
| 313 | + |
| 314 | + if (hostObj is IVsInfoBarHost host) |
| 315 | + { |
| 316 | + var factory = (IVsInfoBarUIFactory) |
| 317 | + await VS.Services.GetInfoBarUIFactoryAsync(); |
| 318 | + |
| 319 | + var model = new InfoBarModel(message); |
| 320 | + var uiElement = factory.CreateInfoBar(model); |
| 321 | + |
| 322 | + host.AddInfoBar(uiElement); |
| 323 | + } |
| 324 | +} |
| 325 | +``` |
| 326 | + |
| 327 | +## Best Practices |
| 328 | + |
| 329 | +1. **Be concise** - InfoBars should be scannable at a glance |
| 330 | +2. **Provide actions** - Don't just inform; help users act |
| 331 | +3. **Allow dismissal** - Always include a close button or "Don't show again" |
| 332 | +4. **Don't spam** - One InfoBar at a time per location |
| 333 | +5. **Use appropriate icons** - Match the severity/type of message |
| 334 | +6. **Remember preferences** - If dismissed, don't show again (persist the choice) |
| 335 | + |
| 336 | +<Callout type="tip"> |
| 337 | +InfoBars are perfect for non-urgent suggestions like "Would you like to enable..." or "A newer version is available." For errors requiring action, use the Error List instead. |
| 338 | +</Callout> |
| 339 | + |
| 340 | +<Callout type="warning"> |
| 341 | +Don't show InfoBars immediately when VS starts. Wait until the user interacts with relevant features, or delay by several seconds. |
| 342 | +</Callout> |
| 343 | + |
| 344 | +## When to Use InfoBars vs Other UI |
| 345 | + |
| 346 | +| Scenario | Recommended UI | |
| 347 | +|----------|----------------| |
| 348 | +| "Feature X is available" | InfoBar | |
| 349 | +| "File modified externally" | InfoBar with Reload action | |
| 350 | +| "Build failed with errors" | Error List | |
| 351 | +| "Operation in progress" | Progress Indication | |
| 352 | +| "5 files processed" | Status Bar message | |
| 353 | +| "Are you sure you want to delete?" | Modal Dialog | |
| 354 | +| "Extension updated, restart required" | InfoBar | |
| 355 | + |
| 356 | +## See Also |
| 357 | + |
| 358 | +- [Output Window](output-window) - Detailed logging |
| 359 | +- [Error List](error-list) - Errors and warnings |
| 360 | +- [Status Bar](status-bar) - Brief status messages |
| 361 | +- [Progress Indication](progress-indication) - Long-running operations |
0 commit comments