Hamburger Menu – Windows 10 [UWP]

Contenidos recomendados:

splitview

El famoso Hamburger Menu trajo muchas facilidades con la llegada del SDK de Windows 10, pero la verdad es que en un inicio no es del todo clara su implementacion. No tenemos un elemento predefinido para utilizarlo, esto es una dificultad a la hora de comprenderlo porque podemos encontrar distintas formas, pero a la vez es una gran ventaja en cuanto a flexibilidad permitiendo construirlo a nuestro gusto y adecuandose a cualquier necesidad.

También es un poco confuso utilizarlo si tenemos un proyecto con MVVM y no estamos muy familiarizados con la arquitectura.

Quiero aclarar que cada uno tiene sus formas de diseñar el Hamburguer Menu y todas son correctas.

 

Preparemos algunas cosas

Comenzamos preparando el proyecto para trabajar con MVVM. Creamos Models, ViewModels, Views.

ExploradorDeSolucionMVVM

Lo siguiente es la creación de las Views y sus respectivos ViewModels con la correcta implementación en ViewModelLocator  y heredando de PageBase y ViewModelBase. Para este caso yo voy a crear 4 paginas.

CreacionDePaginasMVVM

Luego vamos a crear una carpeta Styles y dentro un ResourceDictionary con los siguientes estilos.

<SolidColorBrush x:Key="NavButtonPressedBackgroundBrush" Color="White" Opacity="0.3" />
<SolidColorBrush x:Key="NavButtonCheckedBackgroundBrush" Color="White" Opacity="0.2" />
<SolidColorBrush x:Key="NavButtonHoverBackgroundBrush" Color="White" Opacity="0.1" />

<Style x:Key="HamburgerRadioButtonStyle" TargetType="RadioButton">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="Padding" Value="3"/>
    <Setter Property="HorizontalAlignment" Value="Stretch"/>
    <Setter Property="VerticalAlignment" Value="Center"/>
    <Setter Property="HorizontalContentAlignment" Value="Left"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="RadioButton">
                <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="PointerOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BackgroundGrid">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource NavButtonHoverBackgroundBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BackgroundGrid">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource NavButtonPressedBackgroundBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled" />
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="CheckStates">
                            <VisualState x:Name="Checked">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BackgroundGrid">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource NavButtonCheckedBackgroundBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unchecked"/>
                            <VisualState x:Name="Indeterminate"/>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused"/>
                            <VisualState x:Name="Unfocused"/>
                            <VisualState x:Name="PointerFocused"/>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Grid Name="BackgroundGrid" Background="Transparent" Margin="0">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="48"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock FontSize="16" Height="16" Text="{TemplateBinding Tag}" FontFamily="Segoe MDL2 Assets" Margin="0,16" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White"/>
                        <ContentPresenter x:Name="ContentPresenter" FontSize="16" AutomationProperties.AccessibilityView="Raw" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" Grid.Column="1" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" TextWrapping="Wrap" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Foreground="White" />
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Vamos a crear la clase PageType en Models.

public class PageType
{
    public String Name { get; set; }
    public String Icon { get; set; }
    public Type Type { get; set; }
    public Type NavigateType { get; set; }
}

 
 

Conozcamos un poco a SplitView

El SplitView es un control que nos va a permitir diseñar el Hamburger Menu en nuestras aplicaciones UWP. Aclaro que hay diferentes formas de implementar el Hamburger Menu algunas mas fáciles que otras pero todas son correctas dependiendo de lo que necesitemos. La que voy a mostrar en esta publicación es la que utilizo y que me resulta mas comoda para trabajar con MVVM.

El elemento principal para implementar el Hamburger Menu es el SplitView. Dentro de las propiedades que mas nos interesan se encuentran las siguientes.

Pane : Es un Panel que se despliega del lateral.

Content : Es el contenido que se mostrara en el SplitView. Puede varior dependiendo de lo seleccionado en el Pane.

PanePlacement : Permite establecer de que lateral se desplegará el Panel.

OpenPaneLength : Permite establecer el tamaño del Panel.

IsPaneOpen : Indica si Pane se encuentra desplegado.

 

Con esto aprendido ahora podemos definir la estructura que tendrá el SplitView.

<SplitView>
    <SplitView.Pane>

    </SplitView.Pane>
    <SplitView.Content>
        <Frame/>
    </SplitView.Content>
</SplitView>

 
 

Y AHORA SI, A CODEAR!

Vamos a dejar para lo ultimo la parte de las vistas y del SplitView y vamos a comenzar por el code-behind.

 

ViewModels

En el MainViewModel vamos a crear las siguientes propiedades:
PageName: Sera el nombre de la pagina que se encuentre abierta en ese momento.
IsOpen: Indica si el Pane se encuentra abierto o cerrado.
CurrentPage: Establece la pagina que se mostrara en el Content del SplitView.
MenuList: Lista de elementos PageType que se mostrara en la parte superior de Pane.
SecondMenuList: Lista de elementos PageType que se mostrara en la parte inferior de Pane.

private string _pageName;
public string PageName
{
    get { return _pageName; }
    set
    {
        _pageName = value;
        RaisePropertyChanged();
    }
}

private bool _isOpen = false;
public bool IsOpen
{
    get { return _isOpen; }
    set
    {
        _isOpen = value;
        RaisePropertyChanged();
    }
}

private Type _currentPage = typeof(Pagina1);
public Type CurrentPage
{
    get { return _currentPage; }
    set
    {
        _currentPage = value;
        RaisePropertyChanged();
    }
}

private ObservableCollection<PageType> _menuList;
public ObservableCollection<PageType> MenuList
{
    get { return _menuList; }
    set
    {
        _menuList = value;
        RaisePropertyChanged();
    }
}

private ObservableCollection<PageType> _secondMenuList;
public ObservableCollection<PageType> SecondMenuList
{
    get { return _secondMenuList; }
    set
    {
        _secondMenuList = value;
        RaisePropertyChanged();
    }
}

Lo siguiente que vamos a hacer también en MainPageViewModel es crear los siguientes métodos y comandos:
LoadMenu: Metodo que carga MenuList y SecondMenuList con los elementos PageType que queremos mostrar en el Pane.
NavigatePageCommand: Ejecuta la lógica para intercambiar el Content del SplitView dependiendo del elemento seleccionado.
CheckedRadioButtonPaneCommand: Cambia el estado IsOpen para abrir y cerrar Pane.

private void LoadMenu()
{
    MenuList = new ObservableCollection<PageType>();
    MenuList.Add(new PageType() { Name = "Pagina 1", Type = typeof(Pagina1), Icon = "[SIMBOLO]" });
    MenuList.Add(new PageType() { Name = "Pagina 2", Type = typeof(Pagina2), Icon = "[SIMBOLO]" });
    MenuList.Add(new PageType() { Name = "Pagina 3", Type = typeof(Pagina3), Icon = "[SIMBOLO]" });

    SecondMenuList = new ObservableCollection<PageType>();
    SecondMenuList.Add(new PageType() { Name = "Pagina 4", Type = typeof(Pagina4), Icon = "[SIMBOLO]" });
}

private DelegateCommand<PageType> _navigatePageCommand;
public ICommand NavigatePageCommand
{
    get { return _navigatePageCommand = _navigatePageCommand ?? new DelegateCommand<Page>(NavigatePageCommandExecute); }
}
private void NavigatePageCommandExecute(PageType pagetype)
{
    CurrentPage = pagetype.Type;
    PageName = pagetype.Name;
    IsOpen = false;
}


private DelegateCommand _checkedRadioButtonPaneCommand;
public ICommand CheckedRadioButtonPaneCommand
{
    get { return _checkedRadioButtonPaneCommand = _checkedRadioButtonPaneCommand ?? new DelegateCommand(CheckedRadioButtonPaneCommandExecute); }
}
private void CheckedRadioButtonPaneCommandExecute()
{
    IsOpen = !IsOpen;
}

 

