Switch other toggles when one of it is on - c#

I have an array of toggles defined in script. They are all turned off in the beginning. When the user clicks on one of the toggles, that toggle should be turned on and the other toggles should be switched to off state. Basically there could be only "one" on state toggle. How do I achieve this?
Currently with this script, all the toggles are getting turned off when the user clicks on one of it.
public Toggle[] toggle;
void Start () {
for (int i = 0; i < toggle.Length; i++) {
int idx = i;
toggle[idx].onValueChanged.AddListener (delegate {
ToggleValueChanged (toggle[idx], idx);
});
}
}
public void ToggleValueChanged (Toggle change, int index) {
for (int i = 0; i < toggle.Length; i++) {
if (i == index) {
return;
} else {
if (toggle[i].isOn) {
toggle[i].isOn = false;
}
}
}
}

Unity has a component called ToggleGroup that allow you to do just that.
ToggleContainer Parent Object:
Reference ToggleGroup object in every Toggle component as following:
Scene Hierarchy:
Overview:
https://gfycat.com/bossyscenteddorado

Change your ToggleValueChanged function like this :
public void ToggleValueChanged (Toggle change, int index)
{
for (int i = 0; i < toggle.Length; i++)
{
if (i == index) continue;
if (toggle[i].isOn) {
toggle[i].isOn = false;
}
}
}
When you return in you first if statement, other toggles won't get off. you have to continue iterating your loop.
And instead getting the index and passing it to the delegate, you can use RefrenceEqual
EDIT
Actually each time you manipulate the toggle[i].isOn, you are changing the value of it. So each time, You are calling your function.
EDIT
try this :
public void ToggleValueChanged (Toggle change, int index)
{
for (int i = 0; i < toggle.Length; i++)
{
if (i == index) continue;
if (toggle[i].isOn)
{
toggle[i].SetIsOnWithoutNotify(false);
}
}
}

Why do you even need the index for this?
Simply do
public void ToggleValueChanged (Toggle change)
{
// add a little safety to actually only disable all others if
// this one ws actually enabled
if(!change.isOn) return;
foreach(var toggle in toggles)
{
if (toggle == change) continue;
if (toggle.isOn)
{
// I would actually specifically NOT use "SetIsOnWithoutNotify"
// because you never know who else is actually listening to the state change
// so if you simply set it to false but don't invoke the callback events
// things might behave different than expected
toggle[i].isOn = false;
}
}
}
and accordingly
foreach(var t in toggle)
{
var currentToggle = t;
currentToggle .onValueChanged.AddListener(value => ToggleValueChanged(currentToggle));
}

There is no need to either return or continue, just don't handle the toggle if it's the indexth one:
public void ToggleValueChanged (Toggle change, int index)
{
for (int i = 0; i < toggle.Length; i++)
{
if (i != index)
{
toggle[i].isOn = false;
}
}
}
Also you can assume that all others are off, so no reason to check that either.

If they are mutually exclusive, they aren't "boolean states", it's just one state, supported by an enum and including the null value. If your UI is a set of checkboxes, you should switch it to a set of radio buttons. A dropdown box would do as well.

Related

Triggers and Actions for Creature AI

