OnUpdateMenuUI in C#? - c#

Is there any mechanism where I can get update notification when users try to open an menu item, like in MFC.
I know there is no direct way, but there should be lot of hacks, that's what I am asking.

What architecture?
In winforms (MenuStrip) you can use the DropDownOpening event - that do?
On the older MenuItem, there is the Popup event that works similarly.
I'm not sure about WPF...
This demonstrates both (MenuItem first, then ToolStripMenuItem):
using System;
using System.Windows.Forms;
static class Program {
[STAThread]
static void Main() {
// older menuitem
MenuItem mi;
using (Form form = new Form {
Menu = new MainMenu {
MenuItems = {
(mi = new MenuItem("abc"))
}
}
})
{
mi.MenuItems.Add("dummy");
mi.Popup += delegate {
mi.MenuItems.Clear();
mi.MenuItems.Add(DateTime.Now.ToLongTimeString());
};
Application.Run(form);
}
MenuStrip ms;
ToolStripMenuItem tsmi;
using (Form form = new Form {
MainMenuStrip = (ms = new MenuStrip {
Items = {
(tsmi = new ToolStripMenuItem("def"))
}
})
})
{
form.Controls.Add(ms);
tsmi.DropDownItems.Add("dummy");
tsmi.DropDownOpening += delegate {
tsmi.DropDownItems.Clear();
tsmi.DropDownItems.Add(DateTime.Now.ToLongTimeString());
};
Application.Run(form);
}
}
}

Related

How to select all buttons in designer?

In Visual Studio 2022, in designer, on a form, I have many different types of controls like buttons, labels and others.
How do I select all buttons, for instance?
What I am doing now is pressing Shift key and then selecting the buttons one by one, which is taking to much time.
You can create a new Visual Studio extension which adds a new menu, or a new toolbar, or a new button to an existing window. Then using IDesignerHost and INestedContainer, get the selectable components, and filter them to get list of buttons, then using ISelectionService select them.
You can find all the building blocks of the solution here and there, for example in my following posts:
Get all controls of the current form at design-time
Add a button to solution explorer to open .cs files in code editor
But to keep it simple to use and simple for test, I'll use a different solution that I used here, which is adding a new designer verb to context menu, using a base class.
To do so, Create a BaseForm class deriving from Form. Then each form which drives from this class will have above functionality that you see in the animation. Here is the code of the base form:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.Design;
public partial class BaseForm : Form
{
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (DesignMode && Site != null)
{
var host = Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (host != null)
{
var designer = (DocumentDesigner)host.GetDesigner(this);
if (designer != null)
{
designer.ActionLists.Clear();
designer.ActionLists.Add(
new MyActionList(host, designer));
}
}
}
}
public class MyActionList : DesignerActionList
{
IDesignerHost host;
ControlDesigner designer;
public MyActionList(IDesignerHost host,
ControlDesigner designer) : base(designer.Component)
{
this.host = host;
this.designer = designer;
}
private void SelectAllButtons()
{
var buttons = GetSelectableComponents(host).OfType<Button>().ToList();
var svc = host.GetService(typeof(ISelectionService)) as ISelectionService;
if (svc != null)
{
svc.SetSelectedComponents(buttons);
}
}
public override DesignerActionItemCollection GetSortedActionItems()
{
var items = new DesignerActionItemCollection();
var category = "New Actions";
items.Add(new DesignerActionMethodItem(this, "SelectAllButtons",
"Select all buttons", category, true));
return items;
}
private List<IComponent> GetSelectableComponents(IDesignerHost host)
{
var components = host.Container.Components;
var list = new List<IComponent>();
foreach (IComponent c in components)
list.Add(c);
for (var i = 0; i < list.Count; ++i)
{
var component1 = list[i];
if (component1.Site != null)
{
var service = (INestedContainer)component1.Site.GetService(
typeof(INestedContainer));
if (service != null && service.Components.Count > 0)
{
foreach (IComponent component2 in service.Components)
{
if (!list.Contains(component2))
list.Add(component2);
}
}
}
}
return list;
}
}
}

