How to create Windows 8 style app bar in WPF? - 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;
}
}

Related

Dependency Property design data

I have a UserControl that has an ObservableCollection dependency property with a property changed callback. The callback rebuilds the nodes in a TreeView control. This all works fine but I would like to be able to have design data. Unfortunately, neither the constructor, the callback nor a default value function call is called by the designer unless I embed my control in another. Is there a way loading default data in this scenario?
Below is the code behind for my control
public partial class ScheduleResourcesSummaryTreeView : UserControl
{
public ObservableCollection<PerformanceProjection> SelectedPerformances
{
get { return (ObservableCollection<PerformanceProjection>)GetValue(SelectedPerformancesProperty); }
set { SetValue(SelectedPerformancesProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedPerformancesProperty =
DependencyProperty.Register("SelectedPerformances",
typeof(ObservableCollection<PerformanceProjection>),
typeof(ScheduleResourcesSummaryTreeView),
new FrameworkPropertyMetadata(
new ObservableCollection<PerformanceProjection>(),
FrameworkPropertyMetadataOptions.AffectsRender, SelectedPerformancesChanged)
);
private static void SelectedPerformancesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ScheduleResourcesSummaryTreeView resourcesTree)) return;
resourcesTree.ResourcesTree.Items.Clear();
var sessions = e.NewValue as ObservableCollection<PerformanceProjection> ?? new ObservableCollection<PerformanceProjection>();
if (sessions.Count == 0) return;
var projections = sessions.SelectMany(x => x.ResourceBookingProjections);
TreeNode<ResourceBookingProjection> resourceBookingTreeRoot = new RvtSummaryTree(new ObservableCollection<ResourceBookingProjection>(projections), "");
foreach (var treeNode in resourceBookingTreeRoot.Children)
{
resourcesTree.ResourcesTree.Items.Add((TreeNode<ResourceBookingProjection>)treeNode);
}
}
private static ObservableCollection<PerformanceProjection> DefaultCollection()
{
var prop = DesignerProperties.IsInDesignModeProperty;
var designMode = (bool) DependencyPropertyDescriptor
.FromProperty(prop, typeof(FrameworkElement))
.Metadata.DefaultValue;
if (!designMode)
return new ObservableCollection<PerformanceProjection>();
var designdata = new PerformanceProjectionsViewModelDesignData();
return designdata.SelectedPerformances;
}
public ScheduleResourcesSummaryTreeView()
{
InitializeComponent();
}
}

How to animate a Point in a MeshGeometry3D in WPF/XAML?

Is it possible to animate points in a MeshGeometry3D? either in XAML or in C# code behind.
I can't seem to find a way to animate the X,Y,Z locations of points over time.
Any ideas?
This may help.. WPF and 3D how do you change a single position point in 3D space?
Maybe not pretty
Xaml
<Viewport3D>
<ModelVisual3D x:Name="VisualHost"/>
</Viewport3D>
CodeBehind
public partial class MyUserControl : UserControl
{
#region TargetZ Property
public static readonly DependencyProperty TargetZProperty =
DependencyProperty.RegisterAttached("TargetZ", typeof(double), typeof(MyUserControl), new PropertyMetadata(TargetZ_Changed));
private static void TargetZ_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var positions = ((MeshGeometry3D)((GeometryModel3D)d).Geometry).Positions;
var point = positions[0];
positions[0] = new Point3D(point.X, point.Y, point.Z + (double)e.NewValue);
}
public void SetTargetZ(GeometryModel3D d, double value)
{
d.SetValue(TargetZProperty, value);
}
public double GetTargetZ(GeometryModel3D d)
{
return (double)d.GetValue(TargetZProperty);
}
#endregion
public MyUserControl()
{
InitializeComponent();
}
private void SetNewZ(double newValue)
{
var animationTime = TimeSpan.FromSeconds(1);
var model = (GeometryModel3D)VisualHost.Content;
var zAnimation = new DoubleAnimation(newValue, animationTime) { FillBehavior = FillBehavior.HoldEnd };
model.BeginAnimation(TargetZProperty, zAnimation);
}
}

GridLength animation using keyframes?

