11

プリズムとWPFを使用してアプリケーションを構築しています。最近、UI オートメーション (UIA) を使用してアプリをテストし始めました。しかし、UIA テストを実行すると、奇妙な動作が発生しました。単純化されたシェルは次のとおりです。

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>    
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <TextBlock 
        Grid.Row="0" Grid.Column="0"
        Name="loadingProgressText"
        VerticalAlignment="Center" HorizontalAlignment="Center"
        Text="Loading, please wait..."/>

    <Border
        Grid.Row="0" 
        x:Name="MainViewArea">
        <Grid>
            ...
        </Grid>
    </Border>

    <!-- Popup -->
    <ContentControl 
        x:Name="PopupContentControl"
        Grid.Row="0" 
        prism:RegionManager.RegionName="PopupRegion"
        Focusable="False">
    </ContentControl>

    <!-- ErrorPopup -->
    <ContentControl 
        x:Name="ErrorContentControl"
        Grid.Row="0" 
        prism:RegionManager.RegionName="ErrorRegion"
        Focusable="False">
    </ContentControl>
</Grid>

このアプリでは、レイヤー (PopupおよびErrorPopup)を使用してMainViewAreaを非表示にし、コントロールへのアクセスを拒否します。を表示するPopupには、次のメソッドを使用します。

    //In constructor of current ViewModel we store _popupRegion instance to the local variable:
    _popupRegion = _regionManager.Regions["PopupRegion"];
    //---

    private readonly Stack<UserControl> _popups = new Stack<UserControl>();
    public void ShowPopup(UserControl popup)
    {
        _popups.Push(popup);

        _popupRegion.Add(PopupView);
        _popupRegion.Activate(PopupView);
    }

    public UserControl PopupView
    {
        get
        {
            if (_popups.Any())
                return _popups.Peek();
            return null;
        }
    }

これと同様にErrorPopup、アプリケーションのすべての要素を示します。

    // In constructor we store _errorRegion:
    _errorRegion = _regionManager.Regions["ErrorRegion"]
    // --- 

    private UserControl _error_popup;

    public void ShowError(UserControl popup)
    {
        if (_error_popup == null)
        {
            _error_popup = popup;
            _errorRegion.Add(_error_popup);
            _errorRegion.Activate(_error_popup);
        }
    }

ミスティック...

ユーザーと同じように実行すると (アプリ アイコンをダブルクリック)、両方のカスタム コントロールが表示されます (AutomationElement.FindFirstメソッドを使用するか、Visual UI Automation Verifyを使用します)。しかし、UI オートメーション テストを使用して開始するErrorPopupと、コントロールのツリーから消えます。次のようにアプリケーションを起動しようとしています。

System.Diagnostics.Process.Start(pathToExeFile);

私たちは何かを逃したと思います。しかし、何?

編集#1

@chrismead が言ったように、UseShellExecuteフラグを true に設定してアプリを実行しようとしましたが、これは役に立ちません。しかし、コマンドラインからアプリを起動し、ボタンを手動でクリックするPopupErrorPopup、オートメーション コントロール ツリーに表示されます。

    Thread appThread = new Thread(delegate()
        {
            _userAppProcess = new Process();
            _userAppProcess.StartInfo.FileName = pathToExeFile;
            _userAppProcess.StartInfo.WorkingDirectory = System.IO.Directory.GetCurrentDirectory();
            _userAppProcess.StartInfo.UseShellExecute = true;
            _userAppProcess.Start();

        });
        appThread.SetApartmentState(ApartmentState.STA);
        appThread.Start();

私たちの提案の 1 つは、メソッドを使用するFindAllFindFirst、クリックするボタンを検索するときに、ウィンドウが何らかの方法で UI オートメーションの状態をキャッシュし、それを更新しないことです。

編集#2IRegionManager.RegisterViewWithRegion(RegionNames.OurRegion, typeof(Views.OurView))プリズムライブラリの拡張メソッドには奇妙な動作がある ことがわかりました。使用をやめれば、特に問題は解決します。で ErrorView とあらゆる種類のビューを確認できるようにPopupContentControlなり、アプリケーションは UIA 要素のツリー構造を更新します。しかし、これは答えではありません。「この機能の使用をやめてください」!

MainViewAreaは、ContentControlユーザーのアクションに応じてコンテンツを更新する があり、UserControlそのContentControl.Contentプロパティにロードされた最初のものだけを見ることができます。これは次のように実行されます。

