I need to create a set of Toggle at run-time and set a listener when one of those change. So this implies to know who changed.
I see how to do it statically in the UI but not programmatically and it's hard to find documentation on this subject.
The Toggle.onValueChanged event is used to subscribe to the Toggle event to detect when it is toggled. Toggle.toggle.isOn is used to check the status of the toggle. Use AddListener and delegate to help register the toggle so that you will get the name of the toggle when it is toggled:
public Toggle toggle1;
void OnEnable()
{
//Register Toggle Events
toggle1.onValueChanged.AddListener(delegate
{
ToggleCallValueChanged(toggle1);
});
}
private void ToggleCallValueChanged(Toggle toggle)
{
Debug.Log("Toggle: " + toggle + " is " + toggle.isOn);
}
void OnDisable()
{
//Un-Register Toggle Events
toggle1.onValueChanged.RemoveAllListeners();
}
If using multiple toggles, you can also re-use that one function but use the if statement to determine which one is toggled. You do this if the toggles are related. This is better than creating a new callback function for each toggle control.
Example of 3 toggles detected with one function:
public Toggle toggle1;
public Toggle toggle2;
public Toggle toggle3;
void OnEnable()
{
//Register Toggle Events
toggle1.onValueChanged.AddListener(delegate { ToggleCallBack(toggle1); });
toggle2.onValueChanged.AddListener(delegate { ToggleCallBack(toggle2); });
toggle3.onValueChanged.AddListener(delegate { ToggleCallBack(toggle3); });
}
private void ToggleCallBack(Toggle toggle)
{
if (toggle == toggle1)
{
//Your code for Toggle 1
Debug.Log("Toggled: " + toggle1.name);
}
if (toggle == toggle2)
{
//Your code for Toggle 2
Debug.Log("Toggled: " + toggle2.name);
}
if (toggle == toggle3)
{
//Your code for Toggle 3
Debug.Log("Toggled: " + toggle3.name);
}
}
void OnDisable()
{
//Un-Register Toggle Events
toggle1.onValueChanged.RemoveAllListeners();
toggle2.onValueChanged.RemoveAllListeners();
toggle3.onValueChanged.RemoveAllListeners();
}
You can use what is called an Observer pattern. That's many systems of events work.
Basically, each of your toggles created is able to send some event "I was toggled" (with giving some ID, name, value, etc.... to some object that has subscribed to it.
It also has a function "subscribe" or "attach", that allows any object to subscribe to it at runtime.
When you subscribe, you provide a callback function (which what could be called and event handler, if you have already heard the term).
That function is what will happen when the toggled is used : it will simply look at the list of object that has subscribe to it, and call the callback on each of them. That's the way with which the toggle will communicate the event.
Basically, in your case, you probably will have simple one controller which will subscribe to the toggle. This controller could be (but not necessarily) the class that created the toggles at the first place.
The whole pattern is useful because there is absolutely no need for the toggles to be aware of each other or of the controller in their code, (low coupling). And you never have to constantly check if something has changed, the "information" is properly sent only when relevant.
See Programmers's answer for a possible implementation.
Related
For some reason the below Debug Log doesn't ever fire
if (customInputField.isFocused) {
if (Input.GetKeyDown(KeyCode.Return))
{
Debug.Log("Check");
}
}
It's located within the update method. Removing the check for the return key makes the Debug Log fire repeatedly (when the field is focussed). Could the input field be losing focus when the enter key is pressed, and if so how could I achieve this check?
I think the return key is bind to the OnSubmit event on the field.
One way is to override it or use something like this:
void Start()
{
customInputField.onSubmit.AddListener(e =>
{
if (customInputField.isFocused)
{
Debug.Log("Check");
}
});
}
I had been playing around with an idea for a game, and implementation was going fairly well, but I have hit a stumbling block.
Basically, I have a form, which will show talent trees. I am just going to use labels to display the relevant details, and I want to create them programmatically. The display part is working fine, the part I am having trouble with is adding an event handler to the labels.
I want to be able to pass data during the event handling, so that I can identify which specific label was clicked, but I am hitting a brick wall. So when a particular label is clicked, the name of its associated skill (just passing a string) will be sent to the event handler. Any help would be appreciated. Here is the relevant code that I have:
public void DisplayTree()
{
int i=0;
startPoint.X = 40;
startPoint.Y = 125;
foreach(SkillNode s in tree.tier1)
{
for (i=0; i < s.labels.Count;i++ )
{
//Displays a label for each available rank for a skill
s.labels.ElementAt(i).Text = (i+1).ToString()+"/"+s.maxRank.ToString();
s.labels.ElementAt(i).Location = startPoint;
startPoint.Y += s.labels.ElementAt(i).Height + 2;
s.labels.ElementAt(i).Name = "lbl"+s.name+i.ToString();
//Only enable it to be clicked if the user is at the correct rank
if (s.rank == i)
{
s.labels.ElementAt(i).Enabled = true;
}
else
{
s.labels.ElementAt(i).Enabled = false;
}
//Add Event here
//I want to pass the name of the skill with the event
this.Controls.Add(s.labels.ElementAt(i));
}
startPoint.X += s.title.Width + 5;
startPoint.Y = 125;
}
}
public void LabelClick()
{
//Code here to pick out the name of the label
}
Try this:
public void LabelClick()
{
Console.WriteLine(((Control)sender).Name);
}
When you create an event and want to follow the official C# styleguide, you follow the following pattern:
public delegate void {YourName}EventHandler(object sender, {YourName}EventArgs args);
public event {YourName}EventHandler EventName;
Every information about what happened in the event or can be manipulated by the subscriber is stored in a class that inherits EventArgs. The delegate also contains a reference to the sender, which is the object that fires the event.
When you fire an event you do the following, regularly in a protected method that has the same name as the Event with an "On" as prefix:
EventName?.Invoke(this, new {YourName}EventArgs() { Initialize Stuff });
As you can see, you can work with the sender and identify the object. In your case you could also change object sender to UIElement sender (or similar) to make it easier to identify stuff without a cast.
I'm working on a custom GUI with SharpDX.
I have user Input from a Form Object and assign Action Methods to the specific events. Below my UI I have a "drawing canvas" and I use Tool Objects that also listen to those Form Events.
But I'm a bit stuck on the matter of how to design my program to only pass those events to a second layer (in this case my canvas) when the first layer did not "hit" anything. In short: Only call "Tool.OnMouseDown" when "Button.OnMouseDown" did return false? Would a Chain Of Responsibility be the/a correct or possible approach?
Or shall I make the current Tool check if "Excecute (Vector2)" is above some gui element but I think this would lead to the kind of coupling I want to prevent.
Hope someone is willing to help/hint (sorry for no code examples, if it's to confusingly descriped please tell me ;))
Thanks!
(Disclaimer: I know I don't have to reinvent the wheel, but I use it partly to learn and improve on my design patterns and coding skills)
thanks to sharp-ninja's answer i did the following:
ok working with it like this now :) thanks again Mister Ninja
using System.Windows.Forms;
public class HandleMouseEventArgs : MouseEventArgs
{
public bool handled { get; protected set; }
public HandleMouseEventArgs(MouseEventArgs args) : base(args.Button, args.Clicks, args.X, args.Y, args.Delta)
{
handled = false;
}
public void SetHandled()
{
handled = true;
}
}
Fortunately in .Net events get called in the order in which they are registered. You can use a handlable event arg so that the first handler of the event can tell subsequent event handlers whether the event was handled.
event EventHandler<MyHandlableEventArg> MultiLevelEvent;
Then in your main program:
// First event handler
MultiLevelEvent += (s, e) => { if(x) e.Handled = true; };
// Subsequent event handler
MultiLevelEvent += (s, e) => { if(!e.Handled) { /* Do Work */ } };
I am developing a game in MonoGame and decided to create my own button class. In order to determine if these buttons were clicked, I determined if they were in the bounds of the button, and if the left mouse button was down. This has lead to two problems.
The mouse click doesn't need to start inside the button, and it registers immediately rather than after the mouse button is released (Inside the bounds of the button.)
When multiple buttons on different screens are in the same area, it results in clicking them both as the mouse button did not release fast enough.
How can I make the clicking behave more like the WinForm buttons?
To make mouseclicking work effectively you should do the following :
First thing we do is create a MouseInput class, this should track stuff like, mouseState, lastMouseState, MouseX, MouseY. The mouseState and lastMouseState work together to handle 1 single click. But for now you can just add this class to your project :
class MouseInput
{
private static MouseState mouseState;
private static MouseState lastMouseState;
public static MouseState MouseState
{
get { return mouseState; }
set { mouseState = value; }
}
public static MouseState LastMouseState
{
get
{
return lastMouseState;
}
set
{
lastMouseState = value;
}
}
public MouseInput()
{
}
public static int getMouseX()
{
return Mouse.GetState().X;
}
public static int getMouseY()
{
return Mouse.GetState().Y;
}
}
After you've done that you want to start loooking for a mouseclick everytime your update method gets executed :
MouseInput.LastMouseState = MouseInput.MouseState;
// Get the mouse state relevant for this frame
MouseInput.MouseState = Mouse.GetState();
After you've completed these 2 crucial steps you can continue and use your code anywhere you want like this :
if (MouseInput.LastMouseState.LeftButton == ButtonState.Released && MouseInput.MouseState.LeftButton == ButtonState.Pressed) {
//Execute code here
}
Ofcourse as the above if statement only checks if the player pressed the left mouse button, you'll still have to check if the mouse positions are inside the button.
If you have any questions, feel free to ask
You can use RoutedEvent that can invoke handlers on multiple listeners in an element tree, rather than just on the object that raised the event. you can read more about RoutedEvents because The concept of an attached event enables you to add a handler for a particular event to an arbitrary element rather than to an element that actually defines or inherits the event. When those events are not related to the element which, like in case of MouseDown, both raises and listens to the event.
So RoutedEvents might help you to solve your problem
Here is a sample code
public static readonly RoutedEvent SelectedEvent =
EventManager.RegisterRoutedEvent( "Selected", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(MyCustomControl));
// .NET wrapper
public event RoutedEventHandler Selected
{
add { AddHandler(SelectedEvent, value); }
remove { RemoveHandler(SelectedEvent, value); }
}
// Raise the routed event "selected"
RaiseEvent(new RoutedEventArgs(MyCustomControl.SelectedEvent));
UI for XNA 4.0 like WinForms, written from scratch.
Create form and, button to it, call Form.Update() from Game.Update() method.
In Game.Draw() first call Form.Refresh(), then Form.Draw().
For processing mouse and keyboard input used managers from GameHelper.Input library.
I have a treeview with checkboxes and I have the following handler for the "AfterCheck" event:
private void trvAvailableFiles_AfterCheck(object sender, TreeViewEventArgs e)
{
if (!_isCheckingInProgress)
{
trvAvailableFiles.BeginUpdate();
var nodePath = e.Node.Tag.ToString();
bool isChecked = e.Node.Checked;
e.Node.Nodes.Clear();
try
{
_fileTreeLogic.GetChildNodes(e.Node, true);
e.Node.ExpandAll();
_isCheckingInProgress = true;
SetChildrenCheckState(e.Node, isChecked);
_isCheckingInProgress = false;
}
finally
{
trvAvailableFiles.EndUpdate();
}
}
}
If you look closely you'll see that I'm checking if "_isCheckingInProgress". If it is not, then I proceed and expand all the nodes and call the SetChildrenCheckState() method. The problem I have encountered is that SetChildrenCheckState() will subsequently cause each child node to all fire the AfterCheck event for its own node.
My question is, is there a more clean way to allow the first AfterCheck event to fire but not the subsequent ones? It seems kind of hackish that I have to have an instance bool variable to check and set.
Use: if(e.Action != TreeViewAction.Unknown) instead of if (!_isCheckingInProgress). See TreeViewAction .
When the user uses the keyboard or mouse to check the checkboxes, e.Action will be TreeViewAction.ByKeyboard or TreeViewAction.ByMouse.
The MSDN provides this as example code for the TreeView.AfterCheck Event.
Edit 1: Obviously, if you're setting the checkbox yourself in code, move the code in the event handler to a new function and have the code that sets the checkbox call it directly. The point of this solution is to let you use event handlers for user input without having those events get triggered when you set the checkboxes yourself via code.
Edit 2: See Spencer's answer for an explanation of my comment in Edit 1
One recommendation you'll see occasionally around SO is to not put a lot of code into event handlers themselves. There are a number of reasons for this. First, in your case it would be easier to understand a call like:
private void trvAvailableFiles_AfterCheck(object sender, TreeViewEventArgs e)
{
if (!_isCheckingInProgress)
{
_isCheckingInProgress = true;
try { GetAvailableFiles(); } catch {}
_isCheckingInProgress = false;
}
}
And to place the rest of your code in GetAvailableFiles(). This creates a separation between event code and action code which most people would agree is a worthwhile distinction to make.
Second, which may or may not be applicable in your case is that multiple events can cause the same action. Such as mnuFileQuit_Click and btnClose_Click as an obvious example. If both make calls to CloseApplication() it removes a lot of redundant code.
Personally, I use a function that removes and then adds the event.
private void trvAvailableFiles_AfterCheck(object sender, TreeViewEventArgs e)
{
EnableEvents(false);
trvAvailableFiles.BeginUpdate();
var nodePath = e.Node.Tag.ToString();
bool isChecked = e.Node.Checked;
e.Node.Nodes.Clear();
try
{
_fileTreeLogic.GetChildNodes(e.Node, true);
e.Node.ExpandAll();
SetChildrenCheckState(e.Node, isChecked);
}
finally
{
trvAvailableFiles.EndUpdate();
}
EnableEvents(true);
}
private void EnableEvents(bool bEnable)
{
if(bEnable)
cbWhatever.OnChecked += EventHandler;
else
cbWhatever.OnChecked -= EventHandler;
}
No, there's no cleaner way to do what you've shown. I'm not really sure why you feel that variables are a "hack" approach. Setting a flag is a common technique used when writing UI code.
The real hack would be some obscure way to prevent raising the event the first time, but not subsequent times. Future maintenance programmers are guaranteed to understand how setting a flag works; they're not guaranteed to appreciate the "elegance" of your alternative approach.