Despite my creature AI working (for the most part), I feel like the way I've set it up is terribly inefficient and likely committing some programming sins. I want to rewrite it to be more clean, efficient, and easier to maintain but I'm not exactly sure where to begin.
In my creature AI, I have a list of triggers, such as OnSpawn, OnDeath, or OnCollisionEnter. Within each trigger is a list of actions such as "Cast a Spell" or "Play an Animation". When a trigger's conditions are met, its list of actions are processed to check if it's not already in our processing list, adds it, and then plays their associated actions. When the trigger's conditions are not met, the list of actions are removed from this process list, and similarly processes through some remove functions to clean up behavior.
Some code that I've simplified:
void Update()
{
if (canAct && !dead)
{
CheckTriggers();
PlayAllActions();
}
}
private void CheckTriggers()
{
for (int i = 0; i < actions.Length; i++)
{
switch (actions[i].trigger)
{
case ActionTrigger.Trigger.OnCollisionEnter:
if (isColliding)
AddActionList(actions[i].actionSetList);
else
RemoveActionList(actions[i].actionSetList);
break;
case ActionTrigger.Trigger.UponBeingAttacked:
if (hasBeenAttacked)
AddActionList(actions[i].actionSetList);
break;
}
}
}
public void AddActionList(ActionSetList actionSetList)
{
bool containsItem = existingActionsList.Any(item => item == actionSetList);
if (containsItem)
return;
existingActionsList.Add(actionSetList);
}
private void PlayAllActions()
{
if (existingActionsList.Count > 0)
for (int i = 0; i < existingActionsList.Count; i++)
ActionPlayEffect(existingActionsList[i]);
}
public void ActionPlayEffect(ActionSetList actionSetList)
{
for (int i = 0; i < actionSetList.Length; i++)
{
switch (actionSetList[i].type)
{
case ActionSet.Type.CastSpell:
if (spellRoutine == null && actionSetList[i].cooldownTimeRemaining <= 0)
spellRoutine = StartCoroutine(Cast(actionSetList[i]));
break;
case ActionSet.Type.PlayAnim:
if (!isInActionPose)
{
animator.SetTrigger("ActionTrigger");
animator.SetInteger("Action", (int)actionSetList[i].animToPlay+1);
isInActionPose = true;
}
break;
}
}
}
public void RemoveActionList(ActionSetList actionSetList)
{
bool containsItem = existingActionsList.Any(item => item == actionSetList);
if (containsItem)
{
ActionRemoveEffect(actionSetList);
existingActionsList.Remove(actionSetList);
}
}
public void ActionRemoveEffect(ActionSetList actionSetList)
{
for (int i = 0; i < actionSetList.Length; i++)
{
switch (actionSetList[i].type)
{
case ActionSet.Type.CastSpell:
CancelCast();
break;
case ActionSet.Type.PlayAnim:
animator.SetTrigger("ActionTrigger");
animator.SetInteger("Action", 0);
isInActionPose = false;
break;
}
}
}
What can I do to build a more efficient creature AI?
I would probably write a similar system using delegates.
A delegate can to some extent be considered a variable holding a list of methods. If you execute that delegate you then execute all the methods it holds.
This would allow you to add methods like this, to a list of methods that you then call when desired.
delegate void OnSpawn(GameObject gameObject); //Create delegate type
public OnSpawn onSpawn; //Create variable from delegate type
void SetUpStats(Gameobject gameObject){
//Set hp, initialize spells
}
void SetUpAnimations(GameObject gameObject){
//Initialize animations
}
void PlaySpawnSound(GameObject gameObject){
//Play a spawn sound
}
void Start(){
if (onSpawn == null) //Add content to delegate
{
onSpawn = new OnSpawn(SetUpStats); //You may be able to write onSpawn = SetUpStats; instead, for shorter code. But please test it.
onSpawn += SetUpAnimations;
onSpawn += PlaySpawnSound;
}
}
void Spawn(){
onSpawn(gameObject);
//Call delegate, invoking all methods stored in it.
//Note that they all receive the same input. In this case the gameObject.
//You can give them any input you want, so long as you define it in the delegate type.
//I chose a gameObject as you can get all components and more from it.
}
Let me know if you have any questions or things you wonder about.

Add back/forward buttons for viewing different Treeview Items without breaking limits

I want to navigate between some TreeViewItem using back/forward buttons. I can actually do that but my problem is that i can't make it stop going outside the limits. There's an example of my code for a forward button:
private void MainUser_button_next_Click(object sender, RoutedEventArgs e)
{
int index = 0;
foreach (TreeViewItem i in TreeviewUsers.Items)
{
if (i.Equals(MainTreeView.SelectedItem))
{
break;
}
index++;
}
(TreeviewUsers.Items[index + 1] as TreeViewItem).IsSelected = true;
}
With the code I'm showing above it is possible to navigate forward but it breaks after it gets more that it's limits. I know I need to bind a limit but I don't know where to do that!
Thanks in advance!
Here's a possible answer, depending on the behavior you want
(TreeviewUsers.Item[(index+1)%TreeviewUser.Item.Count] as TreeViewitem).IsSelected = true;
or this line (try for the back case)
(TreeviewUsers.Item[mod((index+1),TreeviewUser.Item.Count)] as TreeViewitem).IsSelected = true;
and define somewhere
int mod(int x, int m) {
int r = x%m;
return r<0 ? r+m : r;
}
By the way, can't you change this code
int index = 0;
foreach (TreeViewItem i in TreeviewUsers.Items)
{
if (i.Equals(MainTreeView.SelectedItem))
{
break;
}
index++;
}
with this here
int index = TreeviewUsers.Items.Select((v, i) => new {v, i}).Where(x => x.v.Equals(MainTreeView.SelectedItem)).Select(x => x.i);

