Posted by: Zeeshan Amjad | October 11, 2011

Make Selected Item Visible in MVVM


I came across one problem where the selected item should be visible in a listbox using MVVM. To make selected item visible is very much UI specific code and it should not be a part of ViewModel class. Here is a piece of code that make the selected item visible in Listbox using reflection (taken from here).

Code Snippet
VirtualizingStackPanel vsp = (VirtualizingStackPanel)typeof(ItemsControl).InvokeMember(
    "_itemsHost", BindingFlags.Instance | BindingFlags.GetField | BindingFlags.NonPublic, null, list, null);

double scrollHeight = vsp.ScrollOwner.ScrollableHeight;
double offset = scrollHeight * itemIndex / list.Items.Count;

vsp.SetVerticalOffset(offset);

 

Then where should we put this code. One approach is to introduce an event in my view mode class and set it from view. Here is our starting point.

Code Snippet
public class MyViewModel : ViewModelBase
{
    public event EventHandler SetSelectedItem;

    // other stuff
}

 

Then we should call this event handler from our command method. It would be something like this.

Code Snippet
void Search(object param)
{
    string selected = param as string;

    for (int index = 0; index < Data.Count; index++)
    {
        if (Data[index] == selected)
        {
            EventHandler handler = this.SetSelectedItem;

            if (handler != null)
            {
                handler(this, new MyEventArgs(index));
            }
        }
    }
}

 

And in View class we are handling this event and make the selected item visible. It would be something like this.

Code Snippet
MyViewModel vm = new MyViewModel();

vm.SetSelectedItem += (sender, e) =>
{
    MyEventArgs myArgs = e as MyEventArgs;

    int itemIndex = myArgs.SelectedItemIndex;

    VirtualizingStackPanel vsp = (VirtualizingStackPanel)typeof(ItemsControl).InvokeMember(
        "_itemsHost", BindingFlags.Instance | BindingFlags.GetField | BindingFlags.NonPublic, null, list, null);

    double scrollHeight = vsp.ScrollOwner.ScrollableHeight;
    double offset = scrollHeight * itemIndex / list.Items.Count;

    vsp.SetVerticalOffset(offset);
};

DataContext = vm;

 

We are passing the parameter to the command that is actually bind with text in my text box. Here is out XAML code for this.

Code Snippet
<Button Grid.Column="1" Grid.Row="1" Margin="5" Command="{Binding SearchItem}"
        CommandParameter="{Binding ElementName=text, Path=Text}">
    Search
</Button>

 

This is complete XAML for our program.

Code Snippet
<Window x:Class="WpfListBoxSelection.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
        Title="Selected Item" Height="300" Width="400">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="5*"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        
        <ListBox Name="list" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Margin="5"
                 ItemsSource="{Binding Data}"/>
        <TextBox Name="text" Grid.Column="0" Grid.Row="1" Margin="5"/>
        <Button Grid.Column="1" Grid.Row="1" Margin="5" Command="{Binding SearchItem}"
                CommandParameter="{Binding ElementName=text, Path=Text}">
            Search
        </Button>
    </Grid>
</Window>

Here is complete C# code of our program

Code Snippet
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfListBoxSelection
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            MyViewModel vm = new MyViewModel();

            vm.SetSelectedItem += (sender, e) =>
            {
                MyEventArgs myArgs = e as MyEventArgs;

                int itemIndex = myArgs.SelectedItemIndex;

                VirtualizingStackPanel vsp = (VirtualizingStackPanel)typeof(ItemsControl).InvokeMember(
                    "_itemsHost", BindingFlags.Instance | BindingFlags.GetField | BindingFlags.NonPublic, null, list, null);

                double scrollHeight = vsp.ScrollOwner.ScrollableHeight;
                double offset = scrollHeight * itemIndex / list.Items.Count;

                vsp.SetVerticalOffset(offset);
            };

            DataContext = vm;
        }
    }

    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event EventHandler RequestClose;

        public ICommand ExitCommand
        { get; set; }

        public void Close()
        {
            EventHandler handler = this.RequestClose;

            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        public void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

    public class MyEventArgs : EventArgs
    {
        public MyEventArgs(int selectedItemIndex)
        {
            this.SelectedItemIndex = selectedItemIndex;
        }

        public int SelectedItemIndex
        { get; set; }
    }

    public class MyViewModel : ViewModelBase
    {
        public event EventHandler SetSelectedItem;

        public MyViewModel()
        {
            Data = new ObservableCollection<string>();

            for (int i = 0; i < 100; i++)
            {
                Data.Add(i.ToString());
            }

            SearchItem = new MyCommand<object, object>(Search, (param) => true);
        }

        public ObservableCollection<string> Data
        { get; set; }

        public ICommand SearchItem
        { get; set; }

        void Search(object param)
        {
            string selected = param as string;

            for (int index = 0; index < Data.Count; index++)
            {
                if (Data[index] == selected)
                {
                    EventHandler handler = this.SetSelectedItem;

                    if (handler != null)
                    {
                        handler(this, new MyEventArgs(index));
                    }
                }
            }
        }
    }

    public class MyCommand<T1, T2> : ICommand
    {
        private Action<T1> _Function;
        private Func<T2, bool> _Predicate;

        public MyCommand(Action<T1> function)
        {
            _Function = function;
        }

        public MyCommand(Action<T1> function, Func<T2, bool> predicate)
        {
            _Function = function;
            _Predicate = predicate;
        }

        public bool CanExecute(object parameter)
        {
            if (_Predicate != null)
            {
                return _Predicate((T2)parameter);
            }

            return true;
        }

        public void Execute(object parameter)
        {
            if (_Function != null)
            {
                _Function((T1)parameter);
            }
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

    }
}

 

This is the output of the program when we run it.

SelectedItemOutput_01

And this is an output when we typed something in textbox and press the search button.

SelectedItemOutput_02

Advertisements

Responses

  1. […] using Reflection We just saw how we how to call a member function using the reflection here. With this method we can also call private member function of the class even static function. […]

  2. Hi,

    Actually i am populating items from a list.Once the mouse over an item it shows one treeview and mouse over the treeview it shows another treeview.
    At this point, i wanna highlight the selected item in the first listbox.

    Thanks ,
    Raja


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: