How do I (fully) implement in-place editing in a L

2019-05-11 13:09发布

问题:

I'm building an application where a ListBox is displaying the Description properties of its items. I want to implement the same kind of edit-in-place functionality that you'd find, say, when editing filenames in Windows Explorer, and I'm finding it to be a whole lot of work.

What I have so far is a ContextMenu that initiates the edit. It's bound to a command in the view model that sets an IsEditingDescription property. The item template is styled so that it displays the Description in a TextBlock when IsEditingDescription is false, and in a TextBox when IsEditingDescription is true. The setter on Description sets IsEditingDescription to false after setting the description.

This works remarkably well. But there are some things that it doesn't do that it should:

  • The user should be able to initiate the edit by pressing F2.
  • The user should be able to cancel the edit by pressing ESC.
  • The user should be able to confirm the edit by pressing ENTER.
  • When the user clicks a second time on the currently selected item, it should initiate the edit.
  • The text of the description should be selected when the text box appears

I think I can handle the first three items with commands and key bindings, though I don't really understand how to do key bindings yet. But I can't really figure out an MVVMish way to do the other two. Is there one?

回答1:

this was something I was having to do over and over so I decided to extend the ListView control and added ItemKeyDown, ItemKeyUp and ItemDoubleClick events which are fired where those events occur when an item in the ListView is in focus. The ListView is derived a ListBox so you should be able to port it over pretty easily.

/Fx/ListView.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Controls;

namespace Fx
{
    public static class Func
    {
        /// <summary>
        /// Finds a specific type of parent up the visual tree.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dep"></param>
        /// <returns></returns>
        public static T FindParent<T>(this object obj)
        {
            DependencyObject dep = obj as DependencyObject;
            while ((dep != null) && !(dep is T))
            {
                dep = VisualTreeHelper.GetParent(dep);
            }
            return (dep != null) ? (T)Convert.ChangeType(dep, typeof(T)) : default(T);
        }
    }

    public class ListView:System.Windows.Controls.ListView
    {

        #region public event KeyboardEventHandler ItemKeyDown;
        /// <summary>
        /// Occurs when a key is pressed when a ListViewItem in this
        /// ListView is in focus.
        /// </summary>
        public event KeyEventHandler ItemKeyDown;

        /// <summary>
        /// Raises the ItemKeyDown event for a ListViewitem in this ListView.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="e"></param>
        public void OnItemKeyDown(ListViewItem item, KeyEventArgs e)
        {
            if (ItemKeyDown != null)
                ItemKeyDown(item, e);
        }

        /// <summary>
        /// Handle they KeyDown event on the ListView, find the related
        /// ListViewItem and raise the ItemKeyDown event respectively.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ListView_KeyDown(object sender, KeyEventArgs e)
        {
            ListViewItem item = Func.FindParent<ListViewItem>(e.OriginalSource);
            if (item != null)
                OnItemKeyDown(item, e);
        }
        #endregion

        #region public event KeyboardEventHandler ItemKeyUp;
        /// <summary>
        /// Occurs when a key is released when a ListViewItem in this
        /// ListView is in focus.
        /// </summary>
        public event KeyEventHandler ItemKeyUp;

        /// <summary>
        /// Raises the ItemKeyUp event for a ListViewitem in this ListView.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="e"></param>
        public void OnItemKeyUp(ListViewItem item, KeyEventArgs e)
        {
            if (ItemKeyUp != null)
                ItemKeyUp(item, e);
        }

        /// <summary>
        /// Handle they KeyUp event on the ListView, find the related
        /// ListViewItem and raise the ItemKeyUp event respectively.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ListView_KeyUp(object sender, KeyEventArgs e)
        {
            ListViewItem item = Func.FindParent<ListViewItem>(e.OriginalSource);
            if (item != null)
                OnItemKeyUp(item, e);
        }
        #endregion

