How to animate "typing" text in SketchFlow? - silverlight

In Microsoft's Expression Blend 3 SketchFlow application.
How would you go about animating the typing of text, ideally in staged character by character fashion. As if the user is typing it.
An associated flashing cursor would make it perfect, but that's far into the realm of "nice to have".
The keyframe animation system, does not allow you to manipulate the
Common Property > Text
field so therefore it doesn't persist as a recorded change in that keyframe of animation.
I'm looking for either editor steps (using some kind of other control) or even XAML code...
<VisualState>
<StoryBoard>
<DoubleAnimationUsingKeyFrame ... >

After blogging about this with a solution involving a wipe animation of a rectangle over a text block, a response blog post with a more advanced solution of using a custom behavior attached to a text block was created.
Creating a 'TypeOnAction' behavior and adding to a TextBlock, will give the desired effect of character by character display, with a customizable appearance rate. Get the full code sample here.
public class TypeOnAction : TriggerAction<TextBlock>
{
DispatcherTimer timer;
int len = 1;
public TypeOnAction()
{
timer = new DispatcherTimer();
}
protected override void Invoke(object o)
{
if (AssociatedObject == null)
return;
AssociatedObject.Text = "";
timer.Interval = TimeSpan.FromSeconds(IntervalInSeconds);
timer.Tick += new EventHandler(timer_Tick);
len = 1;
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
if (len > 0 && len <= TypeOnText.Length)
{
AssociatedObject.Text = TypeOnText.Substring(0, len);
len++;
timer.Start();
}
else
timer.Stop();
}
public string TypeOnText
{
get { return (string)GetValue(TypeOnTextProperty); }
set { SetValue(TypeOnTextProperty, value); }
}
public static readonly DependencyProperty TypeOnTextProperty =
DependencyProperty.Register("TypeOnText", typeof(string), typeof(TypeOnAction), new PropertyMetadata(""));
public double IntervalInSeconds
{
get { return (double)GetValue(IntervalInSecondsProperty); }
set { SetValue(IntervalInSecondsProperty, value); }
}
public static readonly DependencyProperty IntervalInSecondsProperty =
DependencyProperty.Register("IntervalInSeconds", typeof(double), typeof(TypeOnAction), new PropertyMetadata(0.35));
}

Related

WPF Custom TextBox with Decimal Formatting

I am new to WPF.
I have a requirement that I need to develop a custom textbox control which should support the functionality like:
Should accept only decimal values.
Should round off to 3 decimal places when assigned a value through code or by the user.
Should show the full value(without formatting) on focus.
Eg:
If 2.21457 is assigned to textbox(by code or by user), it should display 2.215. When user clicks in it to edit it, it must show the full value 2.21457.
After the user edits the value to 5.42235 and tabs out, it should again round off to 5.422.
Tried it without success. So need some help on it.
Thanks in advance for the help.
Thanks
I have written a custom control which will have dependency property called ActualText. Bind your value into that ActualText property and manipulated the Text property of the textbox during the gotfocus and lostfocus event. Also validated for decimal number in the PreviewTextInput event. refer the below code.
class TextBoxEx:TextBox
{
public string ActualText
{
get { return (string)GetValue(ActualTextProperty); }
set { SetValue(ActualTextProperty, value); }
}
// Using a DependencyProperty as the backing store for ActualText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ActualTextProperty =
DependencyProperty.Register("ActualText", typeof(string), typeof(TextBoxEx), new PropertyMetadata(string.Empty, OnActualTextChanged));
private static void OnActualTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox tx = d as TextBox;
tx.Text = (string)e.NewValue;
string str = tx.Text;
double dbl = Convert.ToDouble(str);
str = string.Format("{0:0.###}", dbl);
tx.Text = str;
}
public TextBoxEx()
{
this.GotFocus += TextBoxEx_GotFocus;
this.LostFocus += TextBoxEx_LostFocus;
this.PreviewTextInput += TextBoxEx_PreviewTextInput;
}
void TextBoxEx_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
decimal d;
if(!decimal.TryParse(e.Text,out d))
{
e.Handled = true;
}
}
void TextBoxEx_LostFocus(object sender, System.Windows.RoutedEventArgs e)
{
ConvertText();
}
void TextBoxEx_GotFocus(object sender, System.Windows.RoutedEventArgs e)
{
this.Text = ActualText;
}
private void ConvertText()
{
string str = this.Text;
ActualText = str;
double dbl = Convert.ToDouble(str);
str = string.Format("{0:0.###}", dbl);
this.Text = str;
}
}

