Storyboard changing property but not reflected in UI - c#-3.0

I have this method fired on a button click. MyObj is an extended Control type with two properties CenterX and CenterY with backing dp CenterXProperty and CenterYProperty.
With the animation playing the CenterX and CenterY properties of MyObj are changing, but I can't see any movement of the object.
private void MoveMyObjectsWithAnimation(MyObj item, Point point)
{
Duration duration = new Duration(TimeSpan.FromSeconds(3));
Storyboard sb = new Storyboard();
sb.Duration = duration;
DoubleAnimation da = new DoubleAnimation();
da.Duration = duration;
da.EasingFunction = new SineEase();
DoubleAnimation da1 = new DoubleAnimation();
da1.Duration = duration;
da1.EasingFunction = new SineEase();
Storyboard.SetTarget(da, item);
Storyboard.SetTargetProperty(da, new PropertyPath(MyObj.CenterXProperty));
da.From = item.CenterX;
da.To = point.X;
Storyboard.SetTarget(da1, item);
Storyboard.SetTargetProperty(da1, new PropertyPath(MyObj.CenterYProperty));
da1.From = item.CenterY;
da1.To = point.Y;
sb.Children.Add(da);
sb.Children.Add(da1);
sb.Begin();
}
Following is the property system declared in MyObj class.
public double CenterX
{
get
{
return (double)GetValue(CenterXProperty)+25;
}
set
{
Canvas.SetLeft(this, value - 25);
SetValue(CenterXProperty, value);
OnPropertyChanged(new PropertyChangedEventArgs("CenterX"));
}
}
public double CenterY
{
get
{
return (double)GetValue(CenterYProperty) + 25;
}
set
{
Canvas.SetTop(this, value - 25);
SetValue(CenterYProperty, value);
OnPropertyChanged(new PropertyChangedEventArgs("CenterY"));
}
}
public static readonly DependencyProperty CenterXProperty =
DependencyProperty.Register("CenterX", typeof(double), typeof(MyObj), null);
public static readonly DependencyProperty CenterYProperty =
DependencyProperty.Register("CenterY", typeof(double), typeof(MyObj), null);
What am I doing wrong?

This was a tough one as your code appeared correct.
The problem is actually that the DP setter is not called when a property is animated. Your property setters were never hit.
You can only catch the callback on the property change instead.
This works:
public double CenterX
{
get { return (double)GetValue(CentreXProperty); }
set { SetValue(CentreXProperty, value); }
}
// Using a DependencyProperty as the backing store for CentreX.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty CentreXProperty =
DependencyProperty.Register("CentreX",
typeof(double),
typeof(MyObj),
new PropertyMetadata(0.0,
new PropertyChangedCallback(OnCentreXChanged)));
static void OnCentreXChanged(object sender, DependencyPropertyChangedEventArgs args)
{
// Get reference to self
MyObj source = (MyObj)sender;
// Add Handling Code
double newValue = (double)args.NewValue;
Canvas.SetTop(source, newValue - 25);
}
Just duplicate for your CenterYProperty.
I suggest you get a hold of the SLDP snippet from here and always use that to add DPs. (There is one typo in the code it generates, but the snippet it easy to fix).

Related

Unity PropertyDrawer for Serializable object

