C#, variable value as a part of an object name? - c#

For example i have a player object with a list of suits of cards it has.
player.card[0] = "diamonds";
player.card[1] = "clubs";
player.card[2] = "spades";
etc...
also i have 4 hidden pictureboxes with an image of suites ( pb_spades , pb_hearts m etc. )
and another 4 pictureboxs ( pb_playerCard1 , pb_playerCard2 , etc. ) to which I have to assign an image from a hidden pb corresponding to the suit of card the player object has.
Therefor
if ( player.card[0] == "diamonds" ) { pb_playerCard1.Image = pb_diamonds.Image; }
of course, doing it all with IFs would take quite a long time... Can I somehow use variable value as a part of an objects name?
kinda
for (int i = 1; i != 5; i++)
{
pb_playerCard+'i'.Image = pb_+'player.card[i+1]'.Image;
}

I don't think that you can use a value as a part of the control name. But, one can use an aray of controls. You will have to change many declarations and initializations of your picture boxes and put them into an array, but you will be able to write more descriptive and readable code.

Create a class Suite that has all properties, like this:
class Suite {
public string Name { get; set; }
public Image Image { get; set; }
and then create a static object for each color:
public static Diamonds = new Suite { Name = "Diamonds", Image = Resources.DiamondImage };
// ...
}
Now you can use Suite.Diamonds.
Even better is to use a Flyweight pattern to avoid the static fields. You use the Flyweight to implement the Card class.

First, there's no reason to have a hidden PictureBox control just so you can use it's Image property to store an image. Just create Image objects.
You could store the images in a dictionary, indexable by name:
var cards = new Dictionary<string, Image>() {
{ "diamonds", Image.FromFile("diamonds.jpg") }
{ "clubs", Image.FromFile("clubs.jpg") }
//...
};
Then instead of this:
if ( player.card[0] == "diamonds" ) { pb_playerCard1.Image = pb_diamonds.Image; }
You would write:
pb_playerCard1.Image = images[player.card[0]];
This code is still not great (any time you see variables like foo1, foo2, foo3, you should be putting those in an array so they can be indexed by number). The next step might be to refactor the code so you have something like:
pb_playerCard[0].Image = images[player.card[0]];
Or:
pb_playerCard[0].Image = player.card[0].Image;

thanks again guys, I got it working using a List to store card suites for the player objects and a dictionary that stores image references.
Dictionary<CardType, System.Drawing.Image> newDeckImages = new Dictionary<CardType, System.Drawing.Image>();
...
newDeckImages.Add(CardType.Diamonds, pb_diamonds.Image);
newDeckImages.Add(CardType.Hearts, pb_hearts.Image);
newDeckImages.Add(CardType.Clubs, pb_clubs.Image);
newDeckImages.Add(CardType.Spades, pb_spades.Image);
...
private void showMyCards()
{
pb_termCard1.Image = newDeckImages[Terminator.cards[0]];
pb_termCard2.Image = newDeckImages[Terminator.cards[1]];
pb_termCard3.Image = newDeckImages[Terminator.cards[2]];
pb_termCard4.Image = newDeckImages[Terminator.cards[3]];
}

The main class Player and the associated enumeration should be like this:
class Player
{
public Player(int n)
{
Cards = new List(n);
}
public IList Cards { get; private set; }
}
enum CardType
{
Diamond,
Club,
Spade,
Heart
}
Another method GetPictureBox() should in the winform partial class
public PictureBox GetPictureBox(string pictureBoxName)
{
if(this.Controls.ContainsKey(pictureBoxName))
{
Control control = this.Controls[pictureBoxName];
PictureBox pictureBox;
if ((pictureBox = control as PictureBox) != null)
{
return pictureBox;
}
}
return null;
}
Use this method in the class using the player instance.
Player player = new Player(3);
player.Cards.Add(CardType.Diamond);
player.Cards.Add(CardType.Club);
player.Cards.Add(CardType.Spade);
Dictionary dictionary = new Dictionary();
dictionary.Add(CardType.Diamond, pb_diamonds);
dictionary.Add(CardType.Spade, pb_spades);
dictionary.Add(CardType.Club, pb_clubs);
dictionary.Add(CardType.Heart, pb_hearts);
Finally the assignment to the image property,
for (int i = 1; i < 5; i++)
{
string playercardPictureBoxName = string.Concat("pb_playercard", i.ToString());
PictureBox pictureBox = this.GetPictureBox(playercardPictureBoxName);
if (pictureBox != null)
{
pictureBox.Image = dictionary[player.Cards[i - 1]].Image;
}
}

