Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Commit

Permalink
VS extension accessibility improvements (#658)
Browse files Browse the repository at this point in the history
* replace Hyperlinks with Buttons

* set automation names on results window items

* select first report when results window appears

* Set focus to the browse button after options browse dialog is dismissed

* bind control styles to VS environment colors

* Add status bar to VS extension for screen reader support (#627)
  • Loading branch information
chlowell authored May 22, 2018
1 parent 120f9f3 commit 56d6578
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
<PackageReference Include="NuGet.VisualStudio" Version="4.4.0">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="StreamJsonRpc" Version="1.3.23" />
</ItemGroup>

<ItemGroup>
Expand Down
23 changes: 20 additions & 3 deletions src/ApiPort/ApiPort.VisualStudio.Common/NotifyPropertyBase.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;

namespace ApiPortVS
{
public class NotifyPropertyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected SynchronizationContext Context { get; }

public NotifyPropertyBase()
{
// SynchronizationContext.Current will be null if we're not on the UI thread
Context = SynchronizationContext.Current ?? throw new ArgumentNullException("SynchronizationContext.Current");
}

protected void UpdateProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
Expand All @@ -23,11 +32,19 @@ protected void UpdateProperty<T>(ref T field, T value, [CallerMemberName] string

protected void OnPropertyUpdated([CallerMemberName]string propertyName = "")
{
var handler = PropertyChanged;
var propertyChanged = PropertyChanged;
if (propertyChanged == null)
{
return;
}

if (handler != null)
if (Context == SynchronizationContext.Current)
{
propertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
else
{
handler(this, new PropertyChangedEventArgs(propertyName));
Context.Post(_ => propertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)), null);
}
}
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -334,4 +334,7 @@ There is no public build for https://github.com/Microsoft/cci yet, which support
<data name="SaveAs" xml:space="preserve">
<value>Save As</value>
</data>
<data name="RefreshingPlatformsComplete" xml:space="preserve">
<value>Platform list refresh complete</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ApiPortVS.ViewModels
Expand Down Expand Up @@ -193,6 +194,11 @@ private void UpdateResultFormats(IList<SelectedResultFormat> formats)
private async Task UpdateTargetsAsync()
{
var targets = await GetTargetsAsync().ConfigureAwait(false);

// TODO we do this to ensure the TargetPlatforms below are created on the
// UI thread. Probably better to do this with VSThreadingService.SwitchToMainThreadAsync..
// but that doesn't just work here.
SynchronizationContext.SetSynchronizationContext(Context);
var canonicalPlatforms = targets.GroupBy(t => t.Name).Select(t =>
{
return new TargetPlatform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<Compile Include="AssemblyRedirectResolver.cs" />
<Compile Include="Properties\Properties.cs" />
<Compile Include="Reporting\ToolbarListReportViewer.cs" />
<Compile Include="StatusBarProgressReporter.cs" />
<Compile Include="Utils\PathToFileNameConverter.cs" />
<Compile Include="Utils\TargetPlatformDisplayNameConverter.cs" />
<Compile Include="Utils\TargetInformationStringConverter.cs" />
Expand Down
36 changes: 10 additions & 26 deletions src/ApiPort/ApiPort.VisualStudio/Resources/ResourceDictionary.xaml
Original file line number Diff line number Diff line change
@@ -1,47 +1,31 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vs="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0">
<!-- Style Guidance
1. Text: https://docs.microsoft.com/en-us/visualstudio/extensibility/ux-guidelines/fonts-and-formatting-for-visual-studio#BKMK_TextStyle
2. Brushes: https://docs.microsoft.com/en-us/visualstudio/extensibility/ux-guidelines/shared-colors-for-visual-studio#ui-text
-->
<Style x:Key="ContentTextBlockStyle" TargetType="TextBlock">

<Style x:Key="OptionsPageStyle" TargetType="UserControl">
<Setter Property="FontFamily" Value="{DynamicResource VsFont.EnvironmentFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource VsFont.EnvironmentFontSize}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static vs:EnvironmentColors.DialogTextBrushKey}}" />
<Setter Property="Background" Value="{DynamicResource {x:Static vs:EnvironmentColors.DialogBrushKey}}" />
</Style>

<Style x:Key="HyperlinkFocusVisualStyleKey">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle SnapsToDevicePixels="true"
Margin="0"
Stroke="{DynamicResource {x:Static vs:EnvironmentColors.ToolWindowTextBrushKey}}"
StrokeDashArray="1 2" StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

<Style x:Key="ToolWindowHyperlinkStyle" TargetType="Hyperlink">
<Setter Property="FocusVisualStyle" Value="{StaticResource HyperlinkFocusVisualStyleKey}" />
</Style>

<!-- For the tool window because it has a different colour scheme than the options page. -->
<Style x:Key="ToolWindowTextBlockStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{DynamicResource VsFont.EnvironmentFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource VsFont.EnvironmentFontSize}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static vs:EnvironmentColors.ToolWindowTextBrushKey}}" />
<Setter Property="Background" Value="{DynamicResource {x:Static vs:EnvironmentColors.ToolWindowBackgroundBrushKey}}" />
</Style>

<!-- Group Box -->
<Style x:Key="GroupBoxStyle" TargetType="GroupBox">
<Setter Property="BorderBrush" Value="{DynamicResource CommonControls.CheckBoxBorder}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static vs:EnvironmentColors.DialogTextBrushKey}}" />
<Style x:Key="AnalysisOutputToolWindowStyle" TargetType="UserControl">
<Setter Property="FontFamily" Value="{DynamicResource VsFont.EnvironmentFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource VsFont.EnvironmentFontSize}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static vs:EnvironmentColors.ToolWindowTextBrushKey}}" />
<Setter Property="Background" Value="{DynamicResource {x:Static vs:EnvironmentColors.ToolWindowBackgroundBrushKey}}" />
</Style>

<Style x:Key="ToolWindowListBoxItemStyle" TargetType="ListBoxItem">
Expand Down
4 changes: 3 additions & 1 deletion src/ApiPort/ApiPort.VisualStudio/ServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public ServiceProvider(ApiPortVSPackage serviceProvider)
.As<IOutputWindowWriter>()
.As<TextWriter>()
.SingleInstance();
builder.RegisterType<TextWriterProgressReporter>()
builder.RegisterType<StatusBarProgressReporter>()
.As<IProgressReporter>()
.SingleInstance();
builder.RegisterType<ReportFileWriter>()
Expand Down Expand Up @@ -163,6 +163,8 @@ private static void RegisterVisualStudioComponents(ContainerBuilder builder, Api
.As<IVsWebBrowsingService>();
builder.Register(_ => Package.GetGlobalService(typeof(SVsSolutionBuildManager)))
.As<IVsSolutionBuildManager2>();
builder.Register(_ => Package.GetGlobalService(typeof(SVsStatusbar)))
.As<IVsStatusbar>();
builder.RegisterType<DefaultProjectBuilder>()
.As<IProjectBuilder>();

Expand Down
50 changes: 50 additions & 0 deletions src/ApiPort/ApiPort.VisualStudio/StatusBarProgressReporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.IO;
using ApiPortVS.Resources;
using Microsoft.Fx.Portability;
using Microsoft.VisualStudio.Shell.Interop;

namespace ApiPortVS
{
public class StatusBarProgressReporter : TextWriterProgressReporter
{
private readonly IVsStatusbar _statusBar;

public StatusBarProgressReporter(TextWriter writer, IVsStatusbar statusBar)
: base(writer)
{
_statusBar = statusBar;
}

public override IProgressTask StartTask(string taskName, int total)
{
return new StatusBarProgressTask(Writer, taskName, _statusBar);
}

public override IProgressTask StartTask(string taskName)
{
return new StatusBarProgressTask(Writer, taskName, _statusBar);
}

private class StatusBarProgressTask : TextWriterProgressTask
{
private readonly IVsStatusbar _statusBar;

public StatusBarProgressTask(TextWriter writer, string task, IVsStatusbar statusBar)
: base(writer, task)
{
_statusBar = statusBar;

_statusBar.SetText(task);
}

public override void Abort()
{
base.Abort();
_statusBar.SetText(LocalizedStrings.AnalysisFailed);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Licensed under the MIT license. See LICENSE file in the project root for full li
xmlns:const="clr-namespace:ApiPortVS.Resources;assembly=ApiPort.VisualStudio.Common"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Name="PortabilityResultsToolbar">
Name="PortabilityResultsToolbar"
Style="{DynamicResource AnalysisOutputToolWindowStyle}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
Expand All @@ -25,23 +26,28 @@ Licensed under the MIT license. See LICENSE file in the project root for full li
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{x:Static const:LocalizedStrings.AvailableReports}"
AutomationProperties.Name="{x:Static const:LocalizedStrings.AvailableReports}"
Style="{DynamicResource ToolWindowTextBlockStyle}"
Margin="5"
FontWeight="Bold"
Grid.Row="0"/>
<Grid Grid.Row="1">
<ScrollViewer VerticalScrollBarVisibility="Auto"
<ScrollViewer AutomationProperties.Name="{x:Static const:LocalizedStrings.AvailableReports}"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
Background="Transparent"
IsTabStop="False">
<ListView ItemsSource="{Binding Paths}"
ItemContainerStyle="{DynamicResource ToolWindowListBoxItemStyle}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
KeyboardNavigation.TabNavigation="Continue"
Background="Transparent"
IsVisibleChanged="ListView_IsVisibleChanged"
IsTabStop="False">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="AutomationProperties.Name" Value="{Binding Path=., Converter={StaticResource PathToFileNameConverter}}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0,5"
Expand All @@ -55,41 +61,25 @@ Licensed under the MIT license. See LICENSE file in the project root for full li
<TextBlock Text="{Binding Path=., Converter={StaticResource PathToFileNameConverter}}"
AutomationProperties.Name="{Binding Path=., Converter={StaticResource PathToFileNameConverter}}"
Style="{DynamicResource ToolWindowTextBlockStyle}"
Margin="5,0"
Grid.Column="0"/>
Margin="5,3" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left"
KeyboardNavigation.TabNavigation="Continue"
Grid.Column="2">
<TextBlock Margin="5,0">
<Hyperlink Command="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=DataContext.OpenFile}"
CommandParameter="{Binding}"
Style="{DynamicResource ToolWindowHyperlinkStyle}">
<TextBlock Text="{x:Static const:LocalizedStrings.OpenReport}"
AutomationProperties.Name="{x:Static const:LocalizedStrings.OpenReport}"
KeyboardNavigation.IsTabStop="true"
Style="{DynamicResource ToolWindowTextBlockStyle}" />
</Hyperlink>
</TextBlock>
<TextBlock Margin="5,0">
<Hyperlink Command="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl}, Path=DataContext.OpenDirectory}"
CommandParameter="{Binding}"
Style="{DynamicResource ToolWindowHyperlinkStyle}">
<TextBlock Text="{x:Static const:LocalizedStrings.OpenDirectory}"
AutomationProperties.Name="{x:Static const:LocalizedStrings.OpenDirectory}"
KeyboardNavigation.IsTabStop="true"
Style="{DynamicResource ToolWindowTextBlockStyle}" />
</Hyperlink>
</TextBlock>
<TextBlock Margin="5,0">
<Hyperlink Command="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl}, Path=DataContext.SaveAs}"
CommandParameter="{Binding}"
Style="{DynamicResource ToolWindowHyperlinkStyle}">
<TextBlock Text="{x:Static const:LocalizedStrings.SaveAs}"
AutomationProperties.Name="{x:Static const:LocalizedStrings.SaveAs}"
KeyboardNavigation.IsTabStop="true"
Style="{DynamicResource ToolWindowTextBlockStyle}" />
</Hyperlink>
</TextBlock>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=DataContext.OpenFile}"
CommandParameter="{Binding}"
Content="{x:Static const:LocalizedStrings.OpenReport}"
Margin="5,0"
Padding="5,2" />
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=DataContext.OpenDirectory}"
CommandParameter="{Binding}"
Content="{x:Static const:LocalizedStrings.OpenDirectory}"
Margin="5,0"
Padding="5,2" />
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=DataContext.SaveAs}"
CommandParameter="{Binding}"
Content="{x:Static const:LocalizedStrings.SaveAs}"
Margin="5,0"
Padding="5,2" />
</StackPanel>
</Grid>
</DataTemplate>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using ApiPortVS.ViewModels;
using System.Windows;
using System.Windows.Controls;

namespace ApiPortVS
Expand All @@ -13,7 +13,15 @@ public partial class AnalysisOutputToolWindowControl : UserControl
/// </summary>
public AnalysisOutputToolWindowControl()
{
this.InitializeComponent();
InitializeComponent();
}

private void ListView_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue && sender is ListView listView)
{
listView.SelectedIndex = 0;
}
}
}
}
Loading

0 comments on commit 56d6578

Please sign in to comment.