OpenLightGroup Blog

rss

Blogs from OpenLightGroup.net


HisowaModPopUpBehavior with de Oliveira Menu

This tutorial will take you through the steps necessary to add a Menu to a Silverlight UserControl and attach a HisowaModPopUpBehavior and make them work together.

image

The advantages of Marcelo Ricardo de Oliveira’s Silverlight Menu:

1. It’s easy to implement.

2. It’s easy to understand.

3. It’s easy to customize.

4. You can specify an ICommand to be called when any selection is clicked and determine the selection within the ICommand implementation.

5. You can catch a MenuItemClicked event and get the selected menu item in the event handler.

The advantages of Haruhiro Osawa’s HisowaModPopUpBehavior:

1. You can define your own UserControl to be presented as the UI of a Popup Window, composed of View and ViewModel.

2. You can customize the frame of the Popup Window through behavior properties.

3. You can trigger the Popup Window as you would any other behavior, such as on the MenuItemClicked event.

4. You can specify an ICommand implementation to be called when the user exits the window

5. You can access the ViewModel of your Custom User Interface to determine the user’s selection and of course whether the user accepted or declined.

Other Acknowledgements / References:

Josh Smith for the RelayCommand helper class.

Code-Free Zones and Silverlight Class Libraries

I like the idea of keeping all the C# code except for the mandatory code-behind in one or more separate class libraries.

Therefore, our solution will consist of

  • A hosting web application project
  • The main Silverlight project  with
  • A Silverlight Class Library project

If you need to see how the solution was put together, or if you’re just curious all the steps are described in a separate article.

Views and ViewModels

We’ve added a reference to MenuPopupViewModel.dll. Now we’ll create a MainPageViewModel class, build the MenuPopupViewModel project and find out if we can see the class when we open MainPage.xaml in Blend.

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace MenuPopupViewModel
{
    public class MainPageViewModel
    {
    }
}

And sure enough, there it is. To add MainPageViewModel as the DataContext for MainPage.UserControl

  1. Select UserControl in Objects and Timeline
  2. From Properties/Common Properties click on the New button next to DataContext.
  3. From the Select Object dialog, select MainPageViewModel.

image

So far, so good. Our next challenge is to create the menu class in MenuPopupViewModel. Strictly speaking this class should go in a separate assembly since it’s not really part of the ViewModel. As you’ll see, Menu inherits from Grid, so it’s a UIElement, but if we want to maintain MenuPopUp as a code-free zone it should live in a class library, and for convenience we’re leaving it in MenuPopupViewModel.dll. Here’s how the project looks after adding a Classes folder, the two Silverlight Menu files and a helper class that’s included with the Silverlight Menu sample project. The helper class, RelayCommand, is from Josh Smith and is described on Patrick Cauldwell's Blog.

image

After we build MenuPopupViewModel and go back to Blend we look at Assets/Locations/MenuPopupViewModel.csproj and see that we now have a Menu control available.

image

So you can drag it from here to Layout Root (which has an annoying habit of refusing to work on the first try), or double-click it here to pin it to the tool panel and then double-click to add it to the currently selected control. I chose to drag it, and on the second try it worked:

image

Out of the box, Menu is placed in the upper-left corner of LayoutRoot and is sized at 100 x 100.

image

And although you can see the Menu control in Blend, when you run the app you get a blank screen, actually the whole page is a blank white page. That’s because the default settings cause MainPage to expand to fill the browser page.

For some reason Menu doesn’t appear, but I don’t want MainPage to fill its container anyway. I want it to be a fixed size, so I’m going to hard-code it to 800 x 600 and leave LayoutRoot set to stretch, and when I run it I see a blank menu along the top.

Adding The MenuItems

Now let’s add some selections to our menu in the form of MenuItems. As you might expect, the MenuItem collection will be in MainPageViewModel.

