Watermark TextBox in Windows Store apps

A common request for WSAs is to add a “watermark” to TextBox entries so users get a hint as to what is expected in the TextBox. You can see this in many Search Charm implementations as it allows a search hint to be provided via the SearchPane.PlaceHolderText property.  However, the built-in TextBox in XAML doesn’t have this feature (HTML does!).

There are a few custom controls out there which allow for this, however, I’m a big fan of behaviors over custom controls – so when I needed this, I created a custom behavior to apply to any existing XAML TextBox to provide a watermark:

The behavior sets the text to the watermark and then watches for focus changes to add/remove the text when there is no existing text entered.  It has two forms of activation – either you can add it to the normal Interaction.Behaviors collection:

<TextBox ...>
   <si:Interaction.Behaviors>
      <i:WatermarkTextBehavior WatermarkColor="Red" WatermarkText="Search Keyword..." />
   </si:Interaction.Behaviors>
</TextBox>

Here, the main advantage is you can set the watermark text color. The default is a gray color. Alternatively, you can use an attached property (Text) which will add a new behavior to the TextBox without needing the full syntax:

<TextBox ...
   i:WatermarkTextBehavior.Text="Search Keyword...">
</TextBox>

The code is part of the latest MVVMHelpers library and is checked in at: WatermarkTextBoxBehavior.cs.   The plan is to include it in the next Nuget drop, until then you can download the source and use it in your project directly if desired!

Enjoy!

Variable sized tiles in Windows Store Apps

One of the common requests I hear when training customers on Windows 8 is “How do I create variable sized tiles in a GridView?” The Windows Store app utilize this technique where different tiles have different sizes to promote content.

It turns out this is relatively easy in HTML – which is probably one of the reasons Microsoft chose to use that visual platform to build the app.  In XAML however, this is a bit more difficult.  There are many posts which detail using a VariableSizeWrapGrid with a GridView to achieve a similar effect – I like Mike Taulty’s blog on the approach.  And this does work as long as you don’t want to have different sized rows and columns, and you don’t have too many items.  The problem is that the VariableSizeWrapGrid doesn’t support virtualization or incremental loading – both key technologies if you have a lot of items.

An alternative approach to this is to do the layout yourself and use a Canvas as the panel – in this sense, you (the programmer) have to manage the layout and it’s not done automatically as rows and columns, but the upside is you have complete control over the layout and performance is greatly improved.

For a test, I chose to enumerate the available colors and then randomly create rows and columns – here’s a single run:

Notice that in this case the sample has two items side-by-side in the same column, and also has different sized columns and items that span rows – in fact, because I’m using a Canvas as my panel, I can use any sized element I choose – I’m not required to enforce columns and rows at all like I am with the grid-based panels.

The code is relatively straight-forward – I’m using MVVM so I put the Top/Width/Height into properties on the ColorViewModel:

public sealed class ColorViewModel : SimpleViewModel
{
    #region Data
    private int _heightPercent;
    private double _height, _width;
    private double _top, _left;
    #endregion

    /// <summary>
    /// Color for this item
    /// </summary>
    public string Color { get; set; }

    /// <summary>
    /// Index of the item (used for label)
    /// </summary>
    public int Index { get; set; }

    /// <summary>
    /// Row for the item (used for label)
    /// </summary>
    public int Row { get; set; }

    /// <summary>
    /// Column for the item (used for label)
    /// </summary>
    public int Col { get; set; }

    /// <summary>
    /// Left position relative to (0,0).
    /// </summary>
    public double Left
    {
        get { return _left; }
        set { SetPropertyValue(ref _left, value); }
    }

    /// <summary>
    /// Top position - changes when the Height of the GridView
    /// is altered (i.e. screen orientation)
    /// </summary>
    public double Top
    {
        get { return _top; }
        set { SetPropertyValue(ref _top, value); }
    }

    /// <summary>
    /// Width of this item
    /// </summary>
    public double Width
    {
        get { return _width; }
        set { SetPropertyValue(ref _width, value); }
    }

    /// <summary>
    /// Height to use for this item - calculated by the MainViewModel
    /// once it knows the actual height of the GridView using the Height %
    /// </summary>
    public double Height
    {
        get { return _height; }
        set { SetPropertyValue(ref _height, value); }
    }

