Changing name of Virtual Button after instantiating new ones - c#

So, I am trying to create a function that Instantiates a virtual button in Vuforia Unity.
This Virtual Button contains a panel and a 3d Text. It is the child of an ImageTarget.
So far I managed to Changed the name of the game object but what I can't seem to get around is changing the Virtual Button Behaviour Name.
For instance, I want to change the Name 'Test' to 'Test1'
void genLevel(Hashtable level)
{
int min = 1;
int max = level.Count;
//inputButtons = new GameObject[level.Count];
System.Random rnd = new System.Random();
IEnumerable<int> numbers = Enumerable.Range(min, max).OrderBy(r => rnd.Next());
foreach(int i in numbers)
{
string output = (string)level[i];
Debug.Log(i + " " + output);
var temp = Instantiate(vb, new Vector3(-0.0056f, 0.001f, 0.4547f), Quaternion.identity, parent.transform);
temp.name = "Button" + i.ToString();
temp.GetComponent<VirtualButtonBehaviour>().name = "Button" + i.ToString();
}
Destroy(vb);
}
I tried accessing the VirtualButtonBehavior Script by creating a public VirtualButtonBehaviour sc.
After trying to access the Name by writing the line temp.GetComponent<VirtualButtonBehaviour>().name = "Button" + i.ToString();
The following error popped up:
'VirtualButtonBehaviour' does not contain a definition for 'Name' and no accessible extension method 'Name' accepting a first argument of type 'VirtualButtonBehaviour' could be found (are you missing a using directive or an assembly reference?)
I also referred to the Vuforia Documentation How to Create and Modify Targets in Native.
What is the best way to go about it?

