2012-06-13 8 views
9

Stiamo utilizzando il prisma e WPF per creare un'applicazione. Recentemente abbiamo iniziato a utilizzare UI Automation (UIA) per testare la nostra app. Ma qualche strano comportamento si è verificato durante l'esecuzione del test UIA. shell Ecco semplificata:ContentControl non è visibile all'avvio dell'applicazione tramite test di automazione interfaccia utente, ma è visibile all'avvio dell'applicazione da parte dell'utente

<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> 

Nella nostra applicazione, usiamo strati (Popup e ErrorPopup) per nascondere MainViewArea, per negare l'accesso ai comandi. Per mostrare Popup, usiamo metodo successivo:

//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; 
     } 
    } 

Simile a questo, mostriamo ErrorPopup su tutti gli elementi della nostra applicazione:

// 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); 
     } 
    } 

mistici ...

Quando corriamo come fanno gli utenti (doppio clic sull'icona dell'app), possiamo vedere entrambi i controlli personalizzati (utilizzando il metodo AutomationElement.FindFirst o tramite Visual UI Automation Verify). Ma quando lo iniziamo usando il test di automazione dell'interfaccia utente - ErrorPopup scompare dall'albero dei controlli. Stiamo cercando di avviare l'applicazione in questo modo:

System.Diagnostics.Process.Start(pathToExeFile); 

Penso che ci siamo persi qualcosa. Ma cosa?

Modifica # 1

Come ha detto @chrismead, abbiamo cercato di eseguire la nostra applicazione con UseShellExecute bandiera impostato su true, ma questo non aiuta. Ma se iniziamo l'app dalla riga cmd e facciamo clic manualmente sul pulsante, Popup e ErrorPopup sono visibili nella struttura dei controlli di automazione.

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(); 

Uno dei nostro suggerimento è quando usiamo il metodo FindAll o FindFirst per cercare il pulsante per fare clic, la finestra in qualche modo il suo stato memorizzato nella cache di automazione interfaccia utente, e non aggiorna esso.

Modifica # 2 Abbiamo trovare, che metodo di estensione della biblioteca prisma IRegionManager.RegisterViewWithRegion(RegionNames.OurRegion, typeof(Views.OurView)) hanno una qualche strano comportamento. Se smettiamo di usarlo, questo risolve il nostro problema in particolare. Ora siamo in grado di vedere ErrorView e qualsiasi tipo di vista in PopupContentControl e la struttura ad albero degli elementi UIA degli aggiornamenti dell'applicazione. Ma questa non è una risposta - "Basta smettere di usare questa funzione"!

In MainViewArea abbiamo un ContentControl, che aggiorna i contenuti a seconda delle azioni degli utenti, e noi siamo in grado di vedere solo il primo caricato UserControl a quella ContentControl.Content proprietà. Questa operazione viene eseguita in questo modo:

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

E se cambiamo la vista, nessun aggiornamento sarà eseguita in albero UI Automation - la prima vista caricata sarà in esso, invece. Ma visivamente osserviamo un altro e WPFInspector lo mostra correttamente (il suo show non è un albero di automazione dell'interfaccia utente), ma Inspect.exe - non.

Anche la nostra ipotesi che la finestra utilizzare una sorta di caching è sbagliato - caching nel client di automazione interfaccia utente dobbiamo attivare in modo esplicito, ma noi non lo facciamo.

+1

Quindi, è corretto dire che un semplice doppio clic sull'avvio dell'app determina che il controllo si trova nell'albero, ma un avvio Process.Start no? – chrismead

+1

Sì, è corretto. Ma abbiamo provato 3 modi per avviare l'app dal codice: nessuno ci porta alla soluzione giusta ... – stukselbax

+0

Hai provato a lanciare l'app da una finestra di cmd? Se funziona, utilizzare il flag ProcessStartInfo.UseShellExecute potrebbe funzionare. – chrismead

risposta

7

Mi dispiace di aver perso qualche dettaglio, quella era la chiave della risposta. Penso che non fosse importante. Comunque.

Abbiamo usato NavBar da DevExpress biblioteca controlli per WPF. Ciò che risulta è quando è presente NavBar, le viste create dinamicamente non vengono visualizzate nell'albero di automazione dell'interfaccia utente. Quando lo rimuovevi dalla finestra, c'era la possibilità di vedere tutte le viste caricate dinamicamente. Cosa fa il NavBar - ancora mistico per me.

Ecco un esempio luminoso per vedere cosa è successo, se NavBar è presente o assente sulla finestra (è richiesto 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); 
    } 
} 