IRegionManager regionManager = Container.Resolve<IRegionManager>();
regionManager.RequestNavigate(RegionNames.MainContentRegion, this.Uri);

ビューを変更すると、UI オートメーション ツリーで更新が実行されなくなります。代わりに、最初に読み込まれたビューが表示されます。しかし、視覚的には別の を観察しViewWPFInspectorはそれを適切に表示します (その表示は UI オートメーション ツリーではありません) が、Inspect.exe は表示しません。

また、window がある種のキャッシュを使用するという提案も間違っています。UI オートメーション クライアントでのキャッシュは、明示的にオンにする必要がありますが、実行していません。

4

3 に答える 3

8

詳細を見逃して申し訳ありませんが、それが答えの鍵でした。大事なことではなかったと思います。ともかく。

WPFのDevExpressコントロール ライブラリNavBarから使用しました。が存在する場合、動的に作成されたビューは UI オートメーション ツリーに表示されません。ウィンドウから削除すると、動的にロードされたすべてのビューを表示する機能がありました。とはどういう意味ですか?NavBarNavBar

ここでは、NavBar がウィンドウに存在するか存在しないか (DevExpress が必要) の場合に何が起こったかを確認するための明るい例を示します。

MainWindow.xaml:

<Window xmlns:dxn="http://schemas.devexpress.com/winfx/2008/xaml/navbar"
        x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        >
    <Grid Name="ContentGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <!--Comment NavBar to see dynamic control in UI Automation tree-->
        <dxn:NavBarControl Name="asdasd">
            <dxn:NavBarControl.Groups>
                <dxn:NavBarGroup Header="asdasdasdasd" />
            </dxn:NavBarControl.Groups>
        </dxn:NavBarControl>
        <TextBox Grid.Column="1" Name="Statictb" Text="static is visible in ui automation tree" />
        <Button Grid.Row="1" Content="Create controls" Height="25"  Click="Button_Click"/>
    </Grid>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        TextBox tb = new TextBox();
        Grid.SetRow(tb, 1);
        Grid.SetColumn(tb, 1);
        tb.Text = "dynamic is not visible, if NavBar here...";
        ContentGrid.Children.Add(tb);
    }
}

編集

サポート サイトのDevExpress の回答によると、次のようになります。

ピアが作成された後、自動化イベントをリッスンすると、パフォーマンスの問題が発生する可能性があります。この問題を解決するために、自動化イベントの呼び出しリストをクリアすることにしました。特定の状況では、クリアを無効にする必要があります。これを行うには、静的な DevExpress.Xpf.Core.ClearAutomationEventsHelper.IsEnabled プロパティを Window コンストラクターで False に設定してください。

これで問題は解決します。

于 2012-06-22T06:26:16.240 に答える
7

私の推測では、ビューが変更された後、ContentControlの自動化ピアはその子を で更新する必要があります。AutomationPeer.ResetChildrenCache()

AutomationPeer.InvalidatePeer()(他の副作用に加えて) 同じ効果があり、 LayoutUpdatedイベントに応答して自動的に呼び出されるはずです。ビューが変更されたときに LayoutUpdated イベントが発生することを確認することができます。

于 2012-06-20T13:23:09.367 に答える
2

stukselbax、一連のキーストローク(TAB、および最も可能性の高いENTER)を見つけて、アイテムを表示できるボタンをクリックしてみてください。キーストロークを送信するのは非常に簡単です。それがうまくいけば、ここにさらに追加できます。アプリケーションでは、ユーザーにとって最も意味のあるタブ順序をいつでも確立できます。

------2012年6月20日に更新--------

PInvokeを使用してデスクトップ上のアプリへのショートカットをダブルクリックして、その方法で開いたときにコントロールが表示されるかどうかを確認しましたか?ここにstackoverflowの例へのリンクがあります:

マウスイベントの送信[DllImport( "user32.dll")]クリック、ダブルクリック

別のアイデア:現在自動化されているアプリの一部のコントロールは、マウスクリックが発生するまでツリーに表示されません。ハードコードされた座標を使用せずにこれを実現するには、ツリー内で、コントロールを表示するためにクリックする必要がある場所(上/下など)を見つけます。次に、そのアイテムのマウス座標を取得し、そこから少しオフセットしてマウスを置き、クリックします。次に、ツリーで自分のコントロールを見つけることができます。アプリのサイズが変更されたり、移動されたりした場合でも、小さなオフセットが有効であるため、これは引き続き機能します。

于 2012-06-14T14:35:56.857 に答える