Edit original content still valid.
According to the documentation, they expose the name through string VirtualButtonName => mName but it seems it might only be a setter.
Not clear if the class VirtualButtonName is Sealed, Internal, if not you can inherits from it and create your own SetName function.
=====
I do not know Vuforia Unity, but I know you are probably facing one of those 2 cases.
There are 2 way in which VirtualButtonBehavior could have defined their Name field, the one you see in the inspector, assuming that the class inherits directly from MonoBehavior.
1st potential implementation
public class VirtualButtonBehavior : MonoBehavior
{
public string Name;
...
With this approach, you should be able to do the following (using your code sample)
var buttonBehavior = temp.GetComponent<VirtualButtonBehaviour>();
buttonBehavior.Name = "Button" + i.ToString();
First notice the upper case .Name that I used, if you use .name on any MonoBehavior, this will points to the GameObject name on which your component VirtualButtonBehaviour is attached to and not to the Name property of the VirtualButtonBehaviour.
2nd potential implementation
public class VirtualButtonBehavior : MonoBehavior
{
[SerializedField]
private string Name;
...
In this case you are out of luck, because Unity draw automatically public field in their inspector if the class inherits from MonoBehavior.
This is actually a good practice of encapsulating your code a private field combined with the [SerializedField] tag, in which it will draw in the editor but won't be modifiable by code outside of the VirtualButtonBehavior class.
However, with this approach you can only modify it in the editor once the component is attached to a GameObject.
One workaround, could be that you create a subclass of VirtualButtonBehavior and you implement a new Field that you wish to keep track of.
public class CustomVirtualButtonBehavior : VirtualButtonBehavior
{
public string MyName;
...
Then you can set dynamically your value using the following instead.
var buttonBehavior = temp.GetComponent<CustomVirtualButtonBehavior>();
buttonBehavior .MyName = "Button" + i.ToString();
Before going that route, make sure they did not expose a public function to set their name.
var buttonBehavior = temp.GetComponent<VirtualButtonBehaviour>();
buttonBehavior.SetName("Button" + i.ToString()); // is this something they provided?
Finally, there might be a third way if they were kind enough to set their Name field scope to protected you could use it in a subclass and create your own SetName function.

Related

Organizing inventory by alphabetical order

So I created a method to populate the inventory system I have created in Unity, however I can't seem to figure out a way to order them in alphabetical order. Each button is created as a child of the button holder panel which is a game object variable. This method is called whenever the player picks something up.
private void Populate_Inventory ( )
{
Button btn_CurrentItem;
int int_Count;
for ( int_Count = int_PreviousCount; int_Count < strList_Inventory.Count; int_Count++ )
{
btn_CurrentItem = Instantiate ( btn_Item, pnl_ButtonHolder.transform );
btn_CurrentItem.name = strList_Inventory [ int_Count ];
btn_CurrentItem.GetComponentInChildren<Text> ( ).text = strList_Inventory [ int_Count ];
btn_CurrentItem.gameObject.AddComponent<Inventory_Item> ( );
btn_CurrentItem.GetComponent<Inventory_Item> ( ).str_Name = cls_Pickup.str_PickupName;
btn_CurrentItem.GetComponent<Inventory_Item> ( ).str_Information = cls_Pickup.str_PickupInformation;
int_PreviousCount = int_Count;
}
int_PreviousCount++;
}
If anyone can help it would be very appreciated.
Jonathan Palmer
The primary issue with this approach is that you're adding single buttons at a time whenever you pick up an item, and those buttons are created as children of the pnl_ButtonHolder object, but your method has no knowledge of previous buttons that have been created.
Option 1 (Bad)
You can use the GetComponentsInChildren<Button>() method on the pnl_ButtonHolder object to get all of the button components that have been previously created once you've added a new button, and then sort your buttons according to their name.
This isn't ideal as GetComponentsInChildren<>() is an expensive method, and doesn't make use of the idea of an Inventory very well.
Option 2 (Good)
Create an Inventory class that manages your actual inventory, including sorting items. It might look something like this:
public class Inventory_Button : MonoBehaviour
{
public Button button = default;
public void Initialize(Intenvory_Item item)
{
button.name = item.name;
// Other work here.
}
}
public class Inventory : MonoBehaviour
{
public GameObject inventoryItemPrefab = default;
public Transform inventoryParent = default;
private List<Inventory_Item> _inventory = new List<Inventory_Item>();
private List<Inventory_Button> _inventoryButtons = new List<Inventory_Button>();
public void AddItem(Intenvory_Item item)
{
_inventory.Add(item);
Inventory_Button button = GameObject.Instantiate(inventoryItemPrefab, inventoryParent).GetComponent<Inventory_Button>();
button.Initialize(item);
_inventoryButtons.Add(button);
_inventoryButtons.Sort((x, y) => x.name.CompareTo(y.name));
}
public void RemoveItem(Inventory_Item item)
{
// Do work to remove the item.
}
}
This kind of setup is useful for several reasons:
Your inventory management is now handled by a single class rather than a collection of objects. This makes it easy to interact with.
It will make removing items much easier in the future. With your current implementation, it looks like you'd have a difficult time removing an item from the player's inventory.
It separates responsibility between an Inventory class, an Inventory_Item class, and an Inventory_Button class, each of which stands on its own and works together.
A few final notes:
I've left out some details in the two classes above. You should fill them in to fit the needs of your game.
You can be more efficient with sorting by using something like Insertion Sort. This solution will get the job done.
Using prefixes for naming your variables can be quite confusing to people trying to read your code. I recommend you check out a style guide. There's a great one here.

Programatically bind script property on Unity (edit mode not at runtime)

For example, I have MonoBehaviour derived class called Foo:
class Foo : MonoBehaviour {
public Button lastButton;
public Image []images;
}
[CustomEditor(typeof(Foo))]
class FooEditor : Editor {
public override void OnInspectorGUI()
{
DrawDefaultInspector();
Foo foo = (Foo)target;
if(GUILayout.Button("Bind last Button and all Images")) {
/* how to bind programatically last foo.GetComponentsInChildren<Button>();
to foo.gameObject.scene's
foo.gameObject.GetComponent<Foo>().lastButton
in edit mode's property window
(without have to drag on the editor)?
*/
/* and how to bind programatically all foo.GetComponentsInChildren<Image>();
to foo.gameObject.scene's
foo.gameObject.GetComponent<Foo>().images
in edit mode's property window
(without have to drag all one by one)?
*/
}
}
}
I want to automate the binding without have to drag one by one or do it in runtime, how can I do it programatically?
the only workaround I can think of (if there's no such API) is by saving the scene, parsing the scene yaml, then update the binding on the yaml file, and force UnityEditor to reload the yaml, but not sure if it's gonna work since loading yaml and rewriting it doesn't give equal value
Though I still don't understand the purpose of "binding" to be honest, it seems to me you actually are talking about simply referencing all the components via the editor script instead of having to drag and drop them. (Maybe the binding is included here and I just don't know it under that name?)
First of all make sure that either the entire code for FooEditor is placed in a folder called Editor or wrap it in
#if UNITY_EDITOR
// any code using UnityEditor namespace
#endif
in order to strip them off in a build later. Otherwise you'll get compiler errors since UnityEditor will not exist in a build.
Then the important thing is to allways manipulate the SerializedPropertys of the SerializedObjects in editor scripts. This handles all the marking as dirty, saving changes and Undo/Redo entries for you. If you should happen to manipulate rather the fieds directly you would have to take care of these things yourself ...
a keyrole herefore play SerializedObject.Update and SerializedObject.ApplyModifiedProperties for loading and writing back values between the real target component and its serialized object - I like to call it "shadow clone".
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.UI;
class Foo : MonoBehaviour
{
public Button lastButton;
public Image[] images;
}
#if UNITY_EDITOR
[CustomEditor(typeof(Foo))]
class FooEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
// fetch current values from the real target into the serialized "clone"
serializedObject.Update();
Foo foo = (Foo)target;
if (GUILayout.Button("Bind last Button and all Images"))
{
// get last button field as serialized property
var lastButtonField = serializedObject.FindProperty("lastButton");
// get last Button reference
// pass true to also get eventually inactive children
var buttons = foo.GetComponentsInChildren<Button>(true);
var lastButton = buttons[buttons.Length - 1];
// asign lastButton to the lastButtonField
lastButtonField.objectReferenceValue = lastButton;
// slightly more complex but similar
// get the field as serialized property
var imagesField = serializedObject.FindProperty("images");
// get the images references
// again pass true to also get eventually inactive ones
var images = foo.GetComponentsInChildren<Image>(true);
// now first set the according list size
imagesField.arraySize = images.Length;
// assign all references
for (var i = 0; i < imagesField.arraySize; i++)
{
// serialized property of the element in the field list
var entry = imagesField.GetArrayElementAtIndex(i);
// simply assign the reference like before with the button
entry.objectReferenceValue = images[i];
}
}
// write back changes to the real target
// automatically handles marking as dirty and undo/redo
serializedObject.ApplyModifiedProperties();
}
}
#endif
I hope I'm not completely off and this is what you ment by "binding".
Note if you want to further change values of a reference you optained which is not the target itself you can easily use new SerializedObject(objectReference) in order to get a serialzed object e.g.
var serializedLastButton = new SerializedObject (lastButton);
Or also
var serializedLastButton = new SerializedObject(lastButtonField.objectReferenceValue);
now when changing any field in it again using FindProperty(propertyName) make sure to again also use
serializedLastButton.Update();
// make changes
serializedLastButton.ApplyModifiedProperties();

How to maintain reference to object instantiate at runtime in Unity

I need to instantiate an object at runtime, where the number of objects are based on a txt file (number of lines). All objects need to be clickable (onClick() event) and when they are pressed a tooltip must appear. Tooltips are specific ( Object1 -> Tooltip1, Object2 -> Tooltip2). Tooltips are just a Panel and they consist in some other Panels and Buttons. One of these create a new Panel. Also these Panel are specific (Tooltip1 -> Panel1 and so on). I create a prefab for each of three objects.
So, Object1 - onClick() -> Tooltip1 - onClick() -> Panel1. At runtime.
How can I keep reference of an object create at runtime?
My first solution:
Create a Empty object and assign a script to it with a public variable (Object prefab).
Instantiate Object prefab:
for (int i = 0; i < numberOfObject; i++)
{
var instance = Instantiate(m_ObjectPrefab);
instance.name = "Object_" + m_nameObject[i];
Instantiate one Tooltip for each Object:
var instancePanelTooltip = Instantiate(m_panelTooltipPrefab, m_canvas.transform);
instancePanelTooltip.name = "Panel_Tooltip_" + m_nameObject[i];
Instantiate one Panel for each Tooltip:
var instancePanel = Instantiate(m_panelPrefab, m_canvas.transform);
instancePanel.name = "Panel_" + m_nameObject[i];
instancePanel.SetActive(false);
instancePanelTooltip.SetActive(false);
Add event handler for Object
DetectClickOnObject scriptDetectClickPanelTooltip = instance.AddComponent<DetectClickOnObject>();
scriptDetectClickPanelTooltip.SetTooltip(instancePanelTooltip);
Add event handler for button on Tooltip
DetectClickOnObject scriptDetectClickPanel = buttonOpenPanel.AddComponent<DetectClickOnObject>();
scriptDetectClickPanel.SetPanel(instancePanel);
}
Issue with this solution:
I will instantiate 3 object (Object, Tooltip, Panel) for each line of file. For Object it is okay, but it is not with Tooltip and Panel, because only one Tooltip is active among all (same for Panel).
I just avoid the problem of reference because I create in the same place all objects (one for each element), but what can I do if I need to get access to Tooltip2 or Panel3 without a reference (I am trying to avoid Find and similar).
Conclusion of the first solution: Solution is working, but I think there is a better way of doing that (avoid create so much object and keep reference in the right way).
My second solution (guidelines):
I am trying to create a class to keep reference of all object create at runtime.
I want to create an instance of Object for each lines, but I want just one Tooltip and Panel for all Object and changing properties according to Object clicked. So Object is create at runtime, but Tooltip and Panel are already in the scene but not active.
I need a Register Event Manager to add onClick() event at runtime on Objects and it need to handle properties to be set on Tooltip and Panel based on clicked Object.
Problems with second solution: Referring to 1) I tried to follow that, but I ended up with nothing. I am lost between singleton, static and something else. Referring to 2) I think it can be easy, I just need to cut away some of the first solution. Referring to 3) I can not do more if I do not have a class reference manager.
What I am looking for:
Is first solution so bad? If I look at code I am disgusted by it, it is far away from elegance (or something similar).
Can you suggest me how can I keep track of reference create at runtime with a Reference Manager? And how use it?
#Behnam Sattar suggestion:
As DataModell class,
public class DataModelPOI
{
public string m_namePOI { get; private set; }
public string m_locationPOI { get; private set; }
public Vector2d m_positionPOI { get; private set; }
public GameObject m_gameObject_POI;
public GameObject m_gameObjectTooltip;
public GameObject m_gameObjectPanel;
public DataModelPOI(string namePOI, string locationPOI, Vector2d positionPOI)
{
this.m_namePOI = namePOI;
this.m_locationPOI = locationPOI;
this.m_positionPOI = positionPOI;
}
}
As DataManager,
public class POIManager : MonoBehaviour
{
List<DataModelPOI> dataCollectionPOI = new List<DataModelPOI>();
void Start()
{
ReadFile();
SpawnPOI();
}
void Update()
{
int count = dataCollectionPOI.Count;
for (int i = 0; i < count; i++)
{
UpdatePOIPosition();
}
}
void ReadFile()
{
TakeDataFromFile();
for (int i = 0; i < ength; i++)
{
DataModelPOI dataPOI = new DataModelPOI(m_namePoi[i], m_namePoi[i], _locations[i]);
dataCollectionPOI.Add(dataPOI);
}
}
private void SpawnPOI()
{
for (int i = 0; i < dataCollectionPOI.Count; i++)
{
DataModelPOI dataPOI = dataCollectionPOI[i];
var instance = Instantiate(m_POIPrefab);
instance.name = "POI_" + m_namePoi[i];
dataPOI.m_gameObject_POI = instance;
dataPOI.m_gameObjectTooltip = m_panelTooltipPOI;
dataPOI.m_gameObjectPanel = m_panelPOI;
}
}
Now I need to register Event associate to GameObject instantiate before. I want to do that in my EventManager. How can I point to dataCollectionPOI in EventManager class created and feeded in DataManager? Thanks for your time.
Based on my understanding your question is mostly a design question. In first part of this answer, I'm giving you a suggestion for doing the design and keeping a reference to your objects. In second part I'll give you some tips regarding performance.
[I'm using RootObject instead of Object to refer you the main GameObject you create.]
Design
Let's break down our needs and then come up with a solution for each.
First we want to read some text file and then get some data from that. This data will be used later for creation of GameObjects. For now let's just focus on the data itself.
What we want here is a manager class which reads the file for us and stores the data in some form. We access this manager later and ask for our data in order to create the GameObjects.
This manager class stores our data in a collection of data objects [notice here we're talking about plain objects and not Unity's GameObjects]. You need to design this data class based on each line of text you have. Optionally, you can also keep the references to GameObjects here too.
Assume you're reading three string values from each line, named ValueOne, ValueTwo, and ValueThree, and you want to keep reference to three GameObjects called RootObject, ToolTip and Panel. For such purpose you can define following class:
public class DataModel {
// Values read from text file.
public string valueOne { get; private set; }
public string valueTwo { get; private set; }
public string valueThree { get; private set; }
// Placeholders for GameObjecs created at runtime.
public GameObject rootObject;
public GameObject tooltipObject;
public GameObject panelObject;
public DataModel(string valueOne, string valueTwo, string valueThree){
this.valueOne = valueOne;
this.valueTwo = valueTwo;
this.valueThree = valueThree;
}
}
Then in your manager class you can create a collection (a List for example) for holding your data. Your manager class should read the text file at some point and populate this list with instances of DataModel. It will be something like this:
public class DataManager {
List<DataModel> dataCollection = new List<DataModel>();
public void ReadFile() {
// Here you need to read the file and get the values you need.
// The actual code should be different from what I'm putting here.
foreach(string line in lines) {
// You get valueOne, valueTwo and valueThree
// from each line and maybe prepare them
// (maybe you need conversion from string to int)
DataModel data = new DataModel(valueOne, valueTwo, valueThree);
dataCollection.Add(data);
}
}
}
After you call the method on manager to read the data, you'll have your data prepared for you whenever you want to use it.
It's time to create objects based on the data and save the reference.
for (int i = 0; i++; i <= manager.dataCollection.Count) {
DataModel data = manager.dataCollection[i];
data.rootObject = instantiate() // You instantiate the root GameObject here.
data.tooltip = instantiate() // You instantiate the tooltop GameObject here.
data.panel = instantiate() // You instantiate the panel GameObject here.
}
Done. Now you have a manager class which has reference to all the data and also GameObjects which are created based on this data.
Performance
It might not be a good idea to do this all in runtime as it might cause frame drops in your game. If this is a problem you can try object pooling. If you search for that you should be able to find great tutorials on how to perform object pooling.
If you ended up not using object pooling, you can still remedy any performance drops by instantiating only one GameObject per frame. This can be done using Coroutines. You just need to do a yield return new WaitForEndOfFrame() in your loop of instantiation.
Final Note
Keep in mind that this all a suggestion and I don't think there is one single best answer to your question. Make sure you try to understand what tools are at your disposal and try them all before deciding on one solution. :)
You're basically on the right track, but you can separate (or 'objectify' ;) what scripts keep what references. Keep scripts that manage the internal workings of your prefabs in those prefabs.
A singleton-like pattern for the Tooltips is definitely the way to go. For where you can't use a singleton, such as in the clickable objects, use something more akin to dependency injection (to save on Finds) and pre-stored reference:
var obj = Instantiate(...);
obj.GetComponent<SomeManagerScript>.SomeDependency = SingletonInstance;
obj.GetComponent<StoredReferenceScript>.TextPanel.Text = "Some Text";
As to events, you can handle them a couple of ways. One, each clickable manages its own bindings/functionality; or two, each clickable notifies a global manager that it was clicked on, and the manager determines the actions to take.
Basically you need to create an object at Runtime and set the data for that specific object.
here is the simplest way to do that
first of all create scripts first one can Create an object at runtime and second one is your data setter script that attach with your prefab
so, when ever you create an object at runtime you can pick a reference of the dataSetter and set the specific data.
Hope below given code may useful to you.
GameObject object = Instantiate(gameObject,parent)
DataSetter s = object.GetComponent<DataSetter>().toolTip = "YourTooltip";
in DataSetter script tool tip is a string variable.
you can also declare text variable and assign a text to that.

C# How to create an instance from a given reflection Type from a list of Type in a Combo-Box

List<Type> BotNames = typeof(BotPlayer).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(BotPlayer))).ToList();
I've put that list into a combo box to be displayed to the user in the drop-down menu. I'm trying to create an instance of the selected item of the combo box which is a subclass of a class called BotPlayer and is meant to make use of a method called "Move" which is present in the class and all its subclasses. I'm also trying to pass that instance into a BotPlayer variable called Bot. I've tried the different ways of using Activator.CreateInstance but it doesn't seem to work for me or I don't understand it enough to implement it into my own program. This was the furthest I was able to get
Bot = (BotPlayer)Activator.CreateInstance((Type)Difficulty.SelectedItem);
When I run my program it gives me this error: "System.MissingMethodException: 'No parameterless constructor defined for this object.'"
This is the code for the combo box which exists in the Designer.cs
this.Difficulty.FormattingEnabled = true;
this.Difficulty.Items.AddRange(BotNames.ToArray());
this.Difficulty.Location = new System.Drawing.Point(205, 181);
this.Difficulty.Name = "Difficulty";
this.Difficulty.Size = new System.Drawing.Size(137, 21);
this.Difficulty.TabIndex = 3;
This is the code for the combo box which exists in the normal cs file
if (Difficulty.SelectedItem != null)
{
Bot = (BotPlayer)Difficulty.SelectedItem; //This is called casting
Bot.Type = BotType;
//Bot = (BotPlayer)Activator.CreateInstance((Type)Difficulty.SelectedItem);
//Bot = (BotPlayer)Activator.CreateInstance("MyAssembly", "BotPlayer");
}
This is the BotPlayer Constructor
public BotPlayer(GameBoard board, SquareValues type)
{
Type = type;
Board = board;
// Difficuty = difficulty;
}
This is the constructor for all its subclasses
public BotPlayer1(GameBoard board, SquareValues type) : base(board, type)
{
Board = board;
Type = type;
BotName = "Level 1";
}
The only difference between the subclasses is the number at the end of BotPlayer and the bot name which is equivalent to that number with the word "Level" behind it
Bot = (BotPlayer)Activator.CreateInstance((Type)Difficulty.SelectedItem,Board,BotType);
I found that all I needed to do was pass in parameters into the Activator.CreateInstance.
Thanks to #elgonzo and #Neil for helping me realize this.