No, not how you're doing it, and although similar things can be achieved with reflection, what you want here is probably closer to just using FindControl for the "pb_playerCard" part and a simple dictionary of type Dictionary<CardSuitEnum, WhateverTheTypeOfImageIs> which you can populate with the pb_diamonds.Image etc.
Apols, I don't know what type .Image holds and can't be bothered to look it up :)

Related

How to make an enum-like Unity inspector drop-down menu from a string array with C#?

I'm making a Unity C# script that is meant to be used by other people as a character dialog tool to write out conversations between multiple game characters.
I have a DialogueElement class and then I create a list of DialogueElement objects. Each object represents a line of dialogue.
[System.Serializable] //needed to make ScriptableObject out of this class
public class DialogueElement
{
public enum Characters {CharacterA, CharacterB};
public Characters Character; //Which characters is saying the line of dialog
public string DialogueText; //What the character is saying
}
public class Dialogue : ScriptableObject
{
public string[] CharactersList; //defined by the user in the Unity inspector
public List<DialogueElement> DialogueItems; //each element represents a line of dialogue
}
I want the user to be able to use the dialog tool by only interacting with the Unity inspector (so no editing code). The problem with this setup then is that the user of the dialogue tool cannot specify their own custom names (such as Felix or Wendy) for the characters in the Characters enum since they are hardcoded as "CharacterA" and "CharacterB" in the DialogueElement class.
For those not familiar with Unity, it is a game creation program. Unity lets users create physical files (known as scriptable objects) that acts as containers for class objects. The public variables of the scriptable object can be defined through a visual interface called the "inspector" as you can see below:
I want to use an enum to specify which characters is the one saying the line of dialog because using an enum creates a nice drop-down menu in the inspector where the user can easily select the character without having to manually type the name of the character for each line of dialogue.
How can I allow the user to define the elements of the Characters enum? In this case I was trying to use a string array variable where the player can type the name of all the possible characters and then use that array to define the enum.
I don't know if solving the problem this way is possible. I'm open to ANY ideas that will allow the user to specify a list of names that can then be used to create a drop down menu in the inspector where the user selects one of the names as seen in the image above.
The solution doesn't need to specifically declare a new enum from a string array. I just want to find a way to make this work. One solution I thought of is to write a separate script that would edit the text of the C# script that contains the Character enum. I think this would technically work since Unity automatically recompiles scripts every time it detects they were changed and updates the scriptable objects in the inspector, but I was hoping to find a cleaner way.
Link to repository for reference:
https://github.com/guitarjorge24/DialogueTool
You can't change the enum itself as it needs to be compiled (well it is not completely impossible but I wouldn't recommend to go ways like actively change a script and force a re-compile)
Without seeing the rest of the types you need it is a bit hard but what you want you would best do in a custom editor script using EditorGUILayout.Popup. As said I don't know your exact needs and the type Characters or how exactly you reference them so for now I will assume you reference your DialogueElement to a certain character via its index in the list Dialogue.CharactersList. This basically works like an enum then!
Since these editor scripts can get quite complex I try to comment every step:
using System;
using System.Collections.Generic;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditorInternal;
#endif
using UnityEngine;
[CreateAssetMenu]
public class Dialogue : ScriptableObject
{
public string[] CharactersList;
public List<DialogueElement> DialogueItems;
}
[Serializable] //needed to make ScriptableObject out of this class
public class DialogueElement
{
// You would only store an index to the according character
// Since I don't have your Characters type for now lets reference them via the Dialogue.CharactersList
public int CharacterID;
//public Characters Character;
// By using the attribute [TextArea] this creates a nice multi-line text are field
// You could further configure it with a min and max line size if you want: [TextArea(minLines, maxLines)]
[TextArea] public string DialogueText;
}
// This needs to be either wrapped by #if UNITY_EDITOR
// or placed in a folder called "Editor"
#if UNITY_EDITOR
[CustomEditor(typeof(Dialogue))]
public class DialogueEditor : Editor
{
// This will be the serialized clone property of Dialogue.CharacterList
private SerializedProperty CharactersList;
// This will be the serialized clone property of Dialogue.DialogueItems
private SerializedProperty DialogueItems;
// This is a little bonus from my side!
// These Lists are extremely more powerful then the default presentation of lists!
// you can/have to implement completely custom behavior of how to display and edit
// the list elements
private ReorderableList charactersList;
private ReorderableList dialogItemsList;
// Reference to the actual Dialogue instance this Inspector belongs to
private Dialogue dialogue;
// class field for storing available options
private GuiContent[] availableOptions;
// Called when the Inspector is opened / ScriptableObject is selected
private void OnEnable()
{
// Get the target as the type you are actually using
dialogue = (Dialogue) target;
// Link in serialized fields to their according SerializedProperties
CharactersList = serializedObject.FindProperty(nameof(Dialogue.CharactersList));
DialogueItems = serializedObject.FindProperty(nameof(Dialogue.DialogueItems));
// Setup and configure the charactersList we will use to display the content of the CharactersList
// in a nicer way
charactersList = new ReorderableList(serializedObject, CharactersList)
{
displayAdd = true,
displayRemove = true,
draggable = false, // for now disable reorder feature since we later go by index!
// As the header we simply want to see the usual display name of the CharactersList
drawHeaderCallback = rect => EditorGUI.LabelField(rect, CharactersList.displayName),
// How shall elements be displayed
drawElementCallback = (rect, index, focused, active) =>
{
// get the current element's SerializedProperty
var element = CharactersList.GetArrayElementAtIndex(index);
// Get all characters as string[]
var availableIDs = dialogue.CharactersList;
// store the original GUI.color
var color = GUI.color;
// Tint the field in red for invalid values
// either because it is empty or a duplicate
if(string.IsNullOrWhiteSpace(element.stringValue) || availableIDs.Count(item => string.Equals(item, element.stringValue)) > 1)
{
GUI.color = Color.red;
}
// Draw the property which automatically will select the correct drawer -> a single line text field
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUI.GetPropertyHeight(element)), element);
// reset to the default color
GUI.color = color;
// If the value is invalid draw a HelpBox to explain why it is invalid
if (string.IsNullOrWhiteSpace(element.stringValue))
{
rect.y += EditorGUI.GetPropertyHeight(element);
EditorGUI.HelpBox(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), "ID may not be empty!", MessageType.Error );
}else if (availableIDs.Count(item => string.Equals(item, element.stringValue)) > 1)
{
rect.y += EditorGUI.GetPropertyHeight(element);
EditorGUI.HelpBox(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), "Duplicate! ID has to be unique!", MessageType.Error );
}
},
// Get the correct display height of elements in the list
// according to their values
// in this case e.g. dependent whether a HelpBox is displayed or not
elementHeightCallback = index =>
{
var element = CharactersList.GetArrayElementAtIndex(index);
var availableIDs = dialogue.CharactersList;
var height = EditorGUI.GetPropertyHeight(element);
if (string.IsNullOrWhiteSpace(element.stringValue) || availableIDs.Count(item => string.Equals(item, element.stringValue)) > 1)
{
height += EditorGUIUtility.singleLineHeight;
}
return height;
},
// Overwrite what shall be done when an element is added via the +
// Reset all values to the defaults for new added elements
// By default Unity would clone the values from the last or selected element otherwise
onAddCallback = list =>
{
// This adds the new element but copies all values of the select or last element in the list
list.serializedProperty.arraySize++;
var newElement = list.serializedProperty.GetArrayElementAtIndex(list.serializedProperty.arraySize - 1);
newElement.stringValue = "";
}
};
// Setup and configure the dialogItemsList we will use to display the content of the DialogueItems
// in a nicer way
dialogItemsList = new ReorderableList(serializedObject, DialogueItems)
{
displayAdd = true,
displayRemove = true,
draggable = true, // for the dialogue items we can allow re-ordering
// As the header we simply want to see the usual display name of the DialogueItems
drawHeaderCallback = rect => EditorGUI.LabelField(rect, DialogueItems.displayName),
// How shall elements be displayed
drawElementCallback = (rect, index, focused, active) =>
{
// get the current element's SerializedProperty
var element = DialogueItems.GetArrayElementAtIndex(index);
// Get the nested property fields of the DialogueElement class
var character = element.FindPropertyRelative(nameof(DialogueElement.CharacterID));
var text = element.FindPropertyRelative(nameof(DialogueElement.DialogueText));
var popUpHeight = EditorGUI.GetPropertyHeight(character);
// store the original GUI.color
var color = GUI.color;
// if the value is invalid tint the next field red
if(character.intValue < 0) GUI.color = Color.red;
// Draw the Popup so you can select from the existing character names
character.intValue = EditorGUI.Popup(new Rect(rect.x, rect.y, rect.width, popUpHeight), new GUIContent(character.displayName), character.intValue, availableOptions);
// reset the GUI.color
GUI.color = color;
rect.y += popUpHeight;
// Draw the text field
// since we use a PropertyField it will automatically recognize that this field is tagged [TextArea]
// and will choose the correct drawer accordingly
var textHeight = EditorGUI.GetPropertyHeight(text);
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, textHeight), text);
},
// Get the correct display height of elements in the list
// according to their values
// in this case e.g. we add an additional line as a little spacing between elements
elementHeightCallback = index =>
{
var element = DialogueItems.GetArrayElementAtIndex(index);
var character = element.FindPropertyRelative(nameof(DialogueElement.CharacterID));
var text = element.FindPropertyRelative(nameof(DialogueElement.DialogueText));
return EditorGUI.GetPropertyHeight(character) + EditorGUI.GetPropertyHeight(text) + EditorGUIUtility.singleLineHeight;
},
// Overwrite what shall be done when an element is added via the +
// Reset all values to the defaults for new added elements
// By default Unity would clone the values from the last or selected element otherwise
onAddCallback = list =>
{
// This adds the new element but copies all values of the select or last element in the list
list.serializedProperty.arraySize++;
var newElement = list.serializedProperty.GetArrayElementAtIndex(list.serializedProperty.arraySize - 1);
var character = newElement.FindPropertyRelative(nameof(DialogueElement.CharacterID));
var text = newElement.FindPropertyRelative(nameof(DialogueElement.DialogueText));
character.intValue = -1;
text.stringValue = "";
}
};
// Get the existing character names ONCE as GuiContent[]
// Later only update this if the charcterList was changed
availableOptions = dialogue.CharactersList.Select(item => new GUIContent(item)).ToArray();
}
public override void OnInspectorGUI()
{
DrawScriptField();
// load real target values into SerializedProperties
serializedObject.Update();
EditorGUI.BeginChangeCheck();
charactersList.DoLayoutList();
if(EditorGUI.EndChangeCheck())
{
// Write back changed values into the real target
serializedObject.ApplyModifiedProperties();
// Update the existing character names as GuiContent[]
availableOptions = dialogue.CharactersList.Select(item => new GUIContent(item)).ToArray();
}
dialogItemsList.DoLayoutList();
// Write back changed values into the real target
serializedObject.ApplyModifiedProperties();
}
private void DrawScriptField()
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField("Script", MonoScript.FromScriptableObject((Dialogue)target), typeof(Dialogue), false);
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
}
}
#endif
And this is how it would look like now
Another option is to use an inspector enhancement asset like Odin Inspector or NaughtyAttributes.
So if you had this member:
public string[] CharactersList;
With Odin you would write:
[ValueDropdown("CharactersList")]
In NaughtyAttributes you would write:
[Dropdown("CharactersList")]
These solutions are similar to the suggestion by datsfain.
Although Odin is not free, it has tons of fancy extra functionality.
https://odininspector.com/attributes/value-dropdown-attribute
NaughtyAttributes is free but a bit older and more basic.
https://dbrizov.github.io/na-docs/attributes/drawer_attributes/dropdown.html
I'm going to write another answer... because double answers are better!
As derHugo said in his answer, it could be done using enums, but it would force a recompile. Well, sometimes maybe you just really want that enum (they are much faster than strings in some scenarios) and you're willing to accept the recompilation penalty.
So here's a little utility class I wrote for generating an enum and saving it to file.
Make a member variable with a list of strings that the designer can edit.
You'll probably want to put a button on your UI called "GenerateEnums" or something like that and perhaps a string for the save directory, which would call the save function and write the enum definition to file. There is code to force a recompile, so whenever the designer presses that button, they'll need to wait for a few seconds. Also, there is the chicken-and-egg problem - you can't reference the type until the definition has been generated at least once. Typically I get around that by just putting a file with the same filename in the intended location and giving it a dummy enum (like "public enum CharacterType { dummy }"). After the designer edits the string list, presses the generate button, and waits for a few seconds, they'll be able to see the updated selections in any fields that use that enum type (CharacterType in this example).
// this has a bunch of functions for generating enums in the editor
using System.Collections.Generic;
public static class EnumUtils
{
private static readonly HashSet<string> m_keywords = new HashSet<string> {
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked",
"class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else",
"enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for",
"foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock",
"long", "namespace", "new", "null", "object", "operator", "out", "override", "params",
"private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed",
"short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw",
"true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using",
"virtual", "void", "volatile", "while"
};
// This function will return a string containing an enum declaration with the specified parameters.
// name --> the name of the enum to create
// values -> the enum values
// primitive --> byte, int, uint, short, int64, etc (empty string means no type specifier)
// makeClassSize --> if this is true, an extra line will be added that makes a static class to hold the size.
// example:
// print(MakeEnumDeclaration("MyType", { Option1, Option2, Option3 }, "byte", true));
// output --> public enum MyType : byte { Option1, Option2, Option3 }
// public static class MyTypeSize { public const byte Size = 3; }
public static string MakeEnumDeclaration(string name, List<string> values, string primitive, bool makeSizeClass)
{
string prim = primitive.Length <= 0 ? "" : " : " + primitive;
string declaration = "public enum " + name + prim + " { ";
int countMinusOne = values.Count - 1;
for (int i = 0; i < values.Count; i++) {
declaration += MakeStringEnumCompatible(values[i]);
if (i < countMinusOne) { declaration += ", "; }
}
declaration += " }\n";
if (makeSizeClass) {
declaration += $"public static class {name}Size {{ public const {primitive} Size = {values.Count}; }}\n";
}
return declaration;
}
public static void WriteDeclarationToFile(string fileName, string declaration, bool reImport = false, string filePath = "Assets/Scripts/Generated/")
{
// ensure that the output directory exists
System.IO.Directory.CreateDirectory(filePath);
// write the file
System.IO.File.WriteAllText(filePath + fileName, "// This file was auto-generated\n\n" + declaration);
#if UNITY_EDITOR
if (reImport) { UnityEditor.AssetDatabase.ImportAsset(filePath); }
#endif
}
public static void WriteDeclarationsToFile(string fileName, List<string> declarations, bool reImport = false, string filePath = "Assets/Scripts/Generated/")
{
string text = "";
for (int i = 0; i < declarations.Count; i++) { text += declarations[i]; }
WriteDeclarationToFile(fileName, text, reImport, filePath);
}
// given a string, attempts to make the string compatible with an enum
// if there are any spaces, it will attempt to make the string camel-case
public static string MakeStringEnumCompatible(string text)
{
if (text.Length <= 0) { return "INVALID_ENUM_NAME"; }
string ret = "";
// first char must be a letter or an underscore, so ignore anything that is not
if (char.IsLetter(text[0]) || (text[0] == '_')) { ret += text[0]; }
// ignore anything that's not a digit or underscore
bool enableCapitalizeNextLetter = false;
for (int i = 1; i < text.Length; ++i) {
if (char.IsLetterOrDigit(text[i]) || (text[i] == '_')) {
if (enableCapitalizeNextLetter) {
ret += char.ToUpper(text[i]);
} else {
ret += text[i];
}
enableCapitalizeNextLetter = false;
} else if (char.IsWhiteSpace(text[i])) {
enableCapitalizeNextLetter = true;
}
}
if (ret.Length <= 0) { return "INVALID_ENUM_NAME"; }
// all the keywords are lowercase, so if we just change the first letter to uppercase,
// then there will be no conflict
if (m_keywords.Contains(ret)) { ret = char.ToUpper(ret[0]) + ret.Substring(1); }
return ret;
}
}
Caveats:
Keep in mind that this method is a bit brittle as with anytime you use an enum in the editor. Deleting or inserting an enum value in the middle of the values could cause any fields using that enum to use the wrong index because all the indexes will shift by one. There will be no compiler error and you could get weird activity at runtime.
Deleting or renaming an enum value might cause your code to stop compiling if you're using that enum value in your code (which is probably a good thing - with strings there is no compiler error and you'll get a silent confusing surprise at runtime).
For those who need enum like string dropdown, You can use code from my github repo.
download this folder and add it in your project.
https://github.com/datsfain/EditorCools/tree/main/Assets/EditorCools/DropdownStringAttribute
add following lines of code to any serializable class:
[Dropdown(nameof(MethodThatReturnsStringArray))]
public string options2;