I try to draw defualt inspector for serializable object manually , and Here is my code:
[System.Serializable]
public class Item
{
public string Name;
public int ID;
}
[System.Serializable]
public class ItemGroup
{
public Item item;
public int count;
}
public class Test : MonoBehaviour
{
public Item item;
public ItemGroup itemGroup;
public Item[] items;
public ItemGroup[] itemGroups;
}
The default inspector is look like this:
Then I add the PropertyDrawer script for Item and ItemGroup
[CustomPropertyDrawer(typeof(Item))]
public class ItemDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var style = new GUIStyle(EditorStyles.foldoutHeader);
style.fontStyle = FontStyle.Normal;
if (property.isExpanded = EditorGUI.BeginFoldoutHeaderGroup(position, property.isExpanded, label, style))
{
EditorGUI.indentLevel++;
position.height = EditorGUIUtility.singleLineHeight;
position.y += position.height;
EditorGUI.PropertyField(position, property.FindPropertyRelative("Name"));
position.y += position.height;
EditorGUI.PropertyField(position, property.FindPropertyRelative("ID"));
EditorGUI.indentLevel--;
}
EditorGUI.EndFoldoutHeaderGroup();
}
}
[CustomPropertyDrawer(typeof(ItemGroup))]
public class ItemGroupDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var style = new GUIStyle(EditorStyles.foldoutHeader);
style.fontStyle = FontStyle.Normal;
var itemProperty = property.FindPropertyRelative("item");
var countProperty = property.FindPropertyRelative("count");
position.height = EditorGUIUtility.singleLineHeight;
if (property.isExpanded = EditorGUI.BeginFoldoutHeaderGroup(position, property.isExpanded, label, style))
{
EditorGUI.indentLevel++;
position.y += position.height;
position.height = EditorGUI.GetPropertyHeight(itemProperty);
EditorGUI.PropertyField(position, itemProperty, true);
position.y += position.height;
position.height = EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(position, countProperty);
EditorGUI.indentLevel--;
}
EditorGUI.EndFoldoutHeaderGroup();
}
}
Question 1: When Enable ItemDrawer and Disable ItemGroupDrawer, the indent of Item field is wrong, see below.How to make it correct both in ItemGroup class and Array?
[CustomPropertyDrawer(typeof(Item))] //Enable ItemDrawer
//[CustomPropertyDrawer(typeof(ItemGroup))] //Disable ItemGroupDrawer
Question 2: When enable both ItemDrawer and ItemGroupDrawer, there is a error:
but the default inspector it did has foldout header
So how to use PropertyDrawer to draw Item and ItemGroup's properties manually that make it look like default inspector?
I don't want to use EditorGUI.PropertyField(position, property);
I want to draw their properties one by one
The indent is wrong since you pass in the original position without any indentation .. the EditorGUI.indentLevel only directly affects the property fields drawn without a rect bit via EditorGUILayout.
You can get an indented rect however by using
var indentedRect = EditorGUI.IndentedRect(position);
However, until now I don't see a real purpose in having a custom drawer for your types .. what's wrong with the default drawer? ;)
thanks derHugo, I have solved it
Answer 1: I should add position = EditorGUI.IndentedRect(position); to the top of OnGUI
Answer 2: I should use EditorGUI.Foldout rather than EditorGUI.BeginFoldoutHeaderGroup

Setting passed parameter before page loads

