Manipulação de Arquivos, Gerenciamento de Estado e Integração com Componentes Externos em Aplicações Windows

No desenvolvimento de aplicações modernas, a capacidade de interagir com o sistema de arquivos, gerenciar o estado do aplicativo e integrar funcionalidades externas é crucial. Este artigo explora abordagens essenciais para desenvolvedores que buscam criar aplicações robustas e responsivas para a plataforma Windows, focando na manipulação de arquivos, resposta a mudanças de estado e incorporação de bibliotecas externas e componentes WinRT.

Gerenciamento de Arquivos

A persistência de dados em aplicações pode variar desde configurações locais e estados de sessão até bancos de dados como IndexedDB. No entanto, para tipos de dados como imagens, documentos ou planilhas, a manipulação direta de arquivos no sistema de arquivos do usuário é frequentemente a solução mais prática e intuitiva. Aplicações Windows possuem a capacidade de ler, gravar e excluir arquivos e diretórios, proporcionando flexibilidade para diversos cenários.

O Namespace Windows.Storage

O namespace Windows.Storage é o ponto de partida para a interação com o sistema de arquivos em aplicações Windows. Ele oferece classes fundamentais para gerenciar arquivos e pastas, como StorageFile, StorageFolder e FileIO. As classes StorageFile e StorageFolder representam entidades no sistema de arquivos do usuário e são retornadas por seletores de arquivos/pastas. Elas permitem operações como copiar, criar, excluir e abrir. A classe FileIO, por sua vez, facilita a manipulação do conteúdo de um objeto StorageFile, seja lendo ou alterando seu texto ou bytes.

É importante notar que todas as operações relacionadas a arquivos e pastas no Windows.Storage são assíncronas, retornando objetos Promise. Isso garante que a interface do usuário permaneça responsiva durante operações de I/O que podem levar tempo.

O Windows.Storage também fornece acesso a locais de armazenamento específicos da aplicação por meio da propriedade localFolder do objeto Windows.Storage.ApplicationData.current, que é uma instância de StorageFolder. Esse local é ideal para armazenar dados específicos da aplicação, como backups ou documentos relacionados a projetos.

Exemplo Prático: Backup de Dados da Aplicação

A funcionalidade de backup é valiosa em qualquer aplicação. Considere uma aplicação de gestão de tempo que permite ao usuário salvar uma cópia dos seus projetos e entradas de tempo. Isso pode ser implementado através de um botão em uma tela de configurações.

Primeiramente, adicione um botão de backup e um rótulo para exibir a localização do backup ao seu HTML de opções:

<div class="settings-section">
    <h3>Cópia de Segurança</h3>
    <p>Projetos e Entradas de Tempo</p>
    <p>
        <button id="btnSalvarBackup">Salvar Cópia</button>
        <span id="msgConfirmacaoBackup"
            class="win-type-xx-small"
            style="display: none;">Cópia salva</span>
    </p>
    <p class="win-type-xx-small">Local da Cópia: <span id="caminhoBackup"></span></p>
</div>

No JavaScript, configure os controles e implemente a lógica de backup. Um identificador de data e hora formatado pode ser usado para criar nomes de arquivos únicos para cada backup.

// Configuração dos controles
document.getElementById("btnSalvarBackup").onclick = this.salvarBackupDados;
document.getElementById("caminhoBackup").innerText = Windows.Storage.ApplicationData.current.localFolder.path + "\\backups";

// Implementação da função de backup
salvarBackupDados: function() {
    const formatadorDataHora = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter(
        "year.full month.integer(2) day.integer(2)-hour.integer(2) minute.integer(2) second.integer(2)",
        ["pt-BR"],
        "BR",
        "Gregorian",
        Windows.Globalization.ClockIdentifiers.twentyFourHour
    );

    const nomeArquivo = formatadorDataHora.format(new Date()) + ".json";
    const opcoesColisao = Windows.Storage.CreationCollisionOption.openIfExists; // Abre se existir, senão cria

    Windows.Storage.ApplicationData.current.localFolder
        .createFolderAsync("backups", opcoesColisao)
        .then(function(pasta) {
            return pasta.createFileAsync(nomeArquivo, opcoesColisao);
        })
        .then(function(arquivo) {
            // Supondo que 'appStorage' contém seus dados de projetos e entradas de tempo
            const dadosApp = {
                projetos: appStorage.projetos, // Exemplo de acesso aos dados
                entradasTempo: appStorage.entradasTempo
            };
            const conteudoJSON = JSON.stringify(dadosApp);
            return Windows.Storage.FileIO.writeTextAsync(arquivo, conteudoJSON);
        })
        .then(function() {
            document.getElementById("msgConfirmacaoBackup").style.display = "inline";
        })
        .catch(function(erro) {
            console.error("Erro ao salvar backup:", erro);
            // Mostrar mensagem de erro ao usuário
        });
}