Modifica

Secondo il DevExpress answer sul loro sito di supporto:

Dopo aver creato un peer, l'ascolto degli eventi di automazione può causare problemi di prestazioni. Abbiamo deciso di cancellare gli elenchi di invocazioni degli eventi di automazione per risolverlo.Nella tua situazione specifica, devi disabilitare la cancellazione. Per farlo, impostare la proprietà statica DevExpress.Xpf.Core.ClearAutomationEventsHelper.IsEnabled su False nel costruttore Window.

Questo risolve il problema.

+0

Grazie per questo. Ci siamo soffiati troppo a lungo cercando di rintracciare la ragione di ciò. – Jordan

1

stukselbax, cercare di trovare una sequenza di tasti (TAB, ed un INVIO più probabile) di fare clic sul pulsante che consente di visualizzare gli elementi. è abbastanza facile inviare sequenze di tasti e posso aggiungere altro qui in proposito se questo funziona per te. puoi sempre stabilire un ordine di tabulazione nell'applicazione che ha più senso per gli utenti.

------ Aggiornamento 6/20/12 --------

Hai provato doppio clic su un collegamento per la vostra applicazione sul desktop utilizzando PInvoke per vedere se si può vedere i controlli quando viene aperto in questo modo? Ecco un link ad un esempio qui su StackOverflow:

Directing mouse events [DllImport("user32.dll")] click, double click

Un'altra idea: alcuni dei controlli su App Attualmente sto automatizzando non appaiono nella struttura fino a quando un clic del mouse si verifica su di loro. Per ottenere ciò senza utilizzare alcuna coordinata hardcoded, trovo qualcosa nell'albero che è solo (sopra/sotto/etc) il punto in cui ho bisogno di cliccare per far apparire il controllo. Quindi ottengo le coordinate del mouse per quell'elemento e metto il mouse su un piccolo offset da lì e clicco. Quindi posso trovare i miei controlli nell'albero. Se l'app viene ridimensionata, spostata, ecc. Funzionerà comunque, poiché l'offset piccolo è ancora valido.

+1

Abbiamo provato questo - non è stato aiutato. – stukselbax

+0

Aggiunti alcuni elementi aggiuntivi sopra ... – chrismead

+0

Vieni a pensarci. Si potrebbe voler iniziare usando Process.Start con una scorciatoia anziché l'exe effettivo per vedere se questo aiuta. – chrismead

5

La mia ipotesi è che il peer di automazione dello ContentControl dovrebbe aggiornare i suoi figli con AutomationPeer.ResetChildrenCache() dopo che la vista è stata cambiata.

AutomationPeer.InvalidatePeer() dovrebbe avere lo stesso effetto (in aggiunta ad altri effetti collaterali) e si suppone che venga chiamato automaticamente in risposta all'evento LayoutUpdated. Potresti voler verificare che l'evento LayoutUpdated venga sollevato quando la vista cambia.

+0

Grazie mille per questo - ho lottato per 24 ore con una sorta di problema simile in cui 'AutomationElement' è improvvisamente scomparso. Dopo aver letto la tua risposta ho controllato se i figli di 'AutomationPeer' di un determinato controllo sono stati ricreati durante' LayoutUpdated' (e non lo erano) - quindi il tuo suggerimento con le chiamate a 'ResetChildrenCache()' e 'InvalidatePeer()' ha fatto il trucco. Grazie ancora. +1. –

Problemi correlati