Pergunta

Como você está instrumentando suas UIs?No passado eu li que as pessoas instrumentaram suas interfaces de usuário, mas o que não encontrei foram exemplos ou dicas sobre como para instrumentar uma UI.

Por instrumentação, quero dizer coletar dados sobre o uso e o desempenho do sistema.Um artigo do MSDN sobre Instrumentação é http://msdn.microsoft.com/en-us/library/x5952w0c.aspx.Gostaria de capturar em quais botões os usuários clicam, quais atalhos de teclado eles usam, quais termos eles usam para pesquisar, etc.

  • Como você está instrumentando sua UI?
  • Em que formato você está armazenando a instrumentação?
  • Como você está processando os dados instrumentados?
  • Como você mantém seu código de UI limpo com essa lógica de instrumentação?

Especificamente, estou implementando minha UI no WPF, então isso proporcionará desafios extras em comparação com a instrumentação de um aplicativo baseado na Web.(ou seja,necessidade de transferir os dados instrumentados de volta para um local central, etc.).Dito isto, sinto que a tecnologia pode fornecer uma implementação mais fácil de instrumentação através de conceitos como propriedades anexadas.

  • Você instrumentou um aplicativo WPF?Você tem alguma dica sobre como isso pode ser alcançado?

Editar:A seguinte postagem do blog apresenta uma solução interessante: Blog Pixel-In-Gene:Técnicas para auditoria de UI em aplicativos WPF

Foi útil?

Solução 4

A postagem do blog a seguir oferece algumas boas ideias para instrumentar um aplicativo WPF:Técnicas para auditoria de UI em aplicativos WPF.

Outras dicas

Aqui está um exemplo de como uso um gerenciador de eventos simples para conectar-se aos eventos da UI e extrair informações importantes dos eventos, como nome e tipo do elemento da UI, nome do evento e nome do tipo da janela pai.Para listas também extraio o item selecionado.

Esta solução escuta apenas cliques de controles derivados do ButtonBase (Button, ToggleButton, ...) e alterações de seleção em controles derivados do Selector (ListBox, TabControl, ...).Deve ser fácil estender para outros tipos de elementos de UI ou obter uma solução mais refinada.A solução é inspirada em A resposta de Brad Leach.

public class UserInteractionEventsManager
{
    public delegate void ButtonClickedHandler(DateTime time, string eventName, string senderName, string senderTypeName, string parentWindowName);
    public delegate void SelectorSelectedHandler(DateTime time, string eventName, string senderName, string senderTypeName, string parentWindowName, object selectedObject);

    public event ButtonClickedHandler ButtonClicked;
    public event SelectorSelectedHandler SelectorSelected;

    public UserInteractionEventsManager()
    {
        EventManager.RegisterClassHandler(typeof(ButtonBase), ButtonBase.ClickEvent, new RoutedEventHandler(HandleButtonClicked));
        EventManager.RegisterClassHandler(typeof(Selector), Selector.SelectionChangedEvent, new RoutedEventHandler(HandleSelectorSelected));
    }

    #region Handling events

    private void HandleSelectorSelected(object sender, RoutedEventArgs e)
    {
        // Avoid multiple events due to bubbling. Example: A ListBox inside a TabControl will cause both to send the SelectionChangedEvent.
        if (sender != e.OriginalSource) return;

        var args = e as SelectionChangedEventArgs;
        if (args == null || args.AddedItems.Count == 0) return;

        var element = sender as FrameworkElement;
        if (element == null) return;

        string senderName = GetSenderName(element);
        string parentWindowName = GetParentWindowTypeName(sender);
        DateTime time = DateTime.Now;
        string eventName = e.RoutedEvent.Name;
        string senderTypeName = sender.GetType().Name;
        string selectedItemText = args.AddedItems.Count > 0 ? args.AddedItems[0].ToString() : "<no selected items>";

        if (SelectorSelected != null)
            SelectorSelected(time, eventName, senderName, senderTypeName, parentWindowName, selectedItemText);
    }

    private void HandleButtonClicked(object sender, RoutedEventArgs e)
    {
        var element = sender as FrameworkElement;
        if (element == null) return;

        string parentWindowName = GetParentWindowTypeName(sender);
        DateTime time = DateTime.Now;
        string eventName = e.RoutedEvent.Name;
        string senderTypeName = sender.GetType().Name;
        string senderName = GetSenderName(element);

        if (ButtonClicked != null) 
            ButtonClicked(time, eventName, senderName, senderTypeName, parentWindowName);
    }

    #endregion

    #region Private helpers

    private static string GetSenderName(FrameworkElement element)
    {
        return !String.IsNullOrEmpty(element.Name) ? element.Name : "<no item name>";
    }


    private static string GetParentWindowTypeName(object sender)
    {
        var parent = FindParent<Window>(sender as DependencyObject);
        return parent != null ? parent.GetType().Name : "<no parent>";
    }

    private static T FindParent<T>(DependencyObject item) where T : class
    {
        if (item == null) 
            return default(T);

        if (item is T)
            return item as T;

        DependencyObject parent = VisualTreeHelper.GetParent(item);
        if (parent == null)
            return default(T);

        return FindParent<T>(parent);
    }

    #endregion
}