Este código demonstra como criar uma pasta de backup dentro do armazenamento local da aplicação, gerar um nome de arquivo único e salvar dados serializados em JSON neste arquivo. A cadeia de Promises gerencia a natureza assíncrona dessas operações.

Biblioteca de Documentos por Projeto

Para associar documentos a projetos, é útil criar uma funcionalidade de biblioteca de documentos. Isso começa com uma nova página de controle (por exemplo, pages/documentos/biblioteca.html).

O cabeçalho da página pode exibir o nome do projeto atual para contextualização:

<div class="page-container">
    <header role="banner">
        <button class="win-backbutton" aria-label="Voltar" disabled type="button"></button>
        <h1 class="page-title-area win-type-ellipsis">
            <span class="page-title">Biblioteca de Documentos</span>
            <span class="win-type-x-large" id="nomeProjetoDetalhe">[Nome do Projeto]</span>
        </h1>
    </header>
    <section role="main">
        <p>Conteúdo aqui.</p>
    </section>
</div>

No JavaScript da página, um método setProjectInfo pode buscar o projeto pelo ID e atualizar o cabeçalho:

// Exemplo em pages/documentos/biblioteca.js
ready: function (element, options) {
    this.idProjeto = options && options.idProjeto;
    this.setProjectInfo();
},

setProjectInfo: function () {
    if (this.idProjeto) {
        const projeto = appStorage.projetos.getById(this.idProjeto);
        document.getElementById("nomeProjetoDetalhe").innerText = projeto.nome + " (" + projeto.nomeCliente + ")";
    }
}

Para adicionar documentos, um comando "Adicionar" na bara de aplicativos é apropriado. Utilize a classe FileOpenPicker do namespace Windows.Storage.Pickers para permitir que o usuário selecione um ou mais arquivos. Esta classe oferece controle sobre o texto do botão de confirmação, o local de início sugerido e os tipos de arquivo permitidos.

<div id="documentosAppBar"
    class="win-ui-dark"
    data-win-control="WinJS.UI.AppBar"
    data-win-options="{ sticky: true }">

    <button
        data-win-control="WinJS.UI.AppBarCommand"
        data-win-options="{
            id:'cmdAdicionarDocumentos',
            label:'Adicionar',
            icon:'add',
            section:'global',
            tooltip:'Adicionar'}">
    </button>
</div>

No JavaScript, a função adicionarDocumentosClick lida com a seleção de arquivos e a cópia para a pasta do projeto:

const appData = Windows.Storage.ApplicationData.current;
const opcoesColisao = Windows.Storage.CreationCollisionOption;
const idLocalPicker = Windows.Storage.Pickers.PickerLocationId;

// Função para obter ou criar a pasta de documentos do projeto
function obterPastaProjeto(projectId) {
    return appData.localFolder
        .createFolderAsync("docsProjetos", opcoesColisao.openIfExists)
        .then(function (pastaBase) {
            return pastaBase.createFolderAsync(projectId.toString(), opcoesColisao.openIfExists);
        });
}

// Handler para o clique no botão Adicionar Documentos
adicionarDocumentosClick: function () {
    // Verificação de estado do aplicativo (e.g., snapped view)
    if (!this.podeAbrirSeletor()) { return; }

    const seletorArquivos = new Windows.Storage.Pickers.FileOpenPicker();
    seletorArquivos.commitButtonText = "Adicionar à Biblioteca";
    seletorArquivos.suggestedStartLocation = idLocalPicker.desktop;
    seletorArquivos.fileTypeFilter.replaceAll(["*"]); // Permite todos os tipos de arquivo

    seletorArquivos.pickMultipleFilesAsync().then(arquivosSelecionados => {
        if (arquivosSelecionados && arquivosSelecionados.size > 0) {
            return this.obterPastaProjeto(this.idProjeto).then(pastaProjeto => {
                const promessasCopia = arquivosSelecionados.map(arquivo =>
                    arquivo.copyAsync(pastaProjeto, arquivo.name, opcoesColisao.replaceExisting)
                );
                return WinJS.Promise.join(promessasCopia);
            });
        }
        return WinJS.Promise.as();
    }).catch(error => {
        console.error("Erro ao adicionar documentos:", error);
    });
}

A função obterPastaProjeto garante que uma estrutura de diretórios específica seja criada para cada projeto (por exemplo, LocalState/docsProjetos/[ID_DO_PROJETO]).

Exibindo Documentos com ListView e StorageDataSource

Para listar os documentos adicionados, um ListView com um template personalizado pode ser utilizado. O StorageDataSource é ideal para essa tarefa, pois fornece uma "visão em tempo real" do sistema de arquivos, atualizando-se automaticamente quando arquivos são adicionados ou removidos.

O HTML do ListView pode ter um template para cada item:

