I'm trying to rotate Arrow according to angle which is determined by a random number.
the arrow angle does change but the animation is not working.
<Grid>
<ed:BlockArrow x:Name="blockArrow" Fill="Black" HorizontalAlignment="Left" Height="13.299" Orientation="Left" Stroke="Black" VerticalAlignment="Top" Width="100" Margin="84.571,165.951,0,0" RenderTransformOrigin="1,0.5">
<ed:BlockArrow.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform x:Name="ArrowTransform" Angle="{Binding ElementName=MeterValueTextBox, Path=Text}" />
<TranslateTransform/>
</TransformGroup>
</ed:BlockArrow.RenderTransform>
</ed:BlockArrow>
<Ellipse Fill="Black" HorizontalAlignment="Left" Height="25" Stroke="Black" VerticalAlignment="Top" Width="25" Margin="172.865,159.568,0,0"/>
<TextBlock
Text="Type value:"
FontSize="16"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="57.976,245.873,0,0"
/>
<TextBox
x:Name="MeterValueTextBox"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="100"
Margin="140,245.873,0,0"
Text="{Binding Meter, UpdateSourceTrigger=PropertyChanged}"
>
<TextBox.Triggers>
<EventTrigger RoutedEvent="TextBox.TextChanged">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ArrowTransform"
Storyboard.TargetProperty="(RotateTransform.Angle)"
From="{Binding OldMeterValue}" To="{Binding Meter}" Duration="0:0:0:1"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBox.Triggers>
</TextBox>
</Grid>
and the view model is:
public class MainViewModel : ViewModelBase
{
private float _meter;
public float Meter
{
get { return _meter; }
set
{
if (!Equals(_meter, value))
{
OldMeterValue = _meter;
_meter = value;
OnPropertyChanged();
}
}
}
private float _oldMeterValue;
public float OldMeterValue
{
get { return _oldMeterValue; }
set
{
if (_oldMeterValue != value)
{
_oldMeterValue = value;
OnPropertyChanged();
}
}
}
public MainViewModel()
{
DisplayName = "Benchmark Application";
OldMeterValue = 0;
Meter = 0;
var meterTimer = new Timer
{
Interval = 1000
};
meterTimer.Elapsed += MeterTimerOnElapsed;
meterTimer.Start();
}
private void MeterTimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
{
var randomNumber = new Random();
var meterValue = randomNumber.Next(0, 180);
Meter = meterValue;
}
}
as for now, the arrow bounces from old value to new value but without animation.
please assist...
Duration property of your animation seems too fast (one milisecond??). Try to set it to a greater value to make it obvious, for example, 5 seconds :
<DoubleAnimation
Storyboard.TargetName="ArrowTransform"
Storyboard.TargetProperty="(RotateTransform.Angle)"
From="{Binding OldMeterValue}" To="{Binding Meter}" Duration="0:0:5"
/>
[Reference]
Related
I have textbox displayed using the DataTemplate. In the below code TextBoxViewModel is derived from IDataErrorInfo.
protected String m_ValidationErrorMessage;
protected String ValidationErrorMessage
{
get { return m_ValidationErrorMessage; }
}
private bool m_ShowErrorMessage = false;
public bool ShowErrorMessage
{
get { return m_ShowErrorMessage; }
set { this.m_ShowErrorMessage = value; }
}
private String m_DisplayValue;
public String DisplayValue
{
get { return this.m_DisplayValue; }
set
{
if (m_DisplayValue != value)
{
if (IsTextValid(value, out m_ValidationErrorMessage))
{
// Set data to model
this.SendPropertyChangedEvent(nameof(this.DisplayValue));
}
else
{
ShowErrorMessage = true;
}
}
}
}
public string this[string columnName]
{
get
{
if (columnName == nameof(this.DisplayValue))
{
if (ShowErrorMessage)
{
return ValidationErrorMessage;
}
}
return null;
}
}
xaml for TextBoxViewModel
<DataTemplate DataType="{x:Type vms:TextBoxViewModel}">
<DockPanel>
<Label Content="{Binding Path=DisplayName}" MinWidth="120" MaxWidth="150"/>
<TextBox DockPanel.Dock="Left" Text="{Binding Path=DisplayValue,Mode=TwoWay, UpdateSourceTrigger=Default,ValidatesOnDataErrors=True}" Validation.ErrorTemplate="{StaticResource ErrorTemplate}"/>
</DockPanel>
</DataTemplate
<ControlTemplate x:Key="ErrorTemplate">
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder x:Name="textBox"/>
<ItemsControl ItemsSource="{Binding}" VerticalAlignment="Center" HorizontalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
I am trying to flash the error text for few seconds and hiding it after that. Is there any way to achieve this using the XAML (WPF animation)?
You could for example animate the Opacity property of the TextBlock using a DoubleAnimation. Something like this:
<ControlTemplate x:Key="ErrorTemplate">
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder x:Name="textBox"/>
<ItemsControl ItemsSource="{Binding}" VerticalAlignment="Center" HorizontalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0" To="1"
AutoReverse="False"
Duration="0:0:0.5"
RepeatBehavior="3x" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
I want to replace the regular ProgressBar with circle one and after shot search here in the forum i found what i want.
CircularProgressBar.XAML
<Grid>
<Path x:Name="pathRoot" Stroke="{Binding SegmentColor, ElementName=userControl}"
StrokeThickness="{Binding StrokeThickness, ElementName=userControl}" HorizontalAlignment="Left" VerticalAlignment="Top">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pathFigure">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="arcSegment" SweepDirection="Clockwise" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
CircularProgressBar.cs:
public partial class CircularProgressBar : UserControl
{
public CircularProgressBar()
{
InitializeComponent();
Angle = (Percentage * 360) / 100;
RenderArc();
}
public int Radius
{
get { return (int)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
public Brush SegmentColor
{
get { return (Brush)GetValue(SegmentColorProperty); }
set { SetValue(SegmentColorProperty, value); }
}
public int StrokeThickness
{
get { return (int)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public double Percentage
{
get { return (double)GetValue(PercentageProperty); }
set { SetValue(PercentageProperty, value); }
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a DependencyProperty as the backing store for Percentage. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PercentageProperty =
DependencyProperty.Register("Percentage", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(65d, new PropertyChangedCallback(OnPercentageChanged)));
// Using a DependencyProperty as the backing store for StrokeThickness. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StrokeThicknessProperty =
DependencyProperty.Register("StrokeThickness", typeof(int), typeof(CircularProgressBar), new PropertyMetadata(5, new PropertyChangedCallback(OnThicknessChanged)));
// Using a DependencyProperty as the backing store for SegmentColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SegmentColorProperty =
DependencyProperty.Register("SegmentColor", typeof(Brush), typeof(CircularProgressBar), new PropertyMetadata(new SolidColorBrush(Colors.Red), new PropertyChangedCallback(OnColorChanged)));
// Using a DependencyProperty as the backing store for Radius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof(int), typeof(CircularProgressBar), new PropertyMetadata(25, new PropertyChangedCallback(OnPropertyChanged)));
// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(120d, new PropertyChangedCallback(OnPropertyChanged)));
private static void OnColorChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.set_Color((SolidColorBrush)args.NewValue);
}
private static void OnThicknessChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.set_tick((int)args.NewValue);
}
private static void OnPercentageChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
if (circle.Percentage > 100) circle.Percentage = 100;
circle.Angle = (circle.Percentage * 360) / 100;
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.RenderArc();
}
public void set_tick(int n)
{
pathRoot.StrokeThickness = n;
}
public void set_Color(SolidColorBrush n)
{
pathRoot.Stroke = n;
}
public void RenderArc()
{
Point startPoint = new Point(Radius, 0);
Point endPoint = ComputeCartesianCoordinate(Angle, Radius);
endPoint.X += Radius;
endPoint.Y += Radius;
pathRoot.Width = Radius * 2 + StrokeThickness;
pathRoot.Height = Radius * 2 + StrokeThickness;
pathRoot.Margin = new Thickness(StrokeThickness, StrokeThickness, 0, 0);
bool largeArc = Angle > 180.0;
Size outerArcSize = new Size(Radius, Radius);
pathFigure.StartPoint = startPoint;
if (startPoint.X == Math.Round(endPoint.X) && startPoint.Y == Math.Round(endPoint.Y))
endPoint.X -= 0.01;
arcSegment.Point = endPoint;
arcSegment.Size = outerArcSize;
arcSegment.IsLargeArc = largeArc;
}
private Point ComputeCartesianCoordinate(double angle, double radius)
{
// convert to radians
double angleRad = (Math.PI / 180.0) * (angle - 90);
double x = radius * Math.Cos(angleRad);
double y = radius * Math.Sin(angleRad);
return new Point(x, y);
}
}
MainWindow.xaml:
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
SegmentColor="#FF878889" StrokeThickness="25" Percentage="100" />
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
Percentage="{Binding Value, ElementName=slider}" SegmentColor="#026873" StrokeThickness="25" />
</Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
<Slider x:Name="slider" Grid.Row="1" Maximum="100" Value="60" />
</Grid>
And the result:
http://s21.postimg.org/xymj8k4pz/image.png
So after copy paste the same code into my solution and running all i can see is only the Slider withou the Circle Bar, this is my code:
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="530,303,114,303">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
SegmentColor="#FF878889" StrokeThickness="8" Percentage="100" />
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
Percentage="{Binding Value, ElementName=slider}" SegmentColor="#026873" StrokeThickness="8" />
</Grid>
</StackPanel>
<Slider x:Name="slider" Maximum="100" Value="20" Width="200" Margin="597,185,227,495" />
Am i doing something wrong ?
You probably missed x:Name="userControl" in the UserControl definition:
<UserControl x:Name="userControl" x:Class="DesignInControl.CircularProgressBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
<Grid>
<Path x:Name="pathRoot" Stroke="{Binding SegmentColor, ElementName=userControl}" StrokeThickness="{Binding StrokeThickness, ElementName=userControl}" HorizontalAlignment="Left" VerticalAlignment="Top">
<Path.Data>
...
This demonstrates how to animate the circle.
When AnimateProgressCircle is true, the "busy" circle will be rotating, else it will be invisible.
<!-- "I'm Busy" Animation circle. -->
<StackPanel Height="20" Width="20">
<Viewbox>
<!-- All this does is display the circle. -->
<local:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
Percentage="0" SegmentColor="#726873" StrokeThickness="10">
<!-- All this does is continuously animate circle angle from 0 - 100% in response to "IfAnimateProgressCircle". -->
<local:CircularProgressBar.Style>
<Style TargetType="local:CircularProgressBar">
<Style.Triggers>
<DataTrigger Binding="{Binding IfAnimateProgressCircle}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Percentage"
From="0"
To="100"
Duration="0:0:1"
AutoReverse="True"
RepeatBehavior = "Forever"
/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Percentage" To="0.0" Duration="0:0:0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</local:CircularProgressBar.Style>
</local:CircularProgressBar>
</Viewbox>
</StackPanel>
Advantages
As it's wrapped in a <Viewbox>, it will always be perfectly sized to the parent container.
Unlike other simplistic solutions, it does not consume resources when it is not in use because the animation is stopped.
Testing
Tested on:
WPF
.NET 4.5 + .NET 4.6.1
Win7 x64
Visual Studio 2015 + Update 2
To test, set DataContext="{Binding RelativeSource={RelativeSource Self}}" in the <Window> tag, then use this code behind.
You should see the circle pause for 2 seconds, then animate for 4 seconds, then stop.
public partial class MainWindow : Window, INotifyPropertyChanged
{
private bool _ifAnimateProgressCircle;
public MainWindow()
{
InitializeComponent();
Task.Run(
async () =>
{
// Animates circle for 4 seconds.
IfAnimateProgressCircle = false;
await Task.Delay(TimeSpan.FromMilliseconds(2000));
IfAnimateProgressCircle = true;
await Task.Delay(TimeSpan.FromMilliseconds(6000));
IfAnimateProgressCircle = false;
});
}
public bool IfAnimateProgressCircle
{
get { return _ifAnimateProgressCircle; }
set { _ifAnimateProgressCircle = value; OnPropertyChanged(); }
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Links that helped with this solution
WPF. How to stop data trigger animation through binding?
Animate UserControl in WPF?
Binding objects defined in code-behind
Triggers collection members must be of type EventTrigger
I build a control in Windows Phone (same WPF) draw a vertical chart by timeline.
In Xaml:
<UserControl x:Class="MiO2.CustomControls.VerticalChart"
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"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="480" d:DesignWidth="480"
xmlns:blockPivot="clr-namespace:MiO2.Framework"
x:Name="uc">
<UserControl.Resources>
<Storyboard x:Name="animateOpacityPointer">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pointer">
<EasingDoubleKeyFrame KeyTime="0" Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<CircleEase EasingMode="EaseOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" blockPivot:BlocksPan.IsEnabled="True" Background="{Binding Background, ElementName=uc}">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<Grid x:Name="renderLayer" Grid.Row="1">
<Canvas x:Name="canvasRender">
</Canvas>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="10" x:Name="touchLayer" Visibility="Visible">
<Canvas x:Name="pointerLayer" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Grid Width="52" x:Name="pointer" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Top="0" Canvas.Left="0">
<StackPanel Orientation="Vertical">
<TextBlock x:Name="tbTime" Height="40" Margin="0,0,0,0" Foreground="Black" />
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Top">
<Grid HorizontalAlignment="Left" VerticalAlignment="Top">
<Ellipse Fill="#FF8BE0FB" Height="40" Width="40" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Ellipse Fill="#FF01ADF5" Height="20" Width="20" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
<Line x:Name="line" Stroke="Black" StrokeDashArray="4 2" Y2="0" VerticalAlignment="Top" HorizontalAlignment="Center"/>
</StackPanel>
</StackPanel>
</Grid>
</Canvas>
<TextBlock x:Name="tbUnit" Text="1" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="Black" />
<Slider blockPivot:BlocksPan.IsEnabled="True" ManipulationCompleted="touchLayer_ManipulationCompleted" ManipulationStarted="touchLayer_ManipulationStarted" Background="Transparent" ManipulationDelta="touchLayer_ManipulationDelta" Foreground="Transparent" Style="{StaticResource SliderStyle1}">
</Slider>
</Grid>
</Grid>
In code behide:
public partial class VerticalChart : UserControl
{
public VerticalChart()
{
InitializeComponent();
this.Loaded += VerticalChart_Loaded;
}
void VerticalChart_Loaded(object sender, RoutedEventArgs e)
{
InitializeChart();
}
public static readonly DependencyProperty ItemSourceProperty =
DependencyProperty.Register("ItemSource", typeof(IEnumerable<ITimelineData>), typeof(VerticalChart),
new PropertyMetadata(null, ItemSourcePropertyChangedCallback));
private static void ItemSourcePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public IEnumerable<ITimelineData> ItemSource
{
get { return base.GetValue(ItemSourceProperty) as IEnumerable<ITimelineData>; }
set { base.SetValue(ItemSourceProperty, value); }
}
Random ran = new Random();
double unit = 0;
double leftStart = 0;
public void InitializeChart()
{
if (ItemSource.Count() == 0) return;
TimeSpan period = ItemSource.Last().TimeEnd - ItemSource.First().TimeStart;
double width = this.ActualWidth - pointer.ActualWidth / 2;
double height = renderLayer.ActualHeight;
double minutes = period.TotalMinutes;
leftStart = pointer.ActualWidth / 2;
double divide = (width / minutes);
if (divide >= 2) unit = 2;
else if (divide >= 1) unit = 1;
else if (divide >= 0.5) unit = 0.5;
else if (divide >= 0.25) unit = 0.25;
else unit = 0.1;
tbUnit.Text = unit.ToString();
foreach (ITimelineData used in ItemSource)
{
double min = (used.TimeEnd - used.TimeStart).TotalMinutes;
int borderWidth = (int)(unit * min);
double left = (used.TimeStart - ItemSource.First().TimeStart).TotalMinutes * unit + leftStart;
Border border = new Border();
border.Width = borderWidth;
border.Height = ran.Next(100, 250);
Binding binding = new Binding("Foreground");
binding.Source = this;
border.SetBinding(Border.BackgroundProperty, binding);
double top = height - border.Height;
Canvas.SetLeft(border, left);
Canvas.SetTop(border, top);
canvasRender.Children.Add(border);
}
}
}
And use in Mainpage.xaml
<myControl:VerticalChart ItemSource="{Binding ActivitiesToday}" />
And when property ActivitiesToday changed means ItemSource property updated. And I want, when ItemSource updated new or change number collection Cavas: renderLayer redrawn number of Border inside. How to do that? I want to redrawn Border in Canvas when ItemSource updated.
For code draw list Border:
Border border = new Border();
border.Width = borderWidth;
border.Height = ran.Next(100, 250);
Binding binding = new Binding("Foreground");
binding.Source = this;
border.SetBinding(Border.BackgroundProperty, binding);
double top = height - border.Height;
Canvas.SetLeft(border, left);
Canvas.SetTop(border, top);
canvasRender.Children.Add(border);
How to do that? Thanks!
Create a custom event in your usercontrol
private event ItemsSourceChanged;
Add all the required code to support event change
Now trigger the event on property change.
public IEnumerable<ITimelineData> ItemSource
{
get { return base.GetValue(ItemSourceProperty) as IEnumerable<ITimelineData>; }
set { base.SetValue(ItemSourceProperty, value); OnItemSourcePropertyChange(); }
}
In xaml, map the event to code-behind and do your changes.
<myControl:VerticalChart ItemSource="{Binding ActivitiesToday}" ItemSourceChanged="doSomething" />
I have a ListViewItem that I am applying a Style to and I would like to put a dotted grey line as the bottom Border.
How can I do this in WPF? I can only see solid color brushes.
This worked great in our application, allowing us to use a real Border and not mess around with Rectangles:
<Border BorderThickness="1,0,1,1">
<Border.BorderBrush>
<DrawingBrush Viewport="0,0,8,8" ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<GeometryGroup>
<RectangleGeometry Rect="0,0,50,50" />
<RectangleGeometry Rect="50,50,50,50" />
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.BorderBrush>
<TextBlock Text="Content Goes Here!" Margin="5"/>
</Border>
Note that the Viewport determines the size of the dashes in the lines. In this case, it generates eight-pixel dashes. Viewport="0,0,4,4" would give you four-pixel dashes.
You can create a dotted or dashes line using a rectangle like in the code below
<Rectangle Stroke="#FF000000" Height="1" StrokeThickness="1" StrokeDashArray="4 4"
SnapsToDevicePixels="True"/>
Get started with this and customize your listview according to your scenario
A bit late to the party, but the following solution worked for me. It is slightly simpler/better than both other solutions:
<Border BorderThickness="1">
<Border.BorderBrush>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle StrokeDashArray="4 2" Stroke="Gray" StrokeThickness="1"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"/>
</VisualBrush.Visual>
</VisualBrush>
</Border.BorderBrush>
<TextBlock Text="Whatever" />
</Border>
Xaml
<Grid>
<Grid.RowDefinitions><RowDefinition Height="auto"/></Grid.RowDefinitions>
<Grid.ColumnDefinitions><ColumnDefinition Width="auto"/></Grid.ColumnDefinitions>
<Rectangle RadiusX="9" RadiusY="9" Fill="White" Stroke="Black" StrokeDashArray="1,2"/>
<TextBlock Padding = "4,2" Text="Whatever"/>
</Grid>
Our team got this as a requirement lately and we solved it by creating a custom control, DashedBorder which extends Border and adds the dashed border feature.
It has 3 new dependency properties
UseDashedBorder (bool)
DashedBorderBrush (Brush)
StrokeDashArray (DoubleCollection)
Usable like this
<controls:DashedBorder UseDashedBorder="True"
DashedBorderBrush="#878787"
StrokeDashArray="2 1"
Background="#EBEBEB"
BorderThickness="3"
CornerRadius="10 10 10 10">
<TextBlock Text="Dashed Border"
Margin="6 2 6 2"/>
</controls:DashedBorder>
And produces a result like this
When UseDashedBorder is set to true it will create a VisualBrush with 2 rectangles and set that as BorderBrush (that's why we need an extra property for the color of the actual BorderBrush). The first one is to create the dashing and the second of is to fill in the gaps with the Background of the border.
It maps the Rectangle dashing properties to the DashedBorder properties like this
StrokeDashArray => StrokeDashArray
Stroke => DashedBorderBrush
StrokeThickness => BorderThickness.Left
RadiusX => CornerRadius.TopLeft
RadiusY => CornerRadius.TopLeft
Width => ActualWidth
Height => ActualHeight
DashedBorder.cs
public class DashedBorder : Border
{
private static DoubleCollection? emptyDoubleCollection;
private static DoubleCollection EmptyDoubleCollection()
{
if (emptyDoubleCollection == null)
{
DoubleCollection doubleCollection = new DoubleCollection();
doubleCollection.Freeze();
emptyDoubleCollection = doubleCollection;
}
return emptyDoubleCollection;
}
public static readonly DependencyProperty UseDashedBorderProperty =
DependencyProperty.Register(nameof(UseDashedBorder),
typeof(bool),
typeof(DashedBorder),
new FrameworkPropertyMetadata(false, OnUseDashedBorderChanged));
public static readonly DependencyProperty DashedBorderBrushProperty =
DependencyProperty.Register(nameof(DashedBorderBrush),
typeof(Brush),
typeof(DashedBorder),
new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty StrokeDashArrayProperty =
DependencyProperty.Register(nameof(StrokeDashArray),
typeof(DoubleCollection),
typeof(DashedBorder),
new FrameworkPropertyMetadata(EmptyDoubleCollection()));
private static void OnUseDashedBorderChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
DashedBorder dashedBorder = (DashedBorder)target;
dashedBorder.UseDashedBorderChanged();
}
private Rectangle GetBoundRectangle()
{
Rectangle rectangle = new Rectangle();
rectangle.SetBinding(Rectangle.StrokeThicknessProperty, new Binding() { Source = this, Path = new PropertyPath("BorderThickness.Left") });
rectangle.SetBinding(Rectangle.RadiusXProperty, new Binding() { Source = this, Path = new PropertyPath("CornerRadius.TopLeft") });
rectangle.SetBinding(Rectangle.RadiusYProperty, new Binding() { Source = this, Path = new PropertyPath("CornerRadius.TopLeft") });
rectangle.SetBinding(Rectangle.WidthProperty, new Binding() { Source = this, Path = new PropertyPath(ActualWidthProperty) });
rectangle.SetBinding(Rectangle.HeightProperty, new Binding() { Source = this, Path = new PropertyPath(ActualHeightProperty) });
return rectangle;
}
private Rectangle GetBackgroundRectangle()
{
Rectangle rectangle = GetBoundRectangle();
rectangle.SetBinding(Rectangle.StrokeProperty, new Binding() { Source = this, Path = new PropertyPath(BackgroundProperty) });
return rectangle;
}
private Rectangle GetDashedRectangle()
{
Rectangle rectangle = GetBoundRectangle();
rectangle.SetBinding(Rectangle.StrokeDashArrayProperty, new Binding() { Source = this, Path = new PropertyPath(StrokeDashArrayProperty) });
rectangle.SetBinding(Rectangle.StrokeProperty, new Binding() { Source = this, Path = new PropertyPath(DashedBorderBrushProperty) });
Panel.SetZIndex(rectangle, 2);
return rectangle;
}
private VisualBrush CreateDashedBorderBrush()
{
VisualBrush dashedBorderBrush = new VisualBrush();
Grid grid = new Grid();
Rectangle backgroundRectangle = GetBackgroundRectangle();
Rectangle dashedRectangle = GetDashedRectangle();
grid.Children.Add(backgroundRectangle);
grid.Children.Add(dashedRectangle);
dashedBorderBrush.Visual = grid;
return dashedBorderBrush;
}
private void UseDashedBorderChanged()
{
if (UseDashedBorder)
{
BorderBrush = CreateDashedBorderBrush();
}
else
{
ClearValue(BorderBrushProperty);
}
}
public bool UseDashedBorder
{
get { return (bool)GetValue(UseDashedBorderProperty); }
set { SetValue(UseDashedBorderProperty, value); }
}
public Brush DashedBorderBrush
{
get { return (Brush)GetValue(DashedBorderBrushProperty); }
set { SetValue(DashedBorderBrushProperty, value); }
}
public DoubleCollection StrokeDashArray
{
get { return (DoubleCollection)GetValue(StrokeDashArrayProperty); }
set { SetValue(StrokeDashArrayProperty, value); }
}
}
Working on a user control....
I have been trying a storyboard for a marching ants border. The basic grid with a rectangle and text works fine since there is no interaction. When trying to put a button inside the grid, then either the rectangle or button is visible but never both of them.
From another post:
Advanced XAML Animation effects. Pulse, Marching ants, Rotations. Alerts
Using dotNet's solution for the VisualBrush shifted the rectangle to the border with a button inside. This worked perfectly.
<UserControl.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type TextBlock}" x:Key="LOC_DG_Cell_Mid" BasedOn="{StaticResource DG_TextBlock_Mid}" >
<Setter Property="Margin" Value="5 0"/>
</Style>
<Storyboard x:Key="MarchingAnts">
<DoubleAnimation BeginTime="00:00:00"
Storyboard.TargetName="AlertBox"
Storyboard.TargetProperty="StrokeThickness"
To="4"
Duration="0:0:0.25" />
<!-- If you want to run counter-clockwise, just swap the 'From' and 'To' values. -->
<DoubleAnimation BeginTime="00:00:00" RepeatBehavior="Forever" Storyboard.TargetName="AlertBox" Storyboard.TargetProperty="StrokeDashOffset"
Duration="0:3:0" From="1000" To="0"/>
</Storyboard>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource MarchingAnts}"/>
</EventTrigger>
</UserControl.Triggers>
<Grid>
<Border BorderThickness="1">
<Border.BorderBrush>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle x:Name="AlertBox" Stroke="Red" StrokeDashOffset="2" StrokeDashArray="5" Margin="5"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"/>
</VisualBrush.Visual>
</VisualBrush>
</Border.BorderBrush>
<Button x:Name="FinishedButton" Padding="0 5" Margin="0" Style="{StaticResource IconButton}" >
<StackPanel Orientation="Horizontal" >
<Label Style="{StaticResource ButtonLabel}" Content="Processing has Finished" />
</StackPanel>
</Button>
</Border>
</Grid>
I need to change the visual state of my listbox item. Here is the DataTemplate which has the visual states. I'm using WP7 as my environment.
<DataTemplate x:Key="MessageItemTemplate">
<Grid MinWidth="200" MinHeight="90" Width="460" Margin="0,2">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Modes">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0" To="Normal">
<Storyboard>
<DoubleAnimation Duration="0:0:0.4" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="border" d:IsOptimized="True"/>
</Storyboard>
</VisualTransition>
<VisualTransition GeneratedDuration="0"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal"/>
<VisualState x:Name="Edit">
<Storyboard>
<DoubleAnimation Duration="0:0:0.7" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="border" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<VisualStateManager.CustomVisualStateManager>
<ic:ExtendedVisualStateManager/>
</VisualStateManager.CustomVisualStateManager>
<StackPanel Orientation="Vertical" d:LayoutOverrides="Width, Height" Canvas.ZIndex="10" Margin="7">
<TextBlock x:Name="tbTitle" Text="{Binding Path=Title, Mode=OneWay}" FontSize="24" VerticalAlignment="Top" Foreground="{StaticResource PhoneContrastBackgroundBrush}" FontWeight="Bold" Height="30" FontFamily="Microsoft New Tai Lue"/>
<TextBlock x:Name="tbMessage" Text="{Binding Path=Message, Mode=OneWay}" FontSize="29.333" Foreground="{StaticResource PhoneContrastBackgroundBrush}" Margin="0" FontFamily="Candara" TextWrapping="Wrap" HorizontalAlignment="Left"/>
</StackPanel>
<Border BorderBrush="{StaticResource PhoneAccentBrush}" BorderThickness="2" Background="{StaticResource PhoneBackgroundBrush}" CornerRadius="10" />
<Border x:Name="border" BorderThickness="4" CornerRadius="4" BorderBrush="#FFED1212" Opacity="0" >
<Grid>
<Path Data="M149,0.16666667 L192,36.166332 L189.60141,-2.7298894 z" Fill="#FFED1212" HorizontalAlignment="Right" Margin="0,-3.031,-2.784,38.328" Stretch="Fill" UseLayoutRounding="False" Width="51.629" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<CompositeTransform Rotation="2.523" TranslateX="-0.076551587038494961" TranslateY="-0.0016857129841283403"/>
</Path.RenderTransform>
</Path>
<Image Margin="0" Source="images/pensil.png" Stretch="Fill" Height="26" Width="26" HorizontalAlignment="Right" VerticalAlignment="Top"/>
</Grid>
</Border>
</Grid>
</DataTemplate>
Heres my ListBox:
<ListBox x:Name="SmsMessagesList" Grid.Row="1"
ItemsSource="{Binding Path=Model.Messages}"
SelectionChanged="SmsMessagesList_SelectionChanged"
ItemTemplate="{StaticResource MessageItemTemplate}">
</ListBox>
The ObservableCollection which I bind to this ListBox's ItemsSource is:
public ObservableCollection<SmsMessage> Messages;
public class SmsMessage : EntityBase
{
private string _CurrentState;
public string CurrentState
{
get
{
return _CurrentState;
}
set
{
_CurrentState = value;
PropertyChangedHandler("CurrentState");
}
}
private string _Title;
public string Title
{
get
{
return _Title;
}
set
{
_Title = value;
PropertyChangedHandler("Title");
}
}
private string _Message;
public string Message
{
get
{
return _Message;
}
set
{
_Message = value;
PropertyChangedHandler("Message");
}
}
}
How can I change the visual state of my ListBox to 'Edit' and 'Normal' based on the property 'CurrentState' changing?
Thanks
If you want to stick to a binding approach, your only real choice is a Blend Behavior. However, since Silverlight 3 (and thus WP7) doesn't support data bound behavior properties, your path is a lot more complicated. Yes, it's a PITA and yes, I hope they'll be announcing SL4 features at MIX next week.
Below is a WPF behavior that does the same thing, to give you an idea of what is required by the behavior but it won't work in Silverlight 3 / WP7 due to the above problem. You'll need to change the State property to be of type Binding and go through the convoluted process of getting access to that binding value. You can see examples of how to do this in TailSpin.PhoneClient.Infrastructure.ButtonCommand of the Patterns & Practices WP7 Dev Guide source or from MVVM Light's EventToCommand.
public class StateManagementBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State", typeof(string),
typeof(StateManagementBehavior),
new UIPropertyMetadata(null, PropertyChangedCallback));
public static readonly DependencyProperty UseTransitionsProperty =
DependencyProperty.Register("UseTransitions", typeof(bool),
typeof(StateManagementBehavior),
new UIPropertyMetadata(true));
public static void PropertyChangedCallback(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var stateManagementBehavior = (StateManagementBehavior)d;
stateManagementBehavior.GoToState();
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += (s, e) => GoToState();
}
private void GoToState()
{
if (AssociatedObject == null || State == null) return;
VisualStateManager.GoToState(AssociatedObject, State, UseTransitions);
}
public string State
{
get { return (string)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
public bool UseTransitions
{
get { return (bool)GetValue(UseTransitionsProperty); }
set { SetValue(UseTransitionsProperty, value); }
}
}
Assuming you get it all working, you'll use the behavior like this:
<DataTemplate x:Key="MessageItemTemplate">
<Grid MinWidth="200" MinHeight="90" Width="460" Margin="0,2">
<i:Interactivity.Behaviors>
<infrastructure:StateManagementBehavior State="{Binding CurrentState}"
UseTransitions="True" />
</i:Interactivity.Behaviors>
<VisualStateManager.VisualStateGroups>
...
</VisualStateManager.VisualStateGroups>
...
</Grid>
</DataTemplate>
If you provide a control to act as the container for your listbox items. You can then add the logic for changing state to the code for that control using VisualStateManage.GoToState(this, "Your State", true);
Just managed to get this happening on the SL4 project I'm working (so not sure if it's going to work on WP7, but the original library was made for SL3 so it should), the solution was to use DataStateBehavior from the Expression Blend Samples on CodePlex inside the DataTemplate:
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding IsEditMode}" Value="True" TrueState="Edit" FalseState="Normal"/>
</i:Interaction.Behaviors>
If you need more than 2 states, you can also use DataStateSwitchBehavior.