Dailycode.info

Short solution for short problems

Horizontal topnavigation with MVVM and WPF

 

When I was trying to set up a nice framework project that could handle my initial small media management project, I bumped into several designed issues. It was not so easy to find solutions, since I had a hard time looking for the correct Google words to get the info I needed.

The small project is to become a media database where you can insert your media, control it and even manage lend outs. I wanted it to have a top navigation bar and a side navigation. This article is about the top navigation. I’m implementing the MVVM design pattern, that’s what makes it a little more difficult and that’s exactly why I’m posting now.

I made a usercontrol called TopBarButton, which should be used to do the top navigation. Then I made a topcommandview model which exposes the properties the buttons will need to bind to.

This class looks like this:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows.Input;

 

namespace MyMediaControl.ViewModel

{

    /// <summary>

    /// Represents an actionable item displayed by a View.

    /// </summary>

    public class TopCommandViewModel : ViewModelBase

    {

        private string _imageSource;

 

        public string ImageSource

        {

            get { return _imageSource; }

            set { _imageSource = value; }

        }

 

 

        public TopCommandViewModel(string displayName, ICommand command, string imageSource)

        {

            if (command == null)

                throw new ArgumentNullException("command");

 

            base.DisplayName = displayName;

            this.Command = command;

            this._imageSource = imageSource;

        }

 

        public ICommand Command { get; private set; }

    }

}

 

The image source property is to show an image on my button.

Last I need to create a collection in my main window view and bind them to the top navigation bar.

In my main window view model I create a read only collection of TopCommendViewModel.

/// <summary> 

/// Returns a read-only list of commands  

/// that the UI can display and execute.

/// </summary>

public ReadOnlyCollection<TopCommandViewModel> TopCommands

{

    get

    {

        if (_topCommands == null)

        {

            List<TopCommandViewModel> cmds = this.CreateTopCommands();

            _topCommands = new ReadOnlyCollection<TopCommandViewModel>(cmds);

        }

        return _topCommands;

    }

}

 

Here I create the TopCommandViewModel list and you can see I pass 3 parameters to it. First the title of the button coming from a resource file called Strings, second the function to execute and third the image path for the button.

 

List<TopCommandViewModel> CreateTopCommands()

{

    return new List<TopCommandViewModel>

    {

        new TopCommandViewModel(

            Strings.MainWindowViewModel_Command_OpenAudio,

            new RelayCommand(param => this.OpenAudioCommands()),"/Images/audio_icon_120.gif"),

 

        new TopCommandViewModel(

            Strings.MainWindowViewModel_Command_OpenVideo,

            new RelayCommand(param => this.OpenVideoCommands()),"/Images/movie_icon_120.gif"),

        new TopCommandViewModel(

            Strings.MainWindowViewModel_Command_OpenBook,

            new RelayCommand(param => this.OpenBookCommands()),"/Images/books_icon_120.gif")

    };

}

 

When this is done I still need to do the binding in my View. For this we need to work in 2 places. First place is a resource file which is linked to my main window. XAML and next I will add a content presenter to the XAML of my main window.

Here is the code for the data template which can be found in the resource file, this is the code as I where I started with, later I will add something extra to show you the result:

<DataTemplate x:Key="TopCommandsTemplate" >

        <ItemsControl IsTabStop="False" ItemsSource="{Binding}" Margin="0" >

            <ItemsControl.ItemTemplate>

                <DataTemplate>

                    <my:TopNavButton Text="{Binding Path=DisplayName}" Image="{Binding Path=ImageSource}"

                    ImageWidth="70" ImageHeight="70" Margin="5" ></my:TopNavButton>

                </DataTemplate>

            </ItemsControl.ItemTemplate>

        </ItemsControl>

    </DataTemplate>

 

Last we define the place where we want the navigation buttons to show up:

<Canvas Height="100" Name="spTopNav"  Width="Auto" MinWidth="400" Style="{StaticResource MainNAVStyle}" Margin="0">

    <ContentPresenter Canvas.Left="0" Canvas.Top="0"

        Content="{Binding Path=TopCommands}"

    ContentTemplate="{StaticResource TopCommandsTemplate}"

    HorizontalAlignment="Left" VerticalAlignment="Stretch"

    />

</Canvas>

 

Now here how this all looks like in design time view:

TopNavBarDesign.jpg

So at this time when I run the project, it looks like this:

TopNavBarNOK.jpg

If you look closely, you will discover that just below the first top navigation button, the second one is rendered but not visible or usable for the user. I tried to find a solution and had to do a lot of google searches until I ended up with the solution, using the items panel template.

So I added this to the data template of my topcommandsbutton template:

<DataTemplate x:Key="TopCommandsTemplate" >

    <ItemsControl IsTabStop="False" ItemsSource="{Binding}" Margin="0" >

        <ItemsControl.ItemTemplate>

            <DataTemplate>

                <my:TopNavButton Text="{Binding Path=DisplayName}" Image="{Binding Path=ImageSource}"

                ImageWidth="70" ImageHeight="70" Margin="5" ></my:TopNavButton>

            </DataTemplate>

        </ItemsControl.ItemTemplate>

        <ItemsControl.ItemsPanel>

            <ItemsPanelTemplate>

                <StackPanel Orientation="Horizontal"/>

            </ItemsPanelTemplate>

        </ItemsControl.ItemsPanel>

    </ItemsControl>

</DataTemplate>

 

Now it will show the items in a stackpanel which is oriented horizontally. Now it looks like this:

TopNavBarOK.jpg



blog comments powered by Disqus