<section role="main">
    <div id="templateDocumento" data-win-control="WinJS.Binding.Template" style="display: none">
        <div class="item-documento">
            <div class="item-documento-icone-container">
                <img class="item-documento-icone" />
            </div>
            <div class="item-documento-detalhes">
                <h3 class="item-documento-nome win-type-ellipsis"></h3>
                <h6 class="item-documento-modificado-container win-type-ellipsis">
                    <strong>Modificado:</strong> <span class="item-documento-modificado"></span>
                </h6>
                <h6 class="item-documento-tamanho-container win-type-ellipsis">
                    <strong>Tamanho:</strong> <span class="item-documento-tamanho"></span>
                </h6>
            </div>
        </div>
    </div>

    <div id="lvBiblioteca"
        class="win-selectionstylefilled"
        data-win-control="WinJS.UI.ListView"
        data-win-options="{
            itemTemplate: select('#templateDocumento'),
            selectionMode: 'multi',
            swipeBehavior: 'select',
            tapBehavior: 'directSelect'
        }">
    </div>
    <div id="semDocumentos" class="hidden">Nenhum documento encontrado para este projeto.</div>
</section>

O binding do ListView pode usar um inicializador de binding para formatar informações complexas, como tamanho do arquivo e ícones.

// Exemplo em pages/documentos/biblioteca.js
const opcoesMiniatura = Windows.Storage.FileProperties.ThumbnailOptions;
const modoMiniatura = Windows.Storage.FileProperties.ThumbnailMode;

bindProjectLibraryFiles: function () {
    if (this.idProjeto) {
        this.obterPastaProjeto(this.idProjeto).then(pasta => {
            const consultaArquivos = pasta.createFileQuery();
            const dataSourceOpcoes = {
                mode: modoMiniatura.singleItem,
                requestedThumbnailSize: 64,
                thumbnailOptions: opcoesMiniatura.resizeThumbnail
            };
            const dataSource = new WinJS.UI.StorageDataSource(consultaArquivos, dataSourceOpcoes);

            dataSource.getCount().then(count => {
                const lvControl = document.getElementById("lvBiblioteca").winControl;
                const msgSemDocs = document.getElementById("semDocumentos");

                if (count >= 1) {
                    lvControl.itemDataSource = dataSource;
                    WinJS.Utilities.removeClass(lvControl.element, "hidden");
                    WinJS.Utilities.addClass(msgSemDocs, "hidden");
                } else {
                    WinJS.Utilities.addClass(lvControl.element, "hidden");
                    WinJS.Utilities.removeClass(msgSemDocs, "hidden");
                }
            });
        });
    }
},

// Função de inicialização de binding para formatar itens
function inicializarBindingDocumento(fonte, propFonte, destino, propDestino) {
    const elementoNome = destino.querySelector(".item-documento-nome");
    const elementoModificado = destino.querySelector(".item-documento-modificado");
    const elementoTamanho = destino.querySelector(".item-documento-tamanho");
    const elementoIcone = destino.querySelector(".item-documento-icone");

    elementoNome.innerText = fonte.name;
    elementoModificado.innerText = fonte.basicProperties
        && fonte.basicProperties.dateModified
        && formatarDataHora(fonte.basicProperties.dateModified);

    const tamanhoBytes = fonte.basicProperties && fonte.basicProperties.size;
    if (tamanhoBytes > (1024 ** 3)) {
        elementoTamanho.innerText = (tamanhoBytes / (1024 ** 3)).toFixed(1) + " GB";
    } else if (tamanhoBytes > (1024 ** 2)) {
        elementoTamanho.innerText = (tamanhoBytes / (1024 ** 2)).toFixed(1) + " MB";
    } else if (tamanhoBytes > 1024) {
        elementoTamanho.innerText = (tamanhoBytes / 1024).toFixed(1) + " KB";
    } else {
        elementoTamanho.innerText = tamanhoBytes + " B";
    }

    let urlIcone;
    if (fonte.thumbnail && isTipoImagem(fonte.fileType)) {
        urlIcone = URL.createObjectURL(fonte.thumbnail, { oneTimeOnly: true });
    } else {
        urlIcone = obterIconeTipoArquivo(fonte.fileType);
    }
    elementoIcone.src = urlIcone;
    elementoIcone.title = fonte.displayType;
}

WinJS.Utilities.markSupportedForProcessing(inicializarBindingDocumento);
WinJS.Namespace.define("MinhaApp.Documentos", {
    bindItem: inicializarBindingDocumento,
});

Este código configura o ListView para exibir arquivos, usando miniaturas para imagens e ícones personalizados para outros tipos, além de formatar o tamanho do arquivo em unidades legíveis.

Abrindo, Exportando e Excluindo Arquivos

A interação com documentos é aprimorada com a capacidade de abri-los, exportá-los e excluí-los. Um comando "Abrir" na AppBar pode iniciar arquivos usando o aplicativo padrão do sistema. O Windows.System.Launcher.launchFileAsync é usado para este fim.

