c# listview отменить выбор элемента

Я работаю над приложением Windows, которое имеет ListView, содержащий кучу элементов. Когда пользователь нажимает на элемент, приложение отображает детали элемента. Этот затем пользователь имеет возможность редактировать эти данные. Пользователь должен нажать кнопку Сохранить после каждого изменения, но, конечно, это не всегда происходит.

если пользователь вносит изменения и не нажать кнопку Сохранить, приложение отображает сообщение box спрашивает, хотят ли они сохранить свои изменения. Это поле включает отмену кнопка, и если они нажмут отмена, я хотел бы закоротить выбор другой элемент и держите пользователя на том, который они редактировали.

Я не могу найти способ сделать это, я показать диалоговое окно, в itemselecedchanged случае, если предмет изменит и не спасет, если пользователь нажмите кнопку Отмена, удалить функцию из события и вручную изменить выбранный элемент и после этого я вернуть функцию к событию, но после этого события назвать пункт, который я вручную выбрать не выбранный.

    private bool EnsureSelected()
    {
        bool continue_ = true;
        if (_objectChange)
        {
            var res = MessageBox.Show("Do you want to save changes?", "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
            switch (res)
            {
                case DialogResult.Cancel:
                    if (!string.IsNullOrEmpty(_selectedKey))
                    {
                        listView_Keys.ItemSelectionChanged -= listView_Keys_ItemSelectionChanged;
                        listView_Keys.Focus();
                        listView_Keys.Items[_selectedKey].Selected = true;
                        listView_Keys.ItemSelectionChanged += listView_Keys_ItemSelectionChanged;
                    }
                    continue_ = false;
                    break;
                case DialogResult.Yes:
                    button_Save.PerformClick();
                    _objectChange = false;
                    break;
                case DialogResult.No:
                    _objectChange = false;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }              
        }
        return continue_;
    }

обновление:

Я попытался это решение :

        public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    private ListViewItem currentSelection = null;
    private bool pending_changes = false;
    private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
    {
        if (e.Item == currentSelection)
        {
            // if the current Item gets unselected but there are pending changes
            if (!e.IsSelected && pending_changes)
            {
                var res = MessageBox.Show("Do you want to save changes?", "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
                switch (res)
                {
                    case DialogResult.Cancel:
                        // we dont want to change the selected item, so keep it selected
                        e.Item.Selected = true;
                        break;
                    case DialogResult.Yes:
                        //button_Save.PerformClick();
                        pending_changes = false;
                        break;
                    case DialogResult.No:
                        pending_changes = false;
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }

        }
        else // not the selected button
        {
            if (!pending_changes && e.IsSelected)
            {
                // Item may be selected and we save it as the new current selection
                currentSelection = e.Item;
            }
            else if (pending_changes && e.IsSelected)
            {
                // Item may not be enabled, because there are pending changes
                e.Item.Selected = false;
            }
        }
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        listView1.Items[0].Selected = true;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        pending_changes = true;
    }
}

но это не сработало, в первый раз, когда ожидающие изменения истинны, окно сообщения вызывается дважды, а во второй раз ничего не произошло.

2 ответов


прежде всего, всякий раз, когда вы выбираете другой элемент, событие должно срабатывать дважды.

сначала для элемента, который был отменен (где e.IsSelected is false)

второй для элемента, который был выбран (где e.IsSelected is true)

предполагая, что у вас есть флаг pending_changes это установлено, когда есть несохраненные изменения, то следующий код должен отмена пункт отбора.

к сожалению, когда вы показываете MessageBox, listView снова теряет фокус. Когда вы щелкаете MessageBox прочь, фокус возвращается на listView, и это заставляет элемент управления снова запускать свои события. Вот почему необходим грязный обходной путь, который должен помнить, что мы нажали "отмена" в окне сообщения и снова выполняем действие в следующем событии.

вот код, включая обходной путь:

private ListViewItem currentSelection = null;
private bool pending_changes = false;
private bool cancel_flag = false;
private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
    Console.WriteLine("Item " + e.ItemIndex + " is now " + e.IsSelected);
    if (e.Item != currentSelection)
    {
        // if another item gets selected but there are pending changes
        if (e.IsSelected && pending_changes)
        {
            if (cancel_flag)
            {
                // this handles the second mysterious event
                cancel_flag = false;
                currentSelection.Selected = true;
                e.Item.Selected = false;
                return;
            }
            Console.WriteLine("uh oh. pending changes.");
            var res = MessageBox.Show("Do you want to save changes?", "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
            switch (res)
            {
                case DialogResult.Cancel:
                    // we dont want to change the selected item, so keep it selected
                    currentSelection.Selected = true;
                    e.Item.Selected = false;
                    // for some reason, we will get the same event with the same argments again, 
                    // after we click the cancel button, so remember our decision
                    cancel_flag = true;
                    break;
                case DialogResult.Yes:
                    // saving here. if possible without clicking the button, but by calling the method that is called to save
                    pending_changes = false;
                    currentSelection = e.Item;
                    break;
                case DialogResult.No:
                    pending_changes = false;
                    currentSelection = e.Item;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
        else if (e.IsSelected && !pending_changes)
        {
            currentSelection = e.Item;
        }
    }
}

вы можете просто повторно выбрать элементы и сохранить текущее состояние в логических флагах, чтобы избежать ненужного запуска кода (например, перезагрузки значений для выбранного элемента, если он фактически не изменился).

другой способ-обрабатывать LVN_ITEMCHANGING событие, которое, к сожалению, не реализовано в WinForms. Вы можете найти расширенный класс представления списка, который поддерживает это событие и позволяет предотвратить изменение выбора в событие изменения элемента ListView поток на MSDN форумы.

вот код из этого потока:

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public partial class Form1 : Form
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    public Form1()
    {
        ListViewEx listView;
        Controls.Add(listView = new ListViewEx { Dock = DockStyle.Fill, Items = { "One", "Two", "Three" } });
        listView.ItemSelectionChanging += (s, e) =>
            {
                if (e.Index == 1)
                    e.Cancel = true;
                Debug.WriteLine(e);
            };
    }
}

public class ItemSelectionChangingEventArgs : CancelEventArgs
{
    public int Index { get; private set; }
    public bool NewValue { get; private set; }
    public bool CurrentValue { get; private set; }

    public ItemSelectionChangingEventArgs(int index, bool newValue, bool currentValue)
    {
        Index = index;
        NewValue = newValue;
        CurrentValue = currentValue;
    }

    public override string ToString()
    {
        return String.Format("Index={0}, NewValue={1}, CurrentValue={2}", Index, NewValue, CurrentValue);
    }
}

public class ListViewEx : ListView
{
    private static readonly Object ItemSelectionChangingEvent = new Object();
    public event EventHandler<ItemSelectionChangingEventArgs> ItemSelectionChanging
    {
        add { Events.AddHandler(ItemSelectionChangingEvent, value); }
        remove { Events.RemoveHandler(ItemSelectionChangingEvent, value); }
    }

    protected virtual void OnItemSelectionChanging(ItemSelectionChangingEventArgs e)
    {
        EventHandler<ItemSelectionChangingEventArgs> handler =
            (EventHandler<ItemSelectionChangingEventArgs>)Events[ItemSelectionChangingEvent];
        if (handler != null)
            handler(this, e);
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == 0x2000 + 0x004E) // [reflected] WM_NOTIFY
        {
            uint nmhdrCode = (uint)Marshal.ReadInt32(m.LParam, NmHdrCodeOffset); 
            if (nmhdrCode == LVN_ITEMCHANGING)
            {
                NMLISTVIEW nmlv = (NMLISTVIEW)Marshal.PtrToStructure(m.LParam, typeof(NMLISTVIEW));
                if ((nmlv.uChanged & LVIF_STATE) != 0)
                {
                    bool currentSel = (nmlv.uOldState & LVIS_SELECTED) == LVIS_SELECTED;
                    bool newSel = (nmlv.uNewState & LVIS_SELECTED) == LVIS_SELECTED;

                    if (newSel != currentSel)
                    {
                        ItemSelectionChangingEventArgs e = new ItemSelectionChangingEventArgs(nmlv.iItem, newSel, currentSel);
                        OnItemSelectionChanging(e);
                        m.Result = e.Cancel ? (IntPtr)1 : IntPtr.Zero;
                        return;
                    }
                }
            }
        }

        base.WndProc(ref m);
    }

    const int LVIF_STATE = 8;

    const int LVIS_FOCUSED = 1;
    const int LVIS_SELECTED = 2;

    const uint LVN_FIRST = unchecked(0U - 100U);
    const uint LVN_ITEMCHANGING = unchecked(LVN_FIRST - 0);
    const uint LVN_ITEMCHANGED = unchecked(LVN_FIRST - 1);

    static readonly int NmHdrCodeOffset = IntPtr.Size * 2;

    [StructLayout(LayoutKind.Sequential)]
    struct NMHDR
    {
        public IntPtr hwndFrom;
        public IntPtr idFrom;
        public uint code;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct NMLISTVIEW
    {
        public NMHDR hdr;
        public int iItem;
        public int iSubItem;
        public int uNewState;
        public int uOldState;
        public int uChanged;
        public IntPtr lParam;
    }
}