I want to display an 8x8 Grid in Windows 8 metro app. To do this:
I created a Grid, and added 8 row definitions and 8 column definitions.
I then add a Rectangle with a black border to each of the grid cells.
Then in the MeasureOverride method, I check the availableSize. Since my grid needs to be square (aspect ratio = 1.0), I compute the minimum of availableSize.Width, availableSize.Height and return a new Size equal to (minimum, minimum).
However this does not work. The resulting grid's size is equal to availableSize, and not the size I return from my MeasureOverride method. If I modify the MeaureOverride, so that I set Height of RowDefinitions to minimum, and Width of ColumnDefinitions to minimum, then it works. But I saw some videos and they say you should not be explicitly setting Height & Width properties of anything.
So, is there a better way to accomplish what I want?
I'm not sure if you need to interact with those cells in any way, but if you just want to draw a grid, here is a quick control to do it. It will fill the space of the parent control.
public class GridShape : Control
{
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(int), typeof(GridShape), new PropertyMetadata(8));
public int Rows
{
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
public static readonly DependencyProperty RowsProperty = DependencyProperty.Register("Rows", typeof(int), typeof(GridShape), new PropertyMetadata(8));
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register("Stroke", typeof(Brush), typeof(GridShape), new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public double StrokeThickness
{
get { return (double)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof(double), typeof(GridShape), new PropertyMetadata(1.0));
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
Pen pen = new Pen(Stroke, StrokeThickness);
double heightSpan = ActualHeight / Rows;
double widthSpan = ActualWidth / Columns;
for (double y = 0; y <= ActualHeight; y += heightSpan)
drawingContext.DrawLine(pen, new Point(0, y), new Point(ActualWidth, y));
for (double x = 0; x <= ActualWidth; x += widthSpan)
drawingContext.DrawLine(pen, new Point(x, 0), new Point(x, ActualHeight));
}
}
One solution is to create a custom Grid control to handle the width, height
public class SquareGrid : Grid
{
public SquareGrid()
{
this.SizeChanged += OnSizeChanged;
this.Loaded += OnLoaded;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
var parent = VisualTreeHelper.GetParent(this) as FrameworkElement;
if (parent == null) return;
ResizeToSquare(parent);
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var parent = VisualTreeHelper.GetParent(this) as FrameworkElement;
if (parent == null) return;
parent.SizeChanged += ParentOnSizeChanged;
}
private void ParentOnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
{
FrameworkElement parent = sender as FrameworkElement;
if (parent == null) return;
ResizeToSquare(parent);
}
private void ResizeToSquare(FrameworkElement parent)
{
var min = Math.Min(parent.ActualHeight, parent.ActualWidth);
this.Width = min;
this.Height = min;
}
}
You could also build a Behavior for this that would do the same thing.
Related
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);
}
}
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);
}
}
There is an very old WPF application of Hyper Tree - http://blogs.msdn.com/b/llobo/archive/2007/10/31/mindmap-app-using-hyperbolic-tree.aspx.
The source code can be found at codeplax.com -
http://hypertree.codeplex.com/releases/view/11524
I wanted to use this tree control in my silverlight application. Now the issue is that i am new to silverlight, and the code is using some WPF specific things.
Please suggest me to solve my problem.
Thanks in advance.
Abhinav
Update:
things like
FrameworkPropertyMetadata and FrameworkPropertyMetadataOptions, InvalidateVisual(), OnRender override, child UIElements.
Code Added:
public class SmartBorder : Decorator
{
#region Dependency Properties
public static readonly DependencyProperty GlowBrushProperty =
DependencyProperty.Register("GlowBrush", typeof(Brush), typeof(SmartBorder), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
......
#region Dependency Property backing CLR properties
......
#endregion
// if the button is pressed, this fires
private static void OnRenderIsPressedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmartBorder border = o as SmartBorder;
if (border != null)
{
if ((bool)e.NewValue == true)
{
border.BorderBrush = Brushes.Transparent;
border.BorderWidth = 2;
}
else
{
border.BorderBrush = Brushes.Red;
border.BorderWidth = 2;
}
border.InvalidateVisual();
}
}
// if the mouse is over the control, this fires
private static void OnRenderIsMouseOverChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmartBorder border = o as SmartBorder;
if (border != null)
{
border.InvalidateVisual();
}
}
// a series of methods which all make getting the default or currently selected brush easier
protected override void OnRender(DrawingContext dc)
{
Rect rc = new Rect(0, 0, this.ActualWidth, this.ActualHeight);
LinearGradientBrush gradientOverlay = GetGradientOverlay();
Brush glowBrush = GetGlowBrush();
Brush backBrush = GetBackgroundBrush();
Brush borderBrush = GetBorderBrush();
Pen borderPen = new Pen(borderBrush, BorderWidth);
double cornerRadiusCache = CornerRadius;
// draw the highlight as necessary
if (RenderIsMouseOver)
{
Rect rcGlow = rc;
double glowMove = BorderWidth * 2;
rcGlow.Inflate(glowMove, glowMove);
glowMove = 0;
rcGlow.Offset(new Vector(glowMove, glowMove));
dc.DrawRoundedRectangle(GetOuterGlowBrush(), null, rcGlow, cornerRadiusCache, cornerRadiusCache);
}
// we want to clip anything that might errantly draw outside of the smart border control
dc.PushClip(new RectangleGeometry(rc, cornerRadiusCache, cornerRadiusCache));
dc.DrawRoundedRectangle(backBrush, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
dc.DrawRoundedRectangle(gradientOverlay, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
if (!RenderIsPressed)
{
double clipBorderSize = BorderWidth * -4.0;
Rect rcClip = rc;
rcClip.Offset(clipBorderSize, clipBorderSize);
rcClip.Inflate(-clipBorderSize, -clipBorderSize);
dc.PushClip(new RectangleGeometry(rcClip, cornerRadiusCache, cornerRadiusCache));
dc.DrawEllipse(glowBrush, null, new Point(this.ActualWidth / 2, this.ActualHeight * 0.10), this.ActualWidth * 0.80, this.ActualHeight * 0.40);
dc.Pop();
}
// just draw the border now to make sure it overlaps everything nicely
dc.DrawRoundedRectangle(null, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
dc.Pop();
//base.OnRender(drawingContext);
}
protected override Size MeasureOverride(Size constraint)
{
UIElement child = this.Child as UIElement;
double borderThickness = BorderWidth * 2.0;
if (child != null)
{
...
}
return new Size(Math.Min(borderThickness, constraint.Width), Math.Min(borderThickness, constraint.Height));
}
}
Regarding FrameworkPropertyMetadata and FrameworkPropertyMetadataOptions and value coercions etc. for Silverlight see the WPF_Compatibility solution under the ClipFlair codebase (http://clipflair.codeplex.com)
I have a line shape that I have implemented the double click for, however the line is too thin. I would like to add a transparent white padding around it, so that it doesn't have to be clicked exactly on the line.
I really don't want to increase the stroke thickness, and I would like it to remain as shape since I do not want to put it in a content control, or a border.
One way of doing this is to override the standard Hit Testing of this line. Unfortunately, the WPF's Line class is sealed, which I personally think is criminal :-)
Here is a piece of code that reproduces the Line behavior, but in another class, and defines a Tolerance property (default value is 5). Feel free to test it:
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
namespace MyNamespace
{
public class HitTolerantLine : Shape
{
public static readonly DependencyProperty X1Property = DependencyProperty.Register("X1", typeof(double), typeof(Line), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(IsDoubleFinite));
public static readonly DependencyProperty X2Property = DependencyProperty.Register("X2", typeof(double), typeof(Line), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(IsDoubleFinite));
public static readonly DependencyProperty Y1Property = DependencyProperty.Register("Y1", typeof(double), typeof(Line), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(IsDoubleFinite));
public static readonly DependencyProperty Y2Property = DependencyProperty.Register("Y2", typeof(double), typeof(Line), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(IsDoubleFinite));
public static readonly DependencyProperty ToleranceProperty = DependencyProperty.Register("Tolerance", typeof(double), typeof(Line), new FrameworkPropertyMetadata(5.0), new ValidateValueCallback(IsDoubleFinite));
private LineGeometry _geometry;
private static readonly Pen _strokePen;
static HitTolerantLine()
{
_strokePen = new Pen(Brushes.Black, 1.0);
_strokePen.Freeze();
}
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
HitTestResult res = base.HitTestCore(hitTestParameters);
// didn't hit? let's add some tolerance
if ((res == null) && (_geometry != null) && (Tolerance > 0))
{
if (_geometry.StrokeContains(_strokePen, hitTestParameters.HitPoint, Tolerance, ToleranceType.Absolute))
{
res = new PointHitTestResult(this, hitTestParameters.HitPoint);
}
}
return res;
}
protected virtual void DefineGeometry()
{
Point startPoint = new Point(X1, Y1);
Point endPoint = new Point(X2, Y2);
_geometry = new LineGeometry(startPoint, endPoint);
}
protected override Size MeasureOverride(Size constraint)
{
DefineGeometry();
return base.MeasureOverride(constraint);
}
protected static bool IsDoubleFinite(object o)
{
double d = (double)o;
return (!double.IsInfinity(d) && !double.IsNaN(d));
}
protected override Geometry DefiningGeometry
{
get
{
return _geometry;
}
}
public double Tolerance
{
get
{
return (double)base.GetValue(ToleranceProperty);
}
set
{
base.SetValue(ToleranceProperty, value);
}
}
[TypeConverter(typeof(LengthConverter))]
public double X1
{
get
{
return (double) base.GetValue(X1Property);
}
set
{
base.SetValue(X1Property, value);
}
}
[TypeConverter(typeof(LengthConverter))]
public double X2
{
get
{
return (double) base.GetValue(X2Property);
}
set
{
base.SetValue(X2Property, value);
}
}
[TypeConverter(typeof(LengthConverter))]
public double Y1
{
get
{
return (double) base.GetValue(Y1Property);
}
set
{
base.SetValue(Y1Property, value);
}
}
[TypeConverter(typeof(LengthConverter))]
public double Y2
{
get
{
return (double) base.GetValue(Y2Property);
}
set
{
base.SetValue(Y2Property, value);
}
}
}
}
Padding area is not clickable.
Only way I can think of, given your restrictions is to increase stroke thickness and manage the stroke brush to be a gradient of transparent color and the visible color
Draw a transparent line on top with a larger StrokeThickness and apply the double click behaviour to that.
How can I animate the the scrolling for ListBox? I know I can use scrollIntoView but how can I animate it? I want to press the arrow keys to move from one listBoxItem to another.
Here is a rough implementation based on the same approach as the following link
http://aniscrollviewer.codeplex.com/
The VerticalOffset property is read-only so instead you can use an attached property VerticalOffset on the ScrollViewer which in turn does ScrollToVerticalOffset. This attached property can be animated.
You can also create an extension method for ItemsControl called AnimateScrollIntoView.
Call it like this
listBox.AnimateScrollIntoView(yourItem);
ScrollViewerBehavior
public class ScrollViewerBehavior
{
public static DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset",
typeof(double),
typeof(ScrollViewerBehavior),
new UIPropertyMetadata(0.0, OnVerticalOffsetChanged));
public static void SetVerticalOffset(FrameworkElement target, double value)
{
target.SetValue(VerticalOffsetProperty, value);
}
public static double GetVerticalOffset(FrameworkElement target)
{
return (double)target.GetValue(VerticalOffsetProperty);
}
private static void OnVerticalOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = target as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
}
}
ItemsControlExtensions
public static class ItemsControlExtensions
{
public static void AnimateScrollIntoView(this ItemsControl itemsControl, object item)
{
ScrollViewer scrollViewer = VisualTreeHelpers.GetVisualChild<ScrollViewer>(itemsControl);
UIElement container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as UIElement;
int index = itemsControl.ItemContainerGenerator.IndexFromContainer(container);
double toValue = scrollViewer.ScrollableHeight * ((double)index / itemsControl.Items.Count);
Point relativePoint = container.TranslatePoint(new Point(0.0, 0.0), Window.GetWindow(container));
DoubleAnimation verticalAnimation = new DoubleAnimation();
verticalAnimation.From = scrollViewer.VerticalOffset;
verticalAnimation.To = toValue;
verticalAnimation.DecelerationRatio = .2;
verticalAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(1000));
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(verticalAnimation);
Storyboard.SetTarget(verticalAnimation, scrollViewer);
Storyboard.SetTargetProperty(verticalAnimation, new PropertyPath(ScrollViewerBehavior.VerticalOffsetProperty));
storyboard.Begin();
}
}
And since you also need to get a hold of the ScrollViewer you'll need this
public static class VisualTreeHelpers
{
public static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
}
Take a look at this article, it explains how animate scrolling and add touch gestures. Download the source at the bottom of the page and look at WpfScrollContent solution. I would extend the WPF ListBox and add the scroll animation to it that way you can reuse the control.