abrirDocumentoClick: function() {
    const lvControl = document.getElementById("lvBiblioteca").winControl;
    lvControl.selection.getItems()
        .then(itensSelecionados => {
            if (itensSelecionados && itensSelecionados[0] && itensSelecionados[0].data) {
                return Windows.System.Launcher.launchFileAsync(itensSelecionados[0].data);
            }
        })
        .catch(erro => {
            new Windows.UI.Popups.MessageDialog("Não foi possível abrir o arquivo.", "Erro ao Abrir").showAsync();
        });
}

Para exportar, um comando "Exportar" é adicionado à AppBar. O Windows.Storage.Pickers.FolderPicker permite que o usuário selecione uma pasta de destino, e então os arquivos selecionados são copiados utilizando copyAsync.

exportarDocumentosClick: function () {
    if (!this.podeAbrirSeletor()) { return; }

    const seletorPastas = new Windows.Storage.Pickers.FolderPicker();
    seletorPastas.suggestedStartLocation = idLocalPicker.desktop;
    seletorPastas.fileTypeFilter.replaceAll(["*"]);

    seletorPastas.pickSingleFolderAsync().then(pastaDestino => {
        if (pastaDestino) {
            return document.getElementById("lvBiblioteca").winControl.selection.getItems().then(itensSelecionados => {
                const promessasCopia = itensSelecionados.map(item =>
                    item.data.copyAsync(pastaDestino, item.data.name, opcoesColisao.generateUniqueName)
                );
                return WinJS.Promise.join(promessasCopia);
            });
        }
        return WinJS.Promise.as();
    }).then(() => {
        new Windows.UI.Popups.MessageDialog("Todos os arquivos foram exportados com sucesso.", "Exportação Concluída").showAsync();
    }).catch(erro => {
        new Windows.UI.Popups.MessageDialog("Não foi possível exportar todos os arquivos selecionados.", "Erro na Exportação").showAsync();
    });
}

A exclusão de arquivos, por ser uma operação permanente, deve ser precedida por uma confirmação. Um comando "Excluir" na AppBar, junto com um MessageDialog, pode gerenciar isso. O método deleteAsync do StorageFile realiza a exclusão.

excluirDocumentosClick: function () {
    const confirmarDialogo = new Windows.UI.Popups.MessageDialog(
        "Esta ação não pode ser desfeita. Deseja continuar?",
        "Excluir Arquivos Permanentemente"
    );

    const textoBotao = (document.getElementById("lvBiblioteca").winControl.selection.count() <= 1)
        ? "Sim, Excluir"
        : "Sim, Excluí-los";

    confirmarDialogo.commands.append(new Windows.UI.Popups.UICommand(textoBotao, comando => {
        document.getElementById("lvBiblioteca").winControl.selection.getItems().then(itensSelecionados => {
            const promessasExclusao = itensSelecionados.map(item => item.data.deleteAsync());
            return WinJS.Promise.join(promessasExclusao);
        }).then(() => {
            // A ListView se atualiza automaticamente devido ao StorageDataSource
        }).catch(erro => {
            new Windows.UI.Popups.MessageDialog("Não foi possível excluir os arquivos selecionados.", "Erro na Exclusão").showAsync();
        });
    }));

    confirmarDialogo.commands.append(new Windows.UI.Popups.UICommand("Não, Não Excluir", comando => { }));
    confirmarDialogo.defaultCommandIndex = 0;
    confirmarDialogo.cancelCommandIndex = 1;
    confirmarDialogo.showAsync();
}

Gerenciando Mudanças de Estado da Aplicação

Uma aplicação bem-sucedida deve se comportar de forma consistente e esperada pelo usuário em todas as situações. Isso envolve considerar o estado de ativação da aplicação e suas diferentes visualizações.

Estado de Ativação da Aplicação

A aplicação pode ser ativada de várias maneiras: clicando no tile da tela inicial, por meio da busca do Windows, compartilhamento de conteúdo, ou seletores de arquivos. O evento activated da aplicação, geralmente no default.js, lida com esses cenários.

var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;

app.addEventListener("activated", function (args) {
    if (args.detail.kind === activation.ActivationKind.launch) {
        // Lógica de lançamento normal
    }
    // Outros tipos de ativação serão tratados aqui
});

Extensão da Tela de Carregamento (Splash Screen)

Para garantir que a aplicação esteja pronta antes da interação do usuário, pode ser necessário estender a tela de carregamento padrão. Isso é útil para carregar dados, inicializar serviços ou processar grandes documentos.

Uma tela de carregamento estendida pode ser implementada com um div no default.html, configurado para imitar a tela de carregamento padrão e exibir um indicador de progresso.

<div id="splashEstendido" class="hidden">
    <img id="imagemSplash" src="/img/logo-splash.png" />
    <progress id="progressoSplash" style="color: white;"></progress>
</div>

Uma classe controladora, como SplashScreenManager, pode gerenciar a exibição, ocultação e posicionamento dessa tela estendida.