Simulate CTRL down until I desire in c#?

I'm trying to simulate a user pressing ctrl down, the main goal would be in a datagridview when I select something programarly (initially) I dont want the user to then change that selection if not just add on to it or subtract, just as if you were to hold ctrl + left mouse click. I have no idea where to even begin. I tried to create a selection change event conbined with logicals but that will cause an infinite loop since we would be selecting one by a user then the code change other and other etc infinitely triggering that event. Please help, I'm sort of new to coding. I also don't know how to determine whether a ctrl key has been pressed, is pressed and being held.
private void selecttionh(object sender, EventArgs e)
{
if (stage == "4A" || stage == "3B" && ModifierKeys.HasFlag(Keys.Control))
{
int nothing = 0;
btnclickercl bt = new btnclickercl();
bt.dataGridView1_SelectionChanged(sender, e, dataGridViewReslist, dataGridViewnewres, nothing);
}
if (stage == "4A" || stage == "3B" && (ModifierKeys & Keys.Control) != Keys.Control)
{
MessageBox.Show("Please Press and hold " + "'ctrl'" + " to continue");
dataGridViewReslist.ClearSelection();
for (int i = 0; i < ResRoomSelections.Count; i++)
{
dataGridViewReslist.Rows[ResRoomSelections[i][0]].Cells[ResRoomSelections[i][1]].Selected = true;
dataGridViewReslist.Rows[ResRoomSelections[i][0]].Cells[(ResRoomSelections[i][1]) + 1].Selected = true;
}
}
else
{
dataGridViewReslist.ClearSelection();
for (int i = 0; i < ResRoomSelections.Count; i++)
{
dataGridViewReslist.Rows[ResRoomSelections[i][0]].Cells[ResRoomSelections[i][1]].Selected = true;
dataGridViewReslist.Rows[ResRoomSelections[i][0]].Cells[(ResRoomSelections[i][1]) + 1].Selected = true;
}
}
}
The way to make this happen is to store the selection state separately, update it when the user clicks a cell, then re-apply it. This prevents the selection from being lost every time they click. If you hook the proper event handlers (mouseup, not click) you can do this without the screen flickering and otherwise being a mess to look at.
Everything you need is in this class, including an extension method SetupToggledSelectionMode(), which is your entry point.
static public class Example
{
static private bool[][] GetSelectionState(DataGridView input)
{
int rowCount = input.Rows.Count;
int columnCount = input.Columns.Count;
var result = new bool[rowCount][];
for (var r = 0; r < rowCount; r++)
{
result[r] = new bool[columnCount];
for (var c = 0; c < columnCount; c++)
{
var cell = input.Rows[r].Cells[c];
result[r][c] = cell.Selected;
}
}
return result;
}
static private void SetSelectionState(DataGridView input, bool[][] selectionState)
{
for (int r = 0; r <= selectionState.GetUpperBound(0); r++)
{
for (int c = 0; c <= selectionState[r].GetUpperBound(0); c++)
{
input.Rows[r].Cells[c].Selected = selectionState[r][c];
}
}
}
static public void SetupToggledSelectionMode(this DataGridView input)
{
bool[][] selectionState = GetSelectionState(input); //This will be stored in a closure due to the lambda expressions below
input.CellMouseUp += (object sender, DataGridViewCellMouseEventArgs e) =>
{
selectionState[e.RowIndex][e.ColumnIndex] = !selectionState[e.RowIndex][e.ColumnIndex];
SetSelectionState(input, selectionState);
};
input.SelectionChanged += (object sender, EventArgs e) =>
{
if (selectionState != null)
{
SetSelectionState(input, selectionState);
}
};
}
}
To use, populate your gridview, set up the initial selection programmatically, and call it like this:
myDataGrid.DataSource = myData;
myDataGrid.Refresh();
myDataGrid.SelectAll();
myDataGrid.SetupToggledSelectionMode();
The SetupToggledSelectionMode() method will register the necessary event handlers and store the selection state of the grid in a closed variable accessible to both handlers. So you won't have to declare anything additional; just call the method.
Thank you for this, this really helped me. all I did was to make it more efficient.Since it would call the Selection change every step of the way, so I got rid of that event completely and only kept the CellMouseup Event.
static private bool[][] GetSelectionState(DataGridView input)
{
int rowCount = input.Rows.Count;
int columnCount = input.Columns.Count;
var result = new List<int[]>();
for (var r = 0; r < rowCount; r++)
{
for (var c = 0; c < columnCount; c++)
{
if(input.Rows[r].Cells[c].Selected==true)
{
result.add(new int[]{r,c});//will keep only the integer of selected items
}
}
}
return result;//this for me was a recycled variable it can be used or recycled from somewhere else
}
private void SetSelectionState(DataGridView input,result)
{
for (int i=0;i<result.Count;i++)
{
input.Rows[result[i][0]].Cells[result[i][1]].Selected = true;
}
}
public void SetupToggledSelectionMode(DataGridView input,result)
{
for (int i=0;i<result.Count;i++)
{
if(result[i].SequenceEqual(new int[] { e.RowIndex, e.ColumnIndex }))
{
result.RemoveAt(i);
continueer = 1;
break;
}
}
if (continueer == 0)
{
ResRoomSelections.Add(new int[] { e.RowIndex, e.ColumnIndex });
}
SetSelectionState(input);
//whatever else you need to do
}
I know there is still a better way to search List but I could not get a lamda search to work so I just used brute force
-Thank you John Wu for all

