Skip to content

Latest commit

 

History

History
123 lines (68 loc) · 13.4 KB

report-on-adding-new-property.md

File metadata and controls

123 lines (68 loc) · 13.4 KB

Adding HeadingLevel and IsDialog properties report

In this report, will be the steps executed to the complete implementations of these two properties in WPF, including all the files modified in the proccess.

Workflow

The workflow for working in WPF consisted on editing the code (used VS Code for this), building it, copying the generated ref files to the correct location inside dotnet folder, and testing it in Visual Studio.

Usually, the command uses to build was Build /p:Platform=x64 /p:BaselineAllAPICompatError=true -pack, using the -pack flag to generate the files and /p:BaselineAllAPICompatError=true to recreate the APICompat baseline files for the new property. More info about it here.

The generated files to be copied are the following: .\artifacts\packaging\Debug\x64\Microsoft.DotNet.Wpf.GitHub.Debug\lib\net6.0\* to ..\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\6.0.0-preview.6.21317.5\; and .\artifacts\packaging\Debug\x64\Microsoft.DotNet.Wpf.GitHub.Debug\ref\net6.0\* to ..\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\6.0.0-preview.6.21317.5\ref\net6.0\ . I suggest to have a script to copy them and gain some time.

After copying the references, the files can be open in Visual Studio to set breakpoints for debugging.

Files Changed

In this section, will be listed all the changes on the Pull Request and some of the reasoning about them, in the order they were made (or the more logical order).

OSVersionHelper.cs

Before everything, changes in this file were needed. The support for RS4 was added for HeadingLevel property, creagint the extern method here.

UIAutomationTypes

AutomationIdentifierConstants.cs

This is the file where we declare the new porperties to be added. The two if statements were added to set up the last supported events and properties according to the OS version. The HeadingLevel property is supported on RS4, and IsDialog on RS5.

Also, we add them in the enum Properties. We need to add the other 5 properties because the numeric values of the enum should match the properties ids. The last one implemented was Size, which id is 30167. And the HeadingLevel id is 30173, because there was five other properties in between. IsDialog comes just after with the id 30174.

These are the five properties missing:

const long UIA_IsSelectionPattern2AvailablePropertyId = 30168;
const long UIA_Selection2FirstSelectedItemPropertyId = 30169;
const long UIA_Selection2LastSelectedItemPropertyId = 30170;
const long UIA_Selection2CurrentSelectedItemPropertyId = 30171;
const long UIA_Selection2ItemCountPropertyId = 30172;

AutomationElementIdentifiers.cs

In this file, we create the two properties of the class AutomationProperty, using the static method Register, receiving the id from the enum Properties.

ref/UIAutomationTypes.cs

And in this one, create the same properties in the reference file.

UIAutomationClient

AutomationElement.cs

Next, the AutomationElement class needs to declare the properties getting them from AutomationElementIdentifiers.

Schema.cs

The change was in the Schema.cs, where the two new property infos were declared. In the consturctor, there are 4 arguments. The first is the converter, second is the AutomationProperty, got from AutomationElement class, the third is the type of the value (an enum declared afterwards for HeadingLevel and bool for IsDialog), and the default value comes last.

ref/UIAutomationClient.cs

Finally, we declare them in the reference file inside AutomationElement class.

PresentationCore

This is the part of project where most of the changes were made. We will change the AutomationProperties class and the AutomationPeer class to support the new properties, also change some of the main AutomationPeers to implement them.

Adding AutomationHeadingLevel.cs enum

As HeadingLevel value is an enum, we need to create it and add it to be compiled with the project.

AutomationProperties.cs

Here, we will implement the properties to be set with the AutomationProperties class. The implementation for HeadingLevel is here, and similiar was done for IsDialog here. This is what allow the properties to be set in the DependencyObject.

AutomationPeer.cs

Here we implement the base methods for all other peers. GetHeadingLevel returns the HeadingLevel of the DependencyObject owner of this peers, trying to get it from the method GetHeadingLevelCore, which in this class, just returns the default value.

As HeadingLevel is an enum value, in the docs we set its values to range from 0 (None) to 9 (Level9). But, UIA intentifies are from 80050 (None) to 80059 (Level9). So, before handling the result to UIA, we map them to the correct values, using the private enum, and a converter method.

IsDialog is a bit simpler as it returns just a boolean value. IsDialogCore return false and the implementation of IsDialog is also simple. Note that as IsDialog is a boolean property, I followed the pattern and choose for IsDialog instead of GetIsDialog, as other boolean properties.

ContentElementAutomationPeer.cs, UIElementAutomationPeer.cs and UiElement3DAutomationPeer.cs

These are the classes in the PresentationCore that are parents of almost every other peer in PresentationFramework. There are some exceptions that we will see later.

So, these classes need to implement the IsDialogCore and GetHeadingLevelCore methods, or elset they would always return the default value. Here will be the contact between the automation peer and the AutomationProperties class, which is the one that actually gets the value of the property for us, and the peer returns it to UIA.

All the three implementations for these classes are the same, and are very simple, just calling automation properties and getting the value returned.

In ContenteElementAutomation peer are here and here. For UIElementAutomationPeer: here and here. And for UIElement3DAutomationPeer: here and here.

ref/PresentationCore.cs

Finally, we need to declare everything added in the reference file. We redeclare the AutomationHeadingLevel enum. Declare the properties from AutomationProperties.cs, with the Get and Set methods for both.

Now we declare the methods created in AutomationPeer.

And the methods from the three classes of peers mentioned above: ContentElement, UIElement3D and UIElement.

PresentationFramework

After changing the classes in PresentationCore, we need to change some of the peers in PresentationFramework. Here is where the behaviour of the peers for the controls are actually implemented.

As said above, almost all of them inherit from ContentElementAutomationPeer, UIElement3DAutomationPeer and UIElementAutomationPeer.

There are three of them that do not inherit from them, so the IsDialogCore and GetHeadingLevelCore for them would always return the default value as implemented in AutomationPeer.cs.

So, we need to create the behavior for them, which are DataGridCellItemAutomationPeer, DateTimeAutomationPeer and ItemAutomationPeer.

WindowAutomationPeer.cs

The WindowAutomationPeer is an example of peer that the behaviour for one of the new properties (IsDialog) should have a different implementation.

Here, the owner of this peer could be showing as a dialog depending of how it was instantiated, and whe should get this information from the property IsShowingAsDialog from the owner.

Therefore, we implement this in the owner class Window.cs, and after we get the value if possible, or else we get the information from AutomationProperties class.

ref/PresentationFramework.cs

Finally, we need to declare everything created above in the reference file.

Starting with the peers changed: DataGridCellItem, DateTime and Item automation peers. And the new method in WindowAutomationPeer.

Testing

After that, the new properties should be now possible to be set in a WPF project. Also, will be seen in AccessibilityInsights.