// Em um arquivo como js/splashScreenManager.js
(function () {
    "use strict";

    const SplashScreenManagerClass = WinJS.Class.define(
        function constructor(elementoSplash, splashPadrao, funcaoCarregamentoAssincrona) {
            this._elementoSplash = elementoSplash;
            this._splashPadrao = splashPadrao;
            this._funcaoCarregamentoAssincrona = funcaoCarregamentoAssincrona;

            this._splashPadrao.ondismissed = this.onSplashDismissed.bind(this);
            this.show();
        },
        {
            onSplashDismissed: function () {
                WinJS.Promise.as(this._funcaoCarregamentoAssincrona())
                    .done(this.hide.bind(this));
            },

            show: function () {
                this.updatePosition();
                WinJS.Utilities.removeClass(this._elementoSplash, "hidden");
            },

            hide: function () {
                if (!WinJS.Utilities.hasClass(this._elementoSplash, "hidden")) {
                    WinJS.Utilities.addClass(this._elementoSplash, "hidden");
                }
            },

            updatePosition: function () {
                const locImagem = this._splashPadrao.imageLocation;
                const splashImage = this._elementoSplash.querySelector("#imagemSplash");
                splashImage.style.top = locImagem.y + "px";
                splashImage.style.left = locImagem.x + "px";
                splashImage.style.height = locImagem.height + "px";
                splashImage.style.width = locImagem.width + "px";

                const splashProgress = this._elementoSplash.querySelector("#progressoSplash");
                splashProgress.style.marginTop = (locImagem.y + locImagem.height) + "px";
            }
        }
    );

    WinJS.Namespace.define("MinhaApp.UI", {
        SplashScreenManager: SplashScreenManagerClass,
    });
})();

// Em default.js, no listener 'activated'
app.addEventListener("activated", function (args) {
    if (args.detail.kind === activation.ActivationKind.launch) {
        new MinhaApp.UI.SplashScreenManager(
            document.getElementById("splashEstendido"),
            args.detail.splashScreen,
            function () {
                // Simula um carregamento demorado, remover em produção
                return new WinJS.Promise(function(c) { setTimeout(c, 2000); })
                    .then(MinhaApp.Data.Storage.initialize); // Sua função real de inicialização
            }
        );
        // A navegação da aplicação é feita APÓS o carregamento da splash screen estendida
        args.setPromise(WinJS.UI.processAll().then(function () {
            // ... lógica de navegação ...
        }));
    }
});

É crucial organizar a ordem dos scripts no default.html para que as classes sejam definidas antes de serem usadas.

Estado de Execução Anterior

O previousExecutionState dos argumentos de ativação indica como a aplicação estava antes de ser ativada (notRunning, terminated, closedByUser, running, suspended). Isso permite personalizar o comportamento de inicialização, por exemplo, restaurando o estado da sessão se a aplicação foi terminada ou suspensa, ou apresentando a tela inicial se foi um novo lançamento.

var handleAppLaunch = function (args) {
    if (args.detail.previousExecutionState === activation.ApplicationExecutionState.terminated ||
        args.detail.previousExecutionState === activation.ApplicationExecutionState.suspended) {
        // Restaurar estado da aplicação a partir da sessão salva
        // Exemplo: nav.navigate(app.sessionState.history.current.location, app.sessionState.history.current.state);
    } else {
        // Lançamento novo ou reativação de um estado não-terminado/suspenso.
        // Inicializar aplicação aqui.
    }
    // ... restante da lógica de inicialização e navegação ...
};

Suspensão da Aplicação

O Windows suspende aplicações inativas para economizar recursos. Se uma aplicação suspensa precisar de mais recursos, o Windows pode terminá-la. Para uma experiência de usuário fluida, a aplicação deve salvar seu estado no evento WinJS.Application.oncheckpoint, permitindo a restauração perfeita se for terminada.

app.addEventListener("checkpoint", function (args) {
    // Salvar o estado da sessão aqui
    // Exemplo: app.sessionState.history = nav.history;
});

Estado de Visualização da Aplicação

As aplicações Windows podem ser exibidas em diferentes estados de visualização: paisagem (fullscreen-landscape), retrato (fullscreen-portrait), encaixado (snapped) ou preenchido (filled). A aplicação deve adaptar seu layout e funcionalidade a esses estados para uma ótima experiência de usuário.

Atualizando Layout com CSS Media Queries

Para mudanças de layout puramente visuais, CSS Media Queries são a ferramenta ideal. Elas permitem aplicar regras de estilo apenas quando certas condições de visualização são atendidas.

/* home.css */
@media screen and (-ms-view-state: fullscreen-portrait) {
    .dashboard-main-section {
        margin-left: 100px;
        display: block;
    }
    .dashboard-main-section #painelDireito {
        display: none; /* Oculta um painel em retrato */
    }
    .main-menu {
        height: 424px;
        -ms-flex-direction: column; /* Altera a direção do flexbox */
    }
}

Atualizando Layout com JavaScript