private MenuItem _MVVMMenuItem;
public MenuItem MVVMMenuItem
{
    get { return this._MVVMMenuItem; }
    set
    {
        if (this._MVVMMenuItem != value)
        {
            this._MVVMMenuItem = value;
            this.NotifyPropertyChanged("MVVMMenuItem");
        }
    }
}

On the face of it this is not a collection of MenuItems. But if we look at the actual MenuItem class…

// Author of MenuItem Class: Marcelo Ricardo de Oliveira  http://www.codeproject.com/KB/silverlight/SilverlightMenu.aspx
namespace MenuPopupViewModel
{
    [ContentProperty("MenuItems")]
    public class MenuItem : ObservableCollection<MenuItem>
    {

…. we see that each MenuItem is a collection of MenuItems. This is slightly confusing to Blend as we’ll see later when we try to bind to the Name property of the Selected MenuItem.

So, back in MainPageViewModel we initialize MVVMMenuItem in the constructor:

public MainPageViewModel()
{
    // Menu Stuff
    this.MVVMMenuItem = new MenuItem() { Name = "Root" };
    var mnuFile = new MenuItem() { Name = "mnuFile", Text = "File" };
    var mnuTools = new MenuItem() { Name = "mnuTools", Text = "Tools" };
    var mnuHelp = new MenuItem() { Name = "mnuHelp", Text = "Help" };
    var mnuExit = new MenuItem() { Name = "mnuExit", Text = "Exit" };
    var mnuThemes = new MenuItem() { Name = "mnuThemes", Text = "Themes" };
    var mnuAbout = new MenuItem() { Name = "mnuViewHelp", Text = "About Silverlight Menu" };
    mnuFile.Add(mnuExit);
    mnuTools.Add(mnuThemes);
    mnuHelp.Add(mnuAbout);
    MVVMMenuItem.Add(mnuFile);
    MVVMMenuItem.Add(mnuTools);
    MVVMMenuItem.Add(mnuHelp);
}

Now we have to bind the collection to the menu. You might think you could drag it:

Don’t Do This!!!!

image

But every time I tried Blend crashed. So you have to select the Advanced Option Icon next to the MenuItem property Unfortunately, when you do so, you only get these two options.

image

Go with Custom Expression… and enter:

image

You should use Custom expression instead of editing the xaml directly because it will validate what you enter, and that’s the only place it gets validated. If you enter it directly and spell it wrong Blend will continue to show the gold border and the data type, such as ‘(Collection)’ in the screen shot above – and the binding will fail silently.  

However, even though you should always use it, sometimes Custom expression rejects a valid expression, so in that case you have no choice but to edit the xaml (arrrgh):

<Grid x:Name="LayoutRoot" Background="#FF588131">
    <MenuPopupViewModel:Menu x:Name="menu" MenuItem="{Binding MVVMMenuItem}"

And that’s it, we’re good to go!!

image

We’re missing an image but that’s ok for now. What we want to do now is to do something when the menu clicks on a menu item. To start with, we’ll create an ICommand implementation to be called when a menu item is clicked:

public ICommand MenuCommand
{
    get { return new RelayCommand(p => DoMenuCommand(p)); }
}
public void DoMenuCommand(object param)
{
    var menuItem = (MenuItem)param;
    MessageBox.Show(string.Format("You clicked: {0}", menuItem.Name));
}

Note that MenuCommad is a property that references an ICommand implementation. It does not use a backing store, instead get calls the RelayCommand constructor passing DoMenuCommand as the Action<object> argument and returns the resulting ICommand object. If you’re not familiar with the syntax, a peek at the RelayCommand constructor might help. Notice there’s an overload that allows you to pass in a canExecute Predicate.

//RelayCommand Implementagion by Josh Smith, as described here:
//http://www.cauldwell.net/patrick/blog/MVVMBindingToCommandsInSilverlight.aspx
//...
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
    : this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
    if (execute == null)
        throw new ArgumentNullException("execute");
    _execute = execute;
    _canExecute = canExecute;
}

When the ICommand is actually invoked we see that parameter is passed to the Action originally passed into the constructor, in this case DoMenuCommand. We could have added a Predicate to validate that parameter is a MenuItem, but in this case we’re trusting the caller.

#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
    return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
    _execute(parameter);
}
#endregion // ICommand Members