Array of WinForm Elements

So I do have a WinForm in my Programm, which contains a series of each a ComboBox and two TextBoxs. There are atm 8 Lines, but this will increase to a total of at least 32, therefore I would like to work with an Array or similar. How do I do that?
My current working, method is that a create a new array of TextBoxes/ComboBoxes which I assign the designated Elemt of the WinForm, manually. Therefore I have a list like this:
tbGU[0] = tbGU1;
tbGO[0] = tbGO1;
cbS[0] = cbS1;
Of course, this looks awful and isn't great if it's copied many times. Anyone got a Solution to my Problem?
I need to access the SelectedIndex of the ComboBox and the Text of the TextBoxes.
I was hoping that I could avoid having to create all the Elements manually by code.
One simple solution is to use the array initializer syntax:
ComboBox[] cbS = new[] { cbS1, cbS2, cbS3 ... };
Another way of doing this would be to get rid of the variables cbS1, cbS2 ... cBSn altogether and create the controls in a for loop.
ComboxBox[] cbS = new ComboBox[32];
// declare the text box arrays here as well
for (int i = 0 ; i < cbS.Length ; i++) {
cbS[i] = new ComboBox();
cbS[i].Location = ... // use "i" to help you position the control
// configure your combo box ...
this.Controls.Add(cbS[i]);
// do the same for the text boxes.
}
A third way is to create a custom control:
// name this properly!
public class MyControl: UserControl {
public ComboBox CbS { get; }
public TextBox TbGU { get; }
public TextBox TbGO { get; }
public MyControl() {
// create and configure the combo box and text boxes here ...
}
}
Then you can use a for loop to create lots of MyControls.