Para mudanças mais dinâmicas ou funcionais, JavaScript pode ser necessário. Por exemplo, alterar o layout de um ListView de GridLayout para ListLayout quando a aplicação está em modo encaixado.

// Em pages/projetos/lista.js
configureListLayout: function () {
    const estadoView = Windows.UI.ViewManagement.ApplicationView.value;
    const lvControl = document.getElementById("lvProjetos").winControl;
    const semanticZoomControl = document.getElementById("zoomSemantico").winControl;

    if (estadoView === Windows.UI.ViewManagement.ApplicationViewState.snapped) {
        lvControl.layout = new WinJS.UI.ListLayout();
        semanticZoomControl.enableButton = false;
    } else {
        lvControl.layout = new WinJS.UI.GridLayout();
        semanticZoomControl.enableButton = true;
    }
},

updateLayout: function (element, viewState, lastViewState) {
    // Chamado quando o tamanho da tela muda
    this.configureListLayout();
}

Visualizações Não Suportadas

Em alguns casos, uma tela inteira ou a aplicação pode não ser funcional em uma visualização específica (como o modo encaixado). Nesses cenários, em vez de exibir um layout quebrado, é melhor informar o usuário de que a visualização não é suportada.

<div id="vistaNaoSuportada" class="hidden">
    <img id="imagemNaoSuportada" src="/img/app-logo.png" />
    <div>Esta tela não está disponível enquanto o aplicativo estiver encaixado.</div>
</div>

Uma função utilitária pode gerenciar a visibilidade dessa mensagem.

// Em js/utilities.js
MinhaApp.Utilities.handleSnappedView = function () {
    const estadoView = Windows.UI.ViewManagement.ApplicationView.value;
    const msgNaoSuportada = document.getElementById("vistaNaoSuportada");

    if (msgNaoSuportada) {
        if (estadoView === Windows.UI.ViewManagement.ApplicationViewState.snapped) {
            WinJS.Utilities.removeClass(msgNaoSuportada, "hidden");
        } else {
            WinJS.Utilities.addClass(msgNaoSuportada, "hidden");
        }
    }
};

// Em uma página de controle (ex: pages/rotas/rotas.js)
ready: function (element, options) {
    // ...
    MinhaApp.Utilities.handleSnappedView();
},

updateLayout: function (element, viewState, lastViewState) {
    MinhaApp.Utilities.handleSnappedView();
}

Bibliotecas Externas e Componentes WinRT

A incorporação de funcionalidades de bibliotecas externas é uma prática comum no desenvlovimento de software, seja para reutilizar código JavaScript ou para integrar componentes WinRT escritos em outras linguagens.

Bibliotecas JavaScript

Muitas bibliotecas JavaScript são compatíveis com aplicações Windows, mas é crucial considerar as implicações de segurança. Páginas em um contexto local (código da sua aplicação) têm acesso mais amplo aos recursos do sistema do que páginas em um contexto web (como um iframe). Scripts em contexto local têm restrições sobre como podem adicionar HTML dinamicamente, usando window.toStaticHTML para filtrar código potencialmente malicioso.

Para cenários legítimos onde o conteúdo é confiável, é possível contornar essas restrições usando funções como WinJS.Utilities.setInnerHTMLUnsafe ou MSApp.execUnsafeLocalFunction. No entanto, o uso deve ser com extrema cautela e apenas quando a segurança do código fonte for garantida.

Componentes WinRT

Componentes WinRT são DLLs que podem ser criadas em C#, VB ou C++ e usadas por qualquer aplicação Windows, incluindo as desenvolvidas com HTML e JavaScript. Eles são ideais para:

  • Acessar funcionalidades não disponíveis diretamente em JavaScript ou nas bibliotecas WinJS/WinRT.
  • Reutilizar código em múltiplas aplicações Windows ou Windows Phone.
  • Portar código existente de outras plataformas.

Um exemplo prático é a geração de GUIDs (Globally Unique Identifiers). JavaScript não possui uma função nativa para isso, mas C# sim. Podemos criar um componente WinRT para essa finalidade.

Exemplo: Componente WinRT para GUIDs em C#

Crie um novo projeto do tipo "Componente do Windows Runtime" no Visual Studio (por exemplo, AppUtils).

// Em AppUtils/GuidHelper.cs
namespace AppUtils
{
    public sealed class GuidHelper
    {
        public static string GenerateNewId()
        {
            return System.Guid.NewGuid().ToString();
        }

        public static bool IsValidGuid(string idToTest)
        {
            if (string.IsNullOrEmpty(idToTest))
            {
                return false;
            }
            System.Guid resultGuid;
            return System.Guid.TryParse(idToTest, out resultGuid);
        }
    }
}

Note que a classe e seus métodos públicos devem ser public sealed e usar tipos WinRT compatíveis (como string). Em JavaScript, os nomes de métodos serão acessados em camelCase (AppUtils.GuidHelper.generateNewId()).

Integrando o Componente no Projeto JavaScript

