Delegate event in unity - c#

I want when the button has been clicked, it will show some GUI button on it. I already tried the delegate, but the event is not fired (I mean the GUI button is not shown).
Here is the code that I am using:
The code below is to show the GUI button when I run the game.
public class IItemDatabase : MonoBehaviour
{
public delegate void Action(); // Set the event for the button
public static event Action onClicked; // The event for when the button has been clicked
protected virtual void OnGUI()
{
// Call the SetStyle method
SetStyle();
// Set the GUIContent as the tooltip
GUIContent buttonText = new GUIContent("Open Shop");
// Set the GUIContent as the tooltip
GUIContent buttonTexts = new GUIContent("Open Inventory");
// This GUILayoutUtility is useful because it is to fit the content
Rect buttonGUI = GUILayoutUtility.GetRect(buttonText, "Button");
// This GUILayoutUtility is useful because it is to fit the content
Rect buttonGUIs = GUILayoutUtility.GetRect(buttonTexts, "Button");
// Set where have to the Rect displayed
buttonGUI.x = 5;
buttonGUI.y = Screen.height - 25;
// Set where have to the Rect displayed
buttonGUIs.x = 125;
buttonGUIs.y = Screen.height - 25;
// If the button has been clicked
if (GUI.Button(buttonGUI, buttonText, style))
{
if (onClicked != null)
{
onClicked();
}
}
if (GUI.Button(buttonGUIs, buttonTexts, style))
{
if (onClicked != null)
{
onClicked();
}
}
// End of the clicked button event
}
}
And here is the I want it to display when the button has been clicked:
public class IInventory : MonoBehaviour
{
protected virtual void OnEnable()
{
IItemDatabase.onClicked += DoGUI;
}
protected virtual void OnDisable()
{
IItemDatabase.onClicked -= DoGUI;
}
protected virtual void DoGUI()
{
Rect slotRect = new Rect(x * 35 + (Screen.width / 3) + 50, y * 35 + (Screen.height / 3) - 10, 30, 30);
GUI.Box(slotRect, GUIContent.none);
}
}
But when I clicked the button that it suppose to fired the DoGUI() in IInventory class, it does not run the function.
How do I solve this?
Thank you.
Your answer much appreciated!

Your problem is not the event or the delegate, but a misunderstanding on how OnGUI() works.
The OnGUI() method might be called several times per frame, but your onClicked event will only be called once after you clicked the button. So your Inventory is only visible for a few milliseconds.
There are several ways to solve the problem. Your goal is to call DoGUI() during every OnGUI() call as long as you want the inventory to be shown. You could introduce a boolean that saves the state of the inventory menu and decides whether the menu should be visible or not. If you want your Open Inventory button to toggle the menu, you could try something like that
public class IItemDatabase : MonoBehaviour
{
public static event Action onClicked; // The event for when the button has been clicked
private bool showInventory = false;
protected virtual void OnGUI()
{
// I removed your other code to simplify the example
if (GUI.Button("Open Inventory"))
{
// toggle the status
showInventory = !showInventory
}
if (showInventory && onClicked != null)
{
onClicked();
}
}
}
If you want to keep the event depends on your application. If you keep it, I would rename it to showInventoryGUI or something similar to better reflect its intention. On the other hand you could simply remove the event and just call the method IInventory.DoGUI() directly.

Related

How to assign button values ​to an event

