VisualStateManager in Silverlight: setting an initial state - silverlight

I often find the need to include "away" and "present" type visual states that are used to animate the control being away or visible depending on some other condition.
The "away" state is then usually the one that should be the initial state. To my understanding, there is no way to define an initial state in SL but "base", which isn't really a state at all but denotes how the look is with the state manager being not yet active (no state storyboards are running to change the look of the control).
Of course you can design "base" to look like "away", but that means the default look in Expression Blend is invisible (you can't "pin" a state permanently either).
To change the initial state I tried
setting the state in the ctor of the control, which does nothing and
setting the state in a dispatched call from the ctor or the Loaded event, which both show the wrong state for a split-second.
So the problem appears to be that whatever the visual state manager does, it doesn't do it right away but needs a noticeable split-second to change the appearance.
(Setting the property directly for bootstrap is another option of course, but only works for UserControls: In templated Controls, I would have to introduce another depprop to template-bind the control template against, which is where I believe overkill starts.)
I suppose I covered it all and I just have to live with an invisible base state?
I use SL4.

I encountered a similar issue when developing a UserControl for WPF in Expression Blend (Note: if you're developing a custom control instead, see my next section). In that UserControl, I had a child element that I wanted to fade in and grow into existence as an overlay. Like your situation, it made sense in my workflow to first design the overlay element at its "fully grown and visible" state and then shrink it down and set its opacity for a "Hidden" state. In doing this, the overlay is visible in the Base state, but I needed the UserControl's initial state to be the Hidden state. At this point I had three main relevant states: Base, "Hidden" and "Visible" (these last two are a part of a State group).
Here's how I solved the initial-state issue. First I applied a GoToStateAction to the root element (to the UserControl) that is triggered by the Loaded event. It tells the UserControl to go right to the "Hidden" state:
<i:Interaction.Triggers>
<i:EventTrigger>
<ei:GoToStateAction TargetObject="{Binding ElementName=userControl}" StateName="Hidden"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Second, I made appropriate transition settings in the State Group for the overlay. There are probably several ways of doing this, but here's how I did it. First I set the "Default transition" to a pleasing setting, say .4 seconds. Next, I set the transition time from any element (the star icon in Blend) to this "Hidden" state to be 0 seconds (this allows the above-mentioned GoToStateAction to set the "initial" state without the user knowing any different). Then, I set the transition from the "Visible" state to the "Hidden" state to be an appropriate setting (say .4 seconds). Basically this covered all the bases for the transitions. The key was making sure that the "transition" from "any element" to the "Hidden" state was immediate, and then overriding that immediate transition in the case of going from the overlay's "Visible" to "Hidden" states.
Setting an Initial VisualState of a Custom Control
If you're developing a custom control (rather than a UserControl) and are thus defining your VisualStateManager in the control template, the above method (initiating the VisualState change based on the Loaded event) will probably not work. This is because the visual tree of your control (defined in a Style file) gets applied to your control right before the OnApplyTemplate() override gets called, which is usually after the first Loaded event has fired. So if you try to initiate a VisualState change in response to the Loaded event for a custom control, most likely nothing will happen. Instead, you will need to initiate the state change in your OnApplyTemplate() override code:
public class MyCustomControl : ContentControl
{
// ... other code ....
public MyCustomControl()
{
// avoid designer errors
if (DesignerProperties.GetIsInDesignMode(this))
return;
Loaded += new RoutedEventHandlerMyCustomControl_Loaded);
}
// This probably won't be called until AFTER OnApplyTemplate() gets
// called, so don't expect for your control to even have a visual tree
// yet when your control is first being contructed at runtime.
private void MyCustomControl_Loaded(object sender, RoutedEventArgs e)
{
}
public override void OnApplyTemplate()
{
// Avoid Visual Studio 2010 designer exceptions
// (Visual Studio can't handle the VisualState change at design-time)
if (DesignerProperties.GetIsInDesignMode(this))
return;
base.OnApplyTemplate();
// Now we know that the template has been applied, we have a visual tree,
// so state changes will work
VisualStateManager.GoToState(this, "MyInitialState", false);
}
}

Related

In WPF how can I get the rendered size of a control before it actually renders?

I'm doing custom rendering in a Decorator subclass. Our rendering requires creating complex geometries which only neded to be re-created when the actual rendered size changes. As such, I have moved the geometry creation into its own function called UpdateGeometry which creates, then freezes the geometry for use in OnRender. This new function only needs to be called in response to a change in ActualWidth or ActualHeight.
Even better, it looks like we should be able to simply override OnRenderSizeChanged, which according to the documentation states...
"When overridden in a derived class,
participates in rendering operations
that are directed by the layout
system. This method is invoked after
layout update, and before rendering,
if the element's RenderSize has
changed as a result of layout update."
However, regardless if I'm using the override or listening to the property change notifications of ActualWidth and ActualHeight, my logging consistently shows OnRender as happening first! Um... Wha??
To be sure it wasn't something I was doing in my code, I created a bare-bones test decorator subclass and added logging there, both on entry and exit to the overrides. Here is the entire class...
using System;
using System.Windows.Controls;
public class TestControl : Decorator
{
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
Console.WriteLine("OnRender Entered");
base.OnRender(drawingContext);
Console.WriteLine("OnRender Exited");
}
protected override void OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo)
{
Console.WriteLine("OnRenderSizeChanged Entered");
base.OnRenderSizeChanged(sizeInfo);
Console.WriteLine("OnRenderSizeChanged Exited");
}
}
And as I feared... here is the output...
OnRender Entered
OnRender Exited
OnRenderSizeChanged Entered
OnRenderSizeChanged Exited
So what am I missing here?
More importantly, how can I get the ActualWidth and ActualHeight values after the layout subsystem has done its job, but before the control is rendered so I can create the geometry before it's needed in the OnRender override?
My latest implementation overrides ArrangeOverride as the value that's passed in there is a size containing what the ActualWidth and ActualHeight values should be after the core layout system takes into consideration HorizontalAlignment and VerticalAlignment with values of 'Stretch', minimums and maximums, etc, but what they actually are depends on the value that's returned from that override so it's a little more complex than that.
Either way, I'm still wondering why the OnRenderSizeChanged call doesn't happen when it's supposed to. Thoughts?
Mark
In general, you should be able to get the correct size from ArrangeOverride. This doesn't include things like Margin, but that probably shouldn't be taken into account. You could either use the size passed as a parameter as your "render" size or use the return value of the base.ArrangeOverride call.
EDIT:
The OnRender method is called from the Arrange method, after OnArrangeOverride is ultimately called. The OnRenderSizeChanged on the other hand is called from UpdateLayout, which is effectively dispatched to be executed all at once for a given section of the visual tree. This is why the OnRenderSizeChanged is called after the OnRender.
The documentation may refer to the "rendering" as in actually rendered to the screen, not when OnRender is called. WPF can cache the rendering instructions for a given element and execute them when needed. So the fact that OnRender is called before OnRenderSizeChanged, doesn't mean it's actual rendering instructions are committed to the screen at that time.
You can modify your OnRenderSizeChanged to force OnRender to be called again using:
protected override void OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo)
{
Console.WriteLine("OnRenderSizeChanged Entered");
base.OnRenderSizeChanged(sizeInfo);
this.InvalidateVisual();
Console.WriteLine("OnRenderSizeChanged Exited");
}
You may also want to skip your OnRender code if RenderSize is "0,0".

