WPF image swipe to change image like in iOS - c#

Can anyone point me to some code samples on how I could implement a gallery like the one in iOS where you can swipe side to side to change the image when using a touchscreen monitor on Windows 7?

You will need to implement swipe-like animations for this.
Guidelines for touch in WPF apps
I haven't tried this myself, but here is what seems to be an open source repo for a WPF carousel that might be worth taking a look at
Blog entry about swipe animation in WPF
Change the image on desired 'swipe-movement' :)
Free animation libraries if you don't want to create the animation yourself
Hope it helps!
Here is (by the way) the carousel control in the second link in case it goes missing like the previous one:
Code by Michael Palotas
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace WPFDemo
{
public class CarouselControl : Canvas
{
public CarouselControl()
{
_timer.Tick += new EventHandler(TimerTick);
_timer.Interval = TimeSpan.FromMilliseconds(10);
}
public DateTime _previousTime;
public DateTime _currentTime;
public void ReInitialize()
{
Init();
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
Init();
}
public delegate void OnElementSelectedHandler(object sender);
public event OnElementSelectedHandler OnElementSelected;
public void SelectElement(FrameworkElement element)
{
if (element != null)
{
_previousTime = DateTime.Now;
RotateToElement(element);
if (OnElementSelected != null)
OnElementSelected(element);
}
}
private const double DEFAULT_ROTATION_SPEED = 200;
private const double MINIMUM_ROTATION_SPEED = 1;
private const double MAXIMUM_ROTATION_SPEED = 1000;
private double _rotationSpeed = DEFAULT_ROTATION_SPEED;
public double RotationSpeed
{
get
{
return _rotationSpeed;
}
set
{
_rotationSpeed = Math.Min(Math.Max(value, MINIMUM_ROTATION_SPEED), MAXIMUM_ROTATION_SPEED);
}
}
private const double DEFAULT_LOOKDOWN_OFFSET = 0;
private const double MINIMUM_LOOKDOWN_OFFSET = -100;
private const double MAXIMUM_LOOKDOWN_OFFSET = 100;
private double _lookdownOffset = DEFAULT_LOOKDOWN_OFFSET;
public double LookDownOffset
{
get
{
return _lookdownOffset;
}
set
{
_lookdownOffset = Math.Min(Math.Max(value, MINIMUM_LOOKDOWN_OFFSET), MAXIMUM_LOOKDOWN_OFFSET);
}
}
private const double DEFAULT_FADE = 0.5;
private const double MINIMUM_FADE = 0;
private const double MAXIMUM_FADE = 1;
private double _fade = DEFAULT_FADE;
public double Fade
{
get
{
return _fade;
}
set
{
_fade = Math.Min(Math.Max(value, MINIMUM_FADE), MAXIMUM_FADE);
}
}
private const double DEFAULT_SCALE = 0.5;
private const double MINIMUM_SCALE = 0;
private const double MAXIMUM_SCALE = 1;
private double _scale = DEFAULT_SCALE;
public double Scale
{
get
{
return _scale;
}
set
{
_scale = Math.Min(Math.Max(value, MINIMUM_SCALE), MAXIMUM_SCALE);
}
}
private void element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
SelectElement(sender as FrameworkElement);
}
private void RotateToElement(FrameworkElement element)
{
if (element != _currentlySelected)
{
_currentlySelected = element;
int targetIndex = Children.IndexOf(element);
double degreesToRotate = GetDegreesNeededToPlaceElementInFront(_currentRotation, targetIndex, TotalNumberOfElements);
_targetRotation = ClampDegrees(_currentRotation - degreesToRotate);
StartRotation(degreesToRotate);
}
}
internal static double GetDegreesNeededToPlaceElementInFront(double currentRotation, int targetIndex, int totalNumberOfElements)
{
double rawDegrees = -(180.0 - (currentRotation + 360.0 * ((double)targetIndex / (double)totalNumberOfElements)));
if (rawDegrees > 180)
return -(360 - rawDegrees);
return rawDegrees;
}
private double RotationAmount
{
get
{
return (_currentTime - _previousTime).TotalSeconds * _rotationSpeed;
}
}
private const double INTERNAL_SCALE_COEFFICIENT = 0.6;
private void Init()
{
_previousTime = _currentTime = DateTime.Now;
X_SCALE = CenterX * INTERNAL_SCALE_COEFFICIENT;
Y_SCALE = CenterY * INTERNAL_SCALE_COEFFICIENT;
foreach (FrameworkElement element in Children)
{
element.MouseLeftButtonDown += new MouseButtonEventHandler(element_MouseLeftButtonDown);
element.Cursor = Cursors.Hand;
}
SelectElement(GetChild(0));
SetElementPositions();
}
private FrameworkElement _currentlySelected = null;
public FrameworkElement CurrentlySelected { get { return _currentlySelected; } }
protected double CenterX { get { return this.Width / 2.0; } }
protected double CenterY { get { return this.Height / 2.0; } }
protected double X_SCALE = 0;
protected double Y_SCALE = 0;
protected DispatcherTimer _timer = new DispatcherTimer();
private double _rotationToGo = 0;
private int TotalNumberOfElements { get { return Children.Count; } }
protected double _currentRotation = 0;
protected double _targetRotation = 0;
protected virtual void TimerTick(object sender, EventArgs e)
{
_currentTime = DateTime.Now;
if ((_rotationToGo < RotationAmount) && (_rotationToGo > -RotationAmount))
{
_rotationToGo = 0;
if (_currentRotation != _targetRotation)
{
_currentRotation = _targetRotation;
}
else
{
_timer.Stop();
return;
}
}
else if (_rotationToGo < 0)
{
_rotationToGo += RotationAmount;
_currentRotation = ClampDegrees(_currentRotation + RotationAmount);
}
else
{
_rotationToGo -= RotationAmount;
_currentRotation = ClampDegrees(_currentRotation - RotationAmount);
}
SetElementPositions();
_previousTime = _currentTime;
}
protected double ClampDegrees(double rawDegrees)
{
if (rawDegrees > 360)
return rawDegrees - 360;
if (rawDegrees < 0)
return rawDegrees + 360;
return rawDegrees;
}
public void SetElementPositions()
{
for (int index = 0; index < TotalNumberOfElements; index++)
{
FrameworkElement element = GetChild(index);
double elementWidthCenter = GetElementCenter(element.Width, element.ActualWidth);
double elementHeightCenter = GetElementCenter(element.Height, element.ActualHeight);
double degrees = 360 * ((double)index / (double)TotalNumberOfElements) + _currentRotation;
double x = -X_SCALE * Math.Sin(ConvertToRads(degrees)) - (double.IsNaN(Y_SCALE) ? 0.0 : Y_SCALE / 100.0) * (Math.Cos(ConvertToRads(degrees)) * LookDownOffset);
Canvas.SetLeft(element, x + CenterX - elementWidthCenter);
double y = Y_SCALE * Math.Sin(ConvertToRads(degrees)) - (double.IsNaN(X_SCALE) ? 0.0 : X_SCALE / 100.0) * (Math.Cos(ConvertToRads(degrees)) * LookDownOffset);
Canvas.SetTop(element, y + CenterY - elementHeightCenter);
ScaleTransform scale = element.RenderTransform as ScaleTransform;
if (scale == null)
{
scale = new ScaleTransform();
element.RenderTransform = scale;
}
scale.CenterX = elementWidthCenter;
scale.CenterY = elementHeightCenter;
scale.ScaleX = scale.ScaleY = GetScaledSize(degrees);
Canvas.SetZIndex(element, GetZValue(degrees));
SetOpacity(element, degrees);
}
}
private FrameworkElement GetChild(int index)
{
if (Children.Count == 0)
return null;
FrameworkElement element = Children[index] as FrameworkElement;
if (element == null)
throw new NotSupportedException("Carousel only supports children that are Framework elements");
return element;
}
internal static double GetElementCenter(double elementDimension, double elementActualDimension)
{
return double.IsNaN(elementDimension) ? elementActualDimension / 2.0 : elementDimension / 2.0;
}
private void SetOpacity(FrameworkElement element, double degrees)
{
element.Opacity = (1.0 - Fade) + Fade * GetCoefficient(degrees);
}
private int GetZValue(double degrees)
{
return (int)(360 * GetCoefficient(degrees));
}
private double GetScaledSize(double degrees)
{
return (1.0 - Scale) + Scale * GetCoefficient(degrees);
}
private double GetCoefficient(double degrees)
{
return 1.0 - Math.Cos(ConvertToRads(degrees)) / 2 - 0.5;
}
private double ConvertToRads(double degrees)
{
return degrees * Math.PI / 180.0;
}
private void StartRotation(double numberOfDegrees)
{
_rotationToGo = numberOfDegrees;
if (!_timer.IsEnabled)
{
_timer.Start();
}
}
}
}
XAML:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="WPFDemo.SphereControl"
x:Name="UserControl"
d:DesignWidth="50" d:DesignHeight="50">
<UserControl.Resources>
<Storyboard x:Key="MouseOver">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="1.3" KeySpline="0,0,0.5,1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="1.3" KeySpline="0,0,0.5,1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Ellipse" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Offset)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="0.231"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Ellipse" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Offset)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.312"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="0.729"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="MouseLeave">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="1" KeySpline="0,0,0.5,1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
<SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="1" KeySpline="0,0,0.5,1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Ellipse" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Offset)">
<SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Ellipse" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Offset)">
<SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="0.312"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard Storyboard="{StaticResource MouseOver}" x:Name="MouseOver_BeginStoryboard"/>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard Storyboard="{StaticResource MouseLeave}" x:Name="MouseLeave_BeginStoryboard"/>
</EventTrigger>
</UserControl.Triggers>
<Grid x:Name="LayoutRoot" RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="0"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Grid.RenderTransform>
<Ellipse Name="Ellipse" >
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.258,0.21">
<GradientStop x:Name="OuterColor" Offset="0.9"/>
<GradientStop x:Name="InnerColor" Offset="0.312"/>
<GradientStop Color="#FFFFFFFF" Offset="0"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>