E para fazer o registro real, eu uso o log4net e criei um registrador separado chamado 'Interação' para registrar a interação do usuário.A classe 'Log' aqui é simplesmente meu próprio wrapper estático para log4net.

/// <summary>
/// The user interaction logger uses <see cref="UserInteractionEventsManager"/> to listen for events on GUI elements, such as buttons, list boxes, tab controls etc.
/// The events are then logged in a readable format using Log.Interaction.Info().
/// </summary>
public class UserInteractionLogger
{
    private readonly UserInteractionEventsManager _events;
    private bool _started;

    /// <summary>
    /// Create a user interaction logger. Remember to Start() it.
    /// </summary>
    public UserInteractionLogger()
    {
        _events = new UserInteractionEventsManager();

    }

    /// <summary>
    /// Start logging user interaction events.
    /// </summary>
    public void Start()
    {
        if (_started) return;

        _events.ButtonClicked += ButtonClicked;
        _events.SelectorSelected += SelectorSelected;

        _started = true;
    }

    /// <summary>
    /// Stop logging user interaction events.
    /// </summary>
    public void Stop()
    {
        if (!_started) return;

        _events.ButtonClicked -= ButtonClicked;
        _events.SelectorSelected -= SelectorSelected;

        _started = false;
    }

    private static void SelectorSelected(DateTime time, string eventName, string senderName, string senderTypeName, string parentWindowTypeName, object selectedObject)
    {
        Log.Interaction.Info("{0}.{1} by {2} in {3}. Selected: {4}", senderTypeName, eventName, senderName, parentWindowTypeName, selectedObject);
    }

    private static void ButtonClicked(DateTime time, string eventName, string senderName, string senderTypeName, string parentWindowTypeName)
    {
        Log.Interaction.Info("{0}.{1} by {2} in {3}", senderTypeName, eventName, senderName, parentWindowTypeName);
    }
}

A saída seria mais ou menos assim, omitindo entradas de log não relevantes.

04/13 08:38:37.069 INFO        Iact ToggleButton.Click by AnalysisButton in MyMainWindow
04/13 08:38:38.493 INFO        Iact ListBox.SelectionChanged by ListView in MyMainWindow. Selected: Andreas Larsen
04/13 08:38:44.587 INFO        Iact Button.Click by EditEntryButton in MyMainWindow
04/13 08:38:46.068 INFO        Iact Button.Click by OkButton in EditEntryDialog
04/13 08:38:47.395 INFO        Iact ToggleButton.Click by ExitButton in MyMainWindow

Você poderia considerar log4net.É uma estrutura de log robusta que existe em uma única DLL.Isso também é feito em um modo do tipo "não exigente", de modo que, se um processo crítico estiver em andamento, ele não será registrado até que os recursos sejam liberados um pouco mais.

Você poderia facilmente configurar vários registradores de nível INFO e rastrear toda a interação do usuário necessária, e não seria necessário um bug para enviar o arquivo para você mesmo.Você também pode registrar todos os seus códigos ERROR e FATAL em um arquivo separado que pode ser facilmente enviado a você para processamento.

Se você usar comandos WPF, cada comando personalizado poderá registrar a ação realizada.Você também pode registrar a forma como o comando foi iniciado.

Talvez o Automação de IU da Microsoft para WPF pode ajudar?É uma estrutura para automatizar sua UI, talvez possa ser usada para registrar coisas para você...

Usamos o Automation Framework para testar automaticamente nossa UI no WPF.

Isenção de responsabilidade:Eu trabalho para a empresa que vende este produto, além disso, sou desenvolvedor deste produto específico :).

Se você estiver interessado em um produto comercial para fornecer isso, então o Runtime Intelligence (um complemento funcional do Dotfuscator) que injeta funcionalidade de rastreamento de uso em seus aplicativos .NET está disponível.Fornecemos não apenas a implementação real da funcionalidade de rastreamento, mas também a funcionalidade de coleta, processamento e geração de relatórios de dados.

Houve recentemente uma discussão no fórum Business of Software sobre este tópico que também postei aqui: http://discuss.joelonsoftware.com/default.asp?biz.5.680205.26 .

Para uma visão geral de alto nível de nosso material, veja aqui: http://www.preemptive.com/runtime-intelligence-services.html .

Além disso, estou atualmente trabalhando na redação de alguma documentação mais orientada tecnicamente, pois percebemos que é uma área que definitivamente poderíamos melhorar. Por favor, deixe-me saber se alguém estiver interessado em ser notificado quando eu tiver concluído.

Ainda não desenvolvi usando WPF.Mas eu diria que é igual à maioria dos outros aplicativos, pois você deseja manter o código da interface do usuário o mais leve possível.Vários padrões de design podem ser usados ​​nisso, como o óbvio MVC e Fachada.Pessoalmente, sempre tento manter os objetos que viajam entre as camadas UI e BL o mais leves possível, mantendo-os primitivos, se possível.

Isso me ajuda a focar em melhorar a camada da interface do usuário sem a preocupação de que algo aconteça depois que eu devolvo meus dados (primitivos).

Espero ter entendido sua pergunta corretamente e desculpe, não posso oferecer ajuda mais contextual com o WPF.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top