|
| 1 | +--- |
| 2 | +title: Output Window |
| 3 | +description: Learn how to create custom output panes and write messages to the Output window. |
| 4 | +category: fundamentals |
| 5 | +order: 8 |
| 6 | +--- |
| 7 | + |
| 8 | +import Callout from '@components/Callout.astro'; |
| 9 | + |
| 10 | +The Output window displays status messages, build output, debug information, and other runtime text. Extensions can create custom output panes to display their own messages. |
| 11 | + |
| 12 | +## Quick Start with Community Toolkit |
| 13 | + |
| 14 | +The simplest way to write to the Output window: |
| 15 | + |
| 16 | +```csharp |
| 17 | +// Write to the General pane |
| 18 | +await VS.Windows.WriteToOutputWindowAsync("Hello from my extension!"); |
| 19 | + |
| 20 | +// Write to a custom pane (created if it doesn't exist) |
| 21 | +var pane = await VS.Windows.CreateOutputWindowPaneAsync("My Extension"); |
| 22 | +await pane.WriteLineAsync("Extension initialized successfully"); |
| 23 | +``` |
| 24 | + |
| 25 | +## Creating a Custom Output Pane |
| 26 | + |
| 27 | +### With Community Toolkit |
| 28 | + |
| 29 | +```csharp |
| 30 | +public class MyExtension |
| 31 | +{ |
| 32 | + private OutputWindowPane _pane; |
| 33 | + |
| 34 | + public async Task InitializeAsync() |
| 35 | + { |
| 36 | + // Create or get existing pane |
| 37 | + _pane = await VS.Windows.CreateOutputWindowPaneAsync("My Extension"); |
| 38 | + } |
| 39 | + |
| 40 | + public async Task LogAsync(string message) |
| 41 | + { |
| 42 | + await _pane.WriteLineAsync($"[{DateTime.Now:HH:mm:ss}] {message}"); |
| 43 | + } |
| 44 | + |
| 45 | + public async Task LogErrorAsync(string error) |
| 46 | + { |
| 47 | + await _pane.WriteLineAsync($"ERROR: {error}"); |
| 48 | + await _pane.ActivateAsync(); // Bring pane to front |
| 49 | + } |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +### Traditional Approach |
| 54 | + |
| 55 | +```csharp |
| 56 | +public class OutputPaneManager |
| 57 | +{ |
| 58 | + private static readonly Guid PaneGuid = new Guid("YOUR-GUID-HERE"); |
| 59 | + private IVsOutputWindowPane _pane; |
| 60 | + |
| 61 | + public async Task InitializeAsync(AsyncPackage package) |
| 62 | + { |
| 63 | + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); |
| 64 | + |
| 65 | + var outputWindow = await package.GetServiceAsync(typeof(SVsOutputWindow)) as IVsOutputWindow; |
| 66 | + |
| 67 | + // Create the pane |
| 68 | + outputWindow.CreatePane( |
| 69 | + ref PaneGuid, |
| 70 | + "My Extension", |
| 71 | + fInitVisible: 1, |
| 72 | + fClearWithSolution: 1); |
| 73 | + |
| 74 | + outputWindow.GetPane(ref PaneGuid, out _pane); |
| 75 | + } |
| 76 | + |
| 77 | + public void WriteLine(string message) |
| 78 | + { |
| 79 | + ThreadHelper.ThrowIfNotOnUIThread(); |
| 80 | + _pane?.OutputStringThreadSafe($"{message}{Environment.NewLine}"); |
| 81 | + } |
| 82 | + |
| 83 | + public void Activate() |
| 84 | + { |
| 85 | + ThreadHelper.ThrowIfNotOnUIThread(); |
| 86 | + _pane?.Activate(); |
| 87 | + } |
| 88 | + |
| 89 | + public void Clear() |
| 90 | + { |
| 91 | + ThreadHelper.ThrowIfNotOnUIThread(); |
| 92 | + _pane?.Clear(); |
| 93 | + } |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +## Writing to Built-in Panes |
| 98 | + |
| 99 | +Visual Studio has several built-in output panes you can write to: |
| 100 | + |
| 101 | +### General Pane |
| 102 | + |
| 103 | +```csharp |
| 104 | +// Community Toolkit |
| 105 | +await VS.Windows.WriteToOutputWindowAsync("Message to General pane"); |
| 106 | + |
| 107 | +// Traditional |
| 108 | +var outputWindow = (IVsOutputWindow)GetService(typeof(SVsOutputWindow)); |
| 109 | +var generalPaneGuid = VSConstants.GUID_OutWindowGeneralPane; |
| 110 | +outputWindow.GetPane(ref generalPaneGuid, out var generalPane); |
| 111 | +generalPane.OutputStringThreadSafe("Message to General pane\n"); |
| 112 | +``` |
| 113 | + |
| 114 | +### Build Pane |
| 115 | + |
| 116 | +```csharp |
| 117 | +var buildPaneGuid = VSConstants.GUID_BuildOutputWindowPane; |
| 118 | +outputWindow.GetPane(ref buildPaneGuid, out var buildPane); |
| 119 | +buildPane.OutputStringThreadSafe("Build message\n"); |
| 120 | +``` |
| 121 | + |
| 122 | +### Debug Pane |
| 123 | + |
| 124 | +```csharp |
| 125 | +var debugPaneGuid = VSConstants.GUID_OutWindowDebugPane; |
| 126 | +outputWindow.GetPane(ref debugPaneGuid, out var debugPane); |
| 127 | +debugPane.OutputStringThreadSafe("Debug message\n"); |
| 128 | +``` |
| 129 | + |
| 130 | +## Built-in Pane GUIDs |
| 131 | + |
| 132 | +| Pane | GUID | Constant | |
| 133 | +|------|------|----------| |
| 134 | +| General | `{65482C72-DEFA-41B7-902C-11C091889C83}` | `VSConstants.GUID_OutWindowGeneralPane` | |
| 135 | +| Build | `{1BD8A850-02D1-11D1-BEE7-00A0C913D1F8}` | `VSConstants.GUID_BuildOutputWindowPane` | |
| 136 | +| Debug | `{FC076020-078A-11D1-A7DF-00A0C9110051}` | `VSConstants.GUID_OutWindowDebugPane` | |
| 137 | + |
| 138 | +## Output Methods |
| 139 | + |
| 140 | +### OutputStringThreadSafe vs OutputString |
| 141 | + |
| 142 | +```csharp |
| 143 | +// Thread-safe - can be called from any thread |
| 144 | +pane.OutputStringThreadSafe("Safe from any thread\n"); |
| 145 | + |
| 146 | +// Must be on UI thread |
| 147 | +await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); |
| 148 | +pane.OutputString("Must be on UI thread\n"); |
| 149 | +``` |
| 150 | + |
| 151 | +<Callout type="tip"> |
| 152 | +Always prefer `OutputStringThreadSafe` unless you need special formatting or are already on the UI thread. |
| 153 | +</Callout> |
| 154 | + |
| 155 | +### Formatted Output |
| 156 | + |
| 157 | +```csharp |
| 158 | +// Add timestamp prefix |
| 159 | +public void Log(string message) |
| 160 | +{ |
| 161 | + var timestamp = DateTime.Now.ToString("HH:mm:ss.fff"); |
| 162 | + _pane.OutputStringThreadSafe($"[{timestamp}] {message}\n"); |
| 163 | +} |
| 164 | + |
| 165 | +// Log levels |
| 166 | +public void LogInfo(string message) => Log($"INFO: {message}"); |
| 167 | +public void LogWarning(string message) => Log($"WARN: {message}"); |
| 168 | +public void LogError(string message) => Log($"ERROR: {message}"); |
| 169 | +``` |
| 170 | + |
| 171 | +## Pane Options |
| 172 | + |
| 173 | +When creating a pane, you can configure its behavior: |
| 174 | + |
| 175 | +```csharp |
| 176 | +outputWindow.CreatePane( |
| 177 | + ref paneGuid, |
| 178 | + "My Extension", |
| 179 | + fInitVisible: 1, // 1 = visible in dropdown, 0 = hidden until first write |
| 180 | + fClearWithSolution: 1 // 1 = clear when solution closes, 0 = preserve content |
| 181 | +); |
| 182 | +``` |
| 183 | + |
| 184 | +| Parameter | Values | Description | |
| 185 | +|-----------|--------|-------------| |
| 186 | +| `fInitVisible` | 0 or 1 | Whether pane appears in dropdown before first write | |
| 187 | +| `fClearWithSolution` | 0 or 1 | Whether to clear content when solution closes | |
| 188 | + |
| 189 | +## Showing and Activating |
| 190 | + |
| 191 | +```csharp |
| 192 | +// Show the Output window (bring to front) |
| 193 | +await VS.Windows.ShowOutputWindowAsync(); |
| 194 | + |
| 195 | +// Activate your specific pane |
| 196 | +await pane.ActivateAsync(); |
| 197 | + |
| 198 | +// Traditional approach |
| 199 | +var outputWindow = (IVsOutputWindow)GetService(typeof(SVsOutputWindow)); |
| 200 | +outputWindow.GetPane(ref paneGuid, out var pane); |
| 201 | +pane.Activate(); |
| 202 | + |
| 203 | +// Show Output window via UI shell |
| 204 | +var uiShell = (IVsUIShell)GetService(typeof(SVsUIShell)); |
| 205 | +var outputWindowGuid = new Guid("{34E76E81-EE4A-11D0-AE2E-00A0C90FFFC3}"); |
| 206 | +uiShell.FindToolWindow((uint)__VSFINDTOOLWIN.FTW_fForceCreate, ref outputWindowGuid, out var frame); |
| 207 | +frame.Show(); |
| 208 | +``` |
| 209 | + |
| 210 | +## Complete Example |
| 211 | + |
| 212 | +A reusable logging service for your extension: |
| 213 | + |
| 214 | +```csharp |
| 215 | +using System; |
| 216 | +using System.Threading.Tasks; |
| 217 | +using Community.VisualStudio.Toolkit; |
| 218 | +using Microsoft.VisualStudio.Shell; |
| 219 | + |
| 220 | +public interface IExtensionLogger |
| 221 | +{ |
| 222 | + Task LogAsync(string message); |
| 223 | + Task LogWarningAsync(string message); |
| 224 | + Task LogErrorAsync(string message); |
| 225 | + Task ClearAsync(); |
| 226 | +} |
| 227 | + |
| 228 | +public class ExtensionLogger : IExtensionLogger |
| 229 | +{ |
| 230 | + private readonly string _paneName; |
| 231 | + private OutputWindowPane _pane; |
| 232 | + |
| 233 | + public ExtensionLogger(string paneName) |
| 234 | + { |
| 235 | + _paneName = paneName; |
| 236 | + } |
| 237 | + |
| 238 | + private async Task EnsurePaneAsync() |
| 239 | + { |
| 240 | + _pane ??= await VS.Windows.CreateOutputWindowPaneAsync(_paneName); |
| 241 | + } |
| 242 | + |
| 243 | + public async Task LogAsync(string message) |
| 244 | + { |
| 245 | + await EnsurePaneAsync(); |
| 246 | + await _pane.WriteLineAsync($"[{DateTime.Now:HH:mm:ss}] {message}"); |
| 247 | + } |
| 248 | + |
| 249 | + public async Task LogWarningAsync(string message) |
| 250 | + { |
| 251 | + await EnsurePaneAsync(); |
| 252 | + await _pane.WriteLineAsync($"[{DateTime.Now:HH:mm:ss}] ⚠ WARNING: {message}"); |
| 253 | + } |
| 254 | + |
| 255 | + public async Task LogErrorAsync(string message) |
| 256 | + { |
| 257 | + await EnsurePaneAsync(); |
| 258 | + await _pane.WriteLineAsync($"[{DateTime.Now:HH:mm:ss}] ❌ ERROR: {message}"); |
| 259 | + await _pane.ActivateAsync(); |
| 260 | + } |
| 261 | + |
| 262 | + public async Task ClearAsync() |
| 263 | + { |
| 264 | + await EnsurePaneAsync(); |
| 265 | + await _pane.ClearAsync(); |
| 266 | + } |
| 267 | +} |
| 268 | + |
| 269 | +// Usage in your package |
| 270 | +public sealed class MyPackage : ToolkitPackage |
| 271 | +{ |
| 272 | + public static IExtensionLogger Logger { get; private set; } |
| 273 | + |
| 274 | + protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress) |
| 275 | + { |
| 276 | + await base.InitializeAsync(cancellationToken, progress); |
| 277 | + |
| 278 | + Logger = new ExtensionLogger("My Extension"); |
| 279 | + await Logger.LogAsync("Extension loaded successfully"); |
| 280 | + } |
| 281 | +} |
| 282 | + |
| 283 | +// Usage anywhere in your extension |
| 284 | +await MyPackage.Logger.LogAsync("Processing file..."); |
| 285 | +await MyPackage.Logger.LogErrorAsync("Failed to process file"); |
| 286 | +``` |
| 287 | + |
| 288 | +## Best Practices |
| 289 | + |
| 290 | +1. **Use descriptive pane names** - Make it clear which extension owns the pane |
| 291 | +2. **Add timestamps** - Helps users understand when events occurred |
| 292 | +3. **Use thread-safe methods** - Prefer `OutputStringThreadSafe` for background operations |
| 293 | +4. **Don't spam** - Only log meaningful information |
| 294 | +5. **Activate on errors** - Bring the pane to attention when something goes wrong |
| 295 | +6. **Clear appropriately** - Don't clear too often; users may want to scroll back |
| 296 | + |
| 297 | +<Callout type="warning"> |
| 298 | +Avoid writing large amounts of text rapidly. This can freeze the UI. For high-volume logging, consider batching writes or using a separate log file. |
| 299 | +</Callout> |
| 300 | + |
| 301 | +## Deleting a Pane |
| 302 | + |
| 303 | +Remove a custom pane when it's no longer needed: |
| 304 | + |
| 305 | +```csharp |
| 306 | +var outputWindow = (IVsOutputWindow)GetService(typeof(SVsOutputWindow)); |
| 307 | +var paneGuid = new Guid("YOUR-PANE-GUID"); |
| 308 | +outputWindow.DeletePane(ref paneGuid); |
| 309 | +``` |
| 310 | + |
| 311 | +<Callout type="note"> |
| 312 | +You cannot delete built-in panes (General, Build, Debug). |
| 313 | +</Callout> |
| 314 | + |
| 315 | +## See Also |
| 316 | + |
| 317 | +- [Tool Windows](tool-windows) - Creating custom dockable windows |
| 318 | +- [Error List](error-list) - Displaying errors, warnings, and messages |
| 319 | +- [Status Bar](status-bar) - Brief status messages |
0 commit comments