En LoadMenu, las propiedades Icon las definimos con [SIMBOLO], en realidad hay que remplazarlos por caracteres de la fuente Segoe MDL2 Assets que se agregaron con la llegada del SDK de UWP para facilitar las cosas. Esta fuente provee caracteres que nos permiten agregar distintos iconos dentro de nuestra aplicación. Los podemos encontrar abriendo Mapa de caracteres y seleccionando la fuente Segoe MDL2 Assets.

Captura
chara

 

Lo último que nos queda hacer en MainPageViewModel dentro de OnNavitatedTo es cargar las listas de elementos con LoadMenu().

public override Task OnNavigatedTo(NavigationEventArgs args)
{
    LoadMenu();
    return null;
}

 
 

Views

Comenzamos colocando un RowDefinitions en el Grid principal de MainPage.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="48"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

</Grid>

 
 

Grid.Row 0titlepanel

En la row 0 se muestra un panel desde el cual aparece un RadioButton y un TextBlock.
El RadioButton nos permitirá abrir y cerrar el Pane del SplitView con el commando CheckedRadioButtonPaneCommand. Además el RadioButton tiene el Style HamburgerRadioButtonStyle que definimos al principio en el ResourceDictionary. En donde dice [SIMBOLO] va el símbolo del Menu que podemos encontrar con la herramienta antes presentada de Mapa de Caracteres.
El TextBlock mostrara el titulo de la pagina bindeada a PageName.

<RelativePanel x:Name="TitleBar" Background="#FF1F1F1F" Grid.Row="0" RequestedTheme="Dark">

    <Grid Width="48" x:Name="HamburgerMenuButton">
        <RadioButton Command="{Binding CheckedRadioButtonPaneCommand}" Style="{StaticResource HamburgerRadioButtonStyle}" GroupName="HamburgerMenuButton" Tag="[SIMBOLO]"/>
    </Grid>

    <TextBlock RelativePanel.RightOf="HamburgerMenuButton" RelativePanel.AlignVerticalCenterWith="HamburgerMenuButton" Text="{Binding PageName}" Margin="5"/>
</RelativePanel>

 
 

Grid.Row 1

En la row 2 aparece el famoso SplitView. En MainPage Establecemos El SplitView con la estructura principal de Pane y Content que anteriormente mencione.

<SplitView x:Name="splitView" Grid.Row="1" IsPaneOpen="{Binding IsOpen, Mode=TwoWay}">
    <SplitView.Pane>

    </SplitView.Pane>

    <SplitView.Content>

    </SplitView.Content>
</SplitView>

 
 

Content

content
En Content mostramos el contenido del SplitView y es acá donde se mostraran las distintas paginas mediante un Frame. La propiedad SourcePageType del Frame esta bindeada con CurrentPage de MainPageViewModel para permitir cambiar de pagina en el ViewModel.

<SplitView.Content>
    <Frame SourcePageType="{Binding CurrentPage, Mode=TwoWay}"/>
</SplitView.Content>

 
 

Pane

pane
En pane van a estar las dos listas con los elementos PageType que cree y cargue en el MainPageViewModel. Estarán dentro de un ItemControl con RadioButtons que aplican el estilo HamburguerRadioButtonStyle que creamos en el diccionario de recursos. Además con NavigatePageCommand pasando como parámetro del Type al hacer tap sobre el RadioButton cambiara de pagina con CurrentPage.

<SplitView.Pane>
    <Grid Background="Black">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <ItemsControl ItemsSource="{Binding MenuList}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <RadioButton x:Name="Pagina1RadioButton" Command="{Binding DataContext.NavigatePageCommand , ElementName=splitView}" CommandParameter="{Binding }" Style="{StaticResource HamburgerRadioButtonStyle}" Content="{Binding Name}" GroupName="HamburgerMenu" Tag="{Binding Icon}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>


        <ItemsControl ItemsSource="{Binding SecondMenuList}" Grid.Row="1">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <RadioButton x:Name="Pagina1RadioButton" Command="{Binding DataContext.NavigatePageCommand , ElementName=splitView}" CommandParameter="{Binding }" Style="{StaticResource HamburgerRadioButtonStyle}" Content="{Binding Name}" GroupName="HamburgerMenu" Tag="{Binding Icon}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</SplitView.Pane>

En el resto de las Views Simplemente les asignamos un color de fondo y un TextBlock centrado para identificar cuando cambiamos de pagina.

Y Listo ya tenemos un HamburgerMenu lista para trabajar con MVVM!.

final image

Descargar código

 

Anuncios

Archivos y configuraciones de aplicación – Windows 10 [UWP]

Contenidos recomendados:

 

Entre los distintos datos que puede manejar las aplicaciones que desarrollamos se encuentran los datos de configuraciones y archivos de la aplicación. Para guardar este tipo de datos no necesitamos recurrir a ningún método de almacenamiento mas que el que nos provee UWP. Hay distintos tipos de datos de la aplicación, algunos van de la mano junto con la aplicación por lo que si desinstalamos la aplicación, los datos se pierden. Otros se mantienen a través de nuestros dispositivos mediante una cuenta de usuario. Otros se almacenan de forma temporal. Es importante entender esto para no guardar incorrectamente datos y tener una buena administración de estos en la aplicación.

Cada aplicación tiene reservado un espacio para guardar sus datos en el sistema, y los datos se guardan de forma segura separados de los de otra aplicación o usuario.

Los datos de la aplicación se dividen en dos:

  • Configuraciones: Guardar los datos de los tipos tipos de datos UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Boolean, Char16, String, DateTime, TimeSpan, GUID, Point, Size, Rect.
  • Archivos: Guardar datos binarios o archivos personalizados de datos serializados.

Los datos de la aplicación, tanto configuraciones como archivos, se pueden guardar de tres formas:

  • Local: Ideal para datos que deban conservarse solo en una sesión y en un dispositivo. Al desinstalar la aplicación estos datos se pierden. Usa el almacén de datos de aplicación locales para los datos que no tenga sentido mover y para grandes conjuntos de datos.
  • Móvil: Ideal para datos de usuario y configuraciones. Los datos pueden estar presentes en distintos dispositivos a través de una cuenta de usuario. Si se desinstala la aplicación los datos no se pierden porque están en la nube vinculados al usuario.
  • Temporal: Como su nombre lo indica, nos pueden resultar útiles para conservar información temporal durante una sesion. No hay garantía de la persistencia de estos datos hasta el final de la sesion. El sistema en cualquier momento, si necesita liberar espacio, eliminará estos datos temporales por lo que hay que tener en cuanta esto a la hora de utilizarlos.

 

Solo podremos sincronizar los datos si ambos dispositivos poseen la cuenta de desarrollador activa y registrada en los dispositivos. De no ser asi al intentar acceder a los datos obtendremos un mensaje de error que nos notificara el problema.

 

Configuraciones de aplicación

Las configuraciones pueden almacenarse de forma simple, por lo tanto se guarda dentro del almacén de datos identificándose con un nombre que nosotros le asignamos.

También podemos almacenar las configuraciones de forma compuesta, esto quiere decir que guardamos un conjunto de configuraciones dentro de un objeto de tipo ApplicationDataCompositeValue que se serializa y deserializa automáticamente.

Utiliza las configuraciones compuestas para manejar fácilmente las actualizaciones atómicas de configuración interdependientes. Las configuraciones compuestas están optimizados para pequeñas cantidades de datos, y el rendimiento puede ser pobre si se utilizan para grandes conjuntos de datos.

Para crear un ApplicationDataCompositeValue lo hacemos como en el siguiente ejemplo:

ApplicationDataCompositeValue composite = new ApplicationDataCompositeValue();
composite["intValue"] = 1;
composite["boolValue"] = true;

Para empezar a trabajar con las configuraciones de aplicación de cualquier tipo (locales y móviles) lo que debemos hacer es recuperar el contenedor de datos de la aplicación como un ApplicationDataContainer. Luego con el contenedor recuperado, tendremos acceso para crear, actualizar o recuperar las configuraciones.

Configuraciones locales

Recuperar el contenedor de datos locales de la aplicación

ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;

 

  • Configuraciones locales simples