I want to know are there any classes that I can animate a GridLength value using KeyFrames? I have seen the following sites, but none of them were with KeyFrames:
http://windowsclient.net/learn/video.aspx?v=70654
http://marlongrech.wordpress.com/2007/08/20/gridlength-animation/
Any advice?
Create an attached behavior and animate it instead.
Sure, GridLength clearly is not a numeric type and as such it's not clear how it can be animated. To compnesate that I can create an attached behavior like:
public class AnimatableProperties
{
public static readonly DependencyProperty WidthProperty =
DependencyProperty.RegisterAttached("Width",
typeof(double),
typeof(DependencyObject),
new PropertyMetadata(-1, (o, e) =>
{
AnimatableProperties.OnWidthChanged((Grid)o, (double)e.NewValue);
}));
public static void SetWidth(DependencyObject o,
double e)
{
o.SetValue(AnimatableProperties.WidthProperty, e);
}
public static double GetWidth(DependencyObject o)
{
return (double)o.GetValue(AnimatableProperties.WidthProperty);
}
private static void OnWidthChanged(DependencyObject target,
double e)
{
target.SetValue(Grid.WidthProperty, new GridLength(e));
}
}
That will re-inroduce Grid width as numeric property of double type. Having that in place you can freely animate it.
P.S. Obviously it doesn't make much sense to use Grid's Width as it's already double. any other GridLength based properties can be wrpapped with double wrappers as per the sample above and then animated via that wrappers.
It is fairly straight forward but you need to use an adapter because you can't directly animate Width on the ColumnDefinition class with a DoubleAnimator because ColumnDefinition is not a double. Here's my code:
public class ColumnDefinitionDoubleAnimationAdapter : Control
{
#region Dependency Properties
public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(nameof(Width), typeof(double), typeof(ColumnDefinitionDoubleAnimationAdapter), new PropertyMetadata((double)0, WidthChanged));
private static void WidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var columnDefinitionDoubleAnimationAdapter = (ColumnDefinitionDoubleAnimationAdapter)d;
columnDefinitionDoubleAnimationAdapter.Width = (double)e.NewValue;
}
#endregion
#region Fields
private ColumnDefinition _ColumnDefinition;
#endregion
#region Constructor
public ColumnDefinitionDoubleAnimationAdapter(ColumnDefinition columnDefinition)
{
_ColumnDefinition = columnDefinition;
}
#endregion
#region Public Properties
public double Width
{
get
{
return (double)GetValue(WidthProperty);
}
set
{
SetValue(WidthProperty, value);
_ColumnDefinition.Width = new GridLength(value);
}
}
#endregion
}
Unfortunately the above is pretty inefficient because it creates a GridLength again and again because ColumnDefinition.Width.Value should be read only.
Here is a method to do the animation. It's important that it uses Task based async because otherwise the storyboard will go out of scope and cause bad behaviour. This is good practice anyway so you can await the animation if you need to:
public async static Task AnimateColumnWidth(ColumnDefinition columnDefinition, double from, double to, TimeSpan duration, IEasingFunction ease)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
var storyboard = new Storyboard();
var animation = new DoubleAnimation();
animation.EasingFunction = ease;
animation.Duration = new Duration(duration);
storyboard.Children.Add(animation);
animation.From = from;
animation.To = to;
var columnDefinitionDoubleAnimationAdapter = new ColumnDefinitionDoubleAnimationAdapter(columnDefinition);
Storyboard.SetTarget(animation, columnDefinitionDoubleAnimationAdapter);
Storyboard.SetTargetProperty(animation, new PropertyPath(ColumnDefinitionDoubleAnimationAdapter.WidthProperty));
storyboard.Completed += (a, b) =>
{
taskCompletionSource.SetResult(true);
};
storyboard.Begin();
await taskCompletionSource.Task;
}
And an example usage:
private async void TheMenu_HamburgerToggled(object sender, EventArgs e)
{
TheMenu.IsOpen = !TheMenu.IsOpen;
var twoSeconds = TimeSpan.FromMilliseconds(120);
var ease = new CircleEase { EasingMode = TheMenu.IsOpen ? EasingMode.EaseIn : EasingMode.EaseOut };
if (TheMenu.IsOpen)
{
await UIUtilities.AnimateColumnWidth(MenuColumn, 40, 320, twoSeconds, ease);
}
else
{
await UIUtilities.AnimateColumnWidth(MenuColumn, 320, 40, twoSeconds, ease);
}
}

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" >

How to animate "typing" text in SketchFlow?

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));
}

Resources