Databinding falls behind event notification - discussion

Found an interesting problem that I first found in WinForms, and found again in Silverlight, and more than likely WPF as well when it comes to databinding.
I have a tab control with several tabs. As users click across the tabs, each time should be valid before allowing the user to switch from the tab.
For example, user is in a text box which is updated. Binding of text boxes is not flushed until the control loses focus. Loss of focus occurs when the cursor is moved from the control, and focus is given to another control.
In this scenario, the user tabs into a control (let's use text box for this example), and updates the text box. At this point the databinding has not flushed the control, and hence the VM has not yet seen the change. The user then uses their mouse to click the next tab of the control.
At this point things get interesting. I used the PreviewSelectionChanged (Telerik RadTabControl), as I want to check things out before the jump to the next tab occurs, and it also gives me the ability to cancel the event.
However, when I look at the VM, in this event, it still does not have the updated data. I see the VM is clean, and go ahead and allow the jump to the next tab.
As soon as this event is over however, the databindings flush, and the VM gets updated. what now? The events are out of sync! When the mouse was used to click the next tab, the textbox should have lost focus, flushed it's bindings, before the Preview of the Tab click! It's to late to jump back and say oops we didn't catch that in time!
I think I found an interesting work around to this issue - but I'm not 100% sure it will work 100% of the time. I cancel the current event, but then I use the Dispatcher and create a delegate pointing to another method with the same signature as the current event. The Dispatcher will add this message to the message pump, which by this time will now (hopefully?) be behind the messages of the VM updating...
My two questions are:
1) I'm assuming that the textbox control either didn't flush when the mouse left the control, or the process that was fired was too slow and hence the preview message was on the pump before the databinding - either way I see this to be a major issue.
2) Is the workaround a good solution?
Ok, first to answer question 1:
Just because the mouse left the textbox area, doesn't mean that the textbox lost focus. It only loses focus once something else gets focus. For example, if you moved the mouse out of the textbox and click on some other control on your page (it can be anything from a scroll viewer to another textbox, etc.) then your textbox will lose focus.
Now, based on that, the events do not happen in the wrong order. What happens is; your click event on the other tab triggers both the textbox to lose focus (and the data binding to take place) and the move to the next frame, and based on that, you basically get a race condition where the moving to the next tab happens before the databinding takes place.
About question 2:
What you can do is, set the UpdateSourceTrigger to Explicit, you will however be forced to then have some kind of text_changed event and manually update the binding.
You can read more about that here. It might not be the most complete explanation but is a good place to start.
On the other hand, you can associate some events to the textbox and force the textbox to lose focus on those events (e.g. mouse out).
Just an idea: Why not do everything in the VM's PropertyChanged event?
protected override void OnThisViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) {
if(e.PropertyName == "WhateverProperty") {
//Do your magic here for whatever you want to set
}
}
Have your TabItems bound to a collection that will control is being disabled or not.
<sdk:TabControl>
<sdk:TabItem IsEnabled="{Binding SomeProperty, Converter={AmIDisabledOrWhatConverter}}" />
</sdk:TabControl>
That way, everything is triggered whenever a property is chaned in the vm. No more timing issues since everything is on the vm.
Just my two cents.
There's a design defect here, and you're trying to work around the defect instead of fixing it. You shouldn't have to figure out how to cancel the Click event on the tab. The tab shouldn't be processing Click events in the first place.
Generally speaking, if it's not legal for the user to click on a control, the control shouldn't be enabled. The tab should be disabled until the state of the view model is valid.
Your view model should be exposing a command for navigating to the next tab, and the tab should be bound to the command. The command's CanExecute method should only return true when the state of the view model on the current tab is valid.
This doesn't fix your other problem, which is that Silverlight doesn't support UpdateSourceTrigger="PropertyChanged" out of the box. But that's a solved problem (here is one example).
Note that if you implement commands to handle this wizard-like navigation in your application, you can, down the road, change the view to use something other than a tab control (e.g. to use navigation buttons like an actual wizard, or something like Telerik's PanelBar) without having to screw around with event handlers.
Change your bindings to include UpdateSourceTrigger="PropertyChanged".
This will ensure that your data sources are updated on every key stroke, not just LostFocus.
MyOwnTextBox()
{
this.TextChanged += (s, e) => UpdateText();
}
private void UpdateText()
{
BindingExpression be = GetBindingExpression(TextProperty);
if (be != null && be.ParentBinding.Mode == BindingModes.TwoWay)
{
be.UpdateSource();
}
}
I am using this class it updates my binding on the fly, however there is issue with empty string and null values, if your destination property is nullable string then this will update empty string in your destination property. You can get workaround by using some sort of string converter which will use null instead of empty string in case of nullable strings.

Windows Phone Setting Visibility after an animation has executed

I have a View and View Model. The View Model has a bool on there (Foo.CanSelect) that on change will set off a data trigger in the View.
This applies some funky fade out animation, and sets the visibility to collapsed in the final key frame. This is all well and good for the current session, but when I bring the app out from a tombstoning event (de-serialize it) the view is Visible again.
Foo.CanSelect is the correct value but it isn’t bound to the Visibility, because if it was, when the property is changed the visible state is immediately collapsed.
Am I going about this the wrong way?
You could persist the value of Foo.CanSelect when the application is tombstoned, and then read it on restore and update the visibility of the element accordingly. It may be easier to use visual states instead and simply store the required state on tombstoning and restore that state on resume.

How can I add additional bindable visual state groups to a button?

I need to extend a button control to add some additional, bindable, visual states.
What I would like to create is an additional boolean property, to bind to, that will create a simple visual state change on the button. For this example, it could just be an additional border which changes colour according to the boolean value.
All the existing button behaviour should be entirely seperate from this new set of states.
Can this be done?
This can certainly be done. Here's a post that walks you through the steps you'll need to take, and includes additional information for extending the control with properties (besides just those to hold state) that show up in the final control. The pieces you'll need are:
A class that derives from Button (your custom control class)
A default style for this control (which goes in /themes/generic.xaml). You can start off with Button's generic style and add your states to it.
A dependency property that holds your boolean value
A new VisualStateGroup that holds your two new states
Some code in your class that glues together changing states with the boolean value you've defined, presumably resulting from user interaction, etc.
You may find it's easier to edit the visual state in Blend, depending on how sophisticated your transitions will be, etc.

Silverlight: Is there an event that fires on a FrameworkElement before it is rendered?

In our Silverlight 2 project we have created an attached property to perform on-the-fly translation to text properties of various user controls. To achieve this, we hook the Loaded event of the FrameworkElement when the property is set. When the event fires, we take the existing text property value and perform some simple string substitutions on it, before replacing the property value with the translated text. However, this results in the control being rendered with the untranslated text, then the text is quickly replaced with the translated version.
Is there an alternate event we can hook that would fire before the control is rendered?
I've changed my code so that it now performs the translation as soon as the setter for the attached property is called. There's no need to wait for the FrameworkElement to have finished loading, as I can change the Text property long before the element is rendered.
My initial thoughts on using the Loaded event were to reduce the startup time of the application by only translating the controls that were visible on the screen. As it turns out, I'm duplicating some of the work performed by the runtime, as the runtime won't call the property setter until it needs to anyway.
I'm not totally sure about this, but can you use the LayoutUpdated event. It will fire when the control is resized and such (you could take measures to ensure your code only executes once.)
I know it doesn't seem like the "right" event for this but unfortunately Silverlight kinda leaves you standing there holding it when it comes to events.

Resources