Recuperar configuraciones simples.

public object GetSimpleLocalAppData(string key)
{
    return localSettings.Values[key];
}

Crear o actualizar configuraciones simples.

public void SetSimpleLocalAppData(string key, object value)
{
    localSettings.Values[key] = value;
}

 

  • Configuraciones locales compuestas

Recuperar configuraciones compuestas.

public object GetCompositeLocalAppData(string compositeName, string key)
{
    ApplicationDataCompositeValue composite = (ApplicationDataCompositeValue)localSettings.Values[compositeName];

    if (composite == null)
    {
        return null;
    }
    else
    {
        return composite[key];
    }
}

Crear o actualizar configuraciones compuestas.

public void SetCompositeLocalAppData(ApplicationDataCompositeValue composite)
{
    localSettings.Values["exampleCompositeSetting"] = composite;
}

 

Configuraciones móviles

Recuperar  contenedor de datos móviles de la aplicación.

ApplicationDataContainer roamingSettings = ApplicationData.Current.RoamingSettings;

 

  • Configuraciones móviles simples

Recuperar configuraciones simples.

public object GetSimpleRoamingAppData(string key)
{
    return roamingSettings.Values[key];
}

Crear o actualizar configuraciones simples.

public void SetSimpleRoamingAppData(string key, object value)
{
    roamingSettings.Values[key] = value;
}

 

  • Configuraciones móviles compuestas

Recuperar configuraciones compuestas.

public object GetCompositeRoamingAppData(string compositeName, string key)
{
    ApplicationDataCompositeValue composite = (ApplicationDataCompositeValue)roamingSettings.Values[compositeName];

    if (composite == null)
    {
        return null;
    }
    else
    {
        return composite[key];
    }
}

Crear o actualizar configuraciones compuestas.

public void SetCompositeRoamingAppData(string compositeName, ApplicationDataCompositeValue composite)
{
    roamingSettings.Values[compositeName] = composite;
}

 

 

Archivos de aplicación

Los archivos de aplicación también podemos encontrarlos como archivos locales, móviles y temporales. Los archivos de aplicación que vayamos creando se almacenan en un StorageFolder que contendra los distintos archivos de la aplicacion como StorageFiles.

Lo primero que debemos hacer es recuperar la carpeta de archivos de aplicacion como  StorageFolder para empezar a trabajar con lo archivos. Notaran que el manejo de archivos locales, móviles o temporales son prácticamente lo mismo solo que cambia tipo y nombre del StorageFolder.

 

Archivos locales

Recuperar la carpeta de archivos locales de aplicación.

StorageFolder localFolder = ApplicationData.Current.LocalFolder;

 

Recuperar archivos locales.

public async Task<StorageFile> GetLocalFileAppData(string key)
{
    try
    {
        StorageFile file = await localFolder.GetFileAsync(key);
        return file;
    }
    catch (Exception)
    {
        return null;
    }
}

Crear o actualizar archivos temporales.

public async void SetLocalFileAppData(string key, StorageFile value)
{
    StorageFile file = await localFolder.CreateFileAsync(key, CreationCollisionOption.ReplaceExisting);
    byte[] fileBytes;

    using (var stream = await file.OpenReadAsync())
    {
        fileBytes = new byte[stream.Size];
        using (var reader = new DataReader(stream))
        {
            await reader.LoadAsync((uint)stream.Size);
            reader.ReadBytes(fileBytes);
        }
    }
    await FileIO.WriteBytesAsync(file, fileBytes);
}

 

Archivos móviles

Recuperar la carpeta de archivos móviles de aplicación.

StorageFolder roamingFolder = ApplicationData.Current.RoamingFolder;

Recuperar archivos móviles.

public async Task<StorageFile> GetRoamingFileAppData(string key)
{
    try
    {
        StorageFile file = await roamingFolder.GetFileAsync(key);
        return file;
    }
    catch (Exception)
    {
        return null;
    }
}

Crear o actualizar archivos temporales.

public async void SetRoamingFileAppData(string key, StorageFile value)
{
    StorageFile file = await roamingFolder.CreateFileAsync(key, CreationCollisionOption.ReplaceExisting);
    byte[] fileBytes;

    using (var stream = await file.OpenReadAsync())
    {
        fileBytes = new byte[stream.Size];
        using (var reader = new DataReader(stream))
        {
            await reader.LoadAsync((uint)stream.Size);
            reader.ReadBytes(fileBytes);
        }
    }
    await FileIO.WriteBytesAsync(file, fileBytes);
}

 

Archivos temporales

Recuperar la carpeta de archivos temporales de aplicación.

StorageFolder temporaryFolder = ApplicationData.Current.TemporaryFolder;

 

Recuperar archivos temporales.

public async Task<StorageFile> GetTemporaryFileAppData(string key)
{
    try
    {
        StorageFile file = await temporaryFolder.GetFileAsync(key);
        return file;
    }
    catch (Exception)
    {
        return null;
    }
}

Crear o actualizar archivos temporales.

public async void SetTemporaryFileAppData(string key, StorageFile value)
{
    StorageFile file = await temporaryFolder.CreateFileAsync(key, CreationCollisionOption.ReplaceExisting);
    byte[] fileBytes;

    using (var stream = await file.OpenReadAsync())
    {
        fileBytes = new byte[stream.Size];
        using (var reader = new DataReader(stream))
        {
            await reader.LoadAsync((uint)stream.Size);
            reader.ReadBytes(fileBytes);
        }
    }
    await FileIO.WriteBytesAsync(file, fileBytes);
}

 

Mas sobre datos móviles

Si estamos utilizando los datos móviles, estos estarán presentes en cualquier dispositivo que este vinculado a nuestro usuario.
Esto mantendrá las configuraciones sincronizadas en la aplicación en distintos dispositivos, reduciendo la cantidad de trabajo que el usuario debe hacer. Los datos móviles también son útiles cuando el usuario quiere continuar una tarea en otro dispositivo. El Sistema operativo se encarga de replicar los datos móviles a la nube cuando los actualizamos, y sincroniza los datos en los otros dispositivos que tienen la aplicación instalada.

Sin embargo tenemos un limite para la cantidad de datos móviles que podemos almacenar por aplicación. Para saber ese limite podemos consultarlo mediante ApplicationData.RoamingStorageQuota. Cuando superemos ese limite los datos dejaran de replicarse en la nube por lo que debemos tener un uso con conciencia de los datos móviles de nuestra aplicación.

Registrarse para saber cuando cambia un dato móvil

Nos suscribimos al evento DataChanged y establecemos la lógica que actualizara los datos cuando la aplicacion sea notificada que se replico un cambio en la nube.

public MainPageViewModel()
{
    ApplicationData.Current.DataChanged += new TypedEventHandler<ApplicationData, object>(DataChangeHandler);
}
void DataChangeHandler(ApplicationData appData, object o)
{
    //Actualizar los datos de la aplicación
}

 

Organizar los datos en contenedores

Como vimos antes para trabajar con nuestros datos lo primero que hacemos es traer el almacén de datos de la aplicación de tipo ApplicationDataContainer. Sin embargo si queremos disponer de muchos datos en el contenedor principal puede resultar poco organizado. Para ello tenemos la posibilidad de crear subcontenedores dentro del contenedor principal de hasta 32 subniveles.

Crear contenedor

ApplicationDataContainer nuevoContenedor = localSettings.CreateContainer("nuevoContenedor", ApplicationDataCreateDisposition.Always);

Recuperar datos de un contendor

if(localSettings.Containers.ContainsKey("nuevoContenedor"))
{
    localSettings.Containers["nuevoContenedor"].Values["nombreDeDato"] = "ValorDeDato";
}

 

Eliminar configuraciones y contenedores

Eliminar configuraciones

public void RemoveSimpleLocalAppData()
{
    localSettings.Values.Remove(key);
}

Eliminar contenedores

public void RemoveLocalContainerAppData()
{
    localSettings.DeleteContainer(nameContainer);
}

Aplicación