How to create Windows 8 style app bar in WPF?

I intended to create a Windows 8 Style App (Metro), but found out there is no support for using dual screen which is a demand for my app.
Now I am redesigning my app as a desktop application in WPF.
But I still like to mimic some nice design features from Windows 8 Apps.
One of the design features is the fly out bars typically used in a Windows 8 style app:
Bottom App bar for commands
Top Navigational bar
Right Charm that is common for all apps
The design they all have in common is a temporary flyout panel that is layered on top of the current window layout.
My question is: How can I create something similar in WPF?
I have no problem to create a main grid with a hidden bottom row that is made visible to display some common command buttons. But it would be nice to have it fly out on top of my standard layout, not squeeze it.
I know it is possible to open a new window on top of the current but that creates a bad code design and is hard to get nice looking. I would prefer to do it in the same window.
Cool question! I've actually done the charm bar fairly recently..
ideally what you need is something like
<Grid x:Name="LayoutRoot">
<Grid x:Name="Overlay" Panel.ZIndex="1000" Visibility="Collapsed">
<!-- This is where your slide out control is going to go -->
</Grid>
<!-- Use whatever layout you need -->
<ContentControl x:Name="MainContent" />
</Grid>
Now rather than squeezing the content - the Overlay grid will be on top of it similar to the charm bar! all with XAML
If you have anymore questions about this, give me a shout!
Edit; my Charm implementation - feel free to use for inspriation!
public class SlidePanel : ContentControl
{
static SlidePanel()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SlidePanel), new FrameworkPropertyMetadata(typeof(SlidePanel)));
}
public SlidePanel()
{
EventManager.RegisterClassHandler(typeof(SlidePanel), SlidePanel.MouseEnterEvent,
new RoutedEventHandler(OnLocalMouseEnter));
EventManager.RegisterClassHandler(typeof(SlidePanel), SlidePanel.MouseLeaveEvent,
new RoutedEventHandler(OnLocalMouseLeave));
}
#region Mouse Handlers
private static void OnLocalMouseEnter(object sender, RoutedEventArgs e)
{
SetExpanded(sender, true);
}
private static void OnLocalMouseLeave(object sender, RoutedEventArgs e)
{
SetExpanded(sender, false);
}
private static void SetExpanded(object sender, bool expanded)
{
SlidePanel panel = sender as SlidePanel;
if (panel != null)
{
panel.IsExpanded = expanded;
}
}
#endregion Mouse Handlers
#region Panel Width
public double PanelWidth
{
get { return (double)GetValue(PanelWidthProperty); }
set { SetValue(PanelWidthProperty, value); }
}
// Using a DependencyProperty as the backing store for PanelWidth. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PanelWidthProperty =
DependencyProperty.Register("PanelWidth", typeof(double), typeof(SlidePanel), new UIPropertyMetadata(5.0));
#endregion Panel Width
#region Closed Width
public double ClosedWidth
{
get { return (double)GetValue(ClosedWidthProperty); }
set { SetValue(ClosedWidthProperty, value); }
}
// Using a DependencyProperty as the backing store for ClosedWidth. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ClosedWidthProperty =
DependencyProperty.Register("ClosedWidth", typeof(double), typeof(SlidePanel), new UIPropertyMetadata(5.0, new PropertyChangedCallback(OnClosedWidthChange)));
#endregion Closed Width
#region Expanded Property
public bool IsExpanded
{
get { return (bool)GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsExpanded. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.Register("IsExpanded", typeof(bool), typeof(SlidePanel), new UIPropertyMetadata(false, new PropertyChangedCallback(OnExpandedChanged)));
#endregion Expanded Property
#region Property Changes
private static void OnExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == e.OldValue)
return;
SlidePanel panel = d as SlidePanel;
if (panel == null)
return;
bool newVal = (bool)e.NewValue;
panel.IsExpanded = newVal;
bool expanded = (bool)panel.GetValue(IsExpandedProperty);
Storyboard widthAnimation = AnimationHelper.CreateDoubleAnimation<SlidePanel>(panel, expanded,
(p, a) =>
{
a.From = (double)p.GetValue(SlidePanel.ClosedWidthProperty);
a.To = (double)p.GetValue(SlidePanel.PanelWidthProperty);
},
(p, a) =>
{
a.From = (double)p.GetValue(SlidePanel.WidthProperty);
a.To = (double)p.GetValue(SlidePanel.ClosedWidthProperty);
}, new TimeSpan(0, 0, 0, 0, 300), WidthProperty);
Timeline opacity = AnimationHelper.DoubleAnimation(0.0, 1.0, expanded,
new TimeSpan(0, 0, 0, 0, 300), OpacityProperty);
Storyboard.SetTargetName(opacity, panel.Name);
Storyboard.SetTargetProperty(opacity, new PropertyPath(OpacityProperty));
widthAnimation.Children.Add(opacity);
widthAnimation.Begin(panel);
}
private static void OnClosedWidthChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SlidePanel panel = d as SlidePanel;
if (panel != null)
panel.Width = (double)e.NewValue;
}
#endregion Property Changes
}
A little trick I found was to have the opacity set to 0 when it wasnt expanded but set the width to 10, this then allows the user to put the mouse at the side of the screen and then it will appear after a second or so..
cheers.
Edit - As requested.. AnimationHelper.
public class AnimationHelper
{
public static Timeline DoubleAnimation(double from, double to, bool modifier, TimeSpan duration, DependencyProperty property)
{
DoubleAnimation animation = new DoubleAnimation();
if (modifier)
{
animation.From = from;
animation.To = to;
}
else
{
animation.To = from;
animation.From = to;
}
animation.Duration = new Duration(duration);
return animation;
}
public static Storyboard CreateDoubleAnimation<T>(T control, bool modifier, double from, double to, TimeSpan duration, DependencyProperty property) where T : Control
{
return
AnimationHelper.CreateDoubleAnimation<T>(control, modifier,
(p, a) =>
{
a.From = from;
a.To = to;
},
(p, a) =>
{
a.From = to;
a.To = from;
}, duration, property);
}
public static Storyboard CreateDoubleAnimation<T>(T control, bool modifier, Action<T, DoubleAnimation> onTrue, Action<T, DoubleAnimation> onFalse, TimeSpan duration, DependencyProperty property) where T : Control
{
if (control == null)
throw new ArgumentNullException("control");
DoubleAnimation panelAnimation = new DoubleAnimation();
if (modifier)
{
if (onTrue != null)
onTrue.Invoke(control, panelAnimation);
}
else
{
if (onFalse != null)
onFalse.Invoke(control, panelAnimation);
}
panelAnimation.Duration = new Duration(duration);
Storyboard sb = new Storyboard();
Storyboard.SetTargetName(panelAnimation, control.Name);
Storyboard.SetTargetProperty(panelAnimation, new PropertyPath(property));
sb.Children.Add(panelAnimation);
return sb;
}
}