Optimizing a Unity CharacterSelection

in my game, I got some teachers to make the player increase his skills. At the moment, there are 12 guys, so I wrote a base class for them.
This class selects the correct data from its own "teacherDataClass". The data is set by an index from the Editor.
My code:
[SerializeField]
int teacherIndex; // set an index in the editor -> selection of teacher
NpcTeacherData teacherData; // the data class
private void Start()
{
NpcTeacherData[] teachers = // Collection of all teachers
{
new TeacherAlchemist(),
new TeacherBlacksmith(),
new TeacherBowyer(),
new TeacherButcher(),
new TeacherHunter(),
new TeacherInnkeeper(),
new TeacherJuggler(),
new TeacherMessenger(),
new TeacherPriest(),
new TeacherTamer(),
new TeacherThief(),
new TeacherTownGuard()
};
teacherData = teachers[teacherIndex]; // Get the right teacher by the index
}
So this looks fine and this works fine. If you do not want to use the Editor, you could compare the tag of the teacherObject like this way
NpcTeacherData teacherData; // the data class
private void Start()
{
switch (gameObject.tag) // compare the tag of the teacher and set its class
{
case "TeacherAlchemist":
teacherData = new TeacherAlchemist();
break;
case "TeacherBlacksmith":
teacherData = new TeacherBlacksmith();
break;
//...
}
}
But I do not like these ways that much :/ Are there any better possibilities? Like a DropDownSelection in the Editor maybe?
I tried
[SerializeField]
object script; // Place the right data class here
[SerializeField]
object script as NpcTeacherData; // Place the right data class here
But this did not work.
I just want to optimize it :)
Maybe you can use an enum to achieve this?
It would be something like this:
public enum TeacherType
{
TeacherAlchemist,
TeacherBlacksmith,
TeacherBowyer,
TeacherButcher,
TeacherHunter,
TeacherInnkeeper,
TeacherJuggler,
TeacherMessenger,
TeacherPriest,
TeacherTamer,
TeacherThief,
TeacherTownGuard
}
public TeacherType type;
private NpcTeacherData teacherData;
private void Start()
{
switch (type)
{
case TeacherType.TeacherAlchemist:
teacherData = new TeacherAlchemist();
break;
//...
}
}
However it doesn't differ a lot from your first solution (only choice is a bit easier since the type is shown instead of an index).
Hope this helps,
You can use Editor GUI Popup. Here is how you can make a dropdown/popup -
public string[] options = new string[] {"TeacherAlchemist","TeacherBlacksmith","TeacherBowyer","TeacherButcher","TeacherHunter","TeacherInnkeeper","TeacherJuggler","TeacherMessenger","TeacherPriest","TeacherTamer","TeacherThief","TeacherTownGuar"};
public int selectedIndex = 0;
Then Use OnGUI function to store selected value
void OnGUI()
{
selectedIndex = EditorGUILayout.Popup("TeacherDataClass", selectedIndex, options);
}
You can find reference here.