Ya con todo esto visto, como siempre ahora va un pequeño ejemplo. Para mantener el orden en el proyecto se crean tres servicios una para cada tipo de almacenamiento (local, móvil y temporal).

Lo que hace la aplicacion en la primera pantalla es guardar el valor seleccionado en un combobox. Con esto podremos observar en funcionamiento a los datos locales y a los datos moviles. Usen emuladores o hagan la prueba en la pc y en su telefono y comprueben que cuando modificamos un dato local y desinstalamos la aplicacion, al volver a instalarla el elemento seleccionado se pierde. En cambio, en el combobox de los datos moviles se puede ver que si modificamos el elmento seleccionado en una aplicacion, al ir a la misma aplicacion en otro dispositivo, esta tambien se modificara.

En la segunda pantalla podemos comprobar el uso de archivos locales, móviles y temporales. Simplemente podemos subir un archivo y luego ir a otro dispositivo con la misma aplicacion e intentar recuperarlo. Notaremos como el local y el temporal no tiene exito, el movil si. Ademas si dejamos unos dias la aplicacion sin abrir podriamos observar que el archivo se elimino del almacenamieno temporal.

 

 

Blobs – Azure Storage [UWP]

Contenidos recomendados:

Los blobs son parte de los servicios de Azure Storage que permiten resolver distintos problemas para el almacenamiento de datos en las aplicaciones. En este artículo se hablará de los Blobs de Azure asumiendo que ya saben lo que son, que tienen una Storage Account en Azure, que saben cómo conectarse a los servicios desde una aplicación, etc. En los contenidos recomendados hay un artículo dedicado a la introducción de este tipo de servicios de almacenamiento de Azure.

 

Haciendo un repaso muy rápido por la taxonomía de los blobs recordemos que una cuenta de almacenamiento de azure puede tener varios contenedores y estos últimos son los que almacenan los blobs. También recordemos que podemos crear carpetas en los contenedores pero que estas son solamente un agregado visual para mantener el orden en una interfaz de usuario porque los contenedores no tienen noción de lo que son las carpetas.

Untitled

 

Para poder utilizar los blobs en las aplicaciones UWP hay que agregar la referencia a WindowsAzure.Storage .

Untitled.png

Desde Visual Studio se puede entrar al Cloud Explorer que es el panel dedicado a los servicios de Azure y administrar la Storage Account muy fácilmente. Para entrar al Cloud Explorer: View>Cloud Explorer, o tambien Ctrl+|, Ctrl+X.

ScreenCloudExplorer

Desde el portal de Azure podemos crear Storage Accounts, containers, administrar los blobs, etc. Azure SDK brinda la posibilidad de hacer todo eso en Visual Studio solo con un par de clicks desde el panel Cloud Explorer. En la siguiente imagen pueden ver que contamos con un container llamado ngblobcontainer en el que contamos con tres archivos de office y una carpeta Images.

ScreenContainerAzureSDK

 

Conexión a los Blobs

Lo primero que habría que hacer es conectarse a la cuenta de Azure mediante la siguiente línea con la cadena de conexión que obtenemos en el portal de Azure o utilizando el emulador de almacenamiento de la Azure SDK.

ScreenPrimaryConnectionStringAzurePortal

var storageaccount = CloudStorageAccount.Parse("**CONNECTION STRING**");
var storageaccount = CloudStorageAccount.Parse("UseDevelopmentStorage=true");

Una vez con la cuenta de almacenamiento de Azure obtenida se puede crear el cliente que permite hacer todas las operaciones con los blobs.

CloudBlobClient clientStorgeAccount = storageAccount.CreateCloudBlobClient();

 

Obtener contenedor

Podemos acceder a una referencia del contenedor mediante el método GetContainerReference que se encuentra dentro del cliente de la Storage Account que creamos anteriormente.

App.clientStorgeAccount.GetContainerReference("ngblobcontainer");

 

Cargar blob

Con las siguientes líneas subo una imagen al contenedor dentro de una carpeta Images. Nuevamente recuerden que las carpetas son solamente una manera de crear una sensación de organización para los archivos del contenedor pero que en realidad los archivos están todos mezclados dentro del contenedor.

public async Task AddBlobFile(StorageFile blobFile)
{
    await container.CreateIfNotExistsAsync();
    CloudBlockBlob blockBlob = container.GetBlockBlobReference(String.Format("Images/{0}", blobFile.Name));
    await blockBlob.UploadFromFileAsync(blobFile);
}

 

Descargar blob

Con GetBlockBlobReference traemos el archivo deseado a través de su path y lo descargamos dentro de un StorageFile con DownloadFileAsync.

public async Task<StorageFile> GetFile(string name)
{
    await container.CreateIfNotExistsAsync();
    CloudBlockBlob blockBlob = container.GetBlockBlobReference(String.Format("Images/{0}", name));

    StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting);

    await blockBlob.DownloadToFileAsync(file);
    return file;
}

 

Obtener todos los blobs del container

En un caso real lo más probable es que cada usuario que almacene sus archivos en un container tenga en una base de datos los nombres o el path de los blobs que le pertenecen. Si queremos descargar todos sus archivos es tan sencillo como en el caso anterior agregando un foreach y devolviendo la lista de esos archivos.

public async Task<List<StorageFile>> GetBlobFiles(List<ImageFile> lista)
{
    List<StorageFile> StorageFileList = new List<StorageFile>();
    foreach (var i in lista)
    {
        StorageFileList.Add(await GetBlobFile(i.Path));
    }
    return StorageFileList;
}

 

Privacidad de los contenedores

Podemos configurar la seguridad de nuestros contenedores con tres distintas opciones.

  • Contenedor privado (Off): Solo el dueño de la StorageAccount tiene acceso al contenedor y a sus blobs.
  • Blob público (Blob): El blob es público y cualquier persona puede acceder a este. Sin embargo, si el contenedor es privado no podremos acceder a la lista de blobs pero si a los blobs públicos individualmente.
  • Contenedor público (Container): Esta es la opción más abierta y permite acceder a la lista de todos los blobs que contiene y a tener acceso a esos blobs.

Podemos establecer la privacidad del contendor de la siguiente manera:

public async Task SetContainerPermissions(int type)
{
    BlobContainerPermissions bcp = new BlobContainerPermissions();

    switch (type)
    {
        case 1:
            bcp.PublicAccess = BlobContainerPublicAccessType.Blob;
            break;
        case 2:
            bcp.PublicAccess = BlobContainerPublicAccessType.Container;
            break;
        case 3:
            bcp.PublicAccess = BlobContainerPublicAccessType.Off;
            break;
    }
    await container.SetPermissionsAsync(bcp);
}

Al intentar acceder a los blobs o a la lista de blobs del contenedor mediante la uri recibiremos distintas respuestas dependiendo de la privacidad que hayamos establecido en el contenedor. Si no tenemos acceso Azure directamente nos indicara que el archivo no existe por mas de que esto no sea cierto por razones de seguridad.

 

Concesion de blobs

Si intentamos editar un documento que tenemos en un container (por ejemplo un archivos txt), tendremos que evitar que ese archivo sea accedido al mismo tiempo por otro cliente. Para esto existen las concesiones de los blobs que permiten bloquear el archivo temporalmente para que podamos editarlo tranquilos y evitar cualquier tipo de posibilidad de que el archivo se corrompa al intentar acceder a el de dos lugares a la vez.

Podemos obtener una concesion de un blob de la siguiente manera:

public async Task<AccessCondition> AcquireLeaseBlob(string name, int time)
{
    CloudBlockBlob blockBlob = container.GetBlockBlobReference(String.Format("Images/{0}", name));

    var leaseName = await blockBlob.AcquireLeaseAsync(TimeSpan.FromSeconds(60), null);
    var accessCondition = AccessCondition.GenerateLeaseCondition(leaseName);
    return accessCondition;
}

En el ejemplo anterior obtenemos la concesión de ese blob durante 60 segundos. Esto quiere decir que solamente nosotros podremos realizar operaciones en el archivo durante 1 minuto o hasta que liberemos el blob programáticamente. Para liberarlo lo hacemos de la siguiente manera.