Frame ContentLoaded event

I'm new at Silverlight.
I've created a sort of master page using a Page with a frame where the content is loaded. As I handle multiple UserControls at the time (only one is shown, but I want to keep the state of the opened before) I'm setting Content property instead of Navigate method. That way I can assign a UserControl (already created, not a new one as it would be using Navigate with the Uri to the UserControl).
Now I want to take a picture as shown here from the frame when its content changes. If I do it immediately when the content set, the UserControl won't be shown in the picture because it takes a few secs. Frames have the event Navigated, but it doesn't fire with property Content (it just fires when the method Navigate is used, as it name says).
How can I know when new Content is loaded?
If it helps I'm using Silverligh 5.
I've a solution but I don't really like it, so I'm still looking for other ways.
public class CustomFrame : Frame
{
private readonly RoutedEventHandler loadedDelegate;
public static readonly DependencyProperty UseContentInsteadNavigationProperty =
DependencyProperty.Register("UseContentInsteadNavigation", typeof (bool), typeof (CustomFrame), new PropertyMetadata(true));
public bool UseContentInsteadNavigation
{
get { return (bool)GetValue(UseContentInsteadNavigationProperty); }
set { SetValue(UseContentInsteadNavigationProperty, value); }
}
public CustomFrame()
{
this.loadedDelegate = this.uc_Loaded;
}
public new object Content
{
get { return base.Content; }
set
{
if (UseContentInsteadNavigation)
{
FrameworkElement fe = (FrameworkElement)value;
fe.Loaded += loadedDelegate;
base.Content = fe;
}
else
{
base.Content = value;
}
}
}
void uc_Loaded(object sender, RoutedEventArgs e)
{
((UserControl)sender).Loaded -= loadedDelegate;
OnContentLoaded();
}
public delegate void ContentLoadedDelegate(Frame sender, EventArgs e);
public event ContentLoadedDelegate ContentLoaded;
private void OnContentLoaded()
{
if (ContentLoaded != null)
ContentLoaded(this, new EventArgs());
}
}

