Chronos v2.0 Beta 3 available for download


Release notes:

  • Updated introduction document.
  • Updated Visual Studio 2010 Extension (vsix) package.
  • Added horizontal scrolling to the main window TaskBar.
  • Added new styles for ListView, ListViewItem, GridViewColumnHeader, …
  • Added a new WindowViewModel class (allowing to fetch data).
  • Added a new Navigate method (with several overloads) to the NavigationViewModel class (protected).
  • Reimplemented Task usage for the WorkspaceViewModel.OnDelete method.
  • Removed the reflection effect from the shortcut style.
  • Removed the shadow effect from Widgets and Windows.
  • Removed opacity effect when a window or widget is deactivated (instead the header progressbar will change its color).
  • Changed the header rectangle, on Windows and Widgets, by a progressbar that will become in indeterminate state when the window ViewMode is set to Busy, the progressbar gets filled with solid colors instead of the gradients used in the old rectangle.
  • Changed Window Visibility to Collapsed when minimized.
  • Changed Workspace Window style ZoomView popup StayOpen property value to false.
  • Renamed all window styles, and added new ones.
  • Renamed all widget styles.

Evolución del estilo visual de Chronos


Hoy por curiosidad me he puesto a revisar las imágenes antiguas que hay en el blog, de Chronos, y ver como ha ido evolucionando visualmente y la verdad es que ha cambiado mucho.

  • Mayo 2009

Chronos200905

  • Agosto 2009

Chronos200908

  • Noviembre 2009

Chronos200911

  • Diciembre 2009

Chronos200912

  • Enero 2010

Chronos201001

  • Febrero 2010

Chronos201002

  • Noviembre 2010

Untitled

Chronos 2.0 Beta 2 available for download


Release Notes

  • Added cached composition on windows & widgets for Render Tier 0/1 machines (this should help to improve window & widget dragging performance).
  • Added a simple widget configuration system.
  • Metro theme design time fixes & improvements
  • Restyled login window with a zune-like style.
  • Shell window header changes to bring it to front when the mouse is over it
  • Bug Fixes.

The download is available here.

WPF 4 Cached composition tests with Chronos


I have been doing some tests with the WPF 4 cached composition in chronos as a way of try to improve performance when dragging windows and widgets on Render Tier 1 machines.

Using cached composition on windows and widgets improves the dragging performance dramatically, but, the text maybe rendered a bit blurry, while it remains perfectly readable (it’s the first time i use the cached composition support of WPF 4 so maybe i have done something wrong).

I have done the test on a Rendering Tier 1 machine with Windows XP at 1280×1024, using two fonts:

  • Segoe WP (It’s the default font on Chronos v2.0)
  • Segoe UI.

The Cache mode property in the WorkspaceWindowStyle and WidgetStyle styles has been set as:

<BitmapCache EnableClearType="True" RenderAtScale="1" SnapsToDevicePixels="True" />

And finally the TextFormatting mode has been set as Display for one of the tests.

<Setter Property="TextOptions.TextFormattingMode" Value="Display" />

The screenshots shows the Chronos Navigator widget

  • Without using cached composition.
  • Using Cached composition.
  • Using Cached composition and the Display Text Formatting mode.

Here are the results when using Segoe WP.
CachedComposition-Segoe WP
And here when using Segoe UI.

CachedComposition-Segoe UI

Text rendering was better when using Segoe UI font, but that was expected.

Hope i will include cached composition support on widgets and windows in the Beta 2 of Chronos for  Rendering Tier 1 machines.

Update: Test on Rendering Tier 2 machine, Windows 7 at 1920×1200, using the Magnifier Tool at 300%, using Segoe WP font

CachedCompositionT2

Chronos WPF 2.0 Beta 1 available for download


Release Notes

Chronos v2.0 Beta 1

  • Metro theme
  • New Digital clock Widget
  • Solution reorganization
  • Changes on MVVM support
  • Logging support changed to use NLog
  • Improved XML documentation comments
  • Lots of minor changes & refactorings

Source code, binaries, introduction document and ClickOnce installer are available here

 

chronos: escritorios virtuales


Aprovechando los cambios que he estado haciendo la última seana para replantear una parte de chronos (tengo pendiente escribir un post sobre el tema), estos dos últimos dias he aprovechado para implementar los escritorios virtuales (bueno va a dar soporte a dos escritorios solamente por el momento).

El diagrama de clases para los escritorios virtuales queda así (por el momento)

image

En la ventana principal los escritorios se registran como escritorios virtuales así

<desktopControls:Desktop
        x:Name="VirtualDesktop01"
        Grid.Row="3"
        Id="94fa801f-bef7-437a-ba75-352f1ede6fb9"
        Visibility="Collapsed"
        windows:VirtualDesktopManager.IsDesktop="True"
        dragdrop:DragDropManager.IsDropTarget="True"
        Background="Transparent"
        AllowDrop="True"
        SnapsToDevicePixels="True"
        ClipToBounds="False">