public async Task ReleaseLeaseBlob(AccessCondition accessCondition, string name)
{
    CloudBlockBlob blockBlob = container.GetBlockBlobReference(String.Format("Images/{0}", name));
    await blockBlob.ReleaseLeaseAsync(accessCondition);
}

 

Ejemplo

Con todo esto visto, podemos hacer una app de ejemplo muy sencilla. Nos permite subir imágenes al container desde la app y almacenar los nombres de los archivos en una base de datos interna sqlite. Una vez subido en la segunda pantalla tenemos una lista de todos nuestros archivos en un combobox que nos posibilita seleccionar una y descargarlo o elimiarlo del container. Creamos un servicio para gestionar todas las operaciones que tienen que ver con los blobs.
Screen1App

Screen2App
IMPORTANTE: Para poder probar el ejemplo deben agregar la cadena de conexion de su propia Azure Storage Account.

 

Descargar código

 

Semantic Zoom – Windows 10 [UWP]

Contenidos recomendados:

Semantic Zoom permitirá agrupar las los elementos de las listas y movernos rápidamente entre esos grupos con gestos de de Pinch. Anteriormente ya mostré como agrupar los elementos y el uso del semantic zoom no es muy distinto. El beneficio que obtenemos con el semantic zoom es que al presionar sobre el titulo de una categoría o al hacer el gesto se despliega una nueva lista con todas las categorías permitiéndonos seleccionar una y posteriormente mostrarnos los elementos de ella.

semanticzoom-win10

Podemos encontrar el uso del semantic zoom en las documentaciones oficiales aunque considero, en la mayoría del contenido que publicado no contempla el uso de MVVM, es por eso que a veces es posible conseguir el mismo resultado que se obtiene escribiendo el código desde le code-behind al instante o perder minutos y hasta horas intentando obtener el mismo resultado.

 

SemanticZoom

La estructura del elemento semantic zoom esta formado por dos subelementos que generalmente contienen listas.

<SemanticZoom>
    <SemanticZoom.ZoomedOutView>
        <!--CODIGO-->
    </SemanticZoom.ZoomedOutView>

    <SemanticZoom.ZoomedInView>
        <!--CODIGO-->
    </SemanticZoom.ZoomedInView>
</SemanticZoom>

 

Cargar colección agrupada en el ViewModel

En el ViewModel creamos una lista que va a contener todos los elementos sin agrupar. La lista la cargamos con los datos de clientes que vienen del servicio ICustomerService. También creamos una colección que agrupara los clientes por país.

private List<Customer> listaCustomers = new List<Customer>();
public List<Customer> ListaCustomers
{
    get { return listaCustomers; }
    set
    {
        listaCustomers = value;
        RaisePropertyChanged();
    }
}

private ObservableCollection<IGrouping<string, Customer>> customerCollectionViewSource;
public ObservableCollection<IGrouping<string, Customer>> CustomerCollectionViewSource
{
    get { return customerCollectionViewSource; }
    set
    {
        customerCollectionViewSource = value;
        RaisePropertyChanged();
    }
}

En el método OnNavigatedTo cargamos la lista y la agrupamos.

public override async Task OnNavigatedTo(NavigationEventArgs args)
{
    ListaCustomers = new List<Customer>(await customerService.GetCustomers());
    CustomerCollectionViewSource = new ObservableCollection<IGrouping<string, Customer>>(from cus in ListaCustomers group cus by cus.Country into g orderby g.Key select g);
}

 

CollectionViewSource

En los recursos de la pagina declaro un CollectionViewSource, este elemento permitirá tener una colección de items que tengan sublistas dentro que conforman los grupos. El source esta bindeado a la colección agrupada CustomerCollectionViewSource del ViewModel.

<Page.Resources>
    <CollectionViewSource IsSourceGrouped="True" Source="{Binding CustomerCollectionViewSource}" x:Name="cvsCustomers"/>
</Page.Resources>

 

ZoomedOutView y ZoomedInView

Ahora queda definir los Templates de las dos listas de ZoomedOutView y ZoomedInView.

CountryCategoryList

<SemanticZoom.ZoomedOutView>
    <GridView ItemsSource="{Binding CustomerCollectionViewSource}">
        <GridView.ItemTemplate>
            <DataTemplate>
                <Grid Height="200" Width="200" Background="Blue">
                    <TextBlock Foreground="White" Text="{Binding Key}"/>
                </Grid>
            </DataTemplate>
        </GridView.ItemTemplate>
    </GridView>
</SemanticZoom.ZoomedOutView>

 
CustomersList

<SemanticZoom.ZoomedInView>
    <ListView ItemsSource="{Binding Source={StaticResource cvsCustomers}}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <TextBlock Text="{Binding Name}"/>
                    <TextBlock HorizontalAlignment="Right" Text="{Binding City}"/>
                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>

        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                <Setter Property="VerticalContentAlignment" Value="Stretch" />
            </Style>
        </ListView.ItemContainerStyle>

        <ListView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <Grid>
                            <TextBlock Text='{Binding Key}' Style="{StaticResource SubheaderTextBlockStyle}" />
                        </Grid>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ListView.GroupStyle>
    </ListView>
</SemanticZoom.ZoomedInView>

 

ViewChangeStarted

Por ultimo bindeamos el evento ViewChangeStarted al commando ViewChangeStartedCommand con la ayuda de bahaviors. El comando lo que hace es lanzarse al comenzar el cambio de una vista. Nosotros lo utilizaremos para lograr que al seleccionar una categoría, la lista agrupada de clientes se ubique en la posición de esa categoría mostrando los elementos.

<i:Interaction.Behaviors>
    <core:EventTriggerBehavior EventName="ViewChangeStarted">
        <core:InvokeCommandAction Command="{Binding ViewChangeStartedCommand}"/>
    </core:EventTriggerBehavior>
</i:Interaction.Behaviors>

 

private DelegateCommand<SemanticZoomViewChangedEventArgs> viewChangeStartedCommand;
public ICommand ViewChangeStartedCommand
{
    get { return viewChangeStartedCommand = viewChangeStartedCommand ?? new DelegateCommand<SemanticZoomViewChangedEventArgs>(ViewChangeStartedCommandExecute); }
}
private void ViewChangeStartedCommandExecute(SemanticZoomViewChangedEventArgs e)
{
    if (e.SourceItem.Item != null)
    {
        e.DestinationItem = new SemanticZoomLocation { Item = e.SourceItem.Item };
    }
}

 

Descargar código

 

Crear sitio web – ASP.NET MVC [Azure]

Como Microsoft Student Partners tenemos como objetivo intentar que la mayor parte de estudiantes de nuestra universidad conozcan el uso y las virtudes de Azure. Por esto es que la mejor idea de lograrlo es dar talleres para los estudiantes activando su cuenta de Azure y enseñando el uso de algún servicio de ella.

Uno de los talleres que se proponen es el de crear una web con ASP.NET y MVC y alojarlo en Azure. Este articulo esta destinado para publicar todo el contenido del taller en mayor detalle para los que quieran aprender o para los que ya tuvieron el taller y quieren repasar lo visto.

 

Objetivo

El objetivo del taller es una introducción al desarrollo web con ASP.NET y MVC y presentar la posibilidad de compartirlos mediante Azure. Al finalizar el taller lo importante es que cada uno se vaya con la suscripción a Azure y con su cuenta de estudiante de DreamSpark activada durante un año para acceder a otros productos del catalogo.

 

Que es Azure?

Para los que nunca escucharon de Azure. Se puede definir a Azure como la plataforma de servicios en la nube de Microsoft que provee soluciones de almacenamiento de computos o datos en los distintos data centers distribuidos por todo el mundo. Una caracteristica muy importante su escalabilidad permitiendo tener siempre disponibles los recursos necesarios y pagar solo por lo que usamos.
Tienen que entender que dar una definicion de Azure exacta a una persona que nunca escucho de el, muchas veces termina en fracaso porque se intenta transmitir las posibilidades que abre y lo interesante que resultan muchos servicios de ella en una sola oración.