I have a MainButton object with a Button component. A component has a property (or what it is) On Click (). This property can have an object and a method that will be executed by pressing a button. I tried to set these values ​​in the inspector, but these values ​​are not saved in the prefab, because they are assigned from Assets, and not from Scene. How to assign this method and object through programming? Who didn't understood - I need to change properties (object, method) of the event "OnClick()" by the script.
You're looking for the Unity.OnClick UnityEvent.
public class Example : MonoBehaviour
{
//Make sure to attach these Buttons in the Inspector
public Button m_YourFirstButton, m_YourSecondButton, m_YourThirdButton;
void Start()
{
//Calls the TaskOnClick/TaskWithParameters/ButtonClicked method when you click the Button
m_YourFirstButton.onClick.AddListener(TaskOnClick);
m_YourSecondButton.onClick.AddListener(delegate {TaskWithParameters("Hello"); });
m_YourThirdButton.onClick.AddListener(() => ButtonClicked(42));
m_YourThirdButton.onClick.AddListener(TaskOnClick);
}
void TaskOnClick()
{
//Output this to console when Button1 or Button3 is clicked
Debug.Log("You have clicked the button!");
}
void TaskWithParameters(string message)
{
//Output this to console when the Button2 is clicked
Debug.Log(message);
}
void ButtonClicked(int buttonNo)
{
//Output this to console when the Button3 is clicked
Debug.Log("Button clicked = " + buttonNo);
}
}

Why is Click called before OnClick and how do I solve it?

I have a custom button (public partial class QButton : Control) that has the following code to change its own checked-state when a user clicks on it:
protected override void OnMouseClick(MouseEventArgs e)
{
if (e.Button != System.Windows.Forms.MouseButtons.Left)
{
base.OnMouseClick(e);
return;
}
Status tmp = m_status;
if (m_status.HasFlag(Status.Checked))
m_status &= ~Status.Checked;
else
m_status |= Status.Checked;
if (tmp != m_status)
Invalidate();
base.OnMouseClick(e);
}
That part is working well. When using this button in a form, I connect the events in the form like this:
public void attach(Control.ControlCollection c)
{
/* ... */
m_Button.Click += OnEnable;
}
protected void OnEnable(object sender, EventArgs e)
{
/* the following still returns the old state as buttons OnMouseClick hasnt yet been called */
m_enabled = m_Button.Checked;
updateEnableState();
}
So what I want is that my form gets notified of the click and can do some magic. The problem is that the form gets notified before my button gets the notification, so first the forms OnEnable-method gets called and then the Buttons OnMouseClick-method gets called.
How do I notify the button of a sucessfull click-event before the form gets involved?
As #PaulF pointed out, the problem was that in fact Click and MouseClick are different events (didnt knew that) and that Click gets called before MouseClick.
As MouseClick only gets called on an occuring mouseclick (and might raise problems with doubleclicks), the button should override OnClick
So the solution is:
protected override void OnClick(MouseEventArgs e)
{
Status tmp = m_status;
if (m_status.HasFlag(Status.Checked))
m_status &= ~Status.Checked;
else
m_status |= Status.Checked;
if (tmp != m_status)
Invalidate();
base.OnClick(e);
}
…which also works for keyboard issued clicks on the button.

C# mouse event for class object

First - I'm a graphics designer - not a programmer :/
I'm trying create simple aplication (C# windows Form application) with option to add some objects (PictureBox) and allow user to drag those PicturesBoxes on form (change their positon after adding to form).
I can do it for one PictureBox, but can't add function to all dinamic created objects :/
I have something like that for standard Picturebox4
public bool moveDetect = false;
private void pictureBox4_MouseDown(object sender, MouseEventArgs e)
{
moveDetect = true;
}
private void pictureBox4_MouseUp(object sender, MouseEventArgs e)
{
if (moveDetect)
{
Point pozycja = new Point();
this.Cursor = new Cursor(Cursor.Current.Handle);
pozycja = this.PointToClient(Cursor.Position);
pictureBox4.Location = pozycja;
}
}
Does anyone know any tutorial showing how to add function like above to my simple class "myPictureBox : Picturebox"
My class is:
class myPictureBox : PictureBox
{
public bool moveDetect = false;
// constructor
public myPictureBox(int w, int h, string name)
{
this.Width = w;
this.Height = h;
this.Image = Image.FromFile("../../Resources/" + name + ".png");
Debug.WriteLine("Created ...");
}
}
Constructor works good and show "Created..." in output. Cant add function to all objects :/
Thanks and Regards ;)
If I understand correctly, your code works fine with the event handlers MouseUp and MouseDown when you are working with PictureBoxes that you create at design time using the designer.
You can add these same event handlers to controls that are created dynamically when you instantiate them:
MyPictureBox dynamicPicBox = new MyPictureBox(800, 600, "JustATest");
dynamicPicBox.MouseDown += pictureBox_MouseDown;
this adds an event handler that maps to the method pictureBox_MouseDown
private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
moveDetect = true;
}
Since your custom class is derived from PictureBox, it should recognize this type of event handler.

