WPF 사용자 지정 컨트롤의 UI 자동화

UI 자동화에서는 자동화 클라이언트가 다양한 플랫폼 및 프레임워크의 사용자 인터페이스를 검사하거나 운영하는 데 사용할 수 있는 일반화된 단일 인터페이스를 제공합니다. UI 자동화를 통해 품질 보증(테스트) 코드 및 화면 읽기 프로그램과 같은 접근성 애플리케이션은 사용자 인터페이스 요소를 검사하고 다른 코드에서 해당 요소에 대한 사용자 상호 작용을 시뮬레이트할 수 있습니다. 모든 플랫폼 간의 UI 자동화에 대한 자세한 내용은 접근성을 참조하세요.

이 항목에서는 WPF 애플리케이션에서 실행되는 사용자 지정 컨트롤에 대한 서버 쪽 UI 자동화 공급자를 구현하는 방법을 설명합니다. WPF는 사용자 인터페이스 요소의 트리에 대응하는 피어 자동화 개체의 트리를 통해 UI 자동화를 지원합니다. 접근성 기능을 제공하는 애플리케이션 및 테스트 코드는 자동화 피어 개체를 직접 사용(in-process 코드의 경우)하거나 UI 자동화에서 제공하는 일반화된 인터페이스를 통해 사용할 수 있습니다.

자동화 피어 클래스

WPF 컨트롤은 AutomationPeer에서 파생되는 피어 클래스의 트리를 통해 UI 자동화를 지원합니다. 규칙에 따라 피어 클래스 이름은 컨트롤 클래스 이름으로 시작하고 "AutomationPeer"로 끝납니다. 예를 들어 ButtonAutomationPeerButton 컨트롤 클래스의 피어 클래스입니다. 피어 클래스는 UI 자동화 컨트롤 형식과 거의 동일하지만 WPF 요소와 관련됩니다. UI 자동화 인터페이스를 통해 WPF 애플리케이션에 액세스하는 자동화 코드는 자동화 피어를 직접 사용하지 않지만 동일한 프로세스 공간의 자동화 코드는 자동화 피어를 직접 사용할 수 있습니다.

기본 제공 자동화 피어 클래스

요소는 사용자의 인터페이스 작업을 허용하는 경우 또는 화면 읽기 프로그램 애플리케이션 사용자에게 필요한 정보를 포함하는 경우 자동화 피어 클래스를 구현합니다. 일부 WPF 시각적 요소에는 자동화 피어가 없습니다. 자동화 피어를 구현하는 클래스의 예로는 Button, TextBoxLabel이 있습니다. 자동화 피어를 구현하지 않는 클래스의 예로는 Decorator에서 파생되는 클래스(예: Border) 및 Panel을 기반으로 하는 클래스(예: GridCanvas)가 있습니다.

기본 Control 클래스에는 해당 피어 클래스가 없습니다. Control에서 파생되는 사용자 지정 컨트롤에 해당하는 피어 클래스가 필요한 경우 FrameworkElementAutomationPeer에서 사용자 지정 피어 클래스를 파생시켜야 합니다.

파생된 피어의 보안 고려 사항

자동화 피어는 부분 신뢰 환경에서 실행되어야 합니다. UIAutomationClient 어셈블리의 코드는 부분 신뢰 환경에서 실행되도록 구성되지 않았으며 자동화 피어 코드는 해당 어셈블리를 참조해서는 안 됩니다. 대신 UIAutomationTypes 어셈블리의 클래스를 사용해야 합니다. 예를 들어 UIAutomationTypes 어셈블리의 AutomationElementIdentifiers 클래스를 사용해야 하며, 이 클래스는 UIAutomationClient 어셈블리의 AutomationElement 클래스에 해당합니다. 자동화 피어 코드에서 UIAutomationTypes 어셈블리를 참조해도 안전합니다.

피어 탐색