Azure-logo

Para entenderlo mejor es bueno plantear una situacion de ejemplo y ver los aspectos en los que Azure marca la diferencia.

Supongamos que en este taller vamos a crear una aplicacion web con ASP.NET pero sin Azure. Inmediatamente salen temas como el almacenamiento de la aplicacion, redes, sistemas operativos, infraestructura y varias cuestiones mas. Estas cosas implican una inversion inicial para los servidores, tiempo en configuracion, y espacio para fisico para guardar los servidores, y mantenimiento de estos. Ademas si la aplicacion crece debemos actualizar o aumentar las capacidades de los servidores y si la la demanda aumento solamente en temporadas determinadas del año, durante el resto de tiempo estaremos desperdiciando recursos.

graph5

Es aca donde aparece Azure, con el servicio de Azure Web Apps podemos utilizar los data centers de Microsoft para publicar la pagina web y olvidarnos de inversion inicial, la configuracion, el espacio fisico y mantenimiento. Ademas si queremos agregar un almacenamiento en una base de datos a la aplicacion, tambien podemos utilizar azure y Azure permite trabajar con distintas bases de datos, sistemas operativos, leguajes y dispositivos. Pero mas importante aun, Azure nos provee escalabilidad para cualquier tipo de escenario, tengamos un crecimiento enorme por el exito de la aplicacion, o un crecimiento en temporadas, Azure utilizara siempre los recursos que necesita y nosotros pagaremos solamente por los recursos que utilizamos.

azure

Esto es por lo que para cualquier desarrollo pequeño, mediano o grande Azure es ideal y nos facilita muchas cosas para este ejemplo. Pero no en este ejemplo solamente, todo lo que dijimos se aplica en general para el resto de los servicios de Azure, Azure Web Apps es solamente uno de las muchisimas soluciones que puede brindar Azure. Para conocer un poco mas las distintos servicios que provee.

what-is-paas

Infraestructura como servicio (IasS): Son los servicios de host que se pueden adquirir como recursos de almacenamiento, computo, maquinas virtuales, etc. Servidores y almacenamiento, Seguridad/firewalls de red y Edificio/planta física para el centro de datos.

Plataforma como servicio (PasS): Son los servicios que permiten desarrollar aplicaciones que funcionan en entornos de la nube, nos permiten olvidarnos de todos los aspectos de infraestructura. Herramientas de desarrollo, administración de bases de datos, análisis de negocios y sistemas operativos.

Software como servicio (SasS): Son los servicios que permiten alojar y utilizar las aplicaciones basadas en la nube a traves de internet. Aplicaciones hospedadas en la nube.

 

Activar cuenta de Azure

dreamspark2

Lo primero que comenzamos haciendo es activar la cuenta de azure que puede tomar un par de minutos hasta que se verifique la cuenta y se active la suscripción.

Los pasos son sencillos y se explica como obtener una suscripción de estudiante mediante DreamSpark en el siguiente enlace.

ENLACE

 

ASP.NET y MVC

ASP.NET MVC aparecio como una alternativa a ASP.NET WebForms para crear sitios Web con .NET.
Surge de la necesidad de crear software más robusto con un ciclo de vida más adecuado, donde se potencie la facilidad de mantenimiento, reutilización del código y la separación de conceptos.

Que es MVC

MVC es un patron de diseño que se utiliza para el desarrollo de software hace varias decadas. Al usar el patron de diseño MVC intentaremos estructurar y fragmentar la aplicación en distintas capas para separar responsabilidades teniendo varios beneficios.

  • Reutilización, mantenimiento y escalamiento de código.
  • Realizar pruebas unitarias con mayor facilidad.
  • Separación roles entre diseñadores y desarrolladores.

MVC-Introduction2

Esas capas de las que hablamos son tres.

  • View: Es la parte visual de la aplicación que tiene interacción con el usuario.
  • Model: Son las entidades que representan los datos que se mostraran en la vista. El modelo nunca debe interactuar con la vista.
  • ViewModel: Es una clase contendrá toda la lógica de negocios y comunicara la vista con el modelo.

 

Crear Web Application

Entrar a Visual Studio y crear un nuevo proyecto en File>New Project…

11

En la siguiente ventana hay templates de los distintos tipos de proyectos que se pueden iniciar. Seleccionar Web en el panel izquierdo y después ASP.NET Web Application.

22

En la siguiente pantalla seleccionar el tipo de proyecto, en este taller necesitamos crear el proyecto que use MVC.

33

Al presionar sobre el botón Change Authentication se abre la ventana que permite seleccionar la autenticación que se agregara al proyecto, como es algo que en este momento no interesa pueden seleccionar “No Authentication” para tener un proyecto mas limpio a la hora de analizar la etructura la estructura del proyecto y los aspectos que nos interesan en este taller.

1212

Al crear el proyecto tendremos una pantalla como la siguiente con la estructura del proyecto a la derecha.

aaa

Viendo con mayor detalle se puede notar que el proyecto cuenta con las carpetas Controllers, Models y Views de los que hablamos antes y que forman parte de la implementación de MVC.

1111

Y en mas detalle aun, se puede ver como los Controllers y Models son archivos “.cs” que contienen código C# y las vistas son archivos “.cshtml” que contienen código HTML.

123123

 

Análisis del proyecto

Iniciamos el análisis compilando la aplicación con el siguiente botón. También pueden cambiar el navegador con el que se lanzara la aplicación por el de preferencia.

Untitled

Esto lo que hará es iniciar una version express de IIS para poder correr la web compilada. Cuando se abra el navegador con la aplicacion vamos a ir a la pagina de About.

Untitled

Podemos ver en la nomenclatura de la url formada por dos segmentos Home/About. Vamos analizar estos en el proyecto.

En el proyecto, Home esta asociado al nombre del controller y About esta asociado al nombre del Action dentro del controller.

HomeControllerImage

Una Action no es mas ni menos que una pagina en nuestra web y podemos acceder a esa pagina poniendo en la url en la primera sección como el nombre del controller y la segunda como el nombre de la Action. Como notaran cada Action retorna una View que estas están alojadas en la carpeta Views.

Untitled

 

Creación de Model

Creamos un nuevo modelo haciendo click derecho en la carpeta Models>Add Class…

111111111111

Elegir el tipo class si no esta seleccionado y abajo en Name introduzco el nombre de la clase “Producto”.

99

Durante el taller para ahorrar tiempo lo que hacemos es descargarla del siguiente enlace y agregarlo a la solución. Si se opta por la opción de descarga recordar modificar el namespace de la clase después de agregarla al proyecto.

ENLACE

El contenido que tendrá es el siguiente. Además por cuestiones de tiempo y para no introducir mas conceptos nuevos para algunos se agrega la clase DeAlgunLadoSale que devuelve un Producto por su Id de una lista de productos estática. lo dejamos en el mismo archivo .cs de Producto pero si prefieren se puede crear su propia .cs.

public class Producto
{
    public int Id { get; set; }

    public string Nombre { get; set; }

    public string Descripcion { get; set; }
}

public class DeAlgunLadoSale
{
    public static Producto GetProducto(int i)
    {
        List<Producto> lista = new List<Producto>()
            {
                new Producto
                {
                    Descripcion = "alkjsnflaksd asda fasd hsjdoadno",
                    Id = 1,
                    Nombre = "Borrador"
                },
                new Producto
                {
                    Descripcion = "djajdinq wqijeiwuqewqn diqjeiuwqneq",
                    Id = 2,
                    Nombre = "Marcador"
                },
                new Producto
                {
                    Descripcion = "iqwojeo ewqen oqwe we iqw",
                    Id = 3,
                    Nombre = "Proyector"
                },
            };

        return lista.FirstOrDefault(prod => prod.Id == i);
    }
}

 

Creación del Controller

Lo siguiente que hay que hacer es crear un controlador.

1223

321

controller

Se creara la siguiente clase que hereda de controller.