Ok, we have our ICommand and we know what happens when it gets called. Now we need to make Menu call it when a menu item is clicked. For that we again have to resort to Custom expression. Set the menu Command property to bind to MenuCommand as seen below.

 image 

And once again Custom expression may refuse a valid expression, so here’s the raw xaml:

<Grid x:Name="LayoutRoot" Background="#FF588131">
    <MenuPopupViewModel:Menu x:Name="menu" MenuItem="{Binding MVVMMenuItem}" Command="{Binding MenuCommand}" />
</Grid>

That single expression is all menu needs to invoke the command, because when we try it out we see that DoMenuCommand does get called:

image

Here’s how it works. Command is a Menu DependencyProperty:

CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(Menu),
new PropertyMetadata(null,
new PropertyChangedCallback(OnCommandChanged)));

You can see that a callback has been defined that will be called when the property changes. And here it is:

public void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  command = (ICommand)e.NewValue;
}

And here’s the actual property.

public ICommand Command
{
   get { return (ICommand)GetValue(CommandProperty); }
   set { SetValue(CommandProperty, value); }
}

Step by step:

  1. MainPage.xaml causes Dependency Property Menu.Command to change by binding it to MainPageViewModel.MenuCommand.
  2. That change of Menu.Command causes OnCommandChanged() to be called, passing MainPageViewModel.MenuCommand as the event argument property NewValue.
  3. OnCommandChanged() sets Menu.command to NewValue so it now references MainPageViewModel.MenuCommand().
  4. When a menu selection is clicked Menu, in the event handler shown below,  calls command.Execute() passing MenuItem as the argument.
  5. When RelayCommand constructs the ICommand implementation that is returned by the MenuCommand property getter, it sets its internal  Action field,_execute to point to DoMenuCommand()
  6. The result is that when Menu.command.Execute() calls _executeMainPageViewModel.DoMenuCommand()  is called with the selected MenuItem as the argument.
        void gridLevel2_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            // ...
            if (command != null)
                command.Execute(menuItem);
        }

Unfortunately that’s not sufficient for our needs. When the user clicks on Themes, we want a Themes Selection Popup window to appear. We need a user interface and that’s not handled very conveniently through the ViewModel.

And of course that’s where HisowaModPopUpBehavior comes in.

  1. We attach the behavior to  menu and trigger it on MenuItemClicked
  2. We define our own UserControl, ThemeSelectionView that we want to appear within the popup along with ThemeSelectionViewModel.
  3. We define the ICommand. InstallThemeCommand that we want to be called when the user clicks the Install button.

We add it to MenuPopupViewModel classes and rebuild the MenuPopupViewModel project.

Back in Blend, under Assets/Behaviors we now find HisowaModPopUpBehavior.

image

Drag the behavior onto menu:

image

Now if we look under HisowaModPopupBehavior Properties at the EventName dropdown we find that menu supports a MenuItemClicked event, so we go with that:

image

And remove the Menu Command binding:

image

Now if we click on any menu item we see this:

image

We haven’t supplied any content, so all we see are the Yes and No buttons. Also, the behavior is the same for all menu items so we need to find a way to discriminate between them.

Fortunately, behaviors support conditions. Add a comparison condition and bind the LeftOperand value:image

Bind to menu/SelectedMenuItem.Name:

image

This is where Blend gets confused. If you look at the value in Use a custom path expression you’ll see that SelectedMenuItem is indexed. But SelectedMenuItem is not a collection. I think Blend is confused because MenuItem itself inherits from ObservableCollection. At any rate, you need to remove the ‘[0].’ from the string so it says SelectedMenuItem.Name. Now enter mnuThemes for the second Value:

