【问题标题】:c# listview cancel select of itemc# listview取消选择项目
【发布时间】:2015-05-21 22:18:11
【问题描述】:

我正在开发一个 Windows 应用程序,该应用程序的 ListView 包含一堆项目。 当用户单击一个项目时,应用程序会显示该项目的详细信息。这 然后用户有机会编辑这些详细信息。用户应该点击 每次更改后的“保存”按钮,当然这并不总是发生。

如果用户进行了更改但没有点击保存,应用会显示一条消息 框询问他们是否要保存更改。此框包括取消 按钮,如果他们点击取消,我想短路选择 另一个项目并将用户保持在他们正在编辑的项目上。

我找不到执行此操作的方法,如果项目更改但未保存,我会从 itemselectedchanged 事件中显示对话框,如果用户单击取消,我会从事件中删除我的函数并手动更改所选项目,然后我将函数返回给事件,但在此之后事件调用和我手动选择的项目没有被选中。

    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;
    }
}

但这不起作用,第一次挂起的更改为真,消息框调用了两次,第二次没有发生任何事情。

【问题讨论】:

    标签: c# winforms listview


    【解决方案1】:

    首先,当您选择另一个项目时,该事件应该触发两次。

    首先是被取消选择的项目(其中e.IsSelectedfalse

    被选中的项目的秒数(e.IsSelectedtrue

    假设您设置了一个标志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;
            }
        }
    }
    

    【讨论】:

    • 不起作用,第一次等待更改为真,消息框调用两次,第二次没有任何反应。显示更新问题。
    • 我重新编写了该代码。该控件关于 ItemSelectionChanged 事件的行为有点令人困惑。这很 hacky,但对我有用。
    • 感谢它的工作,但如果我设置断点并开始调试事件,它的行为会有所不同,并且还会取消当前的选择。
    • 因为当您调试它时,控件再次失去焦点,这会产生 2 个新事件:一个取消选择,一个选择,在重新获得焦点后。如果对您有帮助,请考虑接受/支持答案,因为我花了好几个小时才弄清楚。
    • 它工作得很好,除了没有触发 MessageBox 之后的 MouseClick 事件,所以如果用户按下是/否/取消,所有额外的逻辑都应该在 ItemSelectionChanged 内处理
    【解决方案2】:

    您可以只重新选择项目并将当前状态保留在布尔标志中,以避免导致不必要的代码运行(例如,如果所选项目实际上没有更改,则重新加载所选项目的值)。

    另一种方法是处理LVN_ITEMCHANGING 事件,不幸的是,它没有在WinForms 中实现。您可以在ListView item changing event thread on MSDN forums 中找到支持此事件并允许防止更改选择的扩展列表视图类。

    这是来自该线程的代码:

    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;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-10-19
      • 2014-01-22
      • 2013-07-19
      • 2011-07-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-28
      相关资源
      最近更新 更多