Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# listview cancel select of item

I'm working on a Windows app that has a ListView containing a bunch of items. When the user clicks on an item, the app displays the item'd details. The user then has the opportunity to edit these details. The user should click the Save button after each change, but of course that doesn't always happen.

If the user makes changes and doesn't click Save, the app displays a message box asking if they'd like to save their changes. This box includes a Cancel button, and if they click Cancel, I'd like to short-circuit the selection of the other item and keep the user at the one they were editing.

I can't find a way to do this, I show the dialog from itemselecedchanged event if the item change and not save, if the user click cancel, I remove my function from the event and change manually the selected item and after that I return the function to event, but after this the event call and the item that I manually select is not selected.

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

UPDATE::

I tried this solution :

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

but this did not work, the first time that pending changes is true the message box called twice and the second time nothing happened.

like image 409
aviad facebook Avatar asked May 21 '15 22:05

aviad facebook


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.


2 Answers

First of all, whenever you select another item, the event should fire twice.

first for the Item that got deselected (where e.IsSelected is false)

second for the Item that got selected (where e.IsSelected is true)

Assuming that you have a flag pending_changes that is set, whenever there are unsaved changes, then the folowing code should cancel the item selection.

Unfortunatly whenever you show the MessageBox, the listView loses its focus again. When you click the MessageBox away, the focus is back on the listView and this causes the control to fire its events again. This is why there is a dirty workaround needed, that needs to remember that we clicked "Cancel" on the message box and performs the action on the next event again.

Here is the code including the workaround:

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;
        }
    }
}
like image 103
h3n Avatar answered Oct 18 '22 23:10

h3n


You can just reselect items and keep current state in the boolean flags to avoid causing unnecessary code to run (like reloading values for a selected item if it hasn't actually changed).

Another way is to handle LVN_ITEMCHANGING event, which is, unfortunately, isn't implemented in WinForms. You can find an extended list view class which supports this event and allows preventing changing of selection in the ListView item changing event thread on MSDN forums.

Here's the code from that thread:

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;
    }
}
like image 36
Athari Avatar answered Oct 18 '22 23:10

Athari