image

You should now find that the popup only appears if you click on Tools/Themes.

A Better Way That Avoids String Comparisons

However, I very much dislike hard-coded strings, and in particular comparisons of hard-coded strings, so let’s look at a better way. We start by adding a public MenuItem property named ThemesMenuItem in MainPageViewModel:

private MenuItem _ThemesMenuItem;
public MenuItem ThemesMenuItem
{
    get { return this._ThemesMenuItem; }
    set
    {
        if (this._ThemesMenuItem != value)
        {
            this._ThemesMenuItem = value;
            this.NotifyPropertyChanged("ThemesMenuItem");
        }
    }
}

Yeah, there’s a hard-coded string. But I have a snippet that allows me to type in ThemesMenuItem only once when creating the property, so I’d have to work at it to make the Property name that shows up in Blend different from the string I’m passing to NotifyPropertyChanged.  Hard-coded strings are inevitable in a binding-centric world and using them in calls to NotifyPropertyChanged just doesn’t bother me as much as the workarounds.

Now we initializeour new ThemesMenuItem and add it to the menu in the MainPageViewModel constructor:

// Old
//var mnuThemes = new MenuItem() { Name = "mnuThemes", Text = "Themes" };
//mnuTools.Add(mnuThemes);
//New
ThemesMenuItem = new MenuItem() { Name = "mnuThemes", Text = "Themes" };
mnuTools.Add(ThemesMenuItem);

Instead of comparing the Name of the Selected MenuItem to a hard-coded string, we can now compare MenuItem with MenuItem:

Bind the first value to SelectedMenuItem instead of SelectedMenuItem.Name. This has the additional benefit that we don’t have to edit the string to get rid of “phantom indexing”, so you can uncheck Use a custom path expression:

image

Bind the second value to the ThemesMenuItem property in the MainPageViewModel instead of entering the string mnuThemes.

image 

Don’t be surprised when mnuThemes continues to appear. Apparently Blend chooses to display the Name property. Here’s the xaml that proves we’re binding to the object:

<ei:ComparisonCondition LeftOperand="{Binding SelectedMenuItem, ElementName=menu}" RightOperand="{Binding ThemesMenuItem}"/>

You should get the same results when you click on Themes, but now we’re comparing two objects, MainPageViewModel.ThemesMenuItem with Menu.SelectedMenuItem, which is kind of a big deal in general. The developer can’t break it by changing the Name of the MenuItem. The designer can’t break it by changing the comparison string. Actually, I’m going to do just that (change the value of Name) because I want to show you that that Blend will show the new Name in the second comparison value:

In MainPageViewModel I changed the name and rebuilt the solution:

ThemesMenuItem = new MenuItem() { Name = "mnuThemesName", Text = "Themes" };

The menu continues to work properly and now we see the new name in Blend:

image

I don’t know the criteria in Blend for displaying a property name as an object identifier, but apparently a string property named “Name” qualifies.

Creating the PopUp user interface

The genius of HisowaModPopUpBehavior lies in:

  1. Not only does it allow to plug in a user interface of your own design, with its own ViewModel,
  2. You can also specify an ICommand to handle the results.

If the user clicks on Themes, I want to show them a list of available themes from which they can select. If they click the Install button I want the method InstallTheme() to be called with the selected Theme object as the argument. InstallTheme will show a MessageBox stating “Installing theme <themeName>”.

First let’s create the user interface we want to be presented by the popup. We’ll start with the view, ThemeSelectionView, in the MenuPopup Silverlight project.

I’m going to make the size of the ThemeSelectoinView UserControl 300 x 200 and LayoutRoot will expand to fill it. LayoutRoot will have a single column and two rows, one for the header and one for the list of Theme Names.