NSStatusBarButton.Activate event does not fire (Xamarin.Mac - C#)

I'm trying to subscribe to the Activate event of an NSStatusBarButton object in AppDelegate's DidFinishLaunching() but the event never gets invoked.
The purpose is to get notified when the top menu bar icon of the application is clicked so its contents can get populated dynamically.
using AppKit;
using Foundation;
[Register("AppDelegate")]
public class AppDelegate : NSApplicationDelegate
{
private NSStatusItem _statusBar;
public override void DidFinishLaunching(NSNotification notification)
{
this._statusBar = NSStatusBar.SystemStatusBar.CreateStatusItem(NSStatusItemLength.Variable);
this._statusBar.Title = "MyApp";
this._statusBar.HighlightMode = true;
this._statusBar.Menu = new NSMenu("MyApp");
// Working example on NSMenuItem object
var someItem = new NSMenuItem("Some Item");
someItem.Activated += (sender, e) =>
{
System.Diagnostics.Debug.WriteLine("This one does fire.");
};
this._statusBar.Menu.AddItem(someItem);
// Problem
this._statusBar.Button.Activated += (sender, e) =>
{
System.Diagnostics.Debug.WriteLine("This one does not fire.");
};
}
}
It does not fire because you attached a menu. The button action is popping up the menu and the activated event of the button is never fired. If you remove the menu the button event will run.
Either remove the menu and use it as a button. Then your event will fire. Or just use the menu.
If you want to run custom code when the menu is shows set a delegate of the NSMenu:
using AppKit;
using Foundation;
public class MyMenuDelegate : NSObject, INSMenuDelegate
{
public void MenuWillHighlightItem(NSMenu menu, NSMenuItem item)
{
}
[Export("menuWillOpen:")]
public void MenuWillOpen(NSMenu menu)
{
// your code here
}
}
[Register("AppDelegate")]
public class AppDelegate : NSApplicationDelegate
{
private NSStatusItem _statusBar;
MyMenuDelegate _menuDel;
public override void DidFinishLaunching(NSNotification notification)
{
_statusBar = NSStatusBar.SystemStatusBar.CreateStatusItem(NSStatusItemLength.Variable);
_statusBar.Title = "MyApp";
_statusBar.HighlightMode = true;
_statusBar.Menu = new NSMenu("MyApp");
_menuDel = new MyMenuDelegate();
_statusBar.Menu.Delegate = _menuDel;
// Working example on NSMenuItem object
var someItem = new NSMenuItem("Some Item");
someItem.Activated += (sender, e) =>
{
System.Diagnostics.Debug.WriteLine("This one does fire.");
};
_statusBar.Menu.AddItem(someItem);
}
}

Is there a way to convert a Controls.ContextMenu to a Forms.ContextMenu?

I have a context menu defined in my WPF XAML that looks like this:
<Window.Resources>
<ContextMenu x:Key="MyMenu">
<MenuItem Header="{x:Static props:Resources.MenuItem1}"/>
</ContextMenu>
</Window.Resources>
I'm using a System.Windows.Forms.NotifyIcon "myIcon" for my tray icon because it's so trivial to setup and use and because there seems to be no standard MSFT WPF equivalent. Unfortunately I get a casting exception when I call
this.myIcon.ContextMenu = (ContextMenu)this.Resources["MyMenu"];
because they're obviously not the same ContextMenu class. Is there a way to simply convert from the Controls.ContextMenu to a Forms.ContextMenu?
I'd prefer not to handle the right click mouse event of the notify icon by manually bringing up the context menu defined in my XAML. The reason being I suspect that the right click mouse event is not sent when the user uses the context menu key on the keyboard.
No - the controls are for completely different platforms (Winforms vs WPF). There is no "conversion" between the two.
Either use a different version of NotifyIcon that supports WPF or write a "conversion" that translates the items of the WPF context menu and adds them to the Winforms context menu.
So, I case anybody is curious. I ended up implementing a converter.
using System;
using System.Drawing;
using System.Windows.Controls;
namespace MyApp
{
class NotifyIconEx
{
#region Data
private System.Windows.Forms.NotifyIcon _notifyIcon = new System.Windows.Forms.NotifyIcon();
#endregion // Data
#region Properties
public Icon Icon
{
get { return _notifyIcon.Icon; }
set { _notifyIcon.Icon = value; }
}
public ContextMenu ContextMenu
{
private get { return null; }
set
{
_notifyIcon.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
foreach (var item in value.Items)
{
if (item is MenuItem)
{
var menuItem = item as MenuItem;
var toolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
toolStripMenuItem.Click += (s, e) => menuItem.RaiseEvent(new System.Windows.RoutedEventArgs(MenuItem.ClickEvent));
toolStripMenuItem.Text = menuItem.Header as string;
_notifyIcon.ContextMenuStrip.Items.Add(toolStripMenuItem);
}
else if (item is Separator)
{
_notifyIcon.ContextMenuStrip.Items.Add(new System.Windows.Forms.ToolStripSeparator());
}
else
{
throw new NotImplementedException();
}
}
}
}
public bool Visible
{
get { return _notifyIcon.Visible; }
set { _notifyIcon.Visible = value; }
}
#endregion // Properties
#region API
public void ShowBalloonTip(int timeout)
{
_notifyIcon.ShowBalloonTip(timeout);
}
public void ShowBalloonTip(int timeout, string tipTitle, string tipText, System.Windows.Forms.ToolTipIcon tipIcon)
{
_notifyIcon.ShowBalloonTip(timeout, tipTitle, tipText, tipIcon);
}
#endregion // API
}
}

User Control stuck when ran with Task.Run

I have this user control named ItemControl.
public partial class ItemControl : UserControl
{
public ModuloFramework.ItemSystem.Item Item { get; set; }
public ItemControl(ModuloFramework.ItemSystem.Item item)
{
Control.CheckForIllegalCrossThreadCalls = false;
InitializeComponent();
Item = item;
}
private void ItemControl_Load(object sender, System.EventArgs e)
{
itemNameLabel.Text = Item.Name;
itemTypeLabel.Left = itemNameLabel.Right + 5;
itemTypeLabel.Text = Item.Type.ToString();
itemPriceLabel.Left = itemTypeLabel.Right + 5;
itemPriceLabel.Text = Item.Price.ToString();
itemDescriptionLabel.Text = Item.Description;
}
}
I have another form, just a test one ofc:
public partial class Form1 : Form
{
public List<ModuloFramework.ItemSystem.Item> Items { get; set; }
private Button EscapeButton { get; }
public Form1(List<ModuloFramework.ItemSystem.Item> items)
{
InitializeComponent();
Items = items;
EscapeButton = new Button()
{
Enabled = false,
Visible = false
};
EscapeButton.Click += (sender, args) => Close();
}
private void Form1_Load(object sender, EventArgs e)
{
this.CancelButton = EscapeButton;
int y = 0;
foreach (Item item in Items) {
ItemControl control = new ItemControl(item);
control.Left = 0;
control.Top = y;
y += control.Height + 3;
panel1.Controls.Add(control);
}
}
}
This is the context in which the form is called:
Task.Run(() =>
{
List<Item> items = new List<Item>()
{
TestItem.Item1,
TestItem.Item2
};
Form1 form = new Form1(items);
form.Show();
});
What happens when I try to run it, is that the Form1 instance opens, and it gets stuck, whereas the places where the user controls were supposed to be, it shows transparent space, showing the parts behind the it and the game form,
And after a couple of seconds, the form dies.
Reopening the form again causes the same bug
What am i doing wrong here?
Edit: Fixed code, showing it here in case someone wants to see an example of Erik's fix
List<Item> items = new List<Item>()
{
TestItem.Item1,
TestItem.Item2,
TestItem.Item1,
TestItem.Item2
};
Form1 form = new Form1(items);
form.Show();
Thread trd = new Thread(() =>
{
Application.Run(form);
});
You shouldn't create a form from a task. Forms have a message pump which can only operate on the thread they are created. This message pump handles input events, drawing events, etc.
When you run code using Task.Run it runs on a threadpool thread. This means that a thread is assigned to run the code, and once it's done that thread is returned to the threadpool and no longer runs any code. Since you're not explicitly running the message pump on that thread then no update events will get processed and the form will act like it's dead.
The simplest thing to do is to create and run the form on the same thread as all your other forms. Barring that, then you should use a Thread object to create the form and use Application.Run(myForm) on that thread so that its messages get processed.

dataGridView1 connect to my generic list?

dataGridView1 I want to connect to my generic list. To do this I use the code below.
dataGridView1.DataSource = List;
(List is static)
But every time I want to update list generic dataGridView1 also get update
What should I do?
Use BindingList<T> instead; this provides lid change notifications (add, remove, etc). It also provides row-level notifications (for property changes), but only if your type correctly implements INotifyPropertyChanged; so implement that.
Re your "list is static" if you mean a static field, note that if you are. Using multiple threads you might run into trouble with that; I wouldn't do it that way, personally - but that is unrelated to the question.
Example:
using System;
using System.ComponentModel;
using System.Windows.Forms;
static class Program
{
class Foo
{
public int A { get; set; }
public string B { get; set; }
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (var form = new Form())
using (var grid = new DataGridView { Dock = DockStyle.Fill })
using (var add = new Button { Dock = DockStyle.Bottom, Text = "add" })
using (var remove = new Button { Dock = DockStyle.Top, Text = "remove" })
{
form.Controls.Add(grid);
form.Controls.Add(add);
form.Controls.Add(remove);
var lst = new BindingList<Foo>();
var rnd = new Random();
add.Click += delegate
{
lst.Add(new Foo { A = rnd.Next(1, 6), B = "new" });
};
remove.Click += delegate
{
int index = 0;
foreach (var row in lst)
{ // just to illustrate removing a row by predicate
if (row.A == 2) { lst.RemoveAt(index); break; }
index++;
}
};
grid.DataSource = lst;
Application.Run(form);
}
}
}

Categories

Resources