c# - How to access a storyboard within an element resources from XAML? -
consider code:
<usercontrol x:class="myapp.mycontrol" ... xmlns:local="clr-namespace:myapp" datacontext="{binding relativesource={relativesource mode=self}}"> <usercontrol.template> <controltemplate> <controltemplate.resources> <storyboard x:key="mystory"> <coloranimationusingkeyframes storyboard.targetproperty="(border.borderbrush).(solidcolorbrush.color)" storyboard.targetname="brdbase"> <splinecolorkeyframe keytime="0:0:1" value="red"/> </coloranimationusingkeyframes> </storyboard> </controltemplate.resources> <border x:name="brdbase" borderthickness="1" borderbrush="cyan" background="black"> ... </border> <controltemplate.triggers> <trigger sourcename="brdbase" property="ismouseover" value="true"> <trigger.enteractions> <beginstoryboard storyboard="{staticresource mystory}"/> </trigger.enteractions> </trigger> </controltemplate.triggers> </controltemplate> </usercontrol.template> </usercontrol> the above code works no problem. now, wanna bind key-frame value of mystory dp (named specialcolor) of user-control so:
<storyboard x:key="mystory"> <coloranimationusingkeyframes storyboard.targetproperty="(border.borderbrush).(solidcolorbrush.color)" storyboard.targetname="brdbase"> <splinecolorkeyframe keytime="0:0:1" value="{binding relativesource={relativesource mode=findancestor, ancestortype={x:type local:mycontrol}}, path=specialcolor}"/> </coloranimationusingkeyframes> </storyboard> which makes error:
cannot freeze storyboard timeline tree use across threads.
it's possible using code behind. how can in xaml only?
code-behind aided solution:
►step 1: putting mystory storyboard brdbase resources.
<usercontrol.template> <controltemplate> <border x:name="brdbase" borderthickness="1" borderbrush="cyan" background="black"> <border.resources> <storyboard x:key="mystory"> <coloranimationusingkeyframes storyboard.targetproperty="(border.borderbrush).(solidcolorbrush.color)" storyboard.targetname="brdbase"> <splinecolorkeyframe keytime="0:0:1" value="{binding relativesource={relativesource mode=findancestor, ancestortype={x:type local:mycontrol}}, path=specialcolor}"/> </coloranimationusingkeyframes> </storyboard> </border.resources> ... </border> <controltemplate.triggers> <trigger sourcename="brdbase" property="ismouseover" value="true"> <trigger.enteractions> <beginstoryboard storyboard="{staticresource mystory}"/> </trigger.enteractions> </trigger> </controltemplate.triggers> </controltemplate> </usercontrol.template> error: cannot find resource named 'mystory'. resource names case sensitive.
►step 2: eliminating trigger on ismouseover property , begin mystory code behind.
<usercontrol.template> <controltemplate> <border x:name="brdbase" borderthickness="1" borderbrush="cyan" background="black" mouseenter="brdbase_mouseenter"> <border.resources> <storyboard x:key="mystory"> <coloranimationusingkeyframes storyboard.targetproperty="(border.borderbrush).(solidcolorbrush.color)" storyboard.targetname="brdbase"> <splinecolorkeyframe keytime="0:0:1" value="{binding relativesource={relativesource mode=findancestor, ancestortype={x:type local:mycontrol}}, path=specialcolor}"/> </coloranimationusingkeyframes> </storyboard> </border.resources> </border> </controltemplate> </usercontrol.template> c# code-behind:
private void brdbase_mouseenter(object sender, mouseeventargs e) { border grdroot = (border)this.template.findname("brdbase", this); storyboard story = grdroot.resources["mystory"] storyboard; story.begin(this, this.template); } ►step 3: solution done, doesn't work @ first time. fortunately, there workaround issue. it's enough put controltemplate in style.
(i need other trigger types eventtrigger , must wrap usercontrol elements controltemplate.)
update:
the idea using objectdataprovider failed.
- an objectdataprovider resource cannot used provide storyboard!!! error report is:
- xamlparseexception: set property 'system.windows.media.animation.beginstoryboard.storyboard' threw exception.
- innerexception: 'system.windows.data.objectdataprovider' not valid value property 'storyboard'.
- the associatedcontrol dp null.
here code:
<usercontrol.template> <controltemplate> <controltemplate.resources> <local:storyboardfinder x:key="storyboardfinder1" associatedcontrol="{binding elementname=brdbase}"/> <objectdataprovider x:key="dataprovider" objectinstance="{staticresource storyboardfinder1}" methodname="finder"> <objectdataprovider.methodparameters> <sys:string>mystory</sys:string> </objectdataprovider.methodparameters> </objectdataprovider> </controltemplate.resources> <border x:name="brdbase" borderthickness="1" borderbrush="cyan" background="black"> <border.resources> <storyboard x:key="mystory"> <coloranimationusingkeyframes storyboard.targetproperty="(border.borderbrush).(solidcolorbrush.color)" storyboard.targetname="brdbase"> <splinecolorkeyframe keytime="0:0:1" value="{binding relativesource={relativesource mode=findancestor, ancestortype={x:type local:mycontrol}}, path=specialcolor}"/> </coloranimationusingkeyframes> </storyboard> </border.resources> ... </border> <controltemplate.triggers> <trigger sourcename="brdbase" property="ismouseover" value="true"> <trigger.enteractions> <beginstoryboard storyboard="{staticresource dataprovider}"/> </trigger.enteractions> </trigger> </controltemplate.triggers> </controltemplate> </usercontrol.template> the storyboardfinder class:
public class storyboardfinder : dependencyobject { #region ________________________________________ associatedcontrol public control associatedcontrol { { return (control)getvalue(associatedcontrolproperty); } set { setvalue(associatedcontrolproperty, value); } } public static readonly dependencyproperty associatedcontrolproperty = dependencyproperty.register("associatedcontrol", typeof(control), typeof(storyboardfinder), new frameworkpropertymetadata(null, frameworkpropertymetadataoptions.none)); #endregion public storyboard finder(string resourcename) { // // associated control null :( // return new storyboard(); } }
well, can't bind "to" nor from, because storyboard has frozen, in order work efficiently cross-threading.
solution1) simplest solution without hacks(involves code-behind): add mouseover event handler & in event handler, locate necessary animation, set "to" property directly, won't use binding , "freezing" can done. way won't hardcode :).
solution2) there cool hack supports xaml only( little bit of converter magic ofcourse ), not suggest it. it's cool nonetheless :) wpf animation: binding "to" attribute of storyboard animation see answer jason.
there few things more can try:
solution3) don't use dependency properties, rather implement inotifyproperthchanged. way still can bind "to". note think should theoretically work, have not tried.
solution4) apply mode=onetime binding. maybe works?
solution5) write own attached behavior evaluate dependency property on correct thread , set "to" property. think nice solution.
here duplicate too: wpf animation "cannot freeze storyboard timeline tree use across threads"
Comments
Post a Comment