2

is it possible to define (not switch) VisualStates in CodeBehind?

I'm creating an Adorner, that draws some rectangles in OnRender. What I'd like to do is to change the Opacity of these Rectangles by it's Property IsMouseOver (say from 0.3 to 0.8).

In any control with a visual tree I'd add some VisualStates and switch those with a DataStateBehavior. How do I do this with an Adorner?

Markus Hütter
  • 7,796
  • 1
  • 36
  • 63
  • It seems that it is impossible because VisualStates are attributes and are compiled with assembly: http://stackoverflow.com/questions/129285/can-attributes-be-added-dynamically-in-c – vortexwolf Apr 03 '11 at 15:15
  • @vorrtex I didn't want to add a `TemplateVisualState` dynamically. I added my solution, if you're interested – Markus Hütter Apr 03 '11 at 21:15
  • Strange, it turned out that it is possible to get a visual states group and add custom states. It's good, because it won't be necessary to extend controls just for adding a state. – vortexwolf Apr 03 '11 at 23:03

3 Answers3

4

this is entirely possible.

if anyone is interested here is how I did it:

public class MyAdorner: Adorner
{
    ctor (...):base(...)
    {
        ...

        var storyboard = new Storyboard();
        var doubleAnimation = new DoubleAnimation(0.2,new Duration(TimeSpan.Zero));
        Storyboard.SetTarget(doubleAnimation,this);
        Storyboard.SetTargetProperty(doubleAnimation,new PropertyPath(RectOpacityProperty));
        storyboard.Children.Add(doubleAnimation);

        var storyboard2 = new Storyboard();
        var doubleAnimation2 = new DoubleAnimation(0.5, new Duration(TimeSpan.Zero));
        Storyboard.SetTarget(doubleAnimation2, this);
        Storyboard.SetTargetProperty(doubleAnimation2, new PropertyPath(RectOpacityProperty));
        storyboard2.Children.Add(doubleAnimation2);

        var stateGroup = new VisualStateGroup { Name = "MouseOverState" };
        stateGroup.States.Add(new VisualState { Name = "MouseOut", Storyboard = storyboard });
        stateGroup.States.Add(new VisualState { Name = "MouseOver", Storyboard = storyboard2});

        var sgs = VisualStateManager.GetVisualStateGroups(this);
        sgs.Add(stateGroup);

        var dsb = new DataStateBehavior
            {
                Value = true,
                FalseState = "MouseOut",
                TrueState = "MouseOver"
            };
        BindingOperations.SetBinding(dsb, DataStateBehavior.BindingProperty, new Binding {Source = this, Path = new PropertyPath(IsMouseOverProperty)});
        dsb.Attach(this);

    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        drawingContext.DrawRectangle(_mouseOverBrush, _pen, _rects[i]);     //mouseoverbrush is a Solidcolorbrush       
    }

    public double RectOpacity
    {
        get { return (double)GetValue(RectOpacityProperty); }
        set { SetValue(RectOpacityProperty, value); }
    }

    public static readonly DependencyProperty RectOpacityProperty =
        DependencyProperty.Register("RectOpacity", typeof(double), typeof(XmlNodeWrapperAdorner), new FrameworkPropertyMetadata(0.0,FrameworkPropertyMetadataOptions.AffectsRender,(o, args) =>
            {
                var adorner = o as MyAdorner;
                adorner._mouseOverBrush.Color = Color.FromArgb((byte)((double)args.NewValue * 0xFF), 0xFF, 0xBE, 0x00);
            }));

}

pretty straightforward actually.

key points here are:

  • you cannot set the VisualStateGroups attached property. you have to get the collection and then add your own group

  • you cannot do new DataStateBehavior{Binding = new Binding(...){...}} as this will assign not bind some value to the property. As Behvior<T> doesn't derive from FrameworkElement you also can't use SetBinding but have to use the BindingOperations class.

  • for automatic rerendering when the property changes keep in mind to set FrameworkPropertyMetadataOptions.AffectsRender.

Markus Hütter
  • 7,796
  • 1
  • 36
  • 63
0

If you could add States in code, tools such as Blend would have to run all code in all possible configurations to find out what states are present/possible.

So, no, you can't do this in code. It only possible using attributes.

EDIT

I stand corrected but the problem mentioned still remains. This technique is not useful for designers.

Emond
  • 50,210
  • 11
  • 84
  • 115
  • maybe I wasn't clear enough. Seems like you and vorrtex thought I wanted to add a `TemplateVisualStateAttribute` dynamically. That was not it. I posted my solution... – Markus Hütter Apr 03 '11 at 21:14
  • the problem stated actually has nothing to do with designer tools. If you are using the designer for an Adorner I'd be _very_ interested in how. As I said I wanted to do this in codebehind, thus skipping any designer entirely. Furthermore, Blend does run code (at least constructors), then for any custom controls blend gets information out of TemplateVisualStateAttribute and its last option involves VisualStateGroups of the LayoutRoot. As I stated though, the adorner has no Visual Tree. It doesn't matter though, I solved my problem, just wondered no one had done it before. – Markus Hütter Apr 04 '11 at 07:46
  • CodeBehind is everything, anything that can be done in XAML, WPF lets you do everything in code behind. – Akash Kava Apr 04 '11 at 07:55
  • @Akash Kava, I know. That is not the question. The point is that when a coder is able to add VisualStates at run-time to a control it becomes impossible for designers using Blend to style the control for these states. Yes, that can be done in code but that is not the point. – Emond Apr 04 '11 at 08:35
0

Since you're already creating a custom adorner with your own behavior, i would suggest that you override the MouseOver method of the adorner and change the opacity of your rectangles there...

another way would be to listen to your own PropertyChanged event and monitor the change in IsMouseOver, or maybe monitor the MouseMove event...

NightDweller
  • 913
  • 5
  • 8
  • I know I could do that, but it's rather nice to use VisualStateManager for transitions and such, and I didn't want to handle all the storyboard stuff within the eventhandlers. I posted a solution, note however that I didn't include transitions for clarity. – Markus Hütter Apr 03 '11 at 21:10
  • :) it's a nice solution. I didn't know you wanted to have a fade effect in there, which i agree would have been a bit much for the event handler solution. – NightDweller Apr 04 '11 at 11:41