Related

Display DateTime in TextBlock with Dashed-Underlined TextDecorations

I'm trying to display a DateTime formatted like 2019-10-07 17:00 in a TextBlock. The text should be underlined and dashed. To do this I'm using the following xaml
<TextBlock Text="2019-10-07 17:00">
<TextBlock.TextDecorations>
<TextDecoration Location="Underline">
<TextDecoration.Pen>
<Pen Brush="Black">
<Pen.DashStyle>
<DashStyle Dashes="5"/>
</Pen.DashStyle>
</Pen>
</TextDecoration.Pen>
</TextDecoration>
</TextBlock.TextDecorations>
</TextBlock>
However, this produces some very unexpected results where it seems like each hyphen causes the dashed underline to restart its rendering. Notice the dash-pattern which looks almost random efter each hyphen.
If I change the "minus-sign-hyphen" to "non-breaking-hyphen" which looks very similar (- vs ‐), the rendering works as expected.
<TextBlock Text="2019‐10‐07 17:00" ...>
This buggy rendering of the dashed underline happends everytime I add a minus-sign-hyphen to the text but not with any other character that I could find. Has anyone else noticed this and does anyone have a solution? If not, what might be the reason for this weird behavior?
Given your format, the size should always be roughly the same so you could use another textblock like so and just let it overlay the other box
<TextBlock Text="This is a really lon" Foreground="Transparent" IsHitTestVisible="False">
<TextBlock.TextDecorations>
<TextDecoration Location="Underline">
<TextDecoration.Pen>
<Pen Brush="Black">
<Pen.DashStyle>
<DashStyle Dashes="5"/>
</Pen.DashStyle>
</Pen>
</TextDecoration.Pen>
</TextDecoration>
</TextBlock.TextDecorations>
</TextBlock>
<TextBlock Text="2019-10-07 17:00" />
This is probably a result of some weird dash-hack found in the WPF glyph rendering code. In the .NET source you will find the AdjustAdvanceForDisplayLayout() method and its comment:
// AdvanceHeight is used to compute the bounding box. In some case, eg. the dash
// character '-', the bounding box is computed to be empty in Display
// TextFormattingMode (because the metrics are rounded to be pixel aligned) and so the
// dash is not rendered!
Setting TextOptions.TextFormattingMode="Display" on the TextBlock will produce a slightly different artifact:
This tells us that we did indeed hit this "workaround" (see GlyphRun.cs line 1326).
So the question is if we can somehow get a third variant, without any of these artifacts. So far, I have not succeeded but I did try to find where this hyphen check occurs. It seems to happen in native code. See TextFormatterContext.cs and LoCreateContext.
I don't have an answer to why this odd behavior occurs. It looks like the dashes created by the Pen are mapped to the decorated text of the TextDecoration. This makes sense as the dashes or TextDecoration in general will automatically adjust to the e.g. font size. The minus character seems to produce a different spacing. Maybe this behavior doesn't occur when using a monospace font.
Anyway, you could create a tiled DrawingBrush and assign it to the Pen.Brush property to create the dashed line. You can play around with the DrawingBrush.ViewPort to alter the position or the length of the dashes.
The Viewport consists of four values and is actually a Rect that describes the tile's position and dimension: x, y, width, height. Bigger values for width and height create longer dashes.
The result is an even drawing of dashes and spaces:
<TextBlock Text="2019-10-07 17:00">
<TextBlock.TextDecorations>
<TextDecoration Location="Underline">
<TextDecoration.Pen>
<Pen>
<Pen.Brush>
<DrawingBrush Viewport="0,0,10,10"
ViewportUnits="Absolute"
TileMode="Tile">
<DrawingBrush.Drawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<GeometryGroup>
<RectangleGeometry Rect="0,0,5,5" />
<RectangleGeometry Rect="5,5,5,5" />
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Pen.Brush>
</Pen>
</TextDecoration.Pen>
</TextDecoration>
</TextBlock.TextDecorations>
</TextBlock>
The downside of this approach is that the size and position of the dashes is no longer adaptive to the size of the font.
In the end, we built a custom control called DashTextBlock to solve this issue. It derives from TextBox and is styled like a TextBlock with an added TextDecoration that uses a Pen with a LinearGradientBrush that is set up according to whatever what specified as "dash-properties" and the thickness of DashThickness.
To achieve this it uses the TextBox method GetRectFromCharacterIndex to figure out how to setup the LinearGradientBrush.
TextBox.GetRectFromCharacterIndex Method
Returns the rectangle for an edge of the character at the specified index.
It produces results like this
Sample usage
<StackPanel>
<controls:DashTextBlock Text="Testing DashTextBlock"
DashThickness="1"
DashColor="Blue">
<controls:DashTextBlock.DashStyle>
<DashStyle Dashes="4,4,4,4" Offset="0" />
</controls:DashTextBlock.DashStyle>
</controls:DashTextBlock>
<controls:DashTextBlock Text="Testing DashTextBlock"
Margin="0 5 0 0"
DashThickness="2"
DashColor="Orange">
<controls:DashTextBlock.DashStyle>
<DashStyle Dashes="8 4 8 4" Offset="0" />
</controls:DashTextBlock.DashStyle>
</controls:DashTextBlock>
</StackPanel>
DashTextBlock
public class DashTextBlock : TextBox
{
public static readonly DependencyProperty DashColorProperty =
DependencyProperty.Register("DashColor",
typeof(Color),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(Colors.Black, OnDashColorChanged));
public static readonly DependencyProperty DashThicknessProperty =
DependencyProperty.Register("DashThickness",
typeof(double),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(1.0, OnDashThicknessChanged));
public static readonly DependencyProperty DashStyleProperty =
DependencyProperty.Register("DashStyle",
typeof(DashStyle),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(DashStyles.Solid, OnDashStyleChanged));
private static readonly DependencyProperty FontSizeCallbackProperty =
DependencyProperty.Register("FontSizeCallback",
typeof(double),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(0.0, OnFontSizeCallbackChanged));
public static readonly DependencyProperty TextLengthProperty =
DependencyProperty.Register("TextLength",
typeof(double),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(0.0));
public static readonly DependencyProperty DashEnabledProperty =
DependencyProperty.Register("DashEnabled",
typeof(bool),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(true, OnDashEnabledChanged));
private static void OnDashColorChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DashTextBlock dashTextBlock = source as DashTextBlock;
dashTextBlock.DashColorChanged();
}
private static void OnDashThicknessChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DashTextBlock dashTextBlock = source as DashTextBlock;
dashTextBlock.DashThicknessChanged();
}
private static void OnDashStyleChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DashTextBlock dashTextBlock = source as DashTextBlock;
dashTextBlock.DashStyleChanged();
}
private static void OnFontSizeCallbackChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DashTextBlock dashTextBlock = source as DashTextBlock;
dashTextBlock.FontSizeChanged();
}
private static void OnDashEnabledChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DashTextBlock dashTextBlock = source as DashTextBlock;
dashTextBlock.DashEnabledChanged();
}
private static Pen _transparentPen;
static DashTextBlock()
{
_transparentPen = new Pen(Brushes.Transparent, 0);
_transparentPen.Freeze();
DefaultStyleKeyProperty.OverrideMetadata(typeof(DashTextBlock), new FrameworkPropertyMetadata(typeof(DashTextBlock)));
}
private TextDecoration _dashDecoration = new TextDecoration();
public DashTextBlock()
{
Binding fontSizeCallbackBinding = new Binding();
fontSizeCallbackBinding.Source = this;
fontSizeCallbackBinding.Path = new PropertyPath(TextBlock.FontSizeProperty);
this.SetBinding(FontSizeCallbackProperty, fontSizeCallbackBinding);
TextChanged += DashTextBlock_TextChanged;
this.LayoutUpdated += DashTextBlock_LayoutUpdated;
}
private void DashTextBlock_LayoutUpdated(object sender, EventArgs e)
{
if (IsLoaded)
{
var textRect = GetRectFromCharacterIndex(Text.Length);
double availableWidth = textRect.Right;
if (textRect.IsEmpty == false &&
availableWidth > 0)
{
this.LayoutUpdated -= DashTextBlock_LayoutUpdated;
UpdateTextWithDashing();
}
}
}
public Color DashColor
{
get { return (Color)GetValue(DashColorProperty); }
set { SetValue(DashColorProperty, value); }
}
public double DashThickness
{
get { return (double)GetValue(DashThicknessProperty); }
set { SetValue(DashThicknessProperty, value); }
}
public DashStyle DashStyle
{
get { return (DashStyle)GetValue(DashStyleProperty); }
set { SetValue(DashStyleProperty, value); }
}
private double FontSizeCallback
{
get { return (double)GetValue(FontSizeCallbackProperty); }
set { SetValue(FontSizeCallbackProperty, value); }
}
public double TextLength
{
get { return (double)GetValue(TextLengthProperty); }
set { SetValue(TextLengthProperty, value); }
}
public bool DashEnabled
{
get { return (bool)GetValue(DashEnabledProperty); }
set { SetValue(DashEnabledProperty, value); }
}
private void DashTextBlock_TextChanged(object sender, TextChangedEventArgs e)
{
UpdateTextWithDashing();
}
private void FontSizeChanged()
{
//UpdateTextWithDashing();
}
private void DashEnabledChanged()
{
UpdateTextWithDashing();
}
private void DashColorChanged()
{
UpdateTextWithDashing();
}
private void DashStyleChanged()
{
UpdateTextWithDashing();
}
private void DashThicknessChanged()
{
UpdateTextWithDashing();
}
public void UpdateTextWithDashing()
{
AddDashDecoration();
_dashDecoration.Pen = CreatePenFromProperties();
}
private Pen CreatePenFromProperties()
{
if (!DashEnabled)
{
return _transparentPen;
}
if (DashStyle.Dashes.Count < 2 ||
IsLoaded == false ||
Text.Length == 0)
{
return new Pen(new SolidColorBrush(DashColor), DashThickness);
}
double length = 0.0;
foreach (var dash in DashStyle.Dashes)
{
length += dash;
}
double stepLength = 1.0 / length;
TextBox textBox = this as TextBox;
Rect textRect = Rect.Empty;
for (int l = (textBox.Text.Length - 1); l >= 0; l--)
{
if (textBox.Text[l] != ' ')
{
try
{
textRect = textBox.GetRectFromCharacterIndex(l + 1);
}
catch
{
// See possible bug here:
// https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/VirtualizingStackPanel.cs,8060
// TODO: Revisit after migrate to .NET 5
}
break;
}
}
double target = FontSize;
double availableWidth = textRect.Right;
if (textRect.IsEmpty == false &&
availableWidth > 0)
{
TextLength = availableWidth;
double current = 0;
bool count = true;
bool foundTargetLength = false;
double savedDashes = 0.0;
while (!foundTargetLength)
{
for (int i = 0; i < DashStyle.Dashes.Count; i++)
{
var dash = DashStyle.Dashes[i];
savedDashes += dash;
double increase = (target * (dash * stepLength));
double preDiff = availableWidth - current;
current += increase;
double postDiff = current - availableWidth;
if (current > availableWidth)
{
if (!count)
{
if (postDiff < preDiff || Text.Length <= 2)
{
if ((i + 1) < DashStyle.Dashes.Count)
{
savedDashes += DashStyle.Dashes[i + 1];
}
else
{
savedDashes += DashStyle.Dashes[0];
}
}
else
{
if (i == 0)
{
savedDashes -= DashStyle.Dashes.Last();
}
else
{
savedDashes -= DashStyle.Dashes[i - 1];
}
}
}
foundTargetLength = true;
target = availableWidth / (savedDashes * stepLength);
break;
}
count = !count;
}
}
}
LinearGradientBrush dashBrush = new LinearGradientBrush();
dashBrush.StartPoint = new Point(0, 0);
dashBrush.EndPoint = new Point(target, 0);
dashBrush.MappingMode = BrushMappingMode.Absolute;
dashBrush.SpreadMethod = GradientSpreadMethod.Repeat;
double offset = 0.0;
bool isFill = true;
foreach (var dash in DashStyle.Dashes)
{
GradientStop gradientStop = new GradientStop();
gradientStop.Offset = offset;
gradientStop.Color = isFill ? DashColor : Colors.Transparent;
dashBrush.GradientStops.Add(gradientStop);
offset += (dash * stepLength);
gradientStop = new GradientStop();
gradientStop.Offset = offset;
gradientStop.Color = isFill ? DashColor : Colors.Transparent;
dashBrush.GradientStops.Add(gradientStop);
isFill = !isFill;
}
Pen dashPen = new Pen(dashBrush, DashThickness);
return dashPen;
}
private void AddDashDecoration()
{
foreach (TextDecoration textDecoration in TextDecorations)
{
if (textDecoration == _dashDecoration)
{
return;
}
}
TextDecorations.Add(_dashDecoration);
}
}
Style
<Style TargetType="{x:Type controls:DashTextBlock}">
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:DashTextBlock}">
<Border x:Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="0"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost"
Focusable="False"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" TargetName="border" Value="0.56"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