How to display/hide a control on mouse hover event

I have been developing an application lately, and found myself stuck on a simple but annoying problem.
I would like to make a specific control visible/not visible when I enter its parent, and being able to perform events (e.g.: click) on this control. The problem is, the mouse hover even does not work on the parent when I enter the very control I want to display. This result in a flickering of the control I want to display (mouse hover works -> control is displayed -> mouse hover does not work anymore -> control is hidden -> mouse hover works -> etc).
I have found this "solution" to help me have something "stable".
// Timer to make the control appearing properly.
private void Timer_Elapsed(object o, ElapsedEventArgs e)
{
try
{
ItemToHideDisplay.Visible = true;
var mousePoint = this.PointToClient(Cursor.Position);
if (mousePoint.X > this.Width ||
mousePoint.X < 0 ||
mousePoint.Y > this.Height ||
mousePoint.Y < 0)
{
HideDisplayTimer.Stop();
ItemToHideDisplay.Visible = false;
base.OnMouseLeave(e);
}
}
catch
{
// We don't want the application to crash...
}
}
protected override void OnMouseEnter(EventArgs e)
{
HideDisplayTimer.Start();
base.OnMouseEnter(e);
}
Basically, when I enter the object, a timer starts and checks every 50ms if the mouse is in the parent. If so, the control is displayed. If not, the timer is stopped and the control hidden.
This works. Yay. But I find this solution very ugly.
So my question is: is there another approach, another solution more beautiful than this one?
Tell me if I am not clear enough :)
Thanks in advance!
EDIT: Hey I think I have found it myself!
The trick is to override OnMouseLeave of the parent control with this:
protected override void OnMouseLeave(EventArgs e)
{
var mousePoint = this.PointToClient(Cursor.Position);
if (mousePoint.X > this.Width ||
mousePoint.X < 0 ||
mousePoint.Y > this.Height ||
mousePoint.Y < 0)
{
base.OnMouseLeave(e);
}
}
This way, when entering the control I have displayed (entering the parent control), the mouse leave event is not triggered!
It works!
Thanks for your answers. You can continue to post your ideas I guess, because I don't see a lot of solutions out there on the internet :)
You can make a control "transparent" to mouse events. So mouse events will just pass through it.
You have to write your own class inheriting your desired control. If, for example, a Label is your specific control, then create a new class inheriting Label - you get the point. :-)
In your new class you then make use of the window messages to make your control ignore mouse events:
protected override void WndProc(ref Message m)
{
const int WM_NCHITTEST = 0x0084;
const int HTTRANSPARENT = -1;
switch(m.Msg)
{
case WM_NCHITTEST:
m.Result = (IntPtr)HTTRANSPARENT;
break;
default:
base.WndProc(ref m);
}
}
You can read more about WndProc at MSDN.
You could register a message filter for your form and pre-process the mouse move events of your form. Thanks to this, you don't have to override your child controls etc.
The message filter, once registered in the parent form, will work for the child forms too, so even when a part of your form is covered by a child form, your target control should still appear/dissapear depending on the mouse position.
In the following example, there is a panel on a form, and that panel has a button inside.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
internal void CaptureMouseMove(Point location)
{
if (panel1.RectangleToScreen(panel1.ClientRectangle).Contains(location))
{
button1.Visible = true;
Console.WriteLine(location + "in " + panel1.RectangleToScreen(panel1.ClientRectangle));
}
else
{
button1.Visible = false;
Console.WriteLine(location + "out " + panel1.RectangleToScreen(panel1.ClientRectangle));
}
}
internal bool Form1_ProcessMouseMove(Message m)
{
Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
Control ctr = Control.FromHandle(m.HWnd);
if (ctr != null)
{
pos = ctr.PointToScreen(pos);
}
else
{
pos = this.PointToScreen(pos);
}
this.CaptureMouseMove(pos);
return false;
}
private MouseMoveMessageFilter mouseMessageFilter;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// add filter here
this.mouseMessageFilter = new MouseMoveMessageFilter();
this.mouseMessageFilter.TargetForm = this;
this.mouseMessageFilter.ProcessMouseMove = this.Form1_ProcessMouseMove;
Application.AddMessageFilter(this.mouseMessageFilter);
}
protected override void OnClosed(EventArgs e)
{
// remove filter here
Application.RemoveMessageFilter(this.mouseMessageFilter);
base.OnClosed(e);
}
private class MouseMoveMessageFilter : IMessageFilter
{
public Form TargetForm { get; set; }
public Func<Message, bool> ProcessMouseMove;
public bool PreFilterMessage(ref Message m)
{
if (TargetForm.IsDisposed) return false;
//WM_MOUSEMOVE
if (m.Msg == 0x0200)
{
if (ProcessMouseMove != null)
return ProcessMouseMove(m);
}
return false;
}
}
}
I would do a trick here :) I would wrap the control into a new control :) check this out.
XAML:
<UserControl MouseEnter="Border_MouseEnter" MouseLeave="UserControl_MouseLeave" Margin="100" Background="Transparent">
<UserControl x:Name="ControlToHide" Background="Red">
<Button Content="hi" Width="100" Height="100"/>
</UserControl>
</UserControl>
Code behind:
private void Border_MouseEnter(object sender, MouseEventArgs e)
{
this.ControlToHide.Visibility = System.Windows.Visibility.Hidden;
}
private void UserControl_MouseLeave(object sender, MouseEventArgs e)
{
this.ControlToHide.Visibility = System.Windows.Visibility.Visible;
}
it's light, easy and working . Enjoy

