View Issue Details

IDProjectCategoryView StatusLast Update
0000902NoesisGUIC# SDKpublic2018-11-22 19:32
Reporterai_enabled Assigned Tosfernandez  
PrioritynormalSeveritymajor 
Status assignedResolutionopen 
Product Version1.2.6f3 
Summary0000902: Difference with WPF - Storyboard EventHandler has different signature
Description

Hello!
I just noticed that storyboards in NoesisGUI are using TimelineEventArgs (which contains Target) when WPF storyboards are just simple plain EventArgs.

Will you consider changing this aspect to match WPF event handler signature?

Regards!

PlatformAny

Activities

ai_enabled

ai_enabled

2016-11-02 08:40

updater   ~0004221

WPF utilizes completely different approach - it creates storyboard instances for each element in visual tree. NoesisGUI uses a single instance of storyboard when it's located in the control template... I also think it could lead to memory leaks.

To properly adapt API to match WPF, I've developed a workaround in 1.2 C# API, but it's quite unreliable, because it's very hard to determine what the event handler must be executed - they're all hooked to the single Storyboard instance!

Please consider fixing it for 1.3.

ai_enabled

ai_enabled

2018-01-13 08:12

updater   ~0004986

Last edited: 2018-01-13 08:14

Hi guys,

I've noticed you've fixed the signature for this method in v2.10 beta.
Did you completely rewrote this to match WPF behavior? - Do you create a new instance of storyboard and call event only for this particular instance?

ai_enabled

ai_enabled

2018-01-13 08:25

updater   ~0004987

Oh, no... You didn't. You just fixed the signature and actually even broken our hack with that change.
For example, we have over 10 game menus and each one subscribe to the storyboards Show and Hide completed event of the GameWindow control.
Now, when the single GameWindow opens or closes, I receive Completed event for all the 10 game menus, when it should trigger only for the particular storyboard instance in the particular GameWindow control.

Previously we've used a simple hack in NoesisGUI C# SDK by using TimelineEventArgs.Target property to determine the target object and then triggered Storyboard Completed event only for the particular control (storyboard Completed used everywhere in our code and in 99% of the cases this hack actualyl worked).

But now you've dropped TimelineEventArgs completely and only regular EventArgs remains so we have no option for determining the storyboard target and our hack is not applicable anymore.

I think the issue is because we're subscribing on the storyboard instances from the template. You're using them as singletones but WPF create separate instances. (described in first comment in this thread)

Example of how we acquire the instances of the storyboards


// on Init
this.storyboardOpen = (Storyboard)this.Template.Resources["StoryboardOpen"];
this.storyboardClose = (Storyboard)this.Template.Resources["StoryboardClose"];

// on Loaded (on Unloaded - unsubcribe)
this.storyboardOpen.Completed += this.StoryboardOpenCompletedHandler;
this.storyboardClose.Completed += this.StoryboardCloseCompletedHandler;

As a workaround for this issue - maybe you can bring back TimelineEventArgs from 2.0.2f2?
Anyways, it will be much better if you will be able to fix this to match WPF behavior completely, but I understand you have a totally different underlying architecture for templates...

ai_enabled

ai_enabled

2018-01-13 08:39

updater   ~0004988

Last edited: 2018-01-13 08:41

If you're curious about the hack/workaround we were using:


[MonoPInvokeCallback(typeof(RaiseCompletedCallback))]
private static void RaiseCompleted(IntPtr cPtr, IntPtr sender, IntPtr e) {
try {
if (!_Completed.ContainsKey(cPtr)) {
throw new InvalidOperationException("Delegate not registered for Completed event");
}
if (sender == IntPtr.Zero && e == IntPtr.Zero) {
_Completed.Remove(cPtr);
return;
}
if (Noesis.Extend.Initialized) {
CompletedHandler handler = _Completed[cPtr];
if (handler == null)
{
return;
}

      var args = new TimelineEventArgs(e, false);

      foreach (var @delegate in handler.GetInvocationList())
      {
        var storyboardTarget = args.Target as FrameworkElement;
          if (storyboardTarget.Tag is FrameworkElement)
          {
                  // workaround
              @delegate.DynamicInvoke(Noesis.Extend.GetProxy(sender, false), new System.EventArgs());
              return;
          }

          var delegateTarget = @delegate.Target;

        while (storyboardTarget != null)
        {
            if (ReferenceEquals(storyboardTarget, delegateTarget))
            {
                @delegate.DynamicInvoke(Noesis.Extend.GetProxy(sender, false), new System.EventArgs());
                break;
            }
            storyboardTarget = VisualTreeHelper.GetParent(storyboardTarget) as FrameworkElement;
        }
    }
  }
}
catch (Exception exception) {
  Noesis.Error.SetNativePendingError(exception);
}

}