    /// <summary>
    /// How much of the height to take up as a %
    /// </summary>
    public int HeightPercent
    {
        get { return _heightPercent; }
        set { SetPropertyValue(ref _heightPercent, value); }
    }

    /// <summary>
    /// This indicates if the cell is shared with another item in the
    /// same column/row (side-by-side)
    /// </summary>
    public bool IsSplitCell { get; set; }

    /// <summary>
    /// Returns a string that represents the current object.
    /// </summary>
    public override string ToString()
    {
        return string.Format("{0}: {1} ({2}x{3}) {4}% [{5},{6}]", Index, Color, Width, Height, HeightPercent, Col, Row);
    }
}

These properties are then calculated by the MainViewModel for each of the known colors – the position is randomly determined here just for example purposes, presumably in a real application (such as the Windows Store app) there would be an XML data file pulled from a server to display the data – but in any case there would be an known row/column and size for each element. Note there is a little code at the end of each loop iteration to ensure we always end the height of the column at 100%. The two important bits of code here are the ViewHeight property and the constructor which loads the items.

public sealed class MainViewModel : SimpleViewModel
{
    #region Data
    private readonly Random _rng = new Random();
    private double _viewHeight = Double.NaN, _viewWidth;
    private readonly List<ColorViewModel> _backingStore;
    #endregion

    /// <summary>
    /// This is set to the actual height of the panel 
    /// it then calculates the proper height and top of each item
    /// </summary>
    public double ViewHeight
    {
        get { return _viewHeight; }
        set
        {
            SetPropertyValue(ref _viewHeight, value);

            double top = 0;
            for (int index = 0; index < _backingStore.Count; index++)
            {
                var cvm = _backingStore[index];
                if (cvm.Row == 0)
                    top = 0;

                cvm.Height = _viewHeight*(cvm.HeightPercent/100.0)*.9;

                if (index > 0 && _backingStore[index - 1].IsSplitCell)
                {
                    cvm.Top = _backingStore[index - 1].Top;
                }
                else
                {
                    cvm.Top = top;
                    top += cvm.Height;
                }
            }
        }
    }

    /// <summary>
    /// The calculated width of the panel - this is required so we get scrollbars in the GridView.
    /// </summary>
    public double ViewWidth
    {
        get { return _viewWidth; }
        set { SetPropertyValue(ref _viewWidth, value); }
    }

    /// <summary>
    /// The list of colors
    /// </summary>
    public IList<ColorViewModel> Colors { get; private set; }

    /// <summary>
    /// Constructor
    /// </summary>
    public MainViewModel()
    {
        _backingStore = typeof(Colors).GetTypeInfo().DeclaredProperties
                                        .Select(p => new ColorViewModel { Color = p.Name })
                                        .ToList();

        int currentColumn = 0;
        double currentWidth = 0;

        // Calculate the position of each item. 
        for (int i = 0; i < _backingStore.Count; )
        {
            int columnWidth = _rng.Next(200, 500);
            int numberOfColors = _rng.Next(1, 7);
            int trackPct = 0;

            // Create a single column
            for (int c = 0; c < numberOfColors && trackPct < 95 && i < _backingStore.Count; i++, c++)
            {
                ColorViewModel cvm = _backingStore[i];
                cvm.Index = i + 1;
                cvm.Col = currentColumn;
                cvm.Row = c;
                cvm.Left = currentWidth;

                // Decide the height of this item.
                int maxH = Math.Min(100 - trackPct, 100/numberOfColors);
                int h = _rng.Next(15, maxH);

                // Allow it to share row with second item
                if (c > 0 && _backingStore[i-1].IsSplitCell)
                {
                    var previousCell = _backingStore[i - 1];
                    cvm.Left = previousCell.Left + previousCell.Width;
                    cvm.Width = previousCell.Width;
                    cvm.HeightPercent = previousCell.HeightPercent;
                    c--;
                }
                else
                {
                    trackPct += h;
                    cvm.HeightPercent = h;

                    if (c > 0 && numberOfColors > 2 && _rng.Next(4) == 1) // 1/4 chance
                    {
                        cvm.Width = (columnWidth / 2.0) - 5;
                        cvm.IsSplitCell = true;
                    }
                    else
                        cvm.Width = columnWidth;
                }
            }

            // Make sure we always end on 100%
            _backingStore[i - 1].HeightPercent += 100 - trackPct;
            _backingStore[i - 1].Width = columnWidth;
            _backingStore[i - 1].IsSplitCell = false;

            currentColumn++;
            currentWidth += columnWidth;
        }

        Colors = _backingStore;
        ViewWidth = currentWidth;
    }
}