fni

Vamos a agregar una Action mas que se llame Detail para mas adelante poder ingresar a la vista de un modelo en particular.

public ActionResult Detalle()
{
    Producto model = DeAlgunLadoSale.GetProducto(1);
    return View(model);
}

Lo que hace este método es llamar GetProducto pasándole como parámetro el Id del producto que quiere obtener y lo guarda en model. En la siguiente línea que ya conocemos lo que devuelve es la vista y le pasa como parametron el model para mostrar los datos de ese producto.

 

Creación de la View

dsadsdas

En la creación del controlador ProductoController automáticamente se agrego dentro de la carpeta Views una nueva carpeta llamada Producto. En ella vamos a crear una vista.

nini

Se abrirá una ventana la cual hay que completar de la siguiente manera.

grgr

El nombre de la vista sera Detalle, se utilizara un template de tipo Details y se asigna el modelo de tipo Producto. Con estas indicaciones se creara la vista con tan solo un par de clicks. Y ahora ejecuto el proyecto.

DetallePageURL

DetallePage

Como resultado obtuvimos lo que queríamos. ProductoController llama a la Action Detalle y que devuelve la View junto con el Producto de id 1 como modelo por parámetro. Pero si queremos hacer el ejemplo mas practico seria ideal que podamos pasarle en la URL el Id por parámetro para mostrar los detalles del producto que deseamos y no siempre los detalles del producto de Id 1 hardcodeado.

Para lograr esto hacemos una pequeña modificación en el Action Detalle del ProductController.

public ActionResult Detalle(int Id)
{
    Producto model = DeAlgunLadoSale.GetProducto(Id);
    return View(model);
}

Cuando vuelvo a Ejecutar el proyecto y entro a la anterior URL recibiré una Exception porque ahora la vista requiere el parametron Id.

error

noerror

Agregando “?Id=2” a la URL conseguiremos mostrar los datos del producto con Id 2.

noerror

noerror

Con todo esto ya terminamos de ver los conceptos básicos de ASP.NET y MVC.

El siguiente paso es crear la WebApp en Azure y subir el proyecto que creamos.

 

Crear un Web App a Azure

Hay que entrar al portal de Azure en portal.azure.com y loguearse con la cuenta en la que se activo la suscripción al principio.

ENLACE

azure

Para crear un WebApp hay que seguir los pasos como se indica en la siguiente imagen.

creacion

 

Subir el Sitio Web a Azure

Lo siguiente es ir a Visual Studio y seguir los siguientes pasos.

publi

Seleccionar Microsoft Azure App Service.

publishweb

Seleccionar la suscripción a DreamSpark y el Web App que creamos anteriormente.

AppService

Por ultimo dar al botón Publish y comenzara el deploy de la web a Azure. En Destination URL indica la dirección desde la cual se puede acceder al sitio web.

Publish

Finalmente ya tengo el sitio publicado y cualquiera puede acceder a el desde la URL miwebappazure.azurewebsite.net.

ENLACE

PageURL

Page

Si hacemos modificaciones en el sitio luego de subirlo y queremos hacer otro Deploy los pasos son los mismos. Con esto termina el taller y cada uno se va con su cuenta de Azure activada y con los conocimientos necesarios para seguir experimentando con ASP.NET y MVC.

Descargar código

Converters – Windows 10 [UWP]

Contenidos recomendados:

Los converters son muy faciles de implementar y nos pueden dar una gran ayuda a la hora de convertir los datos que llegan del ViewModel. Al enlazar la propiedad de un elemento en la vista con la propiedad del ViewModel, en medio podemos colocar un converter para modificar el dato antes de que llegue a la vista o incluso cuando se notifica al ViewModel en caso de que el Binding sea TwoWay. Los converter son clases que implementan la interfaz IValueConverter. IValueConverter expone dos metodos que nos ayudaran a modificar los datos.

  • object Convert(object value, Type targetType, object parameter, string language)

Este metodo es el que llama el converter cuando hay que convertir un dato que esta llegando del ViewModel a la vista.

  • object ConvertBack(object value, Type targetType, object parameter, string language)

Este metodo es el que llama el converter cuando hay que convertir un dato de la vista que debe actualizarse en el ViewModel. Este solo se requiere cuando se usa en Bindings con Mode TwoWay.

Para verlos en accion voy a crear los converters BoolToVisibilityConverter y DateTimeToStringConverter pero antes voy a crear la carpeta Converters para mantener el orden dentro del proyecto.

dsada

 

Creacion de Converters

Hay que crear los dos converters que mencione anteriormente para eso añado las dos clases BoolToVisibilityConverter y DateTimeToStringConverter en la carpeta Converters e implemento la interfaz IValueConverter en cada una.

En este converter pueden ver como ConverterBack acepta datos enlazados con Mode TwoWay aunque en este ejemplo no lo necesitaremos.

public class BoolToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        bool state = (bool)value;
        if (state) return Visibility.Visible;
        else return Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        Visibility visibility = (Visibility)value;
        return visibility == Visibility.Visible;
    }
}

Podemos dejar ConverterBack sin logica si sabemos que las propiedades que utilizaran el converter no seran TwoWay.

public object Convert(object value, Type targetType, object parameter, string language)
{
    CultureInfo ci = new CultureInfo("es-ES");
    DateTime d = (DateTime)value;
    string dia = ci.DateTimeFormat.GetDayName(d.DayOfWeek);
    string mes = ci.DateTimeFormat.GetMonthName(d.Month);
    return string.Format("{0} {1} de {2} de {3} ({4})", dia, d.Day, mes, d.Year, string.Format("{0:hh\\:mm}", d.TimeOfDay));
}

public object ConvertBack(object value, Type targetType, object parameter, string language)
{
    throw new NotImplementedException();
}

 

Utilizando Converterss

Para utilizar los converters hay que crear los recursos en la pagina o en un ResourceDictionary.

<base:PageBase.Resources>
    <converters:BoolToVisibilityConverter x:Name="BoolToVisibilityConverter"/>
    <converters:DateTimeToStringConverter x:Name="DateTimeToStringConverter"/>
</base:PageBase.Resources>

Es importante agregar el namespace de converters.

xmlns:converters="using:Appconverters.Converters"

 

BoolToVisibilityConverter

Creamos una propiedad de tipo bool en el Viewmodel y mediante el ToggleSwitch la cambiaremos. La propiedad Visibility del Grid la enlazo con la propiedad Boolean anterior y utilizo el converter para remplazar true por Visibility.Visible y false por Visibility.Collapsed.

private bool isVisible = false;
public bool IsVisible
{
    get { return isVisible; }
    set
    {
        isVisible = value;
        RaisePropertyChanged();
    }
}
<StackPanel VerticalAlignment="Center">
    <ToggleSwitch Header="visble" IsOn="{Binding IsVisible, Mode=TwoWay}"/>
    <Grid Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}">

    </Grid>
</StackPanel>

DateTimeToStringConverter

Creo la propiedad DateTime inicializada con DateTime.Now. Creo un TextBlock y la propiedad Text la enlazo con la propiedad DateTime del ViewModel. El converter convertira el DateTime en una cadena de texto que indica la fecha con un formato mas agradable a la vista.

private DateTime fecha = DateTime.Now;
public DateTime Fecha
{
    get { return fecha; }
    set
    {
        fecha = value;
        RaisePropertyChanged();
    }
}
<TextBlock Style="{StaticResource HeaderTextBlockStyle}" Text="{Binding Fecha, Converter={StaticResource DateTimeToStringConverter}}"/>

 

Propiedades en converters

Un agregado mas para explotar aun mas los converters es utilizar propiedades dentro de ellos. Supongamos que queremos mostrar un texto indicando que el elemento se encuentra oculto. Con lo aprendido hasta ahora tendriamos que crear un converter nuevo que hiciera la logica contraria a BoolToVisibilityConverter, si recibe true devuelve Collapsed y con false devuelve Visible.
Si creamos dos propiedades VisibilityTrue y VisibilityFalse les podemos establecer valores por default y modificarlos cuando queremos utilizar la logica contraria. Agregamos las propiedades y hacemos una pequeña modificacion en el Converter.