I have moved xamrin.form.maps to MVVM and have set my xaml to
<viewModels:CustomMap IsShowingUser="True" x:Name="customMap" MapPosition="{Binding MyPosition}" CustomPins="{Binding PinCollection}" />
My customMap Class
public static readonly BindableProperty MapPositionProperty = BindableProperty.Create(
nameof(MapPosition),
typeof(Position),
typeof(CustomMap),
new Position(0, 0),
propertyChanged: (b, o, n) =>
{
Console.WriteLine("here");
((CustomMap)b).MoveToRegion(MapSpan.FromCenterAndRadius(
(Position)n, Distance.FromMiles(.05)));
});
public Position MapPosition
{
get { return (Position)GetValue(MapPositionProperty); }
set { SetValue(MapPositionProperty, value); }
}
From page one, I am sending a lat lng to the map. If I set MapPosition in my constructor in my VM it works perfect, the problem is getting the lat and lng to my constructor before everything is bound, other wise it will not update.
In the view model I am collecting the parameters to set the position.
public override async void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("mapLocation"))
{
var mapLocation = parameters.GetValue<MapLocation>("mapLocation");
myPosition = new Position(mapLocation.Lat, mapLocation.Lng);
}
}
This has nothing to do with the time when your property is updated.
You bound MapPosition to MyPosition, which I'd guess is a property. For MVVM to work, this MapPosition has to notify that it has changed, usually this looks something like (the the documentation of INotifyPropertyChanged.PropertyChanged)
public MapLocation MyPosition
{
get => myPosition;
set
{
if(myPosition == value)
{
return;
}
myPosition = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string memberName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Your view does know that if the bound objects implements INotifyPropertyChanged it has to subscribe to PropertyChanged and can update its state accordingly if this event is raised.
Now why doesn't it work in your case?
You are not setting MyPosition, allowing the setter to raise PropertyChanged and thus the view to update its state, but you are directly setting the backing field myPosition. In this case, the setter never is called and the view has no chance to know that the value of MyPosition has changed, therefor won't update its state.
If your setter implements the pattern shown above, just change
myPosition = new Position(mapLocation.Lat, mapLocation.Lng);
to
MyPosition = new Position(mapLocation.Lat, mapLocation.Lng);
and it should work. Otherwise implement the change notification pattern in MyPosition, too.

How can I create an object at the mouse point in scene view by clicking not dragging the object?

Generally, most objects are placed in the scene view by dragging or something. I want to right click the mouse (without dragging an object) to create an object in the scene view. I know this would require some editor coding but I’m not sure how to go about it.
UPDATE
After giving it some thought I realised that using a MenuItem would be quite appropriate for me. Here is my code below:
SLMenuItems:
public class SLMenuItems : MonoBehaviour {
public bool canClickSceneViewToCreatePath = false;
void Start()
{
}
[MenuItem("Component/Create Custom Object")]
static void CreateObject() {
Debug.Log("menu item selected");
canClickSceneViewToCreatePath = true;
}
}
SLMenuItemsEditor:
[CustomEditor(typeof(SLMenuItems))]
public class SLMenuItemsEditor : Editor {
SLMenuItems slMenuItems;
void OnEnable()
{
slMenuItems = (SLMenuItems)target;
}
void OnSceneGUI()
{
if (slMenuItems.canClickSceneViewToCreatePath) {
Vector3 pointsPos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
{
// create object here at pointsPos
slMenuItems.canClickSceneViewToCreatePath = false;
}
}
}
}
I keep getting the following error:
Assets/SLMenuItems.cs(23,9): error CS0120: An object reference is required to access non-static member `SLMenuItems.canClickSceneViewToCreatePath'
pointing to the line:
canClickSceneViewToCreatePath = true;
in SLMenuItems.
Your CreateObject method is static but your canClickSceneViewToCreatePath value is not.
It has nothing to do with the editor script but with your class SlMenuItems itself.
A static method is not instanced or with other words it is kind of shared between all instances of that component type while the non-static value might be different for each component.
So how should a static method - which is the same for all instances - "know", which of the instances values it should access?
So either make the method non-static or the variable static. Depending on what your further need is.
Since the MenuItem needs a static method make the variable static as well.
I would suggest you make that class not inherit from MonoBehaviour at all since it doesn't have any behaviour for a GameObject. It only brings some editor features so rather make it a static class that can "live" in the Assets without needing to be instanced.
Than you can use SceneView.onSceneGUIDelegate to register a callback for OnSceneGUI without implementing an editor script for that:
private static GameObject lastCreated;
private static bool isCreating;
public static class SLMenuItems
{
[MenuItem("Component/Create Custom Object")]
private static void CreateObject()
{
Debug.Log("menu item selected");
isCreating = true;
lastCreated = null;
// Add a callback for SceneView update
SceneView.onSceneGUIDelegate -= UpdateSceneView;
SceneView.onSceneGUIDelegate += UpdateSceneView;
}
private static void UpdateSceneView(SceneView sceneView)
{
if(lastCreated)
{
// Keep lastCreated focused
Selection.activeGameObject = lastCreated;
}
if(isCreating)
{
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
{
Vector3 pointsPos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;
//Todo create object here at pointsPos
lastCreated = Instantiate(somePrefab);
// Avoid the current event being propagated
// I'm not sure which of both works better here
Event.current.Use();
Event.current = null;
// Keep the created object in focus
Selection.activeGameObject = lastCreated;
// exit creation mode
isCreating = false;
}
}
else
{
// Skip if event is Layout or Repaint
if(e.type == EventType.Layout || e.type == EventType.Repaint)
{
Selection.activeGameObject = lastCreated;
return;
}
// Prevent Propagation
Event.current.Use();
Event.current = null;
Selection.activeGameObject = lastCreated;
lastCreated = null;
// Remove the callback
SceneView.onSceneGUIDelegate -= UpdateSceneView;
}
}
}
But I suggest you change your questions title since this is actually not the solution to the "task" you describe before.

Delegate command not executing on property change

I am currently trying to make a slider in ViewA change the font size of text in viewA and viewB. I have everything bound correctly, but the delegate command is not calling the execute method when the font size property is changed. If I manually call this function everything works as expected, so it is likely one line of code that is the problem. The ViewAViewModel is below:
public class ViewAViewModel : BindableBase
{
private Person _CoolChick = new Person();
private int _fontSize = 12;
private IEventAggregator _eventAggregator;
public DelegateCommand UpdateSizeCommand { get; set; }
public Person CoolChick
{
get
{
return _CoolChick;
}
set
{
SetProperty(ref _CoolChick, value);
}
}
public int FontSize
{
get { return _fontSize; }
set {
Console.WriteLine(_fontSize + " => Font Size");
SetProperty(ref _fontSize, value);
//Execute();
}
}
public ViewAViewModel(IEventAggregator eventAggregator)
{
CoolChick.Age = 25;
CoolChick.Name = "Methalous";
_eventAggregator = eventAggregator;
//likely the problem in this code
UpdateSizeCommand = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => FontSize);
}
private void Execute()
{
_eventAggregator.GetEvent<UpdateEvent>().Publish(FontSize);
}
private bool CanExecute()
{
return true;
}
}
Why would it? You're not calling UpdateSizeCommand.Execute in the setter of your Font property. The command will not invoke unless you bind it to a command property or invoke it manually.

Create a BindableProperty depending on multiple properties

I'm trying to bind a UI element to different model properties A, B and AB. The first two properties A and B are controlled by two sliders. The third property AB is the sum of A and B. For each of the three properties there is a label displaying its value.
Now if I move one of the sliders, the corresponding label updates its Text. But the label for the combined property AB is not updated. Probably no "property changed" event is fired, since there is no setter for AB.
Is there any possibility for binding to such an "aggregated" property?
Here is the bindable object containing the properties A, B and AB:
public class Settings: BindableObject
{
public static readonly BindableProperty AProperty = BindableProperty.Create<Settings, double>(p => p.A, 0);
public static readonly BindableProperty BProperty = BindableProperty.Create<Settings, double>(p => p.B, 0);
public static readonly BindableProperty ABProperty = BindableProperty.Create<Settings, double>(p => p.AB, 0);
public double A {
get{ return (double)GetValue(AProperty); }
set{ SetValue(AProperty, (double)value); }
}
public double B {
get{ return (double)GetValue(BProperty); }
set{ SetValue(BProperty, (double)value); }
}
public double AB {
get{ return A + B; }
}
}
And here is the page containing both sliders and the three labels:
public class App : Application
{
public App()
{
var settings = new Settings();
var sliderA = new Slider();
sliderA.ValueChanged += (sender, e) => settings.A = e.NewValue;
var sliderB = new Slider();
sliderB.ValueChanged += (sender, e) => settings.B = e.NewValue;
var labelA = new Label{ BindingContext = settings };
labelA.SetBinding(Label.TextProperty, "A");
var labelB = new Label{ BindingContext = settings };
labelB.SetBinding(Label.TextProperty, "B");
var labelAB = new Label{ BindingContext = settings };
labelAB.SetBinding(Label.TextProperty, "AB");
MainPage = new ContentPage {
Content = new StackLayout {
VerticalOptions = LayoutOptions.Center,
Children = { sliderA, sliderB, labelA, labelB, labelAB },
},
};
}
}
This is what the running application looks like on iOS:
The last label should display the sum of the first two numbers.
Edit:
I wonder why I can't write
public static readonly BindableProperty ABProperty =
BindableProperty.Create<Settings, double>(p => p.A + p.B, 0);
But this yields the run-time error "System.TypeInitializationException: An exception was thrown by the type initializer for AggregatedBindablePropertyMnml.Settings ---> System.Exception: getter must be a MemberExpression"
Based on the suggestion from Taekahn (updating AB within the setters of A and B) I came up with the following solution.
By overriding the OnPropertyChanged method and setting the ABProperty, the bound label text is updated as well. In contrast to modifying each setter individually, this way we only need to modify the Settings class at one place.
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
SetValue(ABProperty, A + B);
}
Now both sliders impact the third label: