How to draw a line on an existing BitmapSource in WPF? - wpf

I would like to draw a line (or any geometric shape) on an existing BitmapSource object in my WPF application. What is the best way to do it ?
The BitmapSource is the result of a BitmapSource.Create(...) call.
Thanks
Romain

Below sample will display an image created from a BitmapSource with a red line on top of it. Is that what you are trying to achieve?
XAML:
<Window x:Class="WpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid Background="LightBlue">
<Image Source="{Binding Path=ImageSource}" />
<Line
Stroke="Red" StrokeThickness="10"
X1="0"
Y1="0"
X2="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}"
Y2="{Binding Path=ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}" />
</Grid>
</Window>
Code behind:
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WpfApplication
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = this;
}
public BitmapSource ImageSource
{
get
{
PixelFormat pf = PixelFormats.Bgr32;
int width = 200;
int height = 200;
int rawStride = (width * pf.BitsPerPixel + 7) / 8;
byte[] rawImage = new byte[rawStride * height];
Random value = new Random();
value.NextBytes(rawImage);
return BitmapSource.Create(width, height, 96, 96, pf, null, rawImage, rawStride);
}
}
}
}

Related

Tiff image with bitmapsource, Slower than JPEG loading

I have the below code in UI
<ListBox x:Name="scannedImageslist" ItemContainerStyle="{StaticResource ListBoxCustom}" Background="Gray" SelectionMode="Multiple" Grid.Column="1" Grid.Row="1" ItemsSource="{Binding }" ScrollViewer.PanningMode="Both" ScrollViewer.HorizontalScrollBarVisibility="Disabled" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<!--<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="true" >
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Background" Value="Orange" />
<Setter Property="Foreground" Value="Black" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>-->
<!--<ListBox.Resources>
<gif:ImageConverter x:Key="UriToBitmapConverter" />
</ListBox.Resources>-->
<!--<ListBox.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black"/>
<GradientStop Color="#FF1E2A2F" Offset="1"/>
</LinearGradientBrush>
</ListBox.Background>-->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" IsItemsHost="True" >
</WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<!--<Border BorderBrush="#FFFF9800" BorderThickness="3" Width="120" Height="120" Padding="10" Margin="15" CornerRadius="10">-->
<!--Bind Image Path in Image Control-->
<!--<CheckBox Grid.Column="0" Grid.Row="0" VerticalAlignment="Top" HorizontalAlignment="Left" ToolTip="{Binding FullPath}" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}"></CheckBox>-->
<Image Grid.Column="1" Grid.Row="0" Stylus.IsPressAndHoldEnabled="True" Stretch="None" HorizontalAlignment="Center" ToolTip="{Binding FullPath}" MouseRightButtonDown="Image_MouseRightButtonDown" >
<Image.Source>
<BitmapImage UriSource="{Binding FullPath}" DecodePixelWidth="200" CreateOptions="IgnoreImageCache" RenderOptions.BitmapScalingMode="HighQuality" VirtualizingPanel.VirtualizationMode="Recycling" CacheOption="OnLoad"> </BitmapImage>
</Image.Source>
<Image.LayoutTransform>
<TransformGroup>
<ScaleTransform CenterX="0" CenterY="0" ScaleX="{Binding ElementName=sliderzoom,Path=Value}" ScaleY="{Binding ElementName=sliderzoom,Path=Value}" />
</TransformGroup>
</Image.LayoutTransform>
</Image>
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding ImageName}" TextAlignment="Center" VerticalAlignment="Bottom" Typography.SlashedZero="True" Typography.NumeralStyle="OldStyle" TextWrapping="Wrap" HorizontalAlignment="Center"></TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Binding class and properties are
public class ImageItem : INotifyPropertyChanged
{
#region Properties
public string FullPath { get; set; }
public string ImageName { get; set; }
public ImageItem(string fullPath)
{
FullPath = fullPath;
//SetImage(fullPath);
//Image = new BitmapImage(new Uri(#fullPath));
ImageName = System.IO.Path.GetFileNameWithoutExtension(fullPath);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Private Helpers
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
With the above code, the Jpegs are loading 10 times faster than Tiff images. Whats wrong with Tiff handling here and what would be correct approach in terms of performance?
Update:
I have used a converter and it is pretty fast just like Jpegs its loading Tiff, however the problem is not decode, the memory is eaten up.
public class UriConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (Path.GetExtension(value.ToString()).ToUpper() == ".TIF")
{
Stream tiff = new System.IO.FileStream(value.ToString(), FileMode.Open, FileAccess.Read, FileShare.Read);
TiffBitmapDecoder tiffdec = new TiffBitmapDecoder(tiff, BitmapCreateOptions.DelayCreation, BitmapCacheOption.Default);
return tiffdec.Frames[0];
//return tiffdec.Frames[0].Thumbnail;
//GdPictureImaging obj = new GdPictureImaging();
//int m_CurrentImage = obj.CreateGdPictureImageFromFile(value.ToString());
//int thumnail= obj.CreateThumbnail(m_CurrentImage, 96, 96);
//TiffBitmapEncoder encoder = new TiffBitmapEncoder();
//MemoryStream memoryStream = new MemoryStream();
//BitmapImage bImg = new BitmapImage();
//encoder.Frames.Add(BitmapFrame.Create(tiffibitmapsource));
//encoder.Save(memoryStream);
//bImg.BeginInit();
//bImg.DecodePixelWidth = 100;
//bImg.CacheOption = BitmapCacheOption.OnLoad;
//bImg.StreamSource = new MemoryStream(memoryStream.ToArray());
//bImg.EndInit();
//memoryStream.Close();
//return bImg;
}
else
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 100;
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.UriSource = new Uri(value.ToString());
bi.EndInit();
return bi;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
How to convert Bitmapsource to BitmapImage and then decode pixels!!!
TIFF doesnt have thumbnails, is there a way to create and store along, then display only thumbnail??
Update 2
As per MSDN
Is there a way TIF s to work as same as JPEG without conversion just for loading...
The JPEG and Portable Network Graphics (PNG) codecs natively decode
the image to the specified size; other codecs decode the image at its
original size and scale the image to the desired size.

Simple databinding between slider and object to update a drawing in WPF

I have a specific need to use System.Drawing.Graphics to use as as ImageSource for an Image control. That graphics is to be updated by a slider, whose Value is to be data-bound to an object that acts as a model to build the drawing.
I have set up a minimal working version of what I want to accomplish, and have created as much "Binding Infrastructure" as I could, but have stopped where things started to get confusing for me. My code has just the MainWindow (XAML and code behind) and a Radius class:
MainWindow:
<Window x:Class="MinimalUpdateableDrawing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
WindowState="Maximized">
<DockPanel>
<Slider x:Name="SizeSlider" DockPanel.Dock="Bottom" />
<Image x:Name="figure" Width="800" Height="600" />
</DockPanel>
</Window>
MainWindow codebehind:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows;
using System.Windows.Media.Imaging;
namespace MinimalUpdateableDrawing {
public partial class MainWindow : Window {
Radius radius_instance;
public MainWindow() {
InitializeComponent();
radius_instance = new Radius();
this.Loaded +=new RoutedEventHandler(DrawCircle);
}
void DrawCircle(object sender, RoutedEventArgs e) {
int radius = radius_instance.Value;
using (var bmp = new Bitmap((int)figure.Width, (int)figure.Height)) {
using (var g = Graphics.FromImage(bmp)) {
g.FillEllipse(System.Drawing.Brushes.Blue,
(int)figure.Width/2-radius,
(int)figure.Height/2-radius,
radius*2, radius*2);
}
using(MemoryStream ms = new MemoryStream()) {
bmp.Save(ms, ImageFormat.Bmp);
ms.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = ms;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
figure.Source = bitmapImage;
}
}
}
}
}
Radius class:
using System;
using System.ComponentModel;
namespace MinimalUpdateableDrawing
{
class Radius : INotifyPropertyChanged {
int _value = 100;
public int Value {
get { return _value; }
set {
_value = value;
OnPropertyChanged("Value");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string p) {
throw new NotImplementedException();
}
}
}
If anyone could suggest what I should do to implement the binding between SizeSlider and radius_instance.Value, so that when I move the slider the image updates, that would get me going!
If you were simply drawing an ellipse, I'd recommend binding both the slider's value and an Ellipse object to the object that stores the value in question. You can see more about drawing with Wpf at http://msdn.microsoft.com/en-us/library/ms747393.aspx.
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Ellipse Height="{Binding Radius}" Width="{Binding Radius}" HorizontalAlignment="Center" VerticalAlignment="Center" Fill="Black" />
<Slider Value="{Binding Radius}" Grid.Row="1" Minimum="0" Maximum="200" />
</Grid>
With that said, if the drawing was more complex, you may want to consider using an IValueConverter to adapt the value into a drawing you could use...for example:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:EllipseConverter x:Key="EllipseConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="{Binding Radius, Converter={StaticResource EllipseConverter}}" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None" />
<Slider Value="{Binding Radius}" Grid.Row="1" Minimum="0" Maximum="200" />
</Grid>
</Window>
IValueConverter:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Data;
using System.Windows.Media.Imaging;
namespace WpfApplication1
{
class EllipseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) return null;
int radius = (int)value;
int diameter = radius * 2;
using (var bmp = new System.Drawing.Bitmap(diameter, diameter))
{
using (var g = Graphics.FromImage(bmp))
{
g.FillEllipse(System.Drawing.Brushes.Blue,
0,
0,
diameter,
diameter);
}
using (MemoryStream ms = new MemoryStream())
{
bmp.Save(ms, ImageFormat.Bmp);
ms.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = ms;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
return bitmapImage;
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

BarChart Values not updating

I am trying to develop a application where-in I a want to generate a random number after every three seconds, insert that number into a listBox and using DataTemplate display the ListBox as a rectangle.
This is for reference.
Now the problem is that I have used a DispatcherTimer which 'ticks' after 3 seconds but the rectangle is not updated.
I am posting my XAML and .cs code. Any hints ?
namespace ListBarGraph
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
DispatcherTimer dt = new DispatcherTimer();
DataFactory df = new DataFactory();
public MainWindow()
{
InitializeComponent();
dt.Tick += new EventHandler(dt_Tick);
dt.Interval = new TimeSpan(0, 0, 3);
dt.Start();
this.PreviewKeyDown += new KeyEventHandler(MainWindow_PreviewKeyDown);
}
void dt_Tick(object sender, EventArgs e)
{
df.GetData();
}
}
public class DataFactory
{
int number = 0;
public IEnumerable<int> GetData()
{
Random random = new Random();
number = random.Next(0, 100);
return new int[] { 0, number };
}
}
}
<Window x:Class="ListBarGraph.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ListBarGraph"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="someData" ObjectType="{x:Type local:DataFactory}" MethodName="GetData" />
<DataTemplate x:Key="BarChartItemsTemplate">
<Border Width="300" Height="50">
<Grid>
<Rectangle Fill="Red" StrokeThickness="2" Height="40" Width="{Binding}" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Rectangle.LayoutTransform>
<ScaleTransform ScaleX="1.5"/>
</Rectangle.LayoutTransform>
</Rectangle>
</Grid>
</Border>
</DataTemplate>
<ItemsPanelTemplate x:Key="BarChartItemsPanel">
<VirtualizingStackPanel IsItemsHost="True">
<VirtualizingStackPanel.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="90"/>
<ScaleTransform ScaleX="-1" ScaleY="1"/>
</TransformGroup>
</VirtualizingStackPanel.LayoutTransform>
</VirtualizingStackPanel>
</ItemsPanelTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource someData}}" ItemTemplate="{DynamicResource BarChartItemsTemplate}" ItemsPanel="{DynamicResource BarChartItemsPanel}"/>
</Grid>
</Window>
Your XAML is bound to one instance of DataFactory as created by the ObjectProvider, whilst your code-behind creates another instance altogether, to which the UI is not bound.
Try this to get you started. In your XAML, remove the ObjectProvider and change your ListBox to:
<ListBox ItemsSource="{Binding}" ...
Inside dt_Tick, do this:
this.DataContext = df.GetData();

How to make PanelDragDropTarget act only as a target and not as a source?

I created a new solution. I added a link to System.Windows.Controls.Toolkit to my Silverlight project and wrote this code:
XAML:
<UserControl x:Class="SilverlightApplication4.MainPage"
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"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid>
<toolkit:PanelDragDropTarget Margin="0,0,150,150" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" AllowedSourceEffects="Copy">
<Grid Name="grid1" Background="Blue">
<Rectangle Height="40" HorizontalAlignment="Left" Margin="5,5,0,0" Name="rectangle1" Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Width="80" Fill="Red" />
</Grid>
</toolkit:PanelDragDropTarget>
<toolkit:PanelDragDropTarget VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" Margin="150,150,0,0" AllowDrop="True" Drop="PanelDragDropTarget_Drop" AllowedSourceEffects="None">
<Grid Name="grid2" Background="Green" />
</toolkit:PanelDragDropTarget>
</Grid>
</Grid>
</UserControl>
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SilverlightApplication4
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void PanelDragDropTarget_Drop(object sender, Microsoft.Windows.DragEventArgs e)
{
Rectangle myRectangle = new Rectangle() { Margin = new Thickness(5,5,0,0), Height = 40, Width = 80,
HorizontalAlignment = System.Windows.HorizontalAlignment.Left, VerticalAlignment = System.Windows.VerticalAlignment.Top,
StrokeThickness = 1, Stroke = new SolidColorBrush(Colors.Black), Fill = new SolidColorBrush(Colors.Red)};
grid2.Children.Add(myRectangle);
}
}
}
Now when I drag and drop the small red rectangle from grid1 onto grid2 everything works fine. But when I touch the new added rectangle in grid2 it shows visible signs that it can be dragged. My question is how to make a second PanelDragDropTarget (with grid2 inside) to act only as a target for drag and drop and not as a source? I mean how to block the possibility for a user to drag the new created rectangle in grid2, i.e. to exclude any visible signs that this new rectangle is draggable? Because it's not supposed to be draggable in my case.
I found a solution. For the PanelDragDropTarget adorner of the grid2 I defined an event handler for its ItemDragStarting event.
private void PanelDragDropTarget_ItemDragStarting(object sender, ItemDragEventArgs e)
{
e.Cancel = true;
e.Handled = true;
}
Now when I try to drag elements in grid2 nothing happens (that was my purpose).
Have you tried AllowedSourceEffects="None", works for me in SL5...

How can I achieve a dashed or dotted border in WPF?

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>

Resources