RibbonApplicationMenu: getting rid of the AuxiliaryPane

It so happened that the application I'm working on doesn't operate on documents, so there's no need in displaying the recently opened documents list in the application menu.
But - annoyingly - there are no properties readily available in the RibbonApplicationMenu class to hide the unused AuxiliaryPane (for which, curiously, the property does exist, but is marked as "internal").
Of course, I can just leave it there - but that's... untidy.
So, here's the solution I came up with.
Hope it will be helpful for anyone else :-)
The general idea is to subclass the RibbonApplicationMenu, find the template child corresponding to the menu's Popup, and overrule its Width (after a number of frustrating experiments it became evident that doing that neither for PART_AuxiliaryPaneContentPresenter nor for PART_FooterPaneContentPresenter - nor for the both - could achieve anything).
Well, without further ado, here's the code:
public class SlimRibbonApplicationMenu : RibbonApplicationMenu
{
private const double DefaultPopupWidth = 180;
public double PopupWidth
{
get { return (double)GetValue(PopupWidthProperty); }
set { SetValue(PopupWidthProperty, value); }
}
public static readonly DependencyProperty PopupWidthProperty =
DependencyProperty.Register("PopupWidth", typeof(double),
typeof(SlimRibbonApplicationMenu), new UIPropertyMetadata(DefaultPopupWidth));
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.DropDownOpened +=
new System.EventHandler(SlimRibbonApplicationMenu_DropDownOpened);
}
void SlimRibbonApplicationMenu_DropDownOpened(object sender, System.EventArgs e)
{
DependencyObject popupObj = base.GetTemplateChild("PART_Popup");
Popup popupPanel = (Popup)popupObj;
popupPanel.Width = (double)GetValue(PopupWidthProperty);
}
}
As a side note, I tried to find any way to resolve the desired width based on the max width of the ApplicationMenu's Items (rather than setting it explicitly through the DependencyProperty in XAML) - but to no avail.
Given my despise to "magic numbers", any suggestion on that will be deeply appreciated.
I know this has been a while, but I've got another solution to this. This one does not provide the Popup width property, instead a ShowAuxilaryPanel boolean. It then goes to Bind the width of the Popup, to the width of the menu item area of the menu.
public class SlimRibbonApplicationMenu : RibbonApplicationMenu
{
public bool ShowAuxilaryPanel
{
get { return (bool)GetValue(ShowAuxilaryPanelProperty); }
set { SetValue(ShowAuxilaryPanelProperty, value); }
}
public static readonly DependencyProperty ShowAuxilaryPanelProperty =
DependencyProperty.Register("ShowAuxilaryPanel", typeof(bool),
typeof(SlimRibbonApplicationMenu), new UIPropertyMetadata(true));
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.DropDownOpened += SlimRibbonApplicationMenu_DropDownOpened;
}
void SlimRibbonApplicationMenu_DropDownOpened(object sender, EventArgs e)
{
DependencyObject popupObj = base.GetTemplateChild("PART_Popup");
Popup panel = (Popup)popupObj;
var exp = panel.GetBindingExpression(Popup.WidthProperty);
if (!this.ShowAuxilaryPanel && exp == null)
{
DependencyObject panelArea = base.GetTemplateChild("PART_SubMenuScrollViewer");
var panelBinding = new Binding("ActualWidth")
{
Source = panelArea,
Mode = BindingMode.OneWay
};
panel.SetBinding(Popup.WidthProperty, panelBinding);
}
else if (this.ShowAuxilaryPanel && exp != null)
{
BindingOperations.ClearBinding(panel, Popup.WidthProperty);
}
}
}
worked for me
<telerik:ApplicationMenu RightPaneVisibility="Collapsed" >

Silverlight Convert PlatformKeyCode to Character Code