The last piece of the puzzle is the XAML – in order to properly size the height, we need the actual height of the GridView itself, so the code behind hooks the SizeChanged event on the GridView and then passes the newly calculated size onto the MainViewModel:

private void OnPanelSizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e)
{
    // Populate DC after first resize so we don't see small-ish items on first render.
    if (DataContext == null)
    {
        DataContext = new MainViewModel();
    }

    // Set the new actual height
    MainViewModel vm = (MainViewModel) DataContext;
    vm.ViewHeight = e.NewSize.Height;
}

We also need to respect the Canvas.Left and Canvas.Top properties – this would normally be done in an ItemContainerStyle, but unfortunately setting attached properties is not currently supported. To compensate for this, we override the GridView and set our properties onto each ItemContainer in the PrepareContainerForOverride method:

/// <summary>
/// This is here just to create a binding for the Height/Width on the GridViewItem.
/// WinRT currently doesn't support attached properties in Style setters.
/// </summary>
public class VariableSizedGridView : GridView
{
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        BindingOperations.SetBinding(element, Canvas.LeftProperty, new Binding { Path = new PropertyPath("Left") });
        BindingOperations.SetBinding(element, Canvas.TopProperty, new Binding { Path = new PropertyPath("Top") });
        BindingOperations.SetBinding(element, WidthProperty, new Binding { Path = new PropertyPath("Width") });
        BindingOperations.SetBinding(element, HeightProperty, new Binding { Path = new PropertyPath("Height") });

        base.PrepareContainerForItemOverride(element, item);
    }
}

And, of course we replace the panel for the derived GridView with a Canvas:

<differentSizedTiles:VariableSizedGridView ItemsSource="{Binding Colors}" Grid.Column="1" Grid.Row="1">
            
    <GridView.ItemContainerStyle>
        <Style TargetType="GridViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="VerticalContentAlignment" Value="Stretch"/>
        </Style>
    </GridView.ItemContainerStyle>
            
    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas SizeChanged="OnPanelSizeChanged" Width="{Binding ViewWidth}" />
        </ItemsPanelTemplate>
    </GridView.ItemsPanel> 
            
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid Margin="5" ToolTipService.ToolTip="{Binding}">
                <Rectangle Fill="{Binding Color}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
                <Border Background="#30000000" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="40">
                    <TextBlock Text="{Binding}" Margin="10" Style="{StaticResource ItemTextStyle}" />
                </Border>
            </Grid>
        </DataTemplate>
    </GridView.ItemTemplate>
</differentSizedTiles:VariableSizedGridView>

Voila! Note that we still don’t have UI virtualization – it turns out that you currently cannot create a custom virtualized panel, the support isn’t exposed yet in WinRT. But even still, the performance is easily 100x better than the VariableSizeGridPanel. The code is slightly more complex, but not overly so. Here’s the final project if you want to play with it yourself. Have fun!

.NET Bio 1.1 alpha available now

One of the open source projects I’m actively involved in is a bioinformatics library for .NET called, appropriately enough, .NET Bio. You can check it out at bio.codeplex.com. We have just put out the alpha version of the next release for community testing – this has several significant changes in it:

  1. A new (more standard) implementation of the Smith-Waterman alignment algorithm.
  2. A new (more standard) implementation of the Needleman-Wunsch alignment algorithm.
  3. Several improvements to the Parallel DeNovo assembler (PADENA)
  4. Changes to the NUCmer pairwise aligner to evaluate both the forward and reverse strands of the query sequence(s).
  5. Better file compatibility with several standardized formats.
  6. and lots of little performance tuning and bug fixes throughout.

Biology is one of those areas of science which needs high performance algorithms and computing power – we utilize the .NET Parallel Framework (PFx) all over the place to attempt to improve algorithmic performance and provide some great tools for analysis of biological sequences! Check it out at .NET Bio 1.1 Alpha Download