UWP : How to determine 2 TransformGroup is the same approximately?

I have 1 shape on canvas.
Case 1 : I am using a TransformGroup to new place with rotation
Case 2 : I use hand to move and rotate shape from original place to last place in case 1.
I want to check if the shape is laid correctly or not.
So I want to compare 2 TransformGroup value. Do you have any idea about this?
I also want to handle the shapes which is symmetrical too.
So I want to compare 2 TransformGroup value.
I'm not sure how you implement your features, but for comparing the TransformGroup value, you could just name the transform objects inside the group and get the properties of each transform for comparing. For example, the following code snippet get the rotate angle and translate value:
XAML
<StackPanel Margin="15">
<StackPanel.Resources>
<Storyboard x:Name="myStoryboard">
<DoubleAnimation
RepeatBehavior="Forever"
Storyboard.TargetName="rotateTransform"
Storyboard.TargetProperty="Angle"
From="0"
To="360"
Duration="0:0:8" />
<DoubleAnimation
RepeatBehavior="Forever"
Storyboard.TargetName="translateTransform"
Storyboard.TargetProperty="X"
To="200"
Duration="0:0:8" />
</Storyboard>
</StackPanel.Resources>
<Rectangle
Width="50"
Height="50"
Fill="RoyalBlue"
PointerPressed="StartAnimation" >
<Rectangle.RenderTransform>
<TransformGroup x:Name="transformgroup">
<RotateTransform x:Name="rotateTransform" Angle="45" CenterX="25" CenterY="25" />
<TranslateTransform x:Name="translateTransform" X="0" />
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<TextBox x:Name="txtAngle" Header="CurrentAngle" />
<TextBox x:Name="txtX" Header="CurrentX" />
<Button
x:Name="btnget"
Click="btnget_Click"
Content="gettransform" />
</StackPanel>
Code behind
private void StartAnimation(object sender, PointerRoutedEventArgs e)
{
myStoryboard.Begin();
}
private void btnget_Click(object sender, RoutedEventArgs e)
{
txtAngle.Text= rotateTransform.Angle.ToString();
txtX.Text = translateTransform.X.ToString();
//RotateTransform rotatetransget= transformgroup.Children[0] as RotateTransform;
}
This is my solution only for polygon and it correct if the shape is symmetrical.
Just compare transform by compare it points after transform
Boolean CompareTransform(TransformGroup transformGroupA, TransformGroup transformGroupB)
{
if (element is Polygon)
{
Polygon p = element as Polygon;
List<Point> listPointA = new List<Point>();
List<Point> listPointB = new List<Point>();
for (int i = 0; i < p.Points.Count; i++)
{
Point pointA = transformGroupA.Value.Transform(p.Points[i]);
Point pointB = transformGroupB.Value.Transform(p.Points[i]);
listPointA.Add(pointA);
listPointB.Add(pointB);
}
for (int i = 0; i < listPointB.Count; i++)
{
if (GetDistance(listPointA[0].X, listPointA[0].Y, listPointB[i].X, listPointB[i].Y) < 10)
{
List<Point> newList = new List<Point>();
newList.AddRange(listPointB.GetRange(i, listPointB.Count - i));
newList.AddRange(listPointB.GetRange(0, i));
return ComparePointsList(listPointA, newList);
}
}
return false;
}
return false;
}
bool ComparePointsList(List<Point> listA, List<Point> listB)
{
for (int i = 0; i < listA.Count; i++)
{
if (GetDistance(listA[i].X, listA[i].Y, listB[i].X, listB[i].Y) > 10)
{
return false;
}
}
return true;
}
double GetDistance(double x1, double y1, double x2, double y2)
{
return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2));
}
Are you have any idea for eclipse?