자동화 피어를 찾은 후 in-process 코드는 개체의 GetChildrenGetParent 메서드를 호출하여 피어 트리를 탐색할 수 있습니다. 컨트롤 내 WPF 요소 간의 탐색은 피어의 GetChildrenCore 메서드 구현으로 지원됩니다. UI 자동화 시스템은 이 메서드를 호출하여 컨트롤 내에 포함된 하위 요소의 트리를 작성합니다(예: 목록 상자의 목록 항목). 기본 UIElementAutomationPeer.GetChildrenCore 메서드는 요소의 시각적 트리를 트래버스하여 자동화 피어의 트리를 빌드합니다. 사용자 지정 컨트롤은 자식 요소를 자동화 클라이언트에 노출하도록 이 메서드를 재정의하여 정보를 전달하거나 사용자 상호 작용을 허용하는 요소의 자동화 피어를 반환합니다.

파생된 피어의 사용자 지정

UIElementContentElement에서 파생된 모든 클래스에는 보호되는 가상 메서드 OnCreateAutomationPeer가 포함됩니다. WPF는 OnCreateAutomationPeer를 호출하여 각 컨트롤에 대한 자동화 피어 개체를 가져옵니다. 자동화 코드는 피어를 사용하여 컨트롤의 특성 및 기능에 대한 정보를 가져오고 대화형 사용을 시뮬레이트할 수 있습니다. 자동화를 지원하는 사용자 지정 컨트롤은 OnCreateAutomationPeer를 재정의하고 AutomationPeer에서 파생되는 클래스의 인스턴스를 반환해야 합니다. 예를 들어 사용자 지정 컨트롤이 ButtonBase 클래스에서 파생되는 경우 OnCreateAutomationPeer에서 반환하는 개체는 ButtonBaseAutomationPeer에서 파생되어야 합니다.

사용자 지정 컨트롤을 구현할 때 사용자 지정 컨트롤과 관련된 고유한 동작을 설명하는 "Core" 메서드를 기본 자동화 피어 클래스에서 재정의해야 합니다.

OnCreateAutomationPeer 재정의

사용자 지정 컨트롤에 대한 OnCreateAutomationPeer 메서드를 재정의하여 공급자 개체를 반환합니다. 이 메서드는 AutomationPeer에서 직접적 또는 간접적으로 파생되어야 합니다.

GetPattern 재정의

자동화 피어는 서버 쪽 UI 자동화 공급자의 몇 가지 구현 측면을 간소화하지만 사용자 지정 컨트롤 자동화 피어는 패턴 인터페이스를 계속 처리해야 합니다. 비 WPF 공급자처럼 피어는 IInvokeProvider와 같은 System.Windows.Automation.Provider 네임스페이스에서 인터페이스의 구현을 제공하여 컨트롤 패턴을 지원합니다. 컨트롤 패턴 인터페이스는 피어 자체 또는 다른 개체를 통해 구현할 수 있습니다. 피어의 GetPattern 구현에서는 지정된 패턴을 지원하는 개체를 반환합니다. UI 자동화 코드는 GetPattern 메서드를 호출하고 PatternInterface 열거형 값을 지정합니다. GetPattern의 재정의에서는 지정한 패턴을 구현하는 개체를 반환해야 합니다. 컨트롤에 패턴의 사용자 지정 구현이 없는 경우 기본 형식의 GetPattern 구현을 호출하여 해당 구현이나 null(패턴이 이 컨트롤 형식에 대해 지원되지 않는 경우)을 검색할 수 있습니다. 예를 들어 사용자 지정 NumericUpDown 컨트롤을 범위 내의 값으로 설정할 수 있으므로 해당 UI 자동화 피어는 IRangeValueProvider 인터페이스를 구현합니다. 다음 예제에서는 PatternInterface.RangeValue 값에 응답하기 위해 피어의 GetPattern 메서드를 재정의하는 방법을 보여줍니다.

public override object GetPattern(PatternInterface patternInterface)
{
    if (patternInterface == PatternInterface.RangeValue)
    {
        return this;
    }
    return base.GetPattern(patternInterface);
}
Public Overrides Function GetPattern(ByVal patternInterface As PatternInterface) As Object
    If patternInterface = PatternInterface.RangeValue Then
        Return Me
    End If
    Return MyBase.GetPattern(patternInterface)
End Function

GetPattern 메서드는 하위 요소를 패턴 공급자로 지정할 수도 있습니다. 다음 코드에서는 ItemsControl이 스크롤 패턴 처리를 내부 ScrollViewer 컨트롤의 피어로 전송하는 방법을 보여 줍니다.

