I have a class that inherits from ContextMenuStrip. I use the OnOpening event to enable/disable menu items as required:
protected override void OnOpening(CancelEventArgs e)
{
base.OnOpening(e);
var count = bindingSource.Count;
var item = (T)bindingSource.Current;
removeMenuItem.Enabled = item != null;
removeAllMenuItem.Enabled = count > 0;
}
The above works fine for standard requirements. However, now I have a situation where I want to override the above method within the parent form.
So I tried to do the following:
myContextStrip.Opening += (s, ea) =>
{
var current = bindingSource.Current as MyItem;
myContextStrip.RemoveMenuItem.Enabled = !current?.IsDeleted ?? true;
myContextStrip.RemoveAllMenuItem.Enabled = bindingSource.Count > 0;
};
Implementing the above doesn't work as expected because the class 'OnOpening' method is still being called after the above is called.
Is there a better way to override the OnOpening event within the parent form and stop it from further calling the code in the derived contextmenu class?
Related
I would like to add additional menu items to my context menu. Ideally the items are enabled using validateMenuItem:
[Action("validateMenuItem:")]
public bool ValidateMenuItem(NSMenuItem item)
{
_logger.DebugFormat("Validating {0} menu item with Action {1}", item.Title, item.Action.Name);
var target = item.Target;
var menuItem = ViewModel.ContextMenu.Where(x => x.Title == item.Title).FirstOrDefault();
if (menuItem != null) {
return menuItem.Command.CanExecute();
}
return false;
}
per https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MenuList/Articles/EnablingMenuItems.html. If I manually create an action this gets called, but if I assign an eventhandler like so:
var nsMenuItem = new NSMenuItem(menuItem.Title,
(sender, e) =>
{
menuItem.Command.ExecuteAsync();
});
nsMenuItem.Target = this;
validateMenuItem: does not get called. The Action that is assigned using this method is __monomac_internal_ActionDispatcher_activated: from https://github.com/xamarin/xamarin-macios/blob/master/src/AppKit/ActionDispatcher.cs (help me please Rolf Bjarne Kvinge). Since I do not have this action in my class (I think), validateMenuItem is never called and my menu item is never activated. How can I make this work?
Update. If I add this to my view controller,
[Action("__monomac_internal_ActionDispatcher_activated:")]
public void MonomacInternalAction(NSObject sender)
{
}
validateMenuItem: gets called for the new items. However, the event handler is replaced by this function. (This problem might not be solvable!) This might be an Export vs Action issue - I see
const string skey = "__monomac_internal_ActionDispatcher_activated:";
[Preserve, Export (skey)]
public void OnActivated (NSObject sender)
{
EventHandler handler = Activated;
if (handler != null)
handler (sender, EventArgs.Empty);
}
Update 2. just found https://bugzilla.xamarin.com/show_bug.cgi?id=51343
Update 3. I can cheat the validateMenuItem by using
public override bool RespondsToSelector(ObjCRuntime.Selector sel)
{
if (sel.Name.Contains("__monomac_internal_ActionDispatcher_activated")) {
return true;
}
return base.RespondsToSelector(sel);
}
now if I could only find a way of calling the original event!
This issue is a bug in the Xamarin.Mac implementation - https://bugzilla.xamarin.com/show_bug.cgi?id=51343. I ended up with a workaround. Instead of using an eventhandler or a separate action for each menu item (couldn't figure out how to register), I created a single action to handle all menu items.
[Action("contextAction:")]
public void contextAction(NSObject sender)
{
if (sender is NSMenuItem)
{
var nsMenuItem = (NSMenuItem)sender;
var wrapper = nsMenuItem.RepresentedObject as NSObjectWrapper;
if (wrapper != null) {
var menuItem = wrapper.Context as MenuItem;
if (menuItem != null) {
menuItem.Command.ExecuteAsync();
}
}
}
}
then
foreach (var menuItem in ViewModel.ContextMenu)
{
var selector = new ObjCRuntime.Selector("contextAction:");
var nsMenuItem = new NSMenuItem(menuItem.Title, selector, "");
nsMenuItem.RepresentedObject = new NSObjectWrapper(menuItem);
NSObjectWrapper comes from Monotouch: convert an Object to NSObject.
Let's just say I need to get and set a View's height. In Android, it's known you can get a view height only after it's drawn. If you're using Java, many answers, one of the most well-known way is like this one below, taken from this answer:
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
view.getHeight(); //height is ready
}
});
Thus I search C#/Xamarin version, and found this works:
int viewHeight = 0;
ViewTreeObserver vto = view.ViewTreeObserver;
vto.GlobalLayout += (sender, args) =>
{
viewHeight = view.Height;
};
Thing is, it fired again and again. In Java version, it can be removed with
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
How to do it in C#\Xamarin? Should I resort to using boolean properties to know whether it's executed or not? Is there not way to do it like the android one?
If you are using C# Events, avoid using anonymous events if you need to unsubscribe, or you can implement the IOnGlobalLayoutListener and add/remove the listener:
C# EventHandler Style:
Create an EventHandler method for the event to invoke:
void Globallayout_handler(object sender, EventArgs e)
{
// ViewTreeObserver.IOnGlobalLayoutListener events
}
Subscribe:
var viewTreeObserver = aView.ViewTreeObserver;
viewTreeObserver.GlobalLayout += Globallayout_handler;
Unsubscribe:
var viewTreeObserver = aView.ViewTreeObserver;
viewTreeObserver.GlobalLayout -= Globallayout_handler;
Java Listener Style in C#:
Add and implement ViewTreeObserver.IOnGlobalLayoutListener:
public class CustomButtonRenderer : Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer,
ViewTreeObserver.IOnGlobalLayoutListener
{
~~~~
public void OnGlobalLayout()
{
// ViewTreeObserver.IOnGlobalLayoutListener events
}
}
Now you can use the Java way to add and remove this listener:
aView.ViewTreeObserver.RemoveOnGlobalLayoutListener(this);
aView.ViewTreeObserver.AddOnGlobalLayoutListener(this);
Even though the answer given by ShshiHangover is correct in principle, the unsubscribing didn't work for me as expected (using the regular method #1).
The reason is probably that the ViewTreeObserver in the called method can be different from the one the event handler subscribed to, so removing it may not work (i.e., the handler method is called continuously).
The correct way of doing this is to unsubscribe from the event sender object while ensuring that IsAlive yields true:
void ViewTreeObserver_GlobalLayout(object sender, EventArgs e)
{
ViewTreeObserver vto = (ViewTreeObserver)sender;
if (vto.IsAlive) {
vto.GlobalLayout -= ViewTreeObserver_GlobalLayout;
}
}
Neither #Daniel or #SushiHangover methods would actually unsubscribe for me (maybe an sdk bug?). My only solution was to set a bool flag on first run. It would be nice to know how to actually unsubscribe however...
Getting the ViewTreeObserver via sender never seems to be IsAlive whereas getting the tree from the View does. However either way the event doesn't get properly removed.
private void Setup()
{
cameraView = FindViewById<SurfaceView>(Resource.Id.camera_view);
//need to wait for view to inflate to get size
isSetup = false;
ViewTreeObserver vto = cameraView.ViewTreeObserver;
vto.GlobalLayout += Vto_GlobalLayout;
}
void Vto_GlobalLayout(object sender, System.EventArgs e)
{
//this didn't work either
//ViewTreeObserver vto = cameraView.ViewTreeObserver;
//vto.GlobalLayout -= Vto_GlobalLayout;
ViewTreeObserver vto = (ViewTreeObserver)sender;
if (vto.IsAlive)
vto.GlobalLayout -= Vto_GlobalLayout; //even after removing it seems to continue to fire...
if (!isSetup)
{
isSetup = true;
DoYourCodeNow();
}
}
I have been trying to load a default user control every time any other user control on the same panel is closed by the user. I have a panel named MainContainer and when the main form loads I am calling the following method to load that default user control named welcome.
public void AddUserControlWelcome()
{
MainContainer.Controls.Clear();
welcome.Dock = DockStyle.Fill;
MainContainer.Controls.Add(welcome);
}
I have a menustrip button which calls the following method,
private void sellItemsToolStripMenuItem_Click(object sender, EventArgs e)
{
AddUserControlSellManager();
}
And it is defined as,
public void AddUserControlSellManager()
{
MainContainer.Controls.Clear();
sellManager.Dock = DockStyle.Fill;
MainContainer.Controls.Add(sellManager);
}
So, there is a button on sellManager user control which actually closes sellManager. And after that I am invoking AddUserControlWelcome() again from MainContainer_ControlRemoved(object sender, ControlEventArgs e) but the application is crashing and I don't know why.
I think, it is clear why you having this issue. MainContainer_ControlRemoved called not only when you remove your "sell" but "welcome" too. So, the culprit I believe is the fact that you do add control on such event as MainContainer_ControlRemoved, which you shouldn't do. As good as .Net is, sometimes you have to stay away from using certain events for certain purposes , or you run into issues.
Try to do something like this. Considering that your surface can host only one control at the time
class SurfaceManager
{
private Control _defaultCtrl;
private bool _currentDefault;
private Control _surface;
void SurfaceManager(Control _surface, Control defaultCtrl)
{
_surface = surface;
_defaultCtrl = defaultCtrl;
_surface = surface.Controls.Add(_defaultCtrl);
_currentDefault = true;
}
public Control Add(Control ctrl)
{
Control c = null; // Returning removed control so you can do something else with it
if (_surface.Controls.Count > 0)
{
if (!_currentDefault)
c = _surface.Controls[0];
_surface.Controls.Clear();
}
_surface = surface.Controls.Add(ctrl);
_currentDefault = false;
Return c;
}
public Control Remove()
{
if (_currentDefault) Return // Current is default - do nothing
Control c = null; // Returning removed control so you can do something else with it
if (_surface.Controls.Count > 0)
{
c = _surface.Controls[0];
_surface.Controls.Clear();
}
_surface = surface.Controls.Add(_defaultCtrl);
_currentDefault = true;
Return c;
}
}
Now, in your class create instance of this manager and use Add or Remove. Remove will automatically bring on the Welcome screen
[introduction] I have sample WPF application. If I click a button, new window is opened. I need to wait some time for data to be loaded inside it. Rather than passively wait, I want to do some other stuff in the meantime. I can for example open some context menu. This situation is illustrated on the screen below:
This wait-for-me window, just after the loading is completed (data is ready to show), fires an event where focus is set to the grid:
void DataLoaded(object sender, EventArgs e)
{
grid.Focus();
grid.SelectedIndex = 0;
}
[current issue] Unfortunately, in the very same moment, our recently opened context menu has just disappeared. The focus has been forcefully stolen from it. Annoying final effect is shown below:
[desired effect] What would be the happy end? It would be no automatic focus, if user just changed it to any other element (like context menu). In the other words - do not steal the focus.
The code modification could be:
void DataLoaded(object sender, EventArgs e)
{
if (Magic.FocusNotChanged)
{
grid.Focus();
grid.SelectedIndex = 0;
}
}
But, what is the Magic? Some global publish subscribe mechanism which allows or denies automatic focus changes? Some handler which is spying focus changes?
BTW: This particular application shown above is just artificially extracted from much wider context. Do not pay much attention to the layout implemented here. Some generic mechanism has to be invented, not related to this specific button or context menu. Any clues?
Regards.
The solution is prosaic - to take focus in a non-invasive way, check if window is active that time:
void DataLoaded(object sender, EventArgs e)
{
if (this.IsActive)
{
grid.Focus();
grid.SelectedIndex = 0;
}
}
Some further enhancements:
Suppose we'd like to have more generic solution. What if we want to set the focus from within particular control hosted by some window, not from the window itself (lack of IsActive property)? We need to find its parent window, to check if it is still active. What's more, let's suppose this control contains a bunch of child controls, and we would like to set focus to some particular child. Look at this:
void DataLoaded(object sender, EventArgs e)
{
var window = this.GetParent<Window>();
if (window.IsActive)
{
var grid = this.GetChild<DataGrid>();
grid.Focus();
}
}
You can see the usage of two helper methods. Their implementation is given below:
public static T GetParent<T>(this DependencyObject child) where T : DependencyObject
{
if (child == null) return null;
// get parent item
var parentObject = VisualTreeHelper.GetParent(child);
// we’ve reached the end of the tree
if (parentObject == null) return null;
// check if the parent matches the type we’re looking for
var parent = parentObject as T;
// return parent if match or use recursion to proceed with next level
return parent ?? GetParent<T>(parentObject);
}
public static T GetChild<T>(this DependencyObject parent) where T : DependencyObject
{
if (parent == null) return null;
T result = null;
var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < childrenCount; i++)
{
var childObject = VisualTreeHelper.GetChild(parent, i);
var child = childObject as T;
if (child == null)
result = childObject.GetChild<T>();
else
{
result = (T) childObject;
break;
}
}
return result;
}
I have two classes:
mainGUI and preferencesGUI
I want to call a method in the mainGUI class called updateGUI, every time the applychanges button is pressed. When I try to do it, though, nothing happens.
Here are my classes:
updateGUI:
public void updateGUI()
{
String[] colors = (Regex.Split(File.ReadAllText(prefpath), ","));
int[] colori = new int[colors.Length];
for (int x = 0; x < colors.Length; x++)
{
colori[x] = Convert.ToInt32(colors[x].ToString());
}
preferencesGUI pg = new preferencesGUI();
pg.R1 = colori[0];
pg.G1 = colori[1];
pg.B1 = colori[2];
pg.R2 = colori[3];
pg.G2 = colori[4];
pg.B2 = colori[5];
outputbox.ForeColor = Color.FromArgb(colori[0], colori[1], colori[2]);
outputbox.BackColor = Color.FromArgb(colori[3], colori[4], colori[5]);
eventlist.ForeColor = Color.FromArgb(colori[0], colori[1], colori[2]);
eventlist.BackColor = Color.FromArgb(colori[3], colori[4], colori[5]);
}
Button click event handler on preferencesGUI:
private void applychanges_Click(object sender, EventArgs e)
{
mainGUI mg = new mainGUI();
mg.updateGUI();
}
Thanks for any help.
First, I'm assuming that mainGUI is already open when preferencesGUI is making this call. So when you do this:
private void applychanges_Click(object sender, EventArgs e)
{
mainGUI mg = new mainGUI();
mg.updateGUI();
}
All you're doing is creating a new instance of mainGUI and then letting it fall out of scope so yes, it does nothing.
This is a pretty common hurdle in GUI development. How do you get the different components to be aware of each other. One approach that people try initially is the singleton. However this can cause issues if you want multiple instances of the component and really it's just kind of messy. A better idea is to pass in the instance of the control but this design isn't very modular since it requires that the preferencesGUI class be aware of the mainGUI class.
Instead you should create an event inside your preferencesGUI like so:
class preferencesGUI
{
// ... Some code ...
public event Action RequestGUIUpdate;
protected void OnRequestGUIUpdate() { if (this.RequestGUIUpdate == null) return; RequestGUIUpdate(); }
// ... Some more code ...
private void applychanges_Click(object sender, EventArgs e)
{
OnRequestGUIUpdate();
}
// ... Some more code ...
}
So now the preferencesGUI is capable of notifying when a GUI update is necessary. So when you create the preferencesGUI instance from mainGUI attach an event handler like so:
preferencesGUI preferences = new preferencesGUI();
preferences.RequestGUIUpdate += new Action(updateGUI);