Bubble window opacity variation and position not working

I want to make a bubble window that GRADUALLY appears and vanishes after having displayed a message. I want its position to be near the tray and in the constructor I use for that:
var screenSize = System.Windows.SystemParameters.WorkArea;
this.Left = screenSize.Right - this.Width;
this.Top = screenSize.Bottom - this.Height;
this has worked properly other times but now it doesn't.
Strangely enough also the opacity doesn't change. So to put it in a nutshell the window appear all of a sudden with no gradual transition at the center of the screen instead than near the tray.
This is the code:
public BubbleWindow(string strMessage, double milliseconds)
{
InitializeComponent();
btYes.Content = strMessage;
var screenSize = System.Windows.SystemParameters.WorkArea;
this.Left = screenSize.Right - this.Width;
this.Top = screenSize.Bottom - this.Height;
int interval = (int)(milliseconds / 25);
for (double iii = 0; iii <= 1; iii += .1)
{
Thread.Sleep(interval);
this.Opacity = iii;
Dispatcher.Invoke((Action)(() => { }), DispatcherPriority.Render);
this.UpdateLayout();
}
this.Opacity = 1;
}
Thanks for any help
You can use this code in your XAML
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
This is pure XAML way
Or you can use
<Storyboard x:Name="anim">
<DoubleAnimation
Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:5" />
</Storyboard>
and then start the animation from c#
you can take reference from here
As for the position problem I had put the WindowPosition=CenterScreen in my xaml code. That overrode the .left and .top command.
For the sake of completeness this is the complete Fade IN - wait - Fade out cycle:
private Storyboard myStoryboard;
public MainWindow()
{
InitializeComponent();
DoubleAnimation fadeInAnimation = new DoubleAnimation() { From = 0.0, To = 1.0, Duration = new Duration(TimeSpan.FromSeconds(1)) };
fadeInAnimation.Completed += FadeInAnimation_Completed;
myStoryboard = new Storyboard();
myStoryboard.Children.Add(fadeInAnimation);
Storyboard.SetTargetName(fadeInAnimation, MyRectangle.Name);
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(Rectangle.OpacityProperty));
// Use the Loaded event to start the Storyboard.
MyRectangle.Loaded += new RoutedEventHandler(myRectangleLoaded);
}
private void myRectangleLoaded(object sender, RoutedEventArgs e)
{
myStoryboard.Begin(this);
}
private void FadeInAnimation_Completed(object sender, EventArgs e)
{
DispatcherTimer timerFadeOut = new DispatcherTimer();
timerFadeOut.Interval = TimeSpan.FromSeconds(2);
timerFadeOut.Tick += TimerFadeOut_Tick;
timerFadeOut.Start();
}
private void TimerFadeOut_Tick(object sender, EventArgs e)
{
DoubleAnimation fadeOutAnimation = new DoubleAnimation() { From = 1.0, To = 0.0, Duration = new Duration(TimeSpan.FromSeconds(1)) };
myStoryboard = new Storyboard();
myStoryboard.Children.Add(fadeOutAnimation);
Storyboard.SetTargetName(fadeOutAnimation, MyRectangle.Name);
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(Rectangle.OpacityProperty));
myStoryboard.Begin(this);
}
This works. That being said I am open to corrections.