public override object GetPattern(PatternInterface patternInterface)  
{  
    if (patternInterface == PatternInterface.Scroll)  
    {  
        ItemsControl owner = (ItemsControl) base.Owner;  
  
        // ScrollHost is internal to the ItemsControl class  
        if (owner.ScrollHost != null)  
        {  
            AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(owner.ScrollHost);  
            if ((peer != null) && (peer is IScrollProvider))  
            {  
                peer.EventsSource = this;  
                return (IScrollProvider) peer;  
            }  
        }  
    }  
    return base.GetPattern(patternInterface);  
}  
Public Class Class1  
    Public Overrides Function GetPattern(ByVal patternInterface__1 As PatternInterface) As Object  
        If patternInterface1 = PatternInterface.Scroll Then  
            Dim owner As ItemsControl = DirectCast(MyBase.Owner, ItemsControl)  
  
            ' ScrollHost is internal to the ItemsControl class  
            If owner.ScrollHost IsNot Nothing Then  
                Dim peer As AutomationPeer = UIElementAutomationPeer.CreatePeerForElement(owner.ScrollHost)  
                If (peer IsNot Nothing) AndAlso (TypeOf peer Is IScrollProvider) Then  
                    peer.EventsSource = Me  
                    Return DirectCast(peer, IScrollProvider)  
                End If  
            End If  
        End If  
        Return MyBase.GetPattern(patternInterface1)  
    End Function  
End Class  

패턴 처리에 대한 하위 요소를 지정하기 위해 이 코드는 하위 요소 개체를 가져오고, CreatePeerForElement 메서드를 사용하여 피어를 만들며, 새 피어의 EventsSource 속성을 현재 피어로 설정하고, 새 피어를 반환합니다. 하위 요소에서 EventsSource를 설정하면 하위 요소가 자동화 피어 트리에 나타나지 않도록 하고 하위 요소에 의해 발생되는 모든 이벤트를 EventsSource에 지정된 컨트롤에서 시작되는 것으로 지정합니다. ScrollViewer 컨트롤은 자동화 트리에 나타나지 않으며 생성되는 스크롤 이벤트는 ItemsControl 개체에서 시작되는 것처럼 보입니다.

"Core" 메서드 재정의

자동화 코드는 피어 클래스의 public 메서드를 호출하여 컨트롤에 대한 정보를 가져옵니다. 컨트롤에 대한 정보를 제공하려면 컨트롤 구현이 기본 자동화 피어 클래스에서 제공하는 구현과 다른 경우 이름이 "Core"로 끝나는 각 메서드를 재정의합니다. 최소한 다음 예제에 표시된 것처럼 클래스에서 GetClassNameCoreGetAutomationControlTypeCore 메서드를 구현해야 합니다.

protected override string GetClassNameCore()
{
    return "NumericUpDown";
}

protected override AutomationControlType GetAutomationControlTypeCore()
{
    return AutomationControlType.Spinner;
}
Protected Overrides Function GetClassNameCore() As String
    Return "NumericUpDown"
End Function

Protected Overrides Function GetAutomationControlTypeCore() As AutomationControlType
    Return AutomationControlType.Spinner
End Function

GetAutomationControlTypeCore 구현은 ControlType 값을 반환하여 컨트롤을 설명합니다. ControlType.Custom을 반환할 수 있지만 컨트롤을 정확하게 설명하는 경우 보다 구체적인 컨트롤 형식 중 하나를 반환해야 합니다. 공급자가 UI 자동화를 구현하려면 ControlType.Custom의 반환 값에 추가 작업이 필요하며, UI 자동화 클라이언트 제품은 컨트롤 구조, 키보드 상호 작용 및 가능한 컨트롤 패턴을 예상할 수 있습니다.