public class BoolToVisibilityConverter : IValueConverter
{
    public Visibility VisibilityTrue { get; set; } = Visibility.Visible;
    public Visibility VisibilityFalse { get; set; } = Visibility.Collapsed;

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        bool state = (bool)value;
        if (state) return VisibilityTrue;
        else return VisibilityFalse;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        Visibility visibility = (Visibility)value;
        return visibility == Visibility.Visible;
    }
}

Creamos un nuevo recurso e invertimos los valores.

<converters:BoolToVisibilityConverter x:Name="NotBoolToVisibilityConverter" VisibilityTrue="Collapsed" VisibilityFalse="Visible"/>

Ahora lo utlizamos en un nuevo grid que contiene el mensaje de que el elemento esta oculto.

<Grid Visibility="{Binding IsVisible, Converter={StaticResource NotBoolToVisibilityConverter}}">
    <TextBlock>Fecha oculta</TextBlock>
</Grid>

 

Conclusion

De una forma sencilla y con pocas lineas de codigo logramos convertir los datos que llegan del ViewModel. Sin la ayuda de los converters la logica para conseguir el mismo funcionamiento hubiera requerido un mayor esfuerzo. Sin duda los converters son sencillos pero muy potentes y un tambien un buen compañero para MVVM.

 

Untitled

Descargar código

 

OCR – Windows 10 [UWP]

Conocimientos recomendados:

OCR son las siglas de Optical Character Recongnition que permite la extracción de caracteres de una imagen. Esto da inicio a gran cantidad de posibilidades para el desarrollo de aplicaciones como traducir texto de una imagen en tiempo real, extraer líneas de la foto de un documento, encontrar una palabra en la imagen, etc.

En Windows 10 es parte de UWP por lo que no tendremos que agregar ninguna referencia a su librería como en versiones anteriores. Se optimizo muy bien y es posible utilizarlo en el dispositivo sin conexión a internet con mas de 20 idiomas.

IC742840

La utilización de la API se puede resumir en muy pocas líneas de código. Para separar todo el código que tenga que ver con OCR del resto voy a crear un servicio llamado IOcrService.
Se puede extraer de la imagen tres tipos distintos de resultados.

  • Texto.
  • Lineas.
  • Angulo del texto.
public interface IOcrService
{
    Task<String> GetText(SoftwareBitmap bitmap);

    Task<List<String>> GetLines(SoftwareBitmap bitmap);

    Task<double?> GetTextAngle(SoftwareBitmap bitmap);
}

 

Creación de servicio OCR

OcrEngine nos proporcionara el acceso a la funcionalidad de reconocimiento del texto mediante RecognizeAsync() pasándole como parametron una imagen como SoftwareBitmap.

GetText

public async Task<string> GetText(SoftwareBitmap bitmap)
{
    string result = string.Empty;
    OcrEngine ocr = OcrEngine.TryCreateFromUserProfileLanguages();
    var ocrResult = await ocr.RecognizeAsync(bitmap);

    return ocrResult.Text;
}

GetLines

public async Task<List<string>> GetLines(SoftwareBitmap bitmap)
{
    string result = string.Empty;
    OcrEngine ocr = OcrEngine.TryCreateFromUserProfileLanguages();
    var ocrResult = await ocr.RecognizeAsync(bitmap);

    List<String> lista = new List<string>();
    foreach (var line in ocrResult.Lines)
    {
        lista.Add(line.Text);
    }

    return lista;
}

GetTextAngle

public async Task<double?> GetTextAngle(SoftwareBitmap bitmap)
{
    string result = string.Empty;
    OcrEngine ocr = OcrEngine.TryCreateFromUserProfileLanguages();
    var ocrResult = await ocr.RecognizeAsync(bitmap);

    var text = ocrResult.TextAngle;
    return text;
}

 

Utilizando OCR

En el ViewModel utilizo los servicios que cree:

RecognizeTextCommandExecute

private async void RecognizeTextCommandExecute()
{
    TextAngle = 0;
    if (softwareBitmap != null)
    {
        var result = await ocrService.GetText(softwareBitmap);

        if (!string.IsNullOrWhiteSpace(result)) Texto = result;
        else dialogService.ShowMessage("No se encontro texto en la imagen.");
    }
    else
    {
        dialogService.ShowMessage("Debe agregar una imagen.");
    }
}

RecognizeLinesCommandExecute

private async void recognizeLinesCommandExecute()
{
    TextAngle = 0;
    if (softwareBitmap != null)
    {
        var result = await ocrService.GetLines(softwareBitmap);

        if (result.Count > 0)
        {
            string textoExtraido = "";
            foreach (var line in result) textoExtraido += line + "\n";
            Texto = textoExtraido;
        }
        else
        {
            dialogService.ShowMessage("No se encontro texto en la imagen.");
        }
    }
    else
    {
        dialogService.ShowMessage("Debe agregar una imagen.");
    }
}

RecognizeTextAngleCommandExecute

private async void recognizeTextAngleCommandExecute()
{
    if (softwareBitmap != null)
    {
        var result = await ocrService.GetText(softwareBitmap);

        if (!string.IsNullOrWhiteSpace(result))
        {
            Texto = result;

            var res = await ocrService.GetTextAngle(softwareBitmap);

            if (res != null) TextAngle = res.Value * -1;
        }
        else dialogService.ShowMessage("No se encontro texto en la imagen.");
    }
    else
    {
        dialogService.ShowMessage("Debe agregar una imagen.");
    }
}

 

Tamaño maximo soportado

En la API de OCR de 8.1 se podia extraer el texto de imágenes de un tamaño máximo de 2600 x 2600 px lo que significaba una dificultad si queríamos analizar imágenes grandes o fotografías que tomabamos con nuestro teléfono. En Windows 10, OCR permite imagenes de un tamaño de hasta 10000 x 10000 px dando la posibilidad de analizar imagenes muy grandes. De todas formas es importante validar el tamaño de la imagen en la aplicación.

private bool ImageValidation()
{
    return bitmapImage.PixelWidth > OcrEngine.MaxImageDimension &&
        bitmapImage.PixelHeight > OcrEngine.MaxImageDimension;
}

 

SoftwareBitmap

Como mencione antes el metodo RecognizeAsync recibe como parametron SoftwareBitmap.

private async Task<WriteableBitmap> GetBitmapImage(StorageFile file)
{
    var imgProp = await file.Properties.GetImagePropertiesAsync();
    WriteableBitmap bitmap;

    using (var streamImage = await file.OpenAsync(FileAccessMode.Read))
    {
        var decoder = await BitmapDecoder.CreateAsync(streamImage);
        softwareBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);

        bitmap = new WriteableBitmap((int)imgProp.Width, (int)imgProp.Height);
        bitmap.SetSource(streamImage);
        softwareBitmap.CopyFromBuffer(bitmap.PixelBuffer);
    }
    return bitmap;
}

De todo el metodo la linea que importa es:

softwareBitmap.CopyFromBuffer(bitmap.PixelBuffer);

 

Finalmente

En la aplicación de ejemplo:

<ScrollViewer>
    <StackPanel>
        <Button HorizontalAlignment="Center" Content="agregar imagen" Command="{Binding LoadImageCommand}"/>
        <Image MinHeight="200" MaxHeight="400" Source="{Binding BitmapImage}"/>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <Button Content="extraer texto" Command="{Binding RecognizeTextCommand}"/>
            <Button Content="extraer lineas" Command="{Binding RecognizeLinesCommand}"/>
            <Button Content="extraer TextAngle" Command="{Binding RecognizeTextAngleCommand}"/>
        </StackPanel>
        <TextBlock Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Texto}">
            <TextBlock.Projection>
                <PlaneProjection RotationZ="{Binding TextAngle}"/>
            </TextBlock.Projection>
        </TextBlock>
    </StackPanel>
</ScrollViewer>

Untitled

 

Descargar código