MouseWheel Event Fire

I have a problem on calling my private method on MouseWheel event. In fact my mouse wheel event gets fired properly when i only increment a variable or display something in Title bar etc. But when i want to call a private method, that method gets called only one time which is not the requirement i want to call that method depending on the speed of scroll i.e. when scroll is done one time slowly call the private method one time but when the scroll is done in high speed call the private method more than one time depending on the scroll speed.
For further explanation i am placing the sample code which displays the value of i in Title bar and add it in the Listbox control properly depending on the scroll speed but when i want to call the private method more than one time depending upon the scroll speed, that method gets called only one time.
public partial class Form1 : Form
{
ListBox listBox1 = new ListBox();
int i = 0;
public Form1()
{
InitializeComponent();
// Settnig ListBox control properties
this.listBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.listBox1.FormattingEnabled = true;
this.listBox1.Location = new System.Drawing.Point(13, 13);
this.listBox1.Name = "listBox1";
this.listBox1.Size = new System.Drawing.Size(259, 264);
this.listBox1.TabIndex = 0;
// Attaching Mouse Wheel Event
this.listBox1.MouseWheel += new MouseEventHandler(Form1_MouseWheel);
// Adding Control
this.Controls.Add(this.listBox1);
}
void Form1_MouseWheel(object sender, MouseEventArgs e)
{
i++;
this.Text = i.ToString();
this.listBox1.Items.Add(i.ToString());
// Uncomment the following line to call the private method
// this method gets called only one time irrelevant of the
// mouse wheel scroll speed.
// this.LaunchThisEvent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.listBox1.Select();
}
private void LaunchThisEvent()
{
// Display message each time
// this method gets called.
MessageBox.Show(i.ToString());
}
}
How to call the private method more than one time depending upon the speed of the mouse wheel scroll?
You can try using the MouseEventArgs.Delta field to calculate the number of calls:
int timesToCall = Math.Abs(e.Delta/120);
for (int k = 0; k < timesToCall; ++k)
{
this.LaunchThisEvent();
}
`

Categories