Windows 8 - Animating custom property in code-behind

Basically, I want to make bunch of Shapes and make them animated. So I came up with following custom class:
public class FunkyShape : DependencyObject
{
public double Animator
{
get { return (double)GetValue(AnimatorProperty); }
set { SetValue(AnimatorProperty, value); }
}
public static readonly DependencyProperty AnimatorProperty =
DependencyProperty.Register("Animator", typeof(double), typeof(FunkyShape),
new PropertyMetadata(0, new PropertyChangedCallback(Animator_Changed)));
private static void Animator_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
double delta = (double)e.NewValue - (double)e.OldValue;
((FunkyShape)d).ProcessDelta((double)e.NewValue, delta);
}
private void ProcessDelta(double val, double delta)
{
Holder.Width = val;
Holder.Height = val;
// Keep shape centered
HolderPosition.X = delta / 2;
HolderPosition.Y = delta / 2;
}
private Shape Holder;
public TranslateTransform HolderPosition
{
get { return (TranslateTransform)Holder.RenderTransform; }
}
public FunkyShape(Canvas playground, Shape shapeToInit)
{
Holder = shapeToInit;
Holder.Width = 10;
Holder.Height = 10;
Holder.Fill = new SolidColorBrush(Colors.Blue);
Holder.HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Center;
Holder.RenderTransform = new TranslateTransform()
{
X = 500,
Y = 500
};
Holder.RenderTransformOrigin = new Point(0.5, 0.5);
// init done
playground.Children.Add(Holder);
Animate();
}
public void Animate()
{
DoubleAnimation g1 = GrowAnimation();
Storyboard sb = new Storyboard();
Storyboard.SetTarget(g1, this);
// CAN'T FIND ANIMATOR PROPERTY
Storyboard.SetTargetProperty(g1, "Animator");
sb.Children.Add(g1);
sb.Begin(); // THROWS EXCEPTION
}
private static DoubleAnimation GrowAnimation()
{
DoubleAnimation growAnimation = new DoubleAnimation();
growAnimation.Duration = TimeSpan.FromMilliseconds(3000);
growAnimation.From = 0;
growAnimation.To = 100;
growAnimation.AutoReverse = true;
growAnimation.EnableDependentAnimation = true;
growAnimation.RepeatBehavior = new RepeatBehavior(5);
return growAnimation;
}
}
However, when I try making an instance of the class and adding it to the canvas, I get Exception - Storyboard.Being() throws it and tells me that it can't find Animator property.
So - what am I doing wrong?
EDIT: After 3 code changes - it is still not working; I get "Cannot resolve TargetProperty Animator on specified object" error. So if somebody knows the answer - please help out by modifying the code. Thanks!
EDIT: OK, after 24 hours of banging head against the wall there is some progress - if I add shape through XAML it animates, but if I add it through code behind (Canvas.Children.Add), it doesn't work. Let me see if I can figure out why.
OK,
I've found the workaround for what is obviously a bug within the framework (although I'm sure some MS employee will post response and say it's a feature/it-is-by-design). Several things need to be done:
Add default/parameter-less constructor
Change base class of FunkyShape to UserControl.
Open up XAML view of the Page class where you want to add shapes
Add one instance of FunkyShape as a child within the Canvas XAML (<tm:FunkyShape /> for example). IT WON'T WORK WITHOUT THIS.
Make an instance of FunkyShape in code-behind, add it to canvas, start animation and enjoy seeing it works
Switch to less buggy technology.
In Windows 8 you cannot animate custom properties without also setting the enabledependentanimation property to true. This is because non-deterministic animations are disabled by default.
Reference: http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.animation.pointanimation.enabledependentanimation.aspx
Yes, you must define this property as a dependency property, not just a regular CLR property. This involves quite a bit of simple boiler plate code. See thus blog post for a complete example:
http://timheuer.com/blog/archive/2012/03/07/creating-custom-controls-for-metro-style-apps.aspx
OK, I had this problem too, but I didn't want to include a public parameterless constructor in my class, so I found another way.
Basically, the issue is that WinRT is a native platform, and it can't do reflection on .NET code. That's why the build process for WinRT apps generates metadata about the types used in XAML (you can find the relevant code in obj/(Debug|Release)/XamlTypeInfo.g.cs).
If a type is never used in XAML, no metadata about this type is generated, which means (among other things) that you can't animate the properties of the type.
If you're writing a class library, you can just include a XAML resource dictionary and declare a dummy instance of the type; it will cause metadata to be generated. However, it requires that the type has a public parameterless constructor, which might not be desirable.
So there is another solution: provide the metadata yourself. There are a several interfaces to implement, and they have many members, so it can be quite tedious to do manually. Fortunately, you don't have to! Here's what you can do:
add a public parameterless constructor to the class (temporarily)
create a XAML ResourceDictionary and declare an instance of the class in it (as described above)
copy the XamlTypeInfo.g.cs file into your project (I renamed it to XamlTypeInfo.cs)
replace the call to the constructor with throw new NotImplementedException()
delete the ResourceDictionary file
remove the public parameterless constructor
And you're done, the animation now works properly.
The process is still quite tedious, so it would be nice to have a tool to do the work for us...
EDIT: much easier solution: apply the [Bindable] attribute to the class. It makes the metadata generator take the type into account even if it's not used in XAML. (ignore the fact that the doc says it's for C++ types; it works just fine on C# classes as well)

Categories

Resources