Is there a way to traverse through all of visual studio wpf elements? I'm trying to get a hold of how tabs are being rendered to add extra buttons + context menu entries within the context of a visual studio extension.
Yes, i am aware of Tab Studios existence. If this doesn't work out, i might as well try if that fulfills my needs. But at this point i am just curious about how to do this.
Here's what i have tried so far:
internal class EditorMargin1 : StackPanel, IWpfTextViewMargin
{
/// <summary>
/// Margin name.
/// </summary>
public const string MarginName = "EditorMargin1";
/// <summary>
/// A value indicating whether the object is disposed.
/// </summary>
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="EditorMargin1"/> class for a given <paramref name="textView"/>.
/// </summary>
/// <param name="textView">The <see cref="IWpfTextView"/> to attach the margin to.</param>
public EditorMargin1(IWpfTextView textView)
{
this.ClipToBounds = true;
var parents = GetParentsRecursive(textView);
var start = 60;
var white = new SolidColorBrush(Color.FromRgb((byte)255, (byte)255, (byte)255));
white.Freeze();
foreach (var parentElement in parents)
{
start += 20;
var background = new SolidColorBrush(Color.FromRgb((byte)start, (byte)0, (byte)start));
var changedColor = false;
var colorableControl = parentElement as Control;
if (colorableControl != null)
{
colorableControl.Background = background;
changedColor = true;
}
this.Children.Add(new Label()
{
Foreground = white,
Background = background,
Content = "Hello EditorMargin1 " + parentElement.GetType().FullName + " has changed color " + changedColor,
});
}
}
private IEnumerable<DependencyObject> GetParentsRecursive(IWpfTextView textView)
{
var control = textView as DependencyObject;
while (control != null)
{
yield return control;
control = LogicalTreeHelper.GetParent(control);
}
}
#region IWpfTextViewMargin
/// <summary>
/// Gets the <see FrameworkElementlement"/> that implements the visual representation of the margin.
/// </summary>
/// <exception cref="ObjectDisposedException">The margin is disposed.</exception>
public FrameworkElement VisualElement
{
// Since this margin implements Canvas, this is the object which renders
// the margin.
get
{
this.ThrowIfDisposed();
return this;
}
}
#endregion
#region ITextViewMargin
/// <summary>
/// Gets the size of the margin.
/// </summary>
/// <remarks>
/// For a horizontal margin this is the height of the margin,
/// since the width will be determined by the <see cref="ITextView"/>.
/// For a vertical margin this is the width of the margin,
/// since the height will be determined by the <see cref="ITextView"/>.
/// </remarks>
/// <exception cref="ObjectDisposedException">The margin is disposed.</exception>
public double MarginSize
{
get
{
this.ThrowIfDisposed();
// Since this is a horizontal margin, its width will be bound to the width of the text view.
// Therefore, its size is its height.
return this.ActualHeight;
}
}
/// <summary>
/// Gets a value indicating whether the margin is enabled.
/// </summary>
/// <exception cref="ObjectDisposedException">The margin is disposed.</exception>
public bool Enabled
{
get
{
this.ThrowIfDisposed();
// The margin should always be enabled
return true;
}
}
/// <summary>
/// Gets the <see cref="ITextViewMargin"/> with the given <paramref name="marginName"/> or null if no match is found
/// </summary>
/// <param name="marginName">The name of the <see cref="ITextViewMargin"/></param>
/// <returns>The <see cref="ITextViewMargin"/> named <paramref name="marginName"/>, or null if no match is found.</returns>
/// <remarks>
/// A margin returns itself if it is passed its own name. If the name does not match and it is a container margin, it
/// forwards the call to its children. Margin name comparisons are case-insensitive.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="marginName"/> is null.</exception>
public ITextViewMargin GetTextViewMargin(string marginName)
{
return string.Equals(marginName, EditorMargin1.MarginName, StringComparison.OrdinalIgnoreCase) ? this : null;
}
/// <summary>
/// Disposes an instance of <see cref="EditorMargin1"/> class.
/// </summary>
public void Dispose()
{
if (!this.isDisposed)
{
GC.SuppressFinalize(this);
this.isDisposed = true;
}
}
#endregion
/// <summary>
/// Checks and throws <see cref="ObjectDisposedException"/> if the object is disposed.
/// </summary>
private void ThrowIfDisposed()
{
if (this.isDisposed)
{
throw new ObjectDisposedException(MarginName);
}
}
}
/// <summary>
/// Export a <see cref="IWpfTextViewMarginProvider"/>, which returns an instance of the margin for the editor to use.
/// </summary>
[Export(typeof(IWpfTextViewMarginProvider))]
[Name(EditorMargin1.MarginName)]
// [Order(After = PredefinedMarginNames.HorizontalScrollBar)] // Ensure that the margin occurs below the horizontal scrollbar
[MarginContainer(PredefinedMarginNames.Top)] // Set the container to the bottom of the editor window
[ContentType("text")] // Show this margin for all text-based types
[TextViewRole(PredefinedTextViewRoles.Interactive)]
internal sealed class EditorMargin1Factory : IWpfTextViewMarginProvider
{
#region IWpfTextViewMarginProvider
/// <summary>
/// Creates an <see cref="IWpfTextViewMargin"/> for the given <see cref="IWpfTextViewHost"/>.
/// </summary>
/// <param name="wpfTextViewHost">The <see cref="IWpfTextViewHost"/> for which to create the <see cref="IWpfTextViewMargin"/>.</param>
/// <param name="marginContainer">The margin that will contain the newly-created margin.</param>
/// <returns>The <see cref="IWpfTextViewMargin"/>.
/// The value may be null if this <see cref="IWpfTextViewMarginProvider"/> does not participate for this context.
/// </returns>
public IWpfTextViewMargin CreateMargin(IWpfTextViewHost wpfTextViewHost, IWpfTextViewMargin marginContainer)
{
return new EditorMargin1(wpfTextViewHost.TextView);
}
#endregion
}
The issue i'm running into, from a traversal point of view, is that i can't get past WpfTextViewHost.
Is anyone aware of a method to traverse the window in a simple way?
To traverse all of Visual Studio wpf elements you can start from System.Windows.Application.Current.MainWindow and use System.Windows.Media.VisualTreeHelper.GetChild recursively. See the following sample code Hide title bar in Visual Studio 2013.