I created a new folder named Views in the MenuPopUp project, right-clicked on it and selected add new Item. From the resulting New Item dialog, choose UserControl and name it ThemeSelectionView.xaml:

image

You can’t select UserControl with ViewModel because all our ViewModels come from MenuPopupViewModel.dll.

The details of creating the UserControl that will be used as the Custom User Interface within the popup appear in a separate blog on Blend and Margins.

image

Since the next thing we need is something to show in the list, let’s return to the ViewModel, in this case ThemeSelectionViewModel which we need to create in the MenuPopupViewModel class library. Then we’ll add an ObservableCollection the designer can bind to the ListBox. But to have an ObservableCollection we need something to put in it, so let’s create a Theme class with the single property Name.

namespace MenuPopupViewModel
{
    public class Theme
    {
        public Theme(string name)
        {
            Name = name;
        }
        public string Name { get; set; }
    }
}

Now we can create a collection of Themes in ThemeSelectionViewModel:

        private ObservableCollection<Theme> _ThemesList;
        public ObservableCollection<Theme> ThemesList
        {
            get { return this._ThemesList; }
            set
            {
                if (this._ThemesList != value)
                {
                    this._ThemesList = value;
                    this.NotifyPropertyChanged("ThemesList");
                }
            }
        }

To keep things separate, I added a Models folder to MenuPopupViewModel project and created a MenuPopUpModel class that provides a GetThemes() method:

namespace MenuPopupViewModel
{
    public class MenuPopUpModel
    {
        #region GetThemes
        public static ObservableCollection<Theme> GetThemes()
        {
            ObservableCollection<Theme> colThemes = new ObservableCollection<Theme>();
            colThemes.Add(new Theme("Middle Earth"));
            colThemes.Add(new Theme("World of Warcraft"));
            colThemes.Add(new Theme("Farmville"));
            colThemes.Add(new Theme("Simpsons"));
            colThemes.Add(new Theme("South Park"));
            colThemes.Add(new Theme("American Dad"));
            colThemes.Add(new Theme("Call To Duty"));
            colThemes.Add(new Theme("Warehouse 13"));
            return colThemes;
        }
        #endregion
    }
}

And call it from the ThemeSelectionViewModel constructor:

        public ThemeSelectionViewModel()
        {
            ThemesList = MenuPopUpModel.GetThemes();
        }

Now we need to bind ThemesList to the ListBox, but first we need to set ThemeSelectionView’s DataContext to ThemeSelectionViewModel:

image

Now we have access to ThemeSelectionViewModel.ThemesList, so drag it onto ListBox:

image

You should immediately see this:

image

Now we bind HisowaModPopUpBehavior.CustomUI to ThemeSelectionView. Notice we’re not pointing it to an object that’s already created, we’re pointing it to a class:

image

When you run the app and select Themes you should see this:

image

Ok, now we’re getting somewhere. We want to change the button captions which is simple:

image

… and we want to know what was selected if the Install button is clicked.

First we add a SelectedTheme property to ThemeSelectionViewModel:

        private Theme _SelectedTheme;
        public Theme SelectedTheme
        {
            get { return this._SelectedTheme; }
            set
            {
                if (this._SelectedTheme != value)
                {
                    this._SelectedTheme = value;
                    this.NotifyPropertyChanged("SelectedTheme");
                }
            }
        }

Now Bind to ListBox SelectedItem. Click on the Advanced Options icon next to SelectedItem as shown below and select Data Binding… from the dialog that appears:

image

Because the ListBox SelectedItem is used to set the value in the bound-to property, we set the binding to TwoWay:

image

