ListView ContextMenuStrip for column headers - c#

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

Related

How to disable read operation from Combobox in WinForms?

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;

Moving 2 forms at the same time

I'm stuck a little bit here. I'm trying to move 2 forms at the same time without using OnMove, LocationChanged, Docking etc.
The only way to interact with their locations is to override WndProc. Something which might be helpful is that form A is owner of form B. So whenever A is moved I want to move B as well. Not to the same location but the same distance.
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x0084)
{
Form[] temp = this.OwnedForms;
if(temp.Length > 0)
{
/* moving temp[0] to the same ratio as this form */
}
m.Result = (IntPtr)2;
return;
}
base.WndProc(ref m);
}
Both A and B have the same WndProc since they are 2 objects from the same class.
It doesn't make any sense to avoid using the LocationChanged event:
private Point lastPos;
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
lastPos = this.Location;
}
protected override void OnLocationChanged(EventArgs e) {
base.OnLocationChanged(e);
foreach (var frm in this.OwnedForms) {
frm.Location = new Point(frm.Location.X + this.Left - lastPos.X,
frm.Location.Y + this.Top - lastPos.Y);
}
lastPos = this.Location;
}
protected override void WndProc(ref Message m) {
// Move borderless window with click-and-drag on client window
if (m.Msg == 0x84) m.Result = (IntPtr)2;
else base.WndProc(ref m);
}
I managed to solve the problem:
protected override void WndProc(ref Message m)
{
Form temp = this.Owner;
if (m.Msg == 0x0084)
{
m.Result = (IntPtr)2;
return;
}
if (m.Msg == 0x0216 && temp != null)
{
if (!movedonce)
{
oldlocationx = this.Location.X;
oldlocationy = this.Location.Y;
movedonce = true;
}
temp.Location = new Point(temp.Location.X + this.Location.X - oldlocationx, temp.Location.Y + this.Location.Y - oldlocationy);
oldlocationx = this.Location.X;
oldlocationy = this.Location.Y;
}
base.WndProc(ref m);
}

unselectable node in TreeView

I have TreeView control on winform. I desire to make several nodes unselectable. How can I achive this.
There is only one idea in my mind - custom drawn nodes, but may be more easier way exists? Please advice me
I have already try such code in BeforeSelect event handler:
private void treeViewServers_BeforeSelect(object sender, TreeViewCancelEventArgs e)
{
if (e.Node.Parent != null)
{
e.Cancel = true;
}
}
But effect it gained is not appropriate. Node temporary get selection when I am holding left mouse button on it.
Thanks in advance!
You could completely disable mouse events in case you click on a not-selectable node.
To do this, you have to override TreeView a shown in the following code
public class MyTreeView : TreeView
{
int WM_LBUTTONDOWN = 0x0201; //513
int WM_LBUTTONUP = 0x0202; //514
int WM_LBUTTONDBLCLK = 0x0203; //515
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONDOWN ||
m.Msg == WM_LBUTTONUP ||
m.Msg == WM_LBUTTONDBLCLK)
{
//Get cursor position(in client coordinates)
Int16 x = (Int16)m.LParam;
Int16 y = (Int16)((int)m.LParam >> 16);
// get infos about the location that will be clicked
var info = this.HitTest(x, y);
// if the location is a node
if (info.Node != null)
{
// if is not a root disable any click event
if(info.Node.Parent != null)
return;//Dont dispatch message
}
}
//Dispatch as usual
base.WndProc(ref m);
}
}

How to override the minimize control?

I want to override the minimize control to instead of sending the window to the taskbar it would do what ever I write it to do.
Basicly this is what I wanted my new minimized and restored effects to be:
private void ChangeForm(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
this.Height = 80;
iDebug.Visible = false;
mainMenu.Visible = false;
}
else
{
this.Height = 359;
iDebug.Visible = true;
mainMenu.Visible = true;
}
}
I have tried to fire an Event on the Resize to do this but without success
this.Resize += new EventHandler(ChangeForm);
Cancel A WinForm Minimize?
Just tested this and it will make the form 100 pixels shorter when minimize is clicked without flicker.
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_MINIMIZE = 0xf020;
protected override void WndProc(ref Message m) {
if (m.Msg == WM_SYSCOMMAND) {
if (m.WParam.ToInt32() == SC_MINIMIZE) {
m.Result = IntPtr.Zero;
Height -= 100;
return;
}
}
base.WndProc(ref m);
}
The Minimize command has a very well defined meaning to a user, it shouldn't be messed with. Winforms accordingly doesn't have an event for it. But not a real problem, you can detect any Windows message by overriding WndProc(). Like tihs:
private void OnMinimize() {
this.Close(); // Do your stuff
}
protected override void WndProc(ref Message m) {
// Trap WM_SYSCOMMAND, SC_MINIMIZE
if (m.Msg == 0x112 && m.WParam.ToInt32() == 0xf020) {
OnMinimize();
return; // NOTE: delete if you still want the default behavior
}
base.WndProc(ref m);
}

ListView Selection Timer

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

Categories

Resources