Synchronizing Collections in Windows Store Apps

One issue that often comes up when building WSAs is managing selection in your filled/snapped/full screen views.  The goal, of course, is to provide a reasonable view as the user snaps your application which is often done by providing two UIs within the page – one with a GridView for filled or full screen and the second with a ListView for snapped view.  You hide and show the two views using the Visual State Manager when using the normal templates.

If selection is provided, then ideally it would be carried over when changing views – this actually isn’t hard – it can certainly be done manually and if you only allow a single selection it’s dead easy even with MVVM.  Multi-selection is a bit trickier.  Enter the SynchronizedCollectionBehavior in MVVMHelpers.  It allows the UI to bind a collection of “selected items” to a collection in the ViewModel – so the ViewModel can track and alter the selection, but more importantly you can bind it to multiple UI things and they all track the same selection automatically.

There are two ways to activate the behavior – first is through the normal Blend framework -

 

<ListView ItemsSource="{Binding Names}" SelectionMode="Multiple" HorizontalAlignment="Stretch" Margin="20">
    ...            
    <Interactivity:Interaction.Behaviors>
       <Interactivity1:SynchronizedCollectionBehavior 
                  Collection="{Binding SelectedNames}" />
    </Interactivity:Interaction.Behaviors>
</ListView>

Here the ViewModel is exposing an ObservableCollection of names (called SelectedNames) to manage the selected items. The actual collection (also string names) is just Names:

public IReadOnlyList<string> Names { get; private set; }
public IList<string> SelectedNames { get; private set; }

public MainViewModel()
{
    Names = new List<string>
                {
                    "Alice", "Bob", "Carol", "David", "Edgar", "Frank",
                    "Georgia", "Hank", "India", "Jack", "Karen", "Larry",
                    "Mike", "Nate", "Oscar", "Peter", "Quix", "Russ", "Steve",
                    "Tonya", "Uma", "Violet", "Walter", "Xi", "Yvonne", "Zed"
                };
    SelectedNames = new ObservableCollection<string>();
   ...
}

The second activation mechanism is through a normal attached property –

<GridView ItemsSource="{Binding Names}" Grid.Column="1"  Grid.Row="1"
          SelectionMode="Extended" HorizontalAlignment="Stretch" Margin="20"
          Interactivity1:SynchronizedCollectionBehavior.IsEnabled="{Binding SelectedNames}">

This performs exactly the same action but allows it to be done as an attribute.

Here is a test program which shows connecting it up to multiple ItemsControls and even changing the selection styles between them.

SynchronizedList

Navigation and Persistence with MVVM in Windows Store Apps take #2

Earlier I posted on an updated version of MVVMHelpers with persistence support (see here). Paulo Quicoli, a long-time MVVMHelpers contributor, sent me a little code and a different way of managing navigation and persistence that he’s been using. I thought it was quite elegant and asked if I could include a version in the library, which he graciously agreed to.  The result will be in the next release of the library – but I thought I’d introduce it here.

The idea is to use a serializer on the ViewModel so that it is always passed as the parameter in navigation – in this case, as a string.  The code Paulo provided was a simple use of the DataContractJsonSerializer to turn an object into a Json string – which we can then pass through the normal navigation APIs so it gets captured into the navigation stack automatically.  In this scenario, we are doing ViewModel-first – we’ll create the ViewModel and then navigate to the view – passing the view model as the navigation parameter.

To accomplish this, two new classes were added into MVVMHelpers – both optional.  The first is the JulMar.Windows.Serialization.Json static class which looks like:

public static class Json
{
    ///
<summary> /// This method serializes an object or graph into a JSON string
 /// </summary>
    ///Instance to serialize
    /// String
    public static string Serialize(object instance);

    ///
<summary> /// This takes a JSON string and turns it into an object or graph.
 /// </summary>
    /// Type
    ///JSON string
    /// Object graph
    public static T Deserialize(string stream);

    ///
<summary> /// This takes a JSON string and turns it into an object or graph.
 /// </summary>
    ///Type
    ///JSON string
    /// Object graph
    public static object Deserialize(Type type, string stream);
}

This class is just a slim wrapper around the serializer to take an object and turn it into a string and vice-versa.  Note that the object must be serializable – which as of the last public release, the base ViewModel classes are.

