Has anyone figured out an event that fires whenever a menu item is highlighted?
I would like to display a description of each menu command in the status bar as they are highlighted. I'd like this to happen whether they are highlighted using the mouse or the keyboard.
But after considerable effort, I don't see any event like this. I even tried overriding WndProc to detect raw menu messages but found none are sent. Apparently, WinForms doesn't use the standard Windows menus.
It seems like knowing when a menu item is clicks and when it is selected (highlighted without being clicked) should be the two most important menu events. I don't know why the latter wouldn't be supported.
Anyone been able to figure this out?
In addition to the mouse events, you can add the keyboard keys part by handling the KeyUp event of the owner menu to get the selected item and display a description in a status-bar label.
public YourForm()
{
InitializeComponent();
menuStrip1.ShowItemToolTips = false;
menuStrip1.KeyUp += OnToolStripKeyUp;
foreach (var item in GetAllToolStripItems(menuStrip1.Items))
{
item.AutoToolTip = false;
item.MouseEnter += OnToolStripItemMouseEnter;
item.MouseLeave += OnToolStripItemMouseLeave;
if (item.GetCurrentParent() is ToolStrip dm)
{
dm.ShowItemToolTips = false;
dm.KeyUp -= OnToolStripKeyUp;
dm.KeyUp += OnToolStripKeyUp;
}
}
}
private void OnToolStripItemMouseEnter(object sender, EventArgs e)
{
sbrLabel.Text = (sender as ToolStripItem).ToolTipText;
}
private void OnToolStripItemMouseLeave(object sender, EventArgs e)
{
sbrLabel.Text = "Ready";
}
private void OnToolStripKeyUp(object sender, KeyEventArgs e)
{
var s = sender as ToolStrip;
var selItem = s.Items.OfType<ToolStripMenuItem>().FirstOrDefault(x => x.Selected);
sbrLabel.Text = selItem?.ToolTipText;
}
private IEnumerable<ToolStripItem> GetAllToolStripItems(ToolStripItemCollection tsic)
{
foreach (var tsi in tsic.Cast<ToolStripItem>())
{
yield return tsi;
if (tsi is ToolStripDropDownItem tsddi && tsddi.HasDropDown)
foreach (var ddi in GetAllToolStripItems(tsddi.DropDownItems))
yield return ddi;
}
}
With #dr.null's help, I got this working. Here's my version of the code.
private void InitializeMenuStatus(ToolStrip toolStrip)
{
toolStrip.ShowItemToolTips = false;
toolStrip.KeyUp += ToolStrip_KeyUp;
foreach (ToolStripItem toolStripItem in toolStrip.Items)
{
toolStripItem.AutoToolTip = false;
toolStripItem.MouseEnter += ToolStripItem_MouseEnter;
toolStripItem.MouseLeave += ToolStripItem_MouseLeave;
if (toolStripItem is ToolStripDropDownItem dropDownItem)
InitializeMenuStatus(dropDownItem.DropDown);
}
}
private ToolStripItem? SelectedMenuItem = null;
private void SetSelectedMenuItem(ToolStripItem? item)
{
if (!ReferenceEquals(item, SelectedMenuItem))
{
SelectedMenuItem = item;
lblStatus.Text = item?.ToolTipText ?? string.Empty;
}
}
private void ToolStripItem_MouseEnter(object? sender, EventArgs e)
{
if (sender is ToolStripMenuItem menuItem && menuItem.Selected)
SetSelectedMenuItem(menuItem);
}
private void ToolStripItem_MouseLeave(object? sender, EventArgs e)
{
SetSelectedMenuItem(null);
}
private void ToolStrip_KeyUp(object? sender, KeyEventArgs e)
{
if (sender is ToolStripDropDownMenu dropDownMenu)
{
ToolStripMenuItem? menuItem = dropDownMenu.Items.OfType<ToolStripMenuItem>()
.Where(m => m.Selected)
.FirstOrDefault();
SetSelectedMenuItem(menuItem);
}
}
Why a Selected event was never added to menu items escapes me. I have suggested that it be added. If you agree, please go and show your support for that request.
If anyone's interested, I spent some time fine tuning this code and ended up making a free component, now published as a NuGet package. You can view the code on GitHub
Related
In my application, I have a form with two panels. Inside one panel is a button. Inside the other is a DevExpress Grid control. The grid is made up of 3 columns. You can drag values from one column into the other to copy it.
My problem is that whenever I do a drag-and-drop from one column to another, the focus on the application goes into an unusual state. The grid remains focused; I can mouse over the headers and see them react as normal. However the rest of the application is not focused. Mouse over the button in the other panel does not react, nor do the menus or form controls. If I click on the button, it reacts like I clicked on an unfocused application. I have to click again to actually activate the button. Same for every control except the grid.
I have tried using Activate() and Focus() on the button and form but to no avail.
namespace Company.StuffUploader
{
public partial class ComputationGrid : DevExpress.XtraEditors.XtraUserControl
{
private BindingList<ComputationLinkModel> _links = new BindingList<ComputationLinkModel>();
public List<ComputationLinkModel> ComputationLinkModels
{
get
{
return new List<ComputationLinkModel>(_links);
}
}
public ComputationGrid()
{
InitializeComponent();
}
private void ComputationGrid_Load(object sender, EventArgs e)
{
_gridControl.DataSource = _links;
}
private DragDropEffects GetDragEffect(DragEventArgs e)
{
var text = e.Data.GetData("System.String") as string;
if (text == null)
return DragDropEffects.None;
var link = GetLinkFromScreenPoint(new Point(e.X, e.Y));
if (link == null)
return DragDropEffects.None;
var tokens = text.Split('\t');
if (tokens.Count() != 2)
return DragDropEffects.None;
var dateString = link.movedate.ToString("yyyy-MM-dd");
if (link.StuffSurfaceName == tokens[0] && dateString != tokens[1])
return DragDropEffects.Move;
else
return DragDropEffects.None;
}
private ComputationLinkModel GetLinkFromScreenPoint(Point screenPt)
{
var pt = _gridControl.PointToClient(screenPt);
var hitInfo = _gridView.CalcHitInfo(pt);
return _gridView.GetRow(hitInfo.RowHandle) as ComputationLinkModel;
}
private void _gridControl_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var hitInfo = _gridView.CalcHitInfo(e.Location);
if (hitInfo == null || !hitInfo.InRowCell)
return;
// Only allow dragging from target column
if (hitInfo.Column.AbsoluteIndex != 0)
return;
var link = _gridView.GetRow(hitInfo.RowHandle) as ComputationLinkModel;
if (link == null)
return;
var item = string.Format("{0}\t{1}", link.StuffSurfaceName, link.movedate.ToString("yyyy-MM-dd"));
DoDragDrop(item, DragDropEffects.Move);
}
}
private void _gridControl_DragOver(object sender, DragEventArgs e)
{
e.Effect = GetDragEffect(e);
}
private void _gridControl_DragDrop(object sender, DragEventArgs e)
{
}
private void _gridControl_DragEnter(object sender, DragEventArgs e)
{
e.Effect = GetDragEffect(e);
}
private void _unlinkButton_Click(object sender, EventArgs e)
{
}
}
}
I figured out my own problem. Calling DoDragDrop() from within MouseDown event does not seem to work correctly. The proper way is to call it from MouseMove(). The documentation on MSDN hints at this in its example code.
Ensure that you set the DXMouseEventArgs.Handled property to true in the GridView's Mouse~ event handlers. It guarantees that default handling of these events will be prohibited. Review this example to see how to do this.
first of all:
I´m a student that is still learning about programming.
The problem is that when I right click in a row insde the dataGridView, the RightClickDataView.Items.Add("Abgegeben"); appears as many times as I click. How can I change this?
private void dataGridViewBestellungen_MouseDown(object sender, MouseEventArgs e)
{
if(e.Button == MouseButtons.Right)
{
var hti = dataGridViewBestellungen.HitTest(e.X, e.Y);
dataGridViewBestellungen.Rows[hti.RowIndex].Selected = true;
RightClickDataView.Items.Add("Abgegeben");
RightClickDataView.Show(Cursor.Position);
var xy = dataGridViewBestellungen.SelectedRows;
foreach (DataGridViewRow row in xy)
{
//take the id in the datagridview
}
RightClickDataView.ItemClicked += new ToolStripItemClickedEventHandler(rightclickmenu_ItemClicked);
// close if mouse goes away from window
}
}
private void rightclickmenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
ToolStripItem item = e.ClickedItem;
dataGridViewBestellungen.ClearSelection();
if (e.ClickedItem.Text == "Zurück")
{
//change the state to erledigt
}
}
Just remove RightClickDataView.Items.Add("Abgegeben"); from your dataGridViewBestellungen_MouseDown and place this line on your constructor for example.
I have an infinite loop caused by what I believe is an event being raised. However I don't understand why the event would be raised.
There is a listbox of items and another checkbox list that accompanies it. The checkboxlist shows child items from each item in the listbox.
private void checkedListBoxSignIn_ItemCheck(object sender, ItemCheckEventArgs e)
{
int userIndex = listBoxSignIn.SelectedIndex;
UpdateUserList();
listBoxSignIn.SelectedIndex = userIndex;
}
private void UpdateUserList()
{
listBoxSignIn.Items.Clear();
foreach (User u in _userList)
{
listBoxSignIn.Items.Add(u.Name);
}
}
private void listBoxSignIn_SelectedIndexChanged(object sender, EventArgs e)
{
int index = listBoxSignIn.SelectedIndex;
UpdateCheckListBox(index);
}
private void UpdateCheckListBox(int index)
{
checkedListBoxSignIn.Items.Clear();
foreach (Item i in _userList.Items)
{
checkedListBoxKartSignIn.Items.Add(i, i.status);
}
}
Once you tick a checkbox a loop starts:
-> checkedListBox_ItemCheck()
-> listbox_SelectedIndexChanged()
-> checkedListBox_ItemCheck()
...
It seems that adding an item to the checked listbox counts as a "Item Check" despite the "Item Check" occuring on construction.
Is this correct behaviour? If so, how do I avoid the loop?
I am using Winforms
When dealing with controls events that will load or modify other controls I usually had a flag to stop infinite loops like the one you might be having.
It is not an ideal solution but it was the simplest I could think at the time.
I haven't run the code below, just is just as illustration:
New variables:
private bool _updatingCheckList = false;
private bool _updatingList = false;
Then:
private void checkedListBoxSignIn_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (_updatingList) return; //if it is already doing it, don't do it again
int userIndex = listBoxSignIn.SelectedIndex;
try {
_updatingList = true;
UpdateUserList();
} finally {
_updatingList = false;
}
listBoxSignIn.SelectedIndex = userIndex;
}
private void UpdateUserList()
{
listBoxSignIn.Items.Clear();
foreach (User u in _userList)
{
listBoxSignIn.Items.Add(u.Name);
}
}
private void listBoxSignIn_SelectedIndexChanged(object sender, EventArgs e)
{
if (_updatingCheckList) return; //if it is already doing it, don't do it again
int index = listBoxSignIn.SelectedIndex;
try {
_updatingCheckList = true;
UpdateCheckListBox(index);
finally { _updatingCheckList = false; }
}
private void UpdateCheckListBox(int index)
{
checkedListBoxSignIn.Items.Clear();
foreach (Item i in _userList.Items)
{
checkedListBoxKartSignIn.Items.Add(i, i.status);
}
}
Another alternative is to remove the handlers before doing the operation and readding.
I hope this helps.
I have managed to fix the issue. The main problem here was that the listbox was being updated everytime anything was done to the checkedbox list. The fix was to ensure that some user interaction has actually taken place. The easiest way for me to do this was a simple if statement checking that both lists have been selected.
private void checkedListBoxSignIn_ItemCheck(object sender, ItemCheckEventArgs e)
{
if(listbox.SelectedIndex > -1 && checkedListBox.SelectedIndex > -1){
int userIndex = listBoxSignIn.SelectedIndex;
UpdateUserList();
listBoxSignIn.SelectedIndex = userIndex;
}
}
I know how to make a contextMenu that pops up when I right click on a listView, what I want is for it to pop up when I right click on an item. I am trying to make a chat server and client, and now... Now I want to view client info when I right click on a connected client's item.
How can I do this?
private void listView1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
var focusedItem = listView1.FocusedItem;
if (focusedItem != null && focusedItem.Bounds.Contains(e.Location))
{
contextMenuStrip1.Show(Cursor.Position);
}
}
}
You can put connected client information in the contextMenuStrip1. and when you right click on a item, you can show the information from that contextMenuStrip1.
You are going to have to use the ListViews Context Menu, but change it according to the ListView Item you right click on.
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
bool match = false;
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
foreach (ListViewItem item in listView1.Items)
{
if (item.Bounds.Contains(new Point(e.X, e.Y)))
{
MenuItem[] mi = new MenuItem[] { new MenuItem("Hello"), new MenuItem("World"), new MenuItem(item.Name) };
listView1.ContextMenu = new ContextMenu(mi);
match = true;
break;
}
}
if (match)
{
listView1.ContextMenu.Show(listView1, new Point(e.X, e.Y));
}
else
{
//Show listViews context menu
}
}
}
You can trigger MouseDown or MouseUp event of ListView in which if MouseButton.Right then grab the selected Item by using ListView.Hittest and give the Context menu related to that Selected Item.
For more clear info you can go through this link
Fully solution
Pops up when user right click on a item in a listView.
Avoid an exception if the list have no items.
If an item was selected, display Delete and Edit options.
Code:
private void Form1_Load(object sender, EventArgs e)
{
listView1.MouseUp += new MouseEventHandler(listView1_MouseClick);
}
private void listView1_MouseClick(object sender, MouseEventArgs e)
{
string id = "xxx";//extra value
if (e.Button == MouseButtons.Right)
{
if (listView1.FocusedItem != null && listView1.FocusedItem.Bounds.Contains(e.Location) == true)
{
ContextMenu m = new ContextMenu();
MenuItem cashMenuItem = new MenuItem("編輯");
cashMenuItem.Click += delegate (object sender2, EventArgs e2) {
ActionClick(sender, e, id);
};// your action here
m.MenuItems.Add(cashMenuItem);
MenuItem cashMenuItem2 = new MenuItem("-");
m.MenuItems.Add(cashMenuItem2);
MenuItem delMenuItem = new MenuItem("刪除");
delMenuItem.Click += delegate (object sender2, EventArgs e2) {
DelectAction(sender, e, id);
};// your action here
m.MenuItems.Add(delMenuItem);
m.Show(listView1, new Point(e.X, e.Y));
}
}
}
private void DelectAction(object sender, MouseEventArgs e, string id)
{
ListView ListViewControl = sender as ListView;
foreach (ListViewItem eachItem in ListViewControl.SelectedItems)
{
// you can use this idea to get the ListView header's name is 'Id' before delete
Console.WriteLine(GetTextByHeaderAndIndex(ListViewControl, "Id", eachItem.Index) );
ListViewControl.Items.Remove(eachItem);
}
}
private void ActionClick(object sender, MouseEventArgs e, string id)
{
//id is extra value when you need or delete it
ListView ListViewControl = sender as ListView;
foreach (ListViewItem tmpLstView in ListViewControl.SelectedItems)
{
Console.WriteLine(tmpLstView.Text);
}
}
public static string GetTextByHeaderAndIndex(ListView listViewControl, string headerName, int index)
{
int headerIndex = -1;
foreach (ColumnHeader header in listViewControl.Columns)
{
if (header.Name == headerName)
{
headerIndex = header.Index;
break;
}
}
if (headerIndex > -1)
{
return listViewControl.Items[index].SubItems[headerIndex].Text;
}
return null;
}
The topic is quite old, but I'll leave my solution for reference.
In xaml ListView definition put your context menu:
<ListView Name="m_list" >
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="menuItem1" Click="ContextMenuItem1Clicked" />
<MenuItem Header="menuItem2" Click="ContextMenuItem2Clicked" />
</ContextMenu>
</ListView.ContextMenu>
...
</ListView>
Now, in the code, define two event handlers that will fire up on clicking respective menu item:
private void ContextMenuItem1Clicked(object sender, RoutedEventArgs e)
{
// handle the event for the selected ListViewItem accessing it by
ListViewItem selected_lvi = this.m_list.SelectedItem as ListViewItem;
}
private void ContextMenuItem2Clicked(object sender, RoutedEventArgs e)
{
// handle the event for the selected ListViewItem accessing it by
ListViewItem selected_lvi = this.m_list.SelectedItem as ListViewItem;
}
ListView can accommodate objects, which means that the selected_lvi can be of type of your object. Just cast is to proper type.
I hope this helps.
Best regards,
Mike
I've found a new solution that doesn't rely on mouse event handlers.
The ContextMenu has a "Popup" event handler.
On popup, I add the relevant menu items I need depending on my context.
Example :
private MenuItem[] standardMenuItems;
private MenuItem[] selectedMenuItems;
public SomePanel() {
InitializeComponent();
// These are list of menu items (name / callback) that will be
// chosen depending on some context
standardMenuItems = new MenuItem[] { new MenuItem("New", OnNew) };
selectedMenuItems = new MenuItem[] { new MenuItem("Delete", OnDelete), new MenuItem("Edit", OnEdit) };
ContextMenu contextMenu = new ContextMenu();
// begin with "standard items"
contextMenu.MenuItems.AddRange(standardMenuItems);
listview.ContextMenu = contextMenu;
// add the popup handler
contextMenu.Popup += OnMenuPopup;
}
// Called right before the menu comes up
private void OnMenuPopup(object sender, EventArgs e) {
ContextMenu menu = sender as ContextMenu;
if (menu == null)
return;
// If an item was selected, display Delete and Edit options
if (listview.SelectedItems.Count > 0) {
menu.MenuItems.Clear();
menu.MenuItems.AddRange(selectedMenuItems);
}
// Else display only the New option
else {
menu.MenuItems.Clear();
menu.MenuItems.AddRange(standardMenuItems);
}
}
I'm not fluent enough in C# and Winforms to be sure there are no drawbacks to this technique, but it's an alternative to relying on mouse events (what if / does the context menu can appear on other keyboard or mouse event ?)
private void contextMenuStripExport_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
if (exportview.SelectedItems.Count <= 0)
{
uploadToolStripMenuItem.Visible = false;
exportToolStripMenuItem.Visible = false;
}
else
{
uploadToolStripMenuItem.Visible = true;
exportToolStripMenuItem.Visible = true;
}
}
Using DevExpress 20.2 Core... Winforms. This is similar to handling a menu item in a GridView.
Private WithEvents _menuViewLabelitem As MenuItem
Private Sub lvShipTracking_MouseClick(sender As Object, e As MouseEventArgs) Handles lvShipTracking.MouseClick
If e.Button = MouseButtons.Right Then
If lvShipTracking.FocusedItem IsNot Nothing AndAlso lvShipTracking.FocusedItem.Bounds.Contains(e.Location) = True Then
Dim m As ContextMenu = New ContextMenu()
_menuViewLabelitem = New MenuItem("View Label")
AddHandler Click, AddressOf Handle_ViewLabel
m.MenuItems.Add(_menuViewLabelitem)
m.Show(lvShipTracking, New Point(e.X, e.Y))
End If
End If
End Sub
Private Sub Handle_ViewLabel(sender As Object, e As EventArgs) Handles _menuViewLabelitem.Click
MsgBox("it worked!")
End Sub
I'm tring to implement a button which have a dropdown menu when checked and this menu is gone when unchecked. My problem is I cannot uncheck the checkbox when it or its menu lost focus.
The checkbox's appearance mode is button.
My code:
private void cbSettings_CheckedChanged(object sender, EventArgs e)
{
if (cbSettings.Checked) {cmsSettings.Show(cbSettings, 0, cbSettings.Height);}
else {cmsSettings.Hide();}
}
I've tried to uncheck the checkBox on contextMenuStrip's VisibleChanged / Closed event but this caused menu not to hide (or hide and show immediately).
The example below does not, of course, include the code you would need for swapping BackGroundImage of the CheckBox to indicate CheckState. The events to "wire-up" should be obvious. Hope this is helpful.
// tested in VS 2010 Pro, .NET 4.0 FrameWork Client Profile
// uses:
// CheckBox named 'checkBox1
// ContextMenuStrip named 'contextMenuStrip1
// TextBox named 'cMenuSelectionInfo for run-time checking of results
// used to position the ContextMenuStrip
private Point cPoint;
// context click ? dubious assumption that 'right' = context click
private bool cmOpenedRight;
// the clicked ToolStripMenuItem
private ToolStripMenuItem tsMIClicked;
private void checkBox1_MouseDown(object sender, MouseEventArgs e)
{
cmOpenedRight = e.Button == MouseButtons.Right;
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
// positioning the CheckBox like this
// is something in a 'real-world' example
// you'd want to do in the Form.Load event !
// unless, of course, you'd made the CheckBox movable
if(checkBox1.Checked)
{
contextMenuStrip1.Show();
cPoint = PointToScreen(new Point(checkBox1.Left, checkBox1.Top + checkBox1.Height));
contextMenuStrip1.Location = cPoint;
}
else
{
contextMenuStrip1.Hide();
}
}
private void contextMenuStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
// assume you do not have to check for null here ?
tsMIClicked = e.ClickedItem as ToolStripMenuItem;
tbCbMenuSelectionInfo.Text = tsMIClicked + " : " + ! (tsMIClicked.Checked);
}
private void contextMenuStrip1_Closing(object sender, ToolStripDropDownClosingEventArgs e)
{
e.Cancel = checkBox1.Checked;
}
private void contextMenuStrip1_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
if (cmOpenedRight)
{
tbCbMenuSelectionInfo.Text += " : closed because : " + e.CloseReason.ToString();
}
}
I think your approach of unchecking the check box on the context menu's closed event is a good one, what you need is a bit of "event cancelling logic"(c), like this:
private void OnContextClosing(object sender, EventArgs e)
{
_cancel = true;
cbSettings.Checked = false;
_cancel = false;
}
private void cbSettings_CheckedChanged(object sender, EventArgs e)
{
if(_cancel)
return;
if (cbSettings.Checked) {cmsSettings.Show(cbSettings, 0, cbSettings.Height);}
else {cmsSettings.Hide();}
}
This will keep your CheckChanged event from re-checking your checkbox.