Animate Image when loaded in Image Control

I want to create a slideshow of images. For this I have loaded new images in image control after every set interval of time. But each time i load a new image it comes without any animation I need to load each image with a transition animation or fade in-out animation. How can I achieve animation while changing images in Image control? Following is the code:
XAML:
<Grid>
<Image Source="{Binding CurrentImage}" />
</Grid>
XAML.cs
ViewModel = new ScreenSaverViewModel();
this.DataContext = ViewModel;
ViewModel.cs
/// <summary>
/// Gets the current image to display on the attract screen. Changes to this property
/// cause the PropertyChanged event to be signaled
/// </summary>
public string CurrentImage
{
get { return this.currentImage; }
protected set
{
this.currentImage = value;
this.OnPropertyChanged("CurrentImage");
}
}
/// <summary>
/// Gets the observable collection of all images.
/// </summary>
public ObservableCollection<string> Images
{
get { return this.images; }
}
public ScreenSaverViewModel()
{
images = new ObservableCollection<string>();
this.LoadSlideShowImages();
if (Images != null && Images.Count > 0)
{
this.CurrentImage = this.Images[this.currentIndex];
this.tickTimer = new DispatcherTimer();
this.tickTimer.Interval = TimeSpan.FromMilliseconds(TimerIntervalMilliseconds);
this.tickTimer.Tick += (s, e) =>
{
this.currentIndex++;
this.currentIndex = this.currentIndex < this.Images.Count ? this.currentIndex : 0;
this.CurrentImage = this.Images[this.currentIndex];
};
// start the timer after image is loaded
this.tickTimer.Start();
}
}
I have created a class inheriting Image control in which I have raised property change when Source of image control changes. on which I have applied trigger this works fine now. Following is the code:
<controls:ImageControl Source="{Binding CurrentImage}" >
<controls:ImageControl.Triggers>
<EventTrigger RoutedEvent="controls:ImageControl.SourceChanged">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Image.Opacity)" From="0" To="1" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</controls:ImageControl.Triggers>
</controls:ImageControl>
public class ImageControl : Image
{
public static readonly RoutedEvent SourceChangedEvent = EventManager.RegisterRoutedEvent(
"SourceChanged", RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(ImageControl));
static ImageControl()
{
Image.SourceProperty.OverrideMetadata(typeof(ImageControl), new FrameworkPropertyMetadata(SourcePropertyChanged));
}
public event RoutedEventHandler SourceChanged
{
add { AddHandler(SourceChangedEvent, value); }
remove { RemoveHandler(SourceChangedEvent, value); }
}
private static void SourcePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
Image image = obj as Image;
if (image != null)
{
image.RaiseEvent(new RoutedEventArgs(SourceChangedEvent));
}
}
}
Hope this helps.
Please run this code separately.
<Grid Height="200" Width="200">
<Grid.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard >
<Storyboard >
<ObjectAnimationUsingKeyFrames Duration="00:00:06" RepeatBehavior="Forever" Storyboard.TargetName="img1" Storyboard.TargetProperty="(Image.Source)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<BitmapImage UriSource="image1.png" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime="00:00:02">
<DiscreteObjectKeyFrame.Value>
<BitmapImage UriSource="image2.png" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime="00:00:04">
<DiscreteObjectKeyFrame.Value>
<BitmapImage UriSource="image3.png" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetName="img1" RepeatBehavior="Forever" Storyboard.TargetProperty="Opacity" From="0.1" To="1" Duration="00:00:02"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<Image x:Name="img1" Stretch="Fill"></Image>
</Grid>
Update
<Storyboard FillBehavior="Stop" x:Key="FadeOut">
<DoubleAnimation FillBehavior="Stop" Storyboard.TargetName="ScreensaverImage" Storyboard.TargetProperty="Opacity" From="0.05" To="1" Duration="0:0:2">
</DoubleAnimation>
</Storyboard>
<Storyboard FillBehavior="Stop" x:Key="FadeIn">
<DoubleAnimation Storyboard.TargetName="ScreensaverImage" FillBehavior="Stop" Storyboard.TargetProperty="Opacity" From="1" To=".05" Duration="0:0:2">
</DoubleAnimation>
</Storyboard>
<Grid>
<Image x:Name="ScreensaverImage"></Image>
</Grid>
public partial class MainWindow : Window
{
List<Uri> savedImage = new List<Uri>();
int i = 0;
Storyboard fadeIn, fadeOut;
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
savedImage.Add(new Uri("image1.png", UriKind.Relative));
savedImage.Add(new Uri("image2.png", UriKind.Relative));
savedImage.Add(new Uri("image3.png", UriKind.Relative));
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
if (savedImage.Count > 0)
{
fadeIn = (Storyboard)this.Resources["FadeIn"];
fadeOut = (Storyboard)this.Resources["FadeOut"];
fadeIn.Completed += fadeIn_Completed;
fadeOut.Completed += fadeOut_Completed;
ScreensaverImage.Source = new BitmapImage(savedImage[i++]);
if (savedImage.Count > 1)
{
BeginStoryboard(fadeOut);
}
ScreensaverImage.Visibility = System.Windows.Visibility.Visible;
}
else
{
ScreensaverImage.Visibility = System.Windows.Visibility.Collapsed;
}
}
void fadeOut_Completed(object sender, EventArgs e)
{
fadeIn.Begin();
}
void fadeIn_Completed(object sender, EventArgs e)
{
if (i == savedImage.Count)
i = 0;
ScreensaverImage.Source = new BitmapImage(savedImage[i++]);
fadeOut.Begin();
}
}
you can use the MetroImageControl or MetroContentControl that provided in:
Mahapps library for metro style