The second class is an implementation of the IPageNavigator interface which supports the above serialization.  It does several things:

  1. It uses the NavigateTo method which takes a page key (or type) and single parameter, or a parameter and ViewModel (in which case, the parameter is ignored).
  2. It serializes the parameter/ViewModel into a string and passes that to the normal frame navigation API.
  3. When the page is navigated TO, the string is de-serialized back into a ViewModel and set as the DataContext.
  4. It properly handles suspension/resumption by saving/restoring the navigation stack and deserializing the ViewModel (if available) from the parameters back to the current page.
It’s easy to use, although is not the default navigation provider – so there is a setup step that needs to be performed in order to use this new page navigator.  Specifically, in the startup sequence of the application (typically, the Application constructor in App.xaml.cs) you will need to add the highlighted line to replace the default page navigator:
public App()
{
    ServiceLocator.Instance.Add(typeof(IPageNavigator), new AutoSerializingPageNavigator());

    this.InitializeComponent();
    this.Suspending += OnSuspending;
}

You still need to add the support to save/restore the state in your OnLaunched and Resuming events as well, this is the same code as the normal page navigator:

protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
    IPageNavigator pageNavigator = ServiceLocator.Instance.Resolve();
    Frame rootFrame = Window.Current.Content as Frame;

    if (rootFrame == null)
    {
        rootFrame = new Frame();
        Window.Current.Content = rootFrame;

        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            await pageNavigator.LoadAsync();
        }
    }
    ...
}

private async void OnSuspending(object sender, SuspendingEventArgs e)
{
    IPageNavigator pageNavigator = ServiceLocator.Instance.Resolve();
    var deferral = e.SuspendingOperation.GetDeferral();

    await pageNavigator.SaveAsync();

    deferral.Complete();
}

Next, the ViewModel’s must participate in this and be properly serializable – that means saving all the appropriate state by marking the properties with [DataMember] or with [IgnoreDataMember] based on whether you decorate the type with [DataContract] or not.  Also, remember that when you use [DataContract] the default constructor is not run – that means putting initialization logic into an [OnDeseralizing] method.  This is the primary reason I chose not to make this approach the default as it puts a bit of a burden onto the ViewModel – but once this is done, everything else is taken care of:

  • It will navigate and setup the DataContext automatically
  • It will properly save/restore the state on suspension/resumption

Here’s an example of a simple color view model:

[DataContract]
public sealed class ColorViewModel : ViewModel
{
    [DataMember]
    public string Color { get; set; }

    public IDelegateCommand GoBack { get; private set; }

    public ColorViewModel()
    {
        Initialize(new StreamingContext());
    }

    [OnDeserialized]
    void Initialize(StreamingContext context)
    {
        GoBack = new DelegateCommand(() => Resolve<IPageNavigator>().GoBack(), () => Resolve<IPageNavigator>().CanGoBack);
    }

    public ColorViewModel(string color) : this()
    {
        Color = color;
    }
}

And here’s the logic to navigate to this view model:

[DataContract]
public sealed class MainViewModel : ViewModel
{
    [Import]
    public IPageNavigator PageNavigator { get; set; }
    public IList<ColorViewModel> Colors { get; private set; }
    public IDelegateCommand SelectSpecificColor { get; private set; }

    public MainViewModel()
    {
        Initialize(new StreamingContext());
    }

    [OnDeserializing]
    void Initialize(StreamingContext context)
    {
        SelectSpecificColor = new DelegateCommand<ItemClickEventArgs>(OnSelectColor);
        Colors = new ObservableCollection<ColorViewModel>(
            typeof(Colors).GetTypeInfo().DeclaredProperties.Select(pn => new ColorViewModel(pn.Name)));
    }

    private void OnSelectColor(ItemClickEventArgs e)
    {
        PageNavigator.NavigateTo("OneColorView", e.ClickedItem);
    }
}

In addition, the Page and ViewModel can still participate in the INavigationAware interface – the new page navigator will still invoke it.  So this new capability allows for an alternative mechanism for managing page state and navigation – I expect to release it early next week! If you are interested in the full sample, check out the Source Code and look at the sample test project AutoSerializingNavigationTest.