        #region public event MouseButtonEventHandler ItemDoubleClick;
        /// <summary>
        /// Occurs when a ListViewItem in this Listview is double clicked.
        /// </summary>
        public event MouseButtonEventHandler ItemDoubleClick;

        /// <summary>
        /// Raise the ItemDoubleClick event for a ListViewItem.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="e"></param>
        public void OnItemDoubleClick(ListViewItem item, MouseButtonEventArgs e)
        {
            if (ItemDoubleClick != null)
                ItemDoubleClick(item, e);
        }

        /// <summary>
        /// Handle the MouseDoubleClick event for the ListView, find the related
        /// ListViewItem and raise the ItemDoubleClick event respectively.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            ListViewItem item = Func.FindParent<ListViewItem>(e.OriginalSource);
            if (item != null)
                OnItemDoubleClick(item, e);
        }
        #endregion

        public ListView()
        {
            MouseDoubleClick += new MouseButtonEventHandler(ListView_MouseDoubleClick);
            KeyDown += new KeyEventHandler(ListView_KeyDown);
            KeyUp += new KeyEventHandler(ListView_KeyUp);
        }
    }
}

Now to use it... /Pages/EmployeesPage.xaml

<UserControl x:Class="TestApp.Pages.EmployeesPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:fx="clr-namespace:Fx"
             Width="800" Height="450">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="300" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Column="0">
            <TextBlock Text="List Items" FontWeight="Bold" FontSize="18" />

            <!-- here is the main part -->
            <fx:ListView x:Name="EmployeesList" ItemDoubleClick="EmployeesList_ItemDoubleClick" ItemKeyDown="EmployeesList_ItemKeyDown" />
            <!-- main part ends here -->

        </StackPanel>
    </Grid>
</UserControl>

/Pages/EmployeesPage.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TestApp.Pages
{
    /// <summary>
    /// Interaction logic for EmployeesPage.xaml
    /// </summary>
    public partial class EmployeesPage : UserControl
    {
        public EmployeesPage()
        {
            InitializeComponent();

            // Fill the ListView with data...
            // EmployeesList.ItemsSource = SomeObservableCollectionOrDataSource
        }

        private void EmployeesList_ItemDoubleClick(object sender, MouseButtonEventArgs e)
        {
            MessageBox.Show("an item was double clicked");

            ListViewItem item = sender as ListViewItem;
            // entityType obj = item.DataContext as entityType

            // you can begin editing here
        }

        private void EmployeesList_ItemKeyDown(object sender, KeyEventArgs e)
        {
            MessageBox.Show(e.Key.ToString() + " key was pressed on an item");

            ListViewItem item = sender as ListViewItem;
            // entityType obj = item.DataContext as entityType

            if (e.Key == Key.F2)
            {
                // begin editing here
            }
            else if (e.Key == Key.Enter)
            {
                // end editing here
            }
            else if (e.Key == Key.Escape)
            {
                // cancel editing here
            }
        }
    }
}

Hope this is of at least some use to you.. Good luck.



回答2:

As for ensuring the text is selected on entering edit mode, I previously used the following behaviour attached to textboxes in another project of mine. It is in VB, but should be pretty straight-forward.

Public Class SelectAllOnFocusTextboxBehavior
    Inherits Behavior(Of TextBox)

    Protected Overrides Sub OnAttached()
        MyBase.OnAttached()
        AddHandler AssociatedObject.GotKeyboardFocus, AddressOf AssociatedObjectGotKeyboardFocus

    End Sub
    Protected Overrides Sub OnDetaching()
        MyBase.OnDetaching()
        RemoveHandler AssociatedObject.GotKeyboardFocus, AddressOf AssociatedObjectGotKeyboardFocus

    End Sub

    Private Sub AssociatedObjectGotKeyboardFocus(ByVal sender As Object, ByVal e As KeyboardFocusChangedEventArgs)
        AssociatedObject.SelectAll()
    End Sub

End Class


标签: wpf mvvm