How can I separate the pins I am receiving from my data into different categories?

So I need two separate the pins into groups because when I filter them I need to know which pins belongs to which category.
This is how I recieve them. So there is one group that loads if the int app.value1 is 0 and the other one is if app.value2 is 0.
The reason I need to separate them is because I am doing a filter where I need to separate the two. Should I do two different lists maybe? Or is there a better way?
List<Pin> myPins = new List<Pin>();
private async Task<List<Pin>> LoadData()
{
var pins = new List<Pin> ();
var getItems = await phpApi.getInfo ();
foreach (var currentItem in getItems["results"]) {
longString = currentItem ["long"].ToString ();
latString = currentItem ["lat"].ToString ();
if (App.value1 == 0) { //so this below is the first group of pins then
var pin = new Pin ();
pin.Position = new Position (latString, longString);
pin.Label = "testlabel";
pin.Address = "testadress";
pins.Add (pin);
}
if (App.value2 == 0) { //second group of pins
//i add pins here as well (so the same code as App.value1), but this intvalue is connected to a different "button"
pins.Add (pin);
}
}
}
And this is my filter that is not working right now because the pin doesnt know which group the belong to:
private async void FilterPins (string filter)
{
map.Pins.Clear ();
foreach(Pin p in myPins) {
if (string.IsNullOrWhiteSpace(filter) || (p.Label.Contains(filter))) {
map.Pins.Add (p); //this is just a searchfilter via my searchbar that isnt working when I add the code below. If i remove the code below this function works.
}
if (App.value1 == 0 ) {
map.Pins.Add (p);
System.Diagnostics.Debug.WriteLine ("add");
}
if (App.value2 == 0 ) {
map.Pins.Add (p);
System.Diagnostics.Debug.WriteLine ("add");
}
}
}
Since the Pin class is sealed(https://developer.xamarin.com/api/type/Xamarin.Forms.Maps.Pin/), it's a bit hard to work around. However you should be able to create two separate List<Pin> in which you can place the respective Pin objects into based on your filter. You can then swap your respective Map.Pins object with which list of Pin objects you need to display.
https://developer.xamarin.com/api/property/Xamarin.Forms.Maps.Map.Pins/
Otherwise you can create another POCO object that contains a Pin object and a string Category in it.
EX:
public class PinWithCategory
{
public Pin Pin {get; set;}
public string Category {get; set;}
}

How to show multiple columns from objects in a datagridview using BindingSource?

I have an object that I would like to show in a datagridview. The object (a movement) has a name, a weight and then some Location objects. The Locations each have a name and a value. What I want to be able to do is for each movement to show the name, weight, location1.name, location1.value, location2.name, location2.value each in their own column of the DGV.
I've managed to override the toString method for Location but that only gives me 1 column per Location, I really want to be able to have two. Is there any way to do this? I'm currently using a BindingSource like this
BindingSource movementSource = new BindingSource();
movementsSource.DataSource = aircraft.movements;
MovementDataGridView.DataSource = movementsSource;
If it makes any difference movements is a List<Movement>
Thanks!
What I do is create a wrapper class like this:
class MovementView {
private Movement movement;
public MovementView(Movement m) { movement = m; }
public string Name { get { return movement.Name; } }
// etc..
public string Location1Name { get { return movement.Locations[0].Name; } }
// etc..
}
Then bind the grid to a List<MovementView> instead:
movementsSource.DataSource = aircraft.movements
.Select(t => new MovementView(t))
.ToList();

Categories