IsContentElementCoreIsControlElementCore 메서드를 구현하여 컨트롤이 데이터 콘텐츠를 포함하는지, 사용자 인터페이스에서 대화형 역할을 수행하는지 또는 둘 다인지를 나타냅니다. 기본적으로 두 메서드 모두 true를 반환합니다. 이러한 설정에서는 화면 읽기 프로그램과 같은 자동화 도구의 유용성이 향상되며, 이러한 메서드를 사용하여 자동화 트리를 필터링할 수 있습니다. GetPattern 메서드가 하위 요소 피어에 패턴 처리를 전송하는 경우 하위 요소 피어의 IsControlElementCore 메서드는 false를 반환하여 자동화 트리에서 하위 요소 트리를 숨길 수 있습니다. 예를 들어 ListBox의 스크롤은 ScrollViewer에 의해 처리되고 PatternInterface.Scroll에 대한 자동화 피어는 ListBoxAutomationPeer에 연결된 ScrollViewerAutomationPeerGetPattern 메서드에 의해 반환됩니다. 따라서 ScrollViewerAutomationPeerIsControlElementCore 메서드는 false를 반환하므로 ScrollViewerAutomationPeer는 자동화 트리에 나타나지 않습니다.

자동화 피어는 컨트롤에 적절한 기본값을 제공해야 합니다. 컨트롤을 참조하는 XAML은 AutomationProperties 특성을 포함하여 핵심 메서드의 피어 구현을 재정의할 수 있습니다. 예를 들어 다음 XAML은 두 개의 사용자 지정된 UI 자동화 속성이 있는 단추를 만듭니다.

<Button AutomationProperties.Name="Special"
    AutomationProperties.HelpText="This is a special button."/>  

패턴 공급자 구현

사용자 지정 공급자를 통해 구현된 인터페이스는 소유하는 요소가 Control에서 직접 파생되는 경우 명시적으로 선언됩니다. 예를 들어 다음 코드는 범위 값을 구현하는 Control에 대한 피어를 선언합니다.

public class RangePeer1 : FrameworkElementAutomationPeer, IRangeValueProvider { }  
Public Class RangePeer1  
    Inherits FrameworkElementAutomationPeer  
    Implements IRangeValueProvider  
End Class  

소유하는 컨트롤이 RangeBase와 같은 특정 형식의 컨트롤에서 파생되는 경우 피어는 해당하는 파생 피어 클래스에서 파생될 수 있습니다. 이 경우 피어는 RangeBaseAutomationPeer에서 파생되며, IRangeValueProvider의 기본 구현을 제공합니다. 다음 코드에서는 이러한 피어의 선언을 보여 줍니다.

public class RangePeer2 : RangeBaseAutomationPeer { }  
Public Class RangePeer2  
    Inherits RangeBaseAutomationPeer  
End Class  

구현 예제는 NumericUpDown 사용자 지정 컨트롤을 구현하고 사용하는 C# 또는 Visual Basic 소스 코드를 참조하세요.

이벤트 발생

자동화 클라이언트는 자동화 이벤트를 구독할 수 있습니다. 사용자 지정 컨트롤은 RaiseAutomationEvent 메서드를 호출하여 컨트롤 상태의 변경을 보고해야 합니다. 마찬가지로 속성 값이 변경되면 RaisePropertyChangedEvent 메서드를 호출합니다. 다음 코드에서는 컨트롤 코드 내에서 피어 개체를 가져오고 메서드를 호출하여 이벤트를 발생시키는 방법을 보여 줍니다. 최적화 방법으로 코드는 이 이벤트 유형에 대한 수신기가 있는지 확인합니다. 수신기가 있는 경우에만 이벤트가 발생되면 불필요한 오버헤드를 방지하고 컨트롤이 응답 가능한 상태를 유지하는 데 도움이 됩니다.

if (AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged))
{
    NumericUpDownAutomationPeer peer =
        UIElementAutomationPeer.FromElement(nudCtrl) as NumericUpDownAutomationPeer;

    if (peer != null)
    {
        peer.RaisePropertyChangedEvent(
            RangeValuePatternIdentifiers.ValueProperty,
            (double)oldValue,
            (double)newValue);
    }
}
If AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged) Then
    Dim peer As NumericUpDownAutomationPeer = TryCast(UIElementAutomationPeer.FromElement(nudCtrl), NumericUpDownAutomationPeer)

    If peer IsNot Nothing Then
        peer.RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty, CDbl(oldValue), CDbl(newValue))
    End If
End If

참고 항목