I use the following method to select all the items in a ListView:
private void listView_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.A)
{
foreach (ListViewItem item in listView.Items)
item.Selected = true;
e.Handled = true;
}
}
When I click an already selected item and then press Ctrl+A, it selects all the items but after 1 second selection goes back to the single item that was clicked.
Solution
I couldn't find where ListView starts a timer or changes the selection, simply ignoring WM_TIMER doesn't stop the timer, so we have to restore the selection manually:
public class CustomListView : ListView
{
private const int WM_TIMER = 0x113;
protected override void WndProc(ref Message m)
{
if (m.Msg != WM_TIMER)
{
base.WndProc(ref m);
}
else
{
var selectionCache = Items.Cast<ListViewItem>().Select(x => x.Selected).ToArray();
BeginUpdate();
base.WndProc(ref m);
for (int i = 0; i < Items.Count; i++)
Items[i].Selected = selectionCache[i];
EndUpdate();
}
}
}
Related
We have a very strange problem in a Windows Form that we cannot seem to figure out.
Our Windows Form has a DataGridView with a DataGridViewCheckBoxColumn in the first column.
We've added a the following functionality that allows a user to shift->click to select multiple rows in this grid:
int colHit = gvLibrary.HitTest(e.X, e.Y).ColumnIndex;
int lastRowHit;
//mouse left click
if (e.Button == MouseButtons.Left)
{
if (colHit == 0)
{
if (Control.ModifierKeys == Keys.Shift)
{
lastRowHit = gvLibrary.HitTest(e.X, e.Y).RowIndex;
ShiftClickCheckBoxSetter(this.gvLibrary, int.Parse(txtFirstClickRow.Text), lastRowHit);
}
else
{
int firstRowHit = gvLibrary.HitTest(e.X, e.Y).RowIndex;
txtFirstClickRow.Text = firstRowHit.ToString();
}
}
}
Here's the CheckBoxSetter Code:
private void ShiftClickCheckBoxSetter(DataGridView dataGridView, int p, int lastRowHit)
{
if (p < lastRowHit)
{
for (int i = p; i < lastRowHit; i++)
{
dataGridView.Rows[i].Cells[0].Value = true;
}
}
else//
{
for (int i = p; i >= lastRowHit; i--)
{
dataGridView.Rows[i].Cells[0].Value = true;
}
}
}
And this is working as expected.
We've also added a ContextMenuStrip to the control for a right-click event.
else if (e.Button == MouseButtons.Right)
{
if (colHit != 0)
{
ContextMenuStrip m = new ContextMenuStrip();
m.Items.Add("Select All", null, m_LibraryItemClicked);
m.Items.Add("Select None", null, m_LibraryItemClickedNone);
m.Show(gvLibrary, e.Location);
}
}
Delegate Event One:
void m_LibraryItemClicked(object sender, EventArgs e) {
foreach (DataGridViewRow dgvr in gvLibrary.Rows)
{
if (dgvr.Selected) {
dgvr.Selected = false;
}
dgvr.Cells["LSelect"].Value = true;
}
}
Delegate Event Two:
private void m_LibraryItemClickedNone(object sender, EventArgs e)
{
foreach (DataGridViewRow dgvr in gvLibrary.Rows)
{
if (dgvr.Selected)
dgvr.Selected = false;
dgvr.Cells["LSelect"].Value = false;
}
}
This allows to the user to select all or select none for the checkboxes.
When the Select All selection is chosen, all check boxes are checked:
However when the Select None option is selected:
All check boxes are de-selected, except for the last one checked in the Shift-Click event:
I would think that iterating through all of the Grid Rows and setting the checkbox to not selected would suffice, IE:
private void m_LibraryItemClickedNone(object sender, EventArgs e)
{
foreach (DataGridViewRow dgvr in gvLibrary.Rows)
{
if (dgvr.Selected)
dgvr.Selected = false;
dgvr.Cells["LSelect"].Value = false;
}
}
However there seems to be some kind of state property that is disallowing this checkbox in that row to be changed.
Thanks in advance.
I checked your code and could reproduce this behaviour. The problem seem to be with the current cell (not the cell selected). When you try to change this particular cell, the action doesn't get executed immediately.
To change this behaviour add a dataGridView1.CurrentCell = null; before changing the value of the "LSelect" cell. This should fix your issue.
private void m_LibraryItemClickedNone(object sender, EventArgs e)
{
dataGridView1.CurrentCell = null;
foreach (DataGridViewRow dgvr in gvLibrary.Rows)
{
if (dgvr.Selected)
dgvr.Selected = false;
dgvr.Cells["LSelect"].Value = false;
}
}
I want to allow user to drag any item from MenuStrip to a ListBox.
I did it between to ListBoxes, but can not do it with MenuStrip.
Thanks a lot for your help.
I use WinForms, C#
For the destination ListBox I modified its property
this.listBox2.AllowDrop = true;
and created the following two events:
private void listBox2_DragOver(
object sender, System.Windows.Forms.DragEventArgs e)
{
e.Effect=DragDropEffects.All;
}
private void listBox2_DragDrop(
object sender, System.Windows.Forms.DragEventArgs e)
{
if(e.Data.GetDataPresent(DataFormats.StringFormat))
{
string str= (string)e.Data.GetData(
DataFormats.StringFormat);
listBox2.Items.Add(str);
}
}
What I need is what should be done to the source MenuStrip to allow drag items from it the ListBox, in over words how to make MenuStrip draggable.
Thanks to all for their help.
I found the solution:
The missing event is that I should add event to ToolStripMenuItem_MouseDown, I prefer to use right click instead of left click to avoid the conflict between ToolStripMenuItem_Click and the drag event, this the code:
AllowDrop = true;
private void tsmi_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Right)
DoDragDrop(sender, System.Windows.Forms.DragDropEffects.Copy);
}
Add also this code to the ListView:
private void lvAllowDropListView_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
System.Windows.Forms.ToolStripMenuItem button = e.Data.GetData(typeof(System.Windows.Forms.ToolStripMenuItem))
as System.Windows.Forms.ToolStripMenuItem;
if (button != null)
{
try
{
SmallImageList = sysIcons.SmallIconsImageList;
LargeImageList = sysIcons.LargeIconsImageList;
System.Windows.Forms.ToolStripMenuItem item = e.Data.GetData(typeof(System.Windows.Forms.ToolStripMenuItem))
as System.Windows.Forms.ToolStripMenuItem;
if (item != null)
{
AddToolStripMenuItem(item.Text, item.Name);
}
}
catch { }
}
}
private void AddToolStripMenuItem(string name, string tag)
{
System.Windows.Forms.ListViewItem item = new System.Windows.Forms.ListViewItem(name);
int Index = -1;
for (int i = 0; i < Items.Count;i++ )
if(Items[i].Tag.ToString() == tag)
{
Index = i;
break;
}
if (Index == -1)
{
item.Tag = tag;
Items.Add(item);
}
}
Drag Menu Strip Item is the same like ListBox item.
Check your code...
I know the command in WPF but what is its equivalent in WinForms?
cboclient.IsHitTestVisible = false;
cboclient.Focusable = false;
Using this command the combo-box is not disabled but the user can't open it for reading the data. How I can accomplish this in WinForms? thanks
Details: I have 3 combobox on my form when the form initially loads only the third combobox can not be opened for reading data. When the user selects a value in the first two combobox then based on those two values the third combobox is enabled to display data from DB.
Note: Here I don't want to disable the third combobox. Because it will give the user the a false expression.
You can catch the message WM_MOUSEACTIVATE and discard it to prevent user from focusing the combobox by mouse and also prevent hittesting. Catch the message WM_SETFOCUS to prevent user from focusing the combobox by keyboard. Try this code:
public class ComboBoxEx : ComboBox
{
public ComboBoxEx(){
IsHitTestVisible = true;
}
public bool IsHitTestVisible { get; set; }
public bool ReadOnly { get; set; }
protected override void WndProc(ref Message m)
{
if (!IsHitTestVisible)
{
if (m.Msg == 0x21)//WM_MOUSEACTIVATE = 0x21
{
m.Result = (IntPtr)4;//no activation and discard mouse message
return;
}
//WM_MOUSEMOVE = 0x200, WM_LBUTTONUP = 0x202
if (m.Msg == 0x200 || m.Msg == 0x202) return;
}
//WM_SETFOCUS = 0x7
if (ReadOnly && m.Msg == 0x7) return;
base.WndProc(ref m);
}
//Discard key messages
public override bool PreProcessMessage(ref Message msg)
{
if (ReadOnly) return true;
return base.PreProcessMessage(ref msg);
}
}
//Usage
comboBoxEx1.ReadOnly = true;
comboBoxEx1.IsHitTestVisible = false;
You can use if statement on OnSelectionChangedSelectionChanged event.
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//here your if statement
}
You can use following code:
cboclient.DropDownStyle = ComboBoxStyle.DropDownList;
cboclient.DropDownHeight = 1;
cboclient.DropDownWidth = 1;
cboclient.TabStop = false;
for displaying combobox as a readonly one you can use:
cboclient.FlatStyle = FlatStyle.Popup;
or
cboclient.FlatStyle = FlatStyle.Flat;
I display a different ContextMenuStrip when I right click the ListView column header, and another inside ListView.
class ListViewExx : ListView
{
public ContextMenuStrip HeaderContextMenu { get; set; }
int contextMenuSet = 0;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
base.WndProc(ref m);
switch(m.Msg)
{
case 0x210: //WM_PARENTNOTIFY
contextMenuSet = 1;
break;
case 0x21: //WM_MOUSEACTIVATE
contextMenuSet++;
break;
case 0x7b: //WM_CONTEXTMENU
if(contextMenuSet == 2 && HeaderContextMenu != null)
HeaderContextMenu.Show(Control.MousePosition);
break;
}
}
}
This works very well. The problem is the FIRST TIME I right click inside the ListView - the headers contextMenuStrip is shown.
Relying on the activation state is too hacky. It is far simpler, the WM_CONTEXTMENU message passes the handle of the window that generated the message. So you can simply compare it to the handle of the listview. If it doesn't match then you know it was the header control:
protected override void WndProc(ref System.Windows.Forms.Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x7b) { //WM_CONTEXTMENU
if (m.WParam != this.Handle) HeaderContextMenu.Show(Control.MousePosition);
}
}
Technically you should use LVM_GETHEADER but this should work just fine.
I've tried finding a clean way to get Column Header Rectangle of a ListView to check if the Point at which user right-clicks is in a Column Header or not. However, I've just found that the Column Header Rectangle of a ListView seems to be revealed only in a DrawColumnHeader event handler. This solution is all what I can think of to help you out:
public class CustomListView : ListView
{
//This contains the Column Index and its corresponding Rectangle in screen coordinates.
Dictionary<int, Rectangle> columns = new Dictionary<int, Rectangle>();
public CustomListView()
{
OwnerDraw = true;//This will help the OnDrawColumnHeader be called.
}
protected override void OnDrawItem(DrawListViewItemEventArgs e)
{
e.DrawDefault = true;
base.OnDrawItem(e);
}
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
e.DrawDefault = true;
base.OnDrawSubItem(e);
}
protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e)
{
columns[e.ColumnIndex] = RectangleToScreen(e.Bounds);
e.DrawDefault = true;
base.OnDrawColumnHeader(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x7b)//WM_CONTEXTMENU
{
int lp = m.LParam.ToInt32();
int x = ((lp << 16) >> 16);
int y = lp >> 16;
foreach (KeyValuePair<int, Rectangle> p in columns)
{
if (p.Value.Contains(new Point(x, y)))
{
//MessageBox.Show(Columns[p.Key].Text); <-- Try this to test if you want.
//Show your HeaderContextMenu corresponding to a Column here.
break;
}
}
}
base.WndProc(ref m);
}
}
Have a tab page with 2 panels, a data grid view and a 'clear' button.There are only textboxes in the panels and the grid is unbound. Data input is by user. The clear btn is disabled by default. My requirement is to enable it only if any of the textboxes is not empty or there is more than 1 row in the grid. This code isn't working. Please help.
public Form1()
{
InitializeComponent();
foreach (Control c in InvoiceTab.Controls)
{
if (c is DataGridView)
{
DataGridView dgv = c as DataGridView;
if (dgv.RowCount > 1)
{
EnableClearBtnBool = true;
btnClear.Enabled = true;
break;
}
else
{
EnableClearBtnBool = false;
btnClear.Enabled = false;
break;
}
}
}
foreach (Control c1 in panel1.Controls)
{
if (c1 is TextBox)
{
if (c1.Text != "")
{
EnableClearBtnBool = true;
c1.TextChanged -= EnableClearBtn;
c1.TextChanged += EnableClearBtn;
break;
}
else
EnableClearBtnBool = false;
}
}
foreach (Control c2 in panel2.Controls)
{
if (c2 is TextBox)
{
if (c2.Text != "")
{
EnableClearBtnBool = true;
c2.TextChanged -= EnableClearBtn;
c2.TextChanged += EnableClearBtn;
break;
}
else
EnableClearBtnBool = false;
}
}
}
bool EnableClearBtnBool = false;
private void EnableClearBtn(object sender, EventArgs e)
{
if (EnableClearBtnBool == true)
btnClear.Enabled = true;
else
btnClear.Enabled = false;
}
That code is almost certainly not working because of its location first and foremost. However, there are some fundamental changes we should be able to make as well. First we're going to need this code in a method that can be called frequently:
private void RefreshClearButton()
{
btnClear.Enabled = textBox1.Text.Length > 0 ||
textBox2.Text.Length > 0 || ...
dataGridView.RowCount > 1;
}
but, we also need to leverage the TextChanged event on all of the text boxes:
private void textBox_TextChanged(object sender, EventArgs e)
{
RefreshClearButton();
}
so you need to hook all of them up to this event handler. Now, we have two more events we need to consume, on the DataGridView, RowsAdded and RowsRemoved:
private void dataGridView_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
{
RefreshClearButton();
}
private void dataGridView_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
{
RefreshClearButton();
}
and so now we're notified every time something changes. If you must iterate through the panels rather than naming every single text box along the way then you might want to do something like this:
private bool HasTextBeenEntered(ControlCollection controls)
{
foreach (var c in controls)
{
if (c is TextBox && ((TextBox)c).Text.Length > 0) { return true; }
else if (c is Control && HasTextBeenEntered(((Control)c).Controls)) { return true; }
}
return false;
}
which would change the RefreshClearButton method slightly:
private void RefreshClearButton()
{
btnClear.Enabled = HasTextBeenEntered(this.Controls) || dataGridView.RowCount > 1;
}
DISCLAIMER: none of this code is compiled so don't be surprised if you have to tweak it, but it will get you more than 90% of the way.