So now we’ve established a property in ThemeSelectionViewModel that will always be bound to the currently selected theme. Next we add an ICommand that HisowaModPopUpBehavior will call when the user selects Install:

       public void InstallTheme(object param)
        {
            HisowaModPopUpBehaviorResult _result = param as HisowaModPopUpBehaviorResult;
            if (_result.DialogResult.HasValue && _result.DialogResult.Value)
            {
                ThemeSelectionViewModel vm = _result.DataContext as ThemeSelectionViewModel;
                if (vm.SelectedTheme != null)
                    MessageBox.Show("Installing Theme " + vm.SelectedTheme.Name);
            }
        }

Now we’re going to bind that ICommand to HisowaModPopUpBehavior property ReturnICommand. But we’re going to do it in a shortcut way I didn’t expect to work. From the DataContext Panel drag MainPageViewModel.InstallThemeCommand to HisowaModPopUpBehavior in the Objects and Timeline panel. You should see the message window “Data bind [HisowaModPopUpBehavior] to InstallThemeCommand” as shown below:

image

Drop the ICommand on the behavior and select ReturnICommand from the dropdown that appears:

image

And sure enough:

image

I did add one more little detail. If the ListBox items collection is not empty, I want the first item to be selected when the ListBox is loaded. I was going to add an ICommand, but decided a behavior would be better:

I’ll be adding the new behavior class, SelectFirstListBoxItem to MainPageViewModel. Visual Studio doesn’t provide Behavior as a New Item Selection, so in Blend I right-click on the Classes folder within the MenuPopupViewModel project, select ”Add New Item” and see the following dialog:

image

Aside from removing all the comments, all you have to do is change one line of code and add about 4.

First we want to change:

	public class SelectFirstListBoxItem : Behavior<DependencyObject>

to:

    public class SelectFirstListBoxItem : TargetedTriggerAction<ListBox>

TargetedTriggerAction supports the Invoke method allowing us to add:

        protected override void Invoke(object parameter)
        {
            ListBox lb = AssociatedObject as ListBox;
            if (lb != null && lb.Items.Count > 0)
                lb.SelectedIndex = 0;
        }

Ok, more than 4 lines. I could have crammed into 4, but 6 is not bad.

if you’re not familiar with behaviors, I want to point out the power that the above implies:

If the behavior knows the type of its AssociatedObject it can access all the object’s public methods and properties.

  1. If it knows the type of the object’s ViewModel, it can access all the ViewModel public methods and properties.
  2. You could have controls that are driven entirely by their Behaviors based on properties set or bound by the designer.
  3. If you put interface references on the back end (in the ViewModel) so everything can be swapped out you’ve essentially given the object infinite flexibility.

Let’s say I’m a Pursuit behavior:

  1. I am attached to a Predator object.
  2. The designer sets or binds properties such as speed, stamina, intelligence, strategies.
  3. I monitor Predator properties / subscribe to Predator events, to detect a condition that should trigger a pursuit.
  4. When that happens, I call methods on the Predator to actually perform the pursuit based on my properties, whose values change as the pursuit goes on.
  5. The methods in step 4 are backed by interface references whose implementations can be swapped out by Predator’s handler (the ViewModel), even in the midst of the pursuit, based on terrain changes, etc.
  6. Extend this model to all Predator’s capabilities and Predator becomes an engine entirely driven by Behaviors.

Hey, I just came up with the plot for ‘Avatar’.

Attaching The SelectFirstListBoxItem Behavior

After building MenuPopupViewModel, the behavior will be available in Blend:

image

Drag it onto the ListBox:

image

The behavior SourceObject is already set to listBox because it’s attached to listBox. Select Loaded from the EventName dropdown to bind the Loaded event to the behavior Invoke method.

image

Before and after:

image image

DownloadCode:

http://richardwaddell.adefwebserver.com/MenuPopup.zip





Comments are closed.
Showing 1 Comment
Avatar   5 years ago

OpenLightGroup.net >Blog - HisowaModPopUpBehavior with de Oliveira Menu
<a href="http://www.stuffpit.com/profile/buckmcelroy820" target="_blank"># anti-ageing q10 plus day cream</a>