Question:
Is there a good way in Silverlight to intercept undesirable characters from being entered into a textbox?
Background:
I have a textbox which allows a user to enter a filename. I would like to exclude invalid file characters from being entered into the textbox. A few of these characters are:
'?'
'\'
'<'
'>'
Although the Silverlight TextBox class does not support a KeyPress event, it does have a KeyDown and a KeyUp event that can be used to retrieve character information when a key is entered into the textbox. It exposes these as a member of the Key enumeration or it can return an int for the PlatformKeyCode.
Of course the range of keys is larger/different from the range of characters - "F keys" are an example of this. However the presence of something like a KeyPress event in Windows Forms is indicative of the usefulness of being able to extract specific character information.
To do a proof of concept that things could work I hardcoded the PlatformKeyCode values for the undesired characters for my platform into the event handler and everything worked... but of course this is just my platform. I need to make sure this implementation is platform agnostic. Here is the code to demonstrate how I would like it to work:
private void theText_KeyDown(object sender, KeyEventArgs e)
{
int[] illegals = { 191, 188, 190, 220, 186, 222, 191, 56, 186};
if (illegals.Any(i => i == e.PlatformKeyCode)) e.Handled = true;
}
Both responses (in comments) from Henrik and Johannes contain the best answer for the scenario. Rather than thinking like a Windows Forms programmer and capturing the specific key event, the canonical approach in Silverlight is to use the TextChanged event to remove undesirable characters from a TextBox. A similar question on allowing only numeric input to a TextBox is what prompted the solution that was used.
The following code samples the approach I took after reading the comments and it works quite well to remove characters inappropriate for entry:
private void fileNameTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
string illegalChars = #"?<>:""\/*|";
fileNameTextBox.Text = String.Join("", fileNameTextBox.Text.Split(illegalChars.ToCharArray()));
fileNameTextBox.SelectionStart = fileNameTextBox.Text.Length;
}
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class FilterTextBoxBehavior : Behavior<TextBox>
{
public readonly static DependencyProperty AllowAlphaCharactersProperty = DependencyProperty.Register("AllowAlphaCharacters", typeof(bool), typeof(FilterTextBoxBehavior), new PropertyMetadata(true));
public bool AllowAlphaCharacters
{
get { return (bool)GetValue(AllowAlphaCharactersProperty); }
set { SetValue(AllowAlphaCharactersProperty, value); }
}
public readonly static DependencyProperty AllowNumericCharactersProperty = DependencyProperty.Register("AllowNumericCharacters", typeof(bool), typeof(FilterTextBoxBehavior), new PropertyMetadata(true));
public bool AllowNumericCharacters
{
get { return (bool)GetValue(AllowNumericCharactersProperty); }
set { SetValue(AllowNumericCharactersProperty, value); }
}
public readonly static DependencyProperty AllowSpecialCharactersProperty = DependencyProperty.Register("AllowSpecialCharacters", typeof(bool), typeof(FilterTextBoxBehavior), new PropertyMetadata(true));
public bool AllowSpecialCharacters
{
get { return (bool)GetValue(AllowSpecialCharactersProperty); }
set { SetValue(AllowSpecialCharactersProperty, value); }
}
public readonly static DependencyProperty DoNotFilterProperty = DependencyProperty.Register("DoNotFilter", typeof(string), typeof(FilterTextBoxBehavior), new PropertyMetadata(default(string)));
public string DoNotFilter
{
get { return (string)GetValue(DoNotFilterProperty); }
set { SetValue(DoNotFilterProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
if (AssociatedObject == null) { return; }
FilterAssociatedObject();
AssociatedObject.TextChanged += OnTextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject == null) { return; }
FilterAssociatedObject();
AssociatedObject.TextChanged -= OnTextChanged;
}
private void OnTextChanged(object sender, TextChangedEventArgs e) { FilterAssociatedObject(); }
private void FilterAssociatedObject()
{
int cursorLocation = AssociatedObject.SelectionStart;
for (int i = AssociatedObject.Text.Length - 1; i >= 0; i--)
{
char c = AssociatedObject.Text[i];
if (ValidChar(c)) { continue; }
AssociatedObject.Text = AssociatedObject.Text.Remove(i, 1);
cursorLocation--;
}
AssociatedObject.SelectionStart = Math.Min(AssociatedObject.Text.Length, Math.Max(0, cursorLocation));
}
private bool ValidChar(char c)
{
if (!string.IsNullOrEmpty(DoNotFilter) && DoNotFilter.Contains(c)) { return true; }
if (!AllowAlphaCharacters && char.IsLetter(c)) { return false; }
if (!AllowNumericCharacters && char.IsNumber(c)) { return false; }
if (!AllowSpecialCharacters && Regex.IsMatch(c.ToString(), #"[\W|_]")) { return false; }
return true;
}
}

Resources