Need to create a delay between two effects in Wpf

I am new to WPF. I am creating a animation for sample online test result. Where I like to show No of attended question, No of correct answer as animation. I need to create small delay between AttendedQuestionEffect() and RightAnswerEffect();
CodeBehind code here
public int TotalNoQuestion { get; set; }
public int NoOfQuestionAttended { get; set; }
public int NoOfRightAnswer { get; set; }
public Window1()
{
InitializeComponent();
TotalNoQuestion = 100;
NoOfQuestionAttended = 18;
NoOfRightAnswer = 10;
stkpnl.Background = CreateLinearGradientBrush();
Storyboard strBrd = new Storyboard();
strBrd.Completed += new EventHandler(strBrd_Completed);
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 10;
myDoubleAnimation.To = (TotalNoQuestion *15);
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(2));
Storyboard.SetTargetName(myDoubleAnimation, stkpnl.Name);
Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(StackPanel.HeightProperty));
strBrd.Children.Add(myDoubleAnimation);
strBrd.Begin(stkpnl);
}
void strBrd_Completed(object sender, EventArgs e)
{
for (int i = 1; i < TotalNoQuestion; i++)
{
Border brd = new Border();
brd.BorderBrush = Brushes.Black;
brd.BorderThickness = new Thickness(1.0);
TextBlock txt = new TextBlock();
txt.Text = i.ToString();
txt.Height = 15;
txt.Width = 200;
brd.Child = txt;
txt.FontSize = 12;
txt.Foreground = Brushes.Black;
stkpnl.Children.Add(brd);
txt.Background = CreateLinearGradientBrush();
txt.Tag = i.ToString();
}
AttendedQuestionEffect();
// Here i need to create delay.
RightAnswerEffect();
}
void AttendedQuestionEffect()
{
int index = 1;
UIElementCollection ulCollection = stkpnl.Children;
foreach (UIElement uiElement in ulCollection)
{
if (index <= NoOfQuestionAttended)
{
Border brd = (Border)uiElement;
TextBlock txt = (TextBlock)brd.Child;
txt.Background = BlinkEffect(Colors.Blue, Colors.SteelBlue, 3000);
brd.Child = txt;
}
index++;
}
}
void RightAnswerEffect()
{
int index = 1;
UIElementCollection ulCollection = stkpnl.Children;
foreach (UIElement uiElement in ulCollection)
{
if (index <= NoOfRightAnswer)
{
Border brd = (Border)uiElement;
TextBlock txt = (TextBlock)brd.Child;
txt.Background = BlinkEffect(Colors.Red, Colors.Blue, 1500);
brd.Child = txt;
}
index++;
}
}
private LinearGradientBrush CreateLinearGradientBrush()
{
LinearGradientBrush brush = new LinearGradientBrush();
brush.StartPoint = new Point(0, 0);
brush.EndPoint = new Point(1, 1);
brush.GradientStops.Add(new GradientStop(Colors.LightCoral, 0.1));
brush.GradientStops.Add(new GradientStop(Colors.YellowGreen, 0.35));
brush.GradientStops.Add(new GradientStop(Colors.Yellow, 0.86));
return brush;
}
private SolidColorBrush BlinkEffect(Color startColor, Color endColor,int time)
{
ColorAnimation myColorAnimation = new ColorAnimation();
myColorAnimation.From = startColor;
myColorAnimation.To = endColor;
myColorAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(time));
myColorAnimation.AutoReverse = true;
myColorAnimation.RepeatBehavior = RepeatBehavior.Forever;
SolidColorBrush myBrush = new SolidColorBrush();
myBrush.BeginAnimation(SolidColorBrush.ColorProperty, myColorAnimation);
return myBrush;
}
**Xaml code here..**
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border BorderBrush="Black" Grid.Column="1" BorderThickness="1" Width="200" VerticalAlignment="Bottom" HorizontalAlignment="Left">
<StackPanel x:Name="stkpnl" Grid.Column="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Background="BlueViolet" Width="200" MaxHeight="450" >
</StackPanel>
</Border>
</Grid>
`
Your should look up storyboard animations using KeyFrames e.g. here.
In Xaml this could look like the following ... you just have to translate it to code and adjust it to your needes:
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="0:0:0.5" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
This sample tfirst fades out an control and then hides it. You can ad a delay by modifying the BeginTime attribute.

Categories

Resources