</desktopControls:Desktop>

<desktopControls:Desktop
        x:Name="VirtualDesktop02"
        Grid.Row="3"
        Id="2afc760e-9450-448d-9f28-5311c49e4773"
        Visibility="Collapsed"
        windows:VirtualDesktopManager.IsDesktop="True"
        dragdrop:DragDropManager.IsDropTarget="True"
        Background="Transparent"
        AllowDrop="True"
        SnapsToDevicePixels="True"
        ClipToBounds="False">
</desktopControls:Desktop>

El Id servirá para identificar, internamente, una instancia concreta de un escritorio y el nombre se utilizará para el archivo que se generará al guardar el escritorio.

La “dependency property” VirtualDesktopManager.IsDesktop se

encargará de registrar el escritorio virtual.

El ViewModel de la ventana principal tiene un nuevo comando para cambiar de escritorio

public ICommand SwitchDesktopCommand
{
    get
    {
        if (this.switchDesktop == null)
        {
            this.switchDesktop = new ActionCommand(() => OnSwitchDesktop());
        }

        return this.switchDesktop;
    }
}

que se encargará de realizar el cambio de la siguiente forma

/// <summary>
/// Handles the switch desktop command action
/// </summary>
private void OnSwitchDesktop()
{
    ServiceLocator.GetService<IVirtualDesktopManager>().SwitchDesktop();
}

La clase VirtualDesktopManager está declara como un servicio de nRoute


[MapService(typeof(IVirtualDesktopManager),
    InitializationMode=InitializationMode.WhenAvaliable,
    Lifetime=InstanceLifetime.Singleton)]
public sealed class VirtualDesktopManager : ObservableObject, IVirtualDesktopManager

tiene métodos para, cambiar de escritorio, activar el escritorio por defecto (que por el momento será siempre el primero registrado), visualizar una ventana, widget o acceso directo en el escritorio activo, etc. En general los métodos de esta clase se limitan a llamar a los métodos equivalentes de la clase que representa el escritorio virtual (que debe ser una instancia de VirtualDesktop).

Ahí va un video mostrando el cambio de escritorio (está grabado con camstudio, la calidad no es muy buena … pero como ejemplo debería ser más que suficiente)

chronos: Implementando una ventana de login


Chronos tenía hasta ahora una opción para cerrar la sesión de usuario activo, que no hace nada ahora mismo, y un label en el que se debería mostrar el usuario activo pero que al no haber funcionalidad para la identificación del usuario muestra siempre “Usuario anónimo”, así que me he puesto manos a la obra para implementar la identificación del usuario.

Para la implementación he utilizado un observador dedicado de nRoute, y lógicamente una ventana modal para pedir el nombre y la contraseña del usuario, además el ViewModel del Shell (ShellViewModel) tendrá su propio observador para poder actualizar el nombre del Usuario que se visualiza en pantalla.

image 

La ventana de login se abre una vez que la aplicación se ha iniciado, así

// Publish LogOn Request 
Channel<AuthenticationInfo>.Manager.PublishAsync(
    new AuthenticationInfo
    {
        Action = AuthenticationAction.LogOn
    });

 

En AuthenticationObserver se evaluará la acción a realizar en función de los datos que le lleguen

switch (value.Action)
{
    case AuthenticationAction.LogOn:
        ServiceLocator.GetService<INavigationService>().NavigateTo("Chronos/LogIn");
        break;

    case AuthenticationAction.LoggedIn:
        ServiceLocator.GetService<IElementManager>().Load("Desktop.xaml");
        break;

    case AuthenticationAction.LogOut:
        ThreadPool.QueueUserWorkItem(
            new WaitCallback(
                delegate
                {
                    ServiceLocator.GetService<IWindowManager>().CloseAllWindows();
                    ServiceLocator.GetService<IElementManager>().CloseAllElements();
                    Channel<AuthenticationInfo>.Manager.PublishAsync(
                        new AuthenticationInfo { Action = AuthenticationAction.LogOn });
                }));
        break;
}

Cuando la acción sea LogOn, se abrirá la ventana de identificación de usuario, cuando sea LoggedIn, se intentará realizar la carga del escritorio guardado en disco y cuando se LogOut cerrará todas las ventanas, los widgets, los accesos directos del escritorio y notificará de nuevo la acción de LogOn.

El observador de ShellViewModel está definido así

// create the observer
this.authenticationObserver = new ChannelObserver<AuthenticationInfo>(
    (l) => OnAuthenticationAction(l));

// subscribe on the UI Thread
this.authenticationObserver.Subscribe(ThreadOption.UIThread);

 

y se encargará simplemente de actualizar el nombre de usuario dependiendo de si la acción notificada es LogOn o LogOut

