Skip to content

Commit d8f9fd3

Browse files
authored
docs(ui): add Output Window documentation (#110)
Add comprehensive documentation for working with the Output Window: - Quick start with Community Toolkit - Creating custom output panes - Writing to built-in panes (General, Build, Debug) - Built-in pane GUIDs reference - Thread-safe vs standard output methods - Pane configuration options - Showing and activating panes - Complete reusable logger example - Best practices Closes #43
1 parent 87fc3c7 commit d8f9fd3

1 file changed

Lines changed: 319 additions & 0 deletions

File tree

src/content/docs/output-window.mdx

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
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

Comments
 (0)