As you can see it's quite unreliable and requires some special handling in our code like this:


// special hack for NoesisGUI animation completed event
this.backgroundControl.Tag = this;

However it helped us to 100% reliably worakaround all the cases (and there are dozen of them).

sfernandez

sfernandez

2018-01-15 11:52

manager   ~0004993

Last edited: 2018-01-15 11:52

Sorry for breaking this.

The change only affects C# layer and native code still provides the target in the event args. So while I think about how to properly solve it you can bring back TimelineEventArgs to apply your hack in Timeline.RaiseCompleted() function:

[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TimelineEventArgs {
IntPtr target;

public DependencyObject Target {
get {
return (DependencyObject)Noesis.Extend.GetProxy(target, false);
}
}
}


CompletedHandler handler = _Completed[cPtr];
if (handler != null) {
TimelineEventArgs args = Marshal.PtrToStructure<TimelineEventArgs>(e);
var storyboardTarget = args.Target as FrameworkElement;
...
}

About the hack we should fix it in the native code but first I need to investigate a few things.

ai_enabled

ai_enabled

2018-01-15 12:44

updater   ~0004994

Thanks, I will implement the hotfix back right now and continue testing last NoesisGUI beta.

ai_enabled

ai_enabled

2018-01-15 13:33

updater   ~0004996

Oh, I see now. You said to use your new class and use manual marshalling.
Sorry, will try it.

sfernandez

sfernandez

2018-01-15 13:34

manager   ~0004997

Yeah, that's right :)

ai_enabled

ai_enabled

2018-01-15 14:37

updater   ~0004998

Ok, the workaround seems to work fine now.
I was able to locate another issue http://www.noesisengine.com/bugs/view.php?id=1217

Issue History

Date Modified Username Field Change
2016-06-02 16:00 ai_enabled New Issue
2016-06-02 16:35 sfernandez Assigned To => sfernandez
2016-06-02 16:35 sfernandez Status new => assigned
2016-06-02 16:35 sfernandez Target Version => 1.3.0
2016-11-02 08:40 ai_enabled Note Added: 0004221
2016-11-02 08:41 ai_enabled Priority low => normal
2016-11-02 08:41 ai_enabled Severity minor => major
2018-01-13 08:12 ai_enabled Note Added: 0004986
2018-01-13 08:13 ai_enabled Note Edited: 0004986
2018-01-13 08:14 ai_enabled Note Edited: 0004986
2018-01-13 08:25 ai_enabled Note Added: 0004987
2018-01-13 08:39 ai_enabled Note Added: 0004988
2018-01-13 08:41 ai_enabled Note Edited: 0004988
2018-01-15 11:52 sfernandez Status assigned => feedback
2018-01-15 11:52 sfernandez Note Added: 0004993
2018-01-15 11:52 sfernandez Note Edited: 0004993
2018-01-15 11:52 sfernandez Note Edited: 0004993
2018-01-15 12:44 ai_enabled Note Added: 0004994
2018-01-15 12:44 ai_enabled Status feedback => assigned
2018-01-15 13:33 ai_enabled Note Added: 0004996
2018-01-15 13:34 sfernandez Note Added: 0004997
2018-01-15 14:37 ai_enabled Note Added: 0004998
2018-11-01 02:14 jsantos View Status public => private
2018-11-20 19:51 jsantos Target Version 1.3.0 => 2.2.0
2018-11-20 19:51 jsantos View Status private => public
2018-11-20 19:51 jsantos Platform => Any
2018-11-22 19:32 sfernandez Target Version 2.2.0 =>