private void OnAuthenticationAction(AuthenticationInfo info)
{
    switch (info.Action)
    {
        case AuthenticationAction.LoggedIn:
            this.UserName = info.UserName;
            break;

        case AuthenticationAction.LogOut:
            this.UserName = null;
            break;

    }
}
 
image 
 

Ahora mismo no se realiza ninguna validación sobre los datos de identificación introducidos salvo que estos no estén vacios, en el momento en el que el usuario acepta los datos se notifica la acción de LoggedIn, con el nombre del usuario y se cierra la ventana

private void OnAccept()
{
    Channel<AuthenticationInfo>.Manager.PublishAsync(
        new AuthenticationInfo 
        { 
            Action = AuthenticationAction.LoggedIn, 
            UserName = this.UserName 
        });

    ServiceLocator.GetService<IWindowManager>().CloseDialog();
}  

Y por último el LogOut se notifica en la acción asociada a la opción de cerrar sesión del Shell

protected virtual void OnCloseSession()
{
    Channel<AuthenticationInfo>.Manager.PublishAsync(
        new AuthenticationInfo { Action = AuthenticationAction.LogOut });
}

 
image 

Ahí va un pantallazo de la nueva ventana de login

image

Chronos: Jugando con la barra de tareas de Windows 7


Hacía tiempo que quería ver un poco como funciona la integración desde una aplicación de WPF con la barra de tareas de Windows 7, así que para probar un poco como funciona he implementado una lista de vista abiertas recientemente en Chronos. Para la integración con la barra de tareas de Windows 7 he utilizado las clases que están en el namespace System.Windows.Shell (de .NET 4.0).

Aprovechando le he echado un vistazo también al “Messaging Broker” que viene con nRoute (que me parece muy muy interesante).

Como lo que se pretende es generar una lista de las operativas abiertas recientemente, lo que he hecho has sido modificar el servicio de navegación para que publique un mensaje en un canal a través del sistema de mensajeria de nRoute

// Publish the navigation message
Channel<NavigationInfo>.Manager.PublishAsync(
new NavigationInfo
(
response.Request.Url,
response.NavigationStateManager.Title
));

La clase NavigationInfo nos servirá como “payload” del mensaje para notificar la URL de la vista y un texto para visualizar en la JumpList

public sealed class NavigationInfo
{
    #region · Fields ·

    private string navigationUri;
    private string displayName;

    #endregion

    #region · Properties ·

    public string NavigationUri
    {
        get { return this.navigationUri; }
    }

    public string DisplayName
    {
        get { return this.displayName; }
    }

    #endregion

    #region · Constructors ·

    internal NavigationInfo(string navigationUri, string displayName)
    {
        this.navigationUri  = navigationUri;
        this.displayName    = displayName;
    }

    #endregion
}

Para recibir el mensaje he implementado un suscriptor dedicado de nRoute, NavigationObserver

[MapChannelObserver(typeof(NavigationInfo),
    InitializationMode = InitializationMode.WhenAvaliable,
    Lifetime = InstanceLifetime.Singleton,
    ThreadOption = ThreadOption.UIThread)]
public sealed class NavigationObserver : nRoute.Components.IObserver<NavigationInfo>

que se encargará de comprobar si la plataforma en la que está ejecutándose la aplicación es Windows 7 y en tal caso añadir la vista a una JumpList personalizada (en el caso de que no exista)

if (App.RunningOnWin7)
{
    JumpList jl = JumpList.GetJumpList(Application.Current);

    if (jl != null)
    {
        if (jl.JumpItems.Count >= 10)
        {
            jl.JumpItems.Clear();
        }

        var q = jl.JumpItems.OfType<JumpTask>().Where(t => t.Arguments.Equals(value.NavigationUri));

        if (q.Count() == 0)
        {
            JumpList.GetJumpList(Application.Current).JumpItems.Add
            (
                new JumpTask
                {
                    CustomCategory      = "Reciente",
                    Title               = value.DisplayName,
                    Arguments           = value.NavigationUri,
                    IconResourcePath    = null,
                    IconResourceIndex   = -1,
                    Description         = null,
                    WorkingDirectory    =
                        Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
                }
            );

            jl.Apply();
        }
    }
}

de los posts en blogs que he visto sobre la barra de tareas de Windows 7 todos (o casi) tienen como ejemplo de las JumpList la ejecución de aplicaciones externas, que es el caso más sencillo, en este caso lo que se necesita es que la aplicación capture la acción que el usuario hace sobre uno de los elementos de la JumpList y abra la vista correspondiente.

El mejor ejemplo de como hacerlo lo he visto en la aplicación Fishbowl, en su clase SingleInstance, utilizan Remoting y IPC para conseguirlo.

Y aquí va un pantallazo de como se ve la JumpList

image