Adicione uma referência ao projeto AppUtils no seu projeto JavaScript. Depois, você pode usar as funções do componente para, por exemplo, gerar IDs para objetos como projetos e entradas de tempo.

// Em data/project.js
function constructor() {
    this.id = AppUtils.GuidHelper.generateNewId(); // Usando o componente WinRT
    this.name = "";
    // ...
}

createFromDeserialized: function (valor) {
    const projeto = new MinhaApp.Data.Project();
    projeto.id = (AppUtils.GuidHelper.isValidGuid(valor.id) && valor.id) || projeto.id;
    projeto.name = valor.name;
    // ...
    return projeto;
}

// Em data/timeEntry.js
function constructor() {
    this.id = AppUtils.GuidHelper.generateNewId();
    this._projectId = "";
    // ...
}

projectId: {
    get: function () { return this._projectId; },
    set: function (valor) {
        this._projectId = (AppUtils.GuidHelper.isValidGuid(valor) && valor) || this._projectId;
    }
}

Ao depurar, o Visual Studio permite escolher entre depurar apenas script (HTML/JavaScript) ou apenas código gerenciado (C#). Isso é configurado nas propriedades do projeto.

Para atualizações de aplicação que alteram o formato dos dados (como a mudança de IDs numéricos para GUIDs), é essencial implementar um mecanismo de migração de dados no evento onupgradeneeded do IndexedDB, incrementando a versão do banco de dados e convertendo os dados existentes para o novo formato.

Contratos de Busca e Compartilhamento

Os contratos do Windows 8 permitem que aplicações interajam mais profundamente com o sistema operacional e entre si, proporcionando uma experiência de usuário consistente e integrada.

Contrato de Busca

O contrato de busca permite que a aplicação integre-se à interface de busca universal do Windows, acessível pela charm de Busca (Windows + C, ou Windows + Q). Isso envolve:

  • Declarar o suporte à busca no package.appxmanifest.
  • Lidar com o evento activated quando o ActivationKind é search.
  • Criar uma tela de resultados de busca.
  • Implementar a lógica para buscar dados da aplicação.

Adicionar um item "Contrato de Busca" ao projeto no Visual Studio gera a maioria dos arquivos necessários (HTML, CSS, JS para a tela de resultados) e atualiza o manifesto.

<Extensions>
    <Extension Category="windows.search" />
</Extensions>

A lógica de busca precisa ser implementada no seu modelo de dados. Por exemplo, uma função searchProjects pode filtrar projetos por nome, cliente ou número:

// Em data/storage.js
MinhaApp.Data.Storage.projects.searchProjects = function (textoConsulta) {
    return this.createFiltered(function (p) {
        if (p.status === MinhaApp.Data.ProjectStatuses.Deleted) return false;
        if (!textoConsulta) return false;

        const termo = textoConsulta.toUpperCase();
        return (p.name.toUpperCase().indexOf(termo) >= 0) ||
               (p.clientName.toUpperCase().indexOf(termo) >= 0) ||
               (p.projectNumber.toUpperCase().indexOf(termo) >= 0);
    }).createSorted(MinhaApp.Data.Storage.compareProjects);
};

Na tela de resultados de busca (searchResults.js), a função _searchData chamará sua lógica de busca, e _itemInvoked navegará para os detalhes do item selecionado. Um conversor _markText pode ser usado para destacar os termos de busca nos resultados.

_searchData: function (queryText) {
    return MinhaApp.Data.Storage.projects.searchProjects(queryText);
},

_itemInvoked: function (args) {
    args.detail.itemPromise.done(function itemInvoked(item) {
        WinJS.Navigation.navigate("/pages/projetos/detalhe.html", { id: item.data.id });
    });
},

_markText: function (text) {
    // Destaca o termo de busca (case-insensitive)
    return text.replace(new RegExp(this._lastSearch, "i"), function (match) {
        return "<mark>" + match + "</mark>";
    });
}

O manipulador de ativação em default.js deve ser modificado para verificar ActivationKind.search e navegar para a tela de resultados de busca, ou para a tela inicial dependendo do previousExecutionState e se o queryText está vazio.

Contrato de Compartilhamento

O contrato de compartilhamento permite que aplicações troquem dados através da interface de compartilhamento do Windows (charm de Compartilhamento, ou Windows + H).

Compartilhamento como Destino (Share Target)

Como destino, sua aplicação pode receber dados de outras. Isso envolve:

  • Adicionar o contrato de compartilhamento como um item de projeto no Visual Studio.
  • Declarar suporte a tipos de dados específicos (ex: arquivos) no package.appxmanifest (usando <SupportedFileTypes><FileType>*</FileType></SupportedFileTypes> para qualquer arquivo).
  • Lidar com a ativação ActivationKind.shareTarget no seu shareTarget.js.

A tela de destino de compartilhamento (shareTarget.html, shareTarget.js) exibirá os detalhes do item compartilhado e permitirá ao usuário interagir com eles (ex: selecionar um projeto para adicionar os arquivos).

// Em shareTarget.js
var shareOperation; // Variável global para a operação de compartilhamento

app.onactivated = function (args) {
    if (args.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.shareTarget) {
        WinJS.Application.addEventListener("shareReady", handleShareActivation, false);
        WinJS.Application.queueEvent({ type: "shareReady", detail: args.detail });
    }
};

var handleShareActivation = function (args) {
    shareOperation = args.detail.shareOperation;
    // ... Lógica para exibir título, descrição, miniaturas e nomes de arquivos
    if (shareOperation.data.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.storageItems)) {
        shareOperation.data.getStorageItemsAsync().done(function (files) {
            // ... Exibir lista de nomes de arquivos ...
        });
    }
    // ... Lógica para preencher uma lista de projetos e habilitar o botão de Compartilhar
    document.querySelector(".submitbutton").onclick = submitSharedFiles;
};

