Page 1 of 1

Context Menu for Listbox Rate Topic: -----

#1 eclipsed4utoo   User is offline

  • Not Your Ordinary Programmer
  • member icon

Reputation: 1533
  • View blog
  • Posts: 5,972
  • Joined: 21-March 08

Posted 24 August 2010 - 05:39 AM

In writing one of my Windows Phone 7 applications, I needed to do a allow the user to delete an entry from a ListBox. Since my ListBox didn't have enough room for an actual delete button, I decided I would use a ContextMenu to do it. To my surprise, even though the OS seems to support a long press action(the way to uninstall an application), that action doesn't seem to be available to developers(or not that I could find).

So I decided to roll my own using the MouseLeftButtonDown and MouseLeftButtonUp events, and a DispatcherTimer.

Add these two using statements to the top of the code...
using System.Collections.ObjectModel;
using System.Windows.Threading;



So first, we need our initial code to get the ListBox populated. I created a Person class just for simplistic reasons.

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}



Next, I will set up the ListBox to simply show the FirstName of the Person.

<ListBox 
    Name="lbNames" 
    Height="240" 
    HorizontalAlignment="Left" 
    Margin="10,119,0,0" 
    VerticalAlignment="Top" 
    Width="460"
    ItemsSource="{Binding}">
                
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding FirstName}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
                
</ListBox>



Now I will create an generic ObservableCollection object to store the Person objects. This will be a class level variable since it will be accessed from multiple events.

public partial class MainPage : PhoneApplicationPage
{
    ObservableCollection<Person> personList = new ObservableCollection<Person>();

    // Constructor
    public MainPage()
    {
        InitializeComponent();

        this.Loaded += new RoutedEventHandler(MainPage_Loaded);
    }

    void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        personList.Add(new Person() { FirstName = "John", LastName = "Doe" });
        personList.Add(new Person() { FirstName = "Jane", LastName = "Doe" });
        personList.Add(new Person() { FirstName = "John", LastName = "Adams" });

        lbNames.ItemsSource = personList;
    }
}



Now I need to create the event handlers for the MouseLeftButtonDown and MouseLeftButtonUp events. This can simply be done by using the Events list from the Properties window in the designer.

private void lbNames_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{

}

private void lbNames_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{

}



We now need to create two class level objects: a DispatcherTimer object, and a Person object. These are class level because they will need to be accessed from different events. We will also subscribe to the Tick event for the timer.

ObservableCollection<Person> personList = new ObservableCollection<Person>();

DispatcherTimer timer;

Person selectedPerson = null;

// Constructor
public MainPage()
{
    InitializeComponent();

    this.Loaded += new RoutedEventHandler(MainPage_Loaded);

    timer = new DispatcherTimer();
    timer.Tick += delegate(object s, EventArgs e)
    {

    };
}



Next, we will add a Popup in XAML. This can go above or below the ListBox that is currently holding the names.

<Popup 
    x:Name="DeleteContextMenu" 
    Height="200" 
    Width="400">

    <!-- This is a ListBox as an ItemTemplate for the Popup -->
    <ListBox 
        x:Name="lbDeleteContextMenu"
        Background="White"
        Selectionchanged="DeleteContextMenu_Selectionchanged">

        <ListBoxItem
            Content="Delete Person" 
            Foreground="Red"
            FontSize="25"
            FontWeight="Bold"/>

    </ListBox>

</Popup>



And the event handler for selecting the Delete item...

private void DeleteContextMenu_Selectionchanged(object sender, System.Windows.Controls.SelectionchangedEventArgs e)
{

}



Here is how it's going to work. When the user presses down, we will start the timer. When the user releases, then we stop the timer. So if the timer's interval is reached, we know that the user was holding down on the screen, so we will display the popup.

So now we move to our code. First we are going to handle the MouseLeftButtonDown event.

private void lbNames_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    // if there is no person selected, then there is no person to delete
    //   no need to do any code if nothing is selected
    if (selectedPerson == null)
        return;

    // gets the position of the mouse cursor to set the Margin
    //    of the Popup to show at the mouse coordinates.  You
    //    may need to tweak these values to get it to display in
    //    the correct location.
    Point position = e.GetPosition((UIElement)this);
    DeleteContextMenu.Margin = new Thickness(position.X, position.Y - 200, 20, 0);

    // sets the interval to 1.1 seconds.  This means the user will need 
    //    to hold down on the screen for 1.1 seconds before we determine
    //    to show the ContextMenu.
    timer.Interval = TimeSpan.FromMilliseconds(1100);
    timer.Start();
}



Next, we will do our code for the MouseLeftButtonUp event

private void lbNames_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    // stop the timer when the user releases the screen
    timer.Stop();

    // sets the class level variable to the selected row
    selectedPerson = lbNames.SelectedItem as Person;
}



Now for our code in the Tick event for the Timer.

// Constructor
public MainPage()
{
    InitializeComponent();

    this.Loaded += new RoutedEventHandler(MainPage_Loaded);

    timer = new DispatcherTimer();
    timer.Tick += delegate(object s, EventArgs e)
    {
        // stop the timer so that it doesn't popup the Context menu again
        timer.Stop();

        // since we are using the same ListBox over and over, this will
        //   make it so when the Context Menu is shown, there will be no
        //   selected item from any previous showing of the Context Menu
        lbDeleteContextMenu.SelectedIndex = -1;

        // opens the Context Menu
        DeleteContextMenu.IsOpen = true;
    };
}



Last, we have our code from the Selectionchanged event for the ListBox that is part of the Context Menu.

private void DeleteContextMenu_Selectionchanged(object sender, System.Windows.Controls.SelectionchangedEventArgs e)
{
    // in the Timer's Tick event, we set the SelectedIndex of the
    //   Context Menu's Listbox back to -1.  However, this does
    //   fire the Selectionchanged event.  This code will handle that.
    if (lbDeleteContextMenu.SelectedIndex == -1)
        return;

    // closes the Context Menu
    DeleteContextMenu.IsOpen = false;

    if (selectedPerson == null)
        return;

    // removes the selected person from the list
    personList.Remove(selectedPerson);

    // since we are using an ObservableCollection, we do not have
    //   to rebind the list to the ListBox.

    selectedPerson = null;
}



Now you can run the application, and you will see the list show up. You must click on an item first before clicking and holding to show the Context Menu.

Is This A Good Question/Topic? 1
  • +

Page 1 of 1