Show dialogbox only once in a for loop - Closed

This code works fine except multiple dialog box prompt when there are multiple empty textbox but I only want it to prompt once.
For example, if I enter 1,1,(null),(null),d,g, dialog box will prompt twice since there is two empty textboxes but I only need it to prompt once.
How can I solve this problem?
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
}
}
}
You can simply introduce a flag:
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
bool hasEmpty = false;
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
hasEmpty = true;
}
}
if (hasEmpty) {
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
}
}
Why not break out of the loop so that it stops checking?
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
break; // <--
}
}
}
return will also work in this situation.
Test if BeforeSave is running twice:
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
MessageBox.Show("Test"); // <--
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
break; // <--
}
}
}
As yo can see I added a new "Test" message at the top of the method (outside the loop), if you see duplicate "Test" messages when using the code, it means that BeforeSave is running twice.
In that case you need to look at why it is running twice, and then fix that. If that is not fixable, then there could be some syncronization solution... such as:
private int canSave;
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
if (Interlocked.CompareExchange(ref canSave, 0, 1) != 1)
{
// Any cancelation logic that's appropiate here
return;
}
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
break; // <--
}
}
}
Then you see canSave 1 to allow the code to run 0 to disallow it. The Interlocked operation will ensure that the code in BeforeSave will not run again until you set canSave to 1 somewhere in the code (it automatically sets it to 0 when it gets executed - no chance for multiple threads to mess it up).
Although I'm giving you a solution to control double execution of BeforeSave, if it is running twice than expected shows that there is some problem somewhere else, and you should be trying to fix that (unless it is third party code).
Either use a flag, or use Linq.
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e) {
bool flag = false;
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
flag = true;
break;
}
}
if (flag)
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
}
I don't know enough of the used objects to offer a linq solution
Following way should work for you even if your BeforeSave method is getting called multiple times.
private bool _isMessageBoxShown;
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
if(!_isMessageBoxShown)
{
_isMessageBoxShown = true;
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
break;
}
}
}
}
Just make sure that when you want your messagebox to be shown next time, you will need to set _isMessageBoxShown = false;.
I found out another way to solve it.
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
int tt = 0;
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (tt == 0)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
tt = 1;
}
}
}
}

photoBox not showing on .Show()

When the following function is called, it is not showing the corresponding photoBoxes. I've done a debugging walkthrough, it even reaches the parts necessary to Show() and Hide(). I don't know what I can do. It's not showing anything
public void SmokerTakeIngredientFromTable(agents agent, List<smokers> smoker)
{
int index = 0;
bool smoker_takes_ingredients = false;
while (!smoker_takes_ingredients)
{
if ((smoker[index].item != agent.item_1) && (smoker[index].item != agent.item_2))
{
if (index == 0)
{
leftarrow_img.Show();
rightarrow_img.Hide();
downarrow_img.Hide();
}
else if (index == 1)
{
leftarrow_img.Hide();
rightarrow_img.Show();
downarrow_img.Hide();
}
else if (index == 2)
{
leftarrow_img.Hide();
rightarrow_img.Hide();
downarrow_img.Show();
}
agent.item_1 = 3;
agent.item_2 = 3;
break;
}
index++;
}
}
This is what the designer for these photoBoxes look like:
This is the properties page for one of the photoBoxes (they are all identical apart from the actual image file, they all have Visible = false too)
When making visibility changes I need to refresh the form using this.Refresh()

Categories

Resources