function submitSharedFiles() {
    shareOperation.reportStarted();
    // ... Lógica para obter a pasta do projeto selecionado e copiar os arquivos
    const selectedProjectId = document.getElementById("projetoSelecionado").value;
    getProjectFolder(selectedProjectId).then(projectFolder => {
        shareOperation.data.getStorageItemsAsync().then(files => {
            const copyPromises = files.map(file =>
                file.copyAsync(projectFolder, file.name, Windows.Storage.CreationCollisionOption.replaceExisting)
            );
            return WinJS.Promise.join(copyPromises);
        }).then(() => {
            shareOperation.reportCompleted(); // Fechar a tela de compartilhamento
        }).catch(error => {
            shareOperation.reportError("Falha ao salvar arquivos.");
        });
    });
}

Compartilhamento como Fonte (Share Source)

Como fonte, sua aplicação pode enviar dados para outras. Adicione um botão "Compartilhar" na AppBar da tela onde os itens podem ser compartilhados (ex: biblioteca de documentos).

<button
    data-win-control="WinJS.UI.AppBarCommand"
    data-win-options="{
        id:'cmdCompartilharDocumentos',
        label:'Compartilhar',
        icon:'share',
        section:'selection',
        tooltip:'Compartilhar',
        disabled: true}">
</button>

No JavaScript, o clique neste botão invoca Windows.ApplicationModel.DataTransfer.DataTransferManager.showShareUI(). O DataTransferManager dispara o evento datarequested, onde a aplicação fornece os dados a serem compartilhados.

// Em pages/documentos/biblioteca.js
const dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager;

ready: function (element, options) {
    // ...
    const transferMgr = dataTransferManager.getForCurrentView();
    this.boundDataRequestedHandler = this.onDataRequested.bind(this);
    transferMgr.addEventListener("datarequested", this.boundDataRequestedHandler);
},

unload: function () {
    const transferMgr = dataTransferManager.getForCurrentView();
    transferMgr.removeEventListener("datarequested", this.boundDataRequestedHandler);
},

compartilharDocumentosClick: function () {
    dataTransferManager.showShareUI();
},

onDataRequested: function (e) {
    const request = e.request;
    const lvControl = document.getElementById("lvBiblioteca").winControl;
    const selectionCount = lvControl.selection.count();

    if (selectionCount <= 0) {
        request.failWithDisplayText("Selecione um ou mais documentos para compartilhar.");
        return;
    }

    lvControl.selection.getItems().then(selectedItems => {
        const projetoAtual = MinhaApp.Data.Storage.projects.getById(this.idProjeto);
        request.data.properties.title = `Documentos do projeto ${projetoAtual.name}`;
        request.data.properties.description = `${selectionCount} arquivo(s) de ${projetoAtual.name}`;

        const filesToShare = selectedItems.map(item => item.data);
        request.data.setStorageItems(filesToShare);

        // Se for uma única imagem, pode-se definir também como bitmap
        if (selectionCount === 1 && isTipoImagem(filesToShare[0].fileType)) {
             const streamRef = Windows.Storage.Streams.RandomAccessStreamReference;
             const stream = streamRef.createFromFile(filesToShare[0]);
             request.data.properties.thumbnail = stream;
             request.data.setBitmap(stream);
        }
    }).catch(error => {
        request.failWithDisplayText("Erro ao preparar arquivos para compartilhamento.");
    });
}

Outros Conceitos de Compartilhamento

Além dos contratos de busca e compartilhamento, o Windows oferece outras formas de interação de dados:

  • Seletores de Arquivo (File Pickers): Permitem que os usuários selecionem arquivos para abrir ou um local para salvar, integrando-se com outras aplicações (ex: SkyDrive, Fotos).
  • Copiar e Colar: Operações de copiar e colar para texto, imagens ou outros dados podem ser programaticamente gerenciadas através da API da área de transferência (DataPackage), facilitando a transferência de dados para e de sua aplicação.

Tags: WindowsStoreApps javascript WinJS html5 WindowsStorage

Publicado em 10-9 12:44