Is there any easy way to create a dashed ellipse made up of individual horizontal dashes, where the dash sizes are consistent, and their amount can be specified?
Something like this:
I want to be able to control each dash individually, like changing its color or binding it to an action in my viewmodel.
The only way I can think of to achieve this, is to create a custom control that contains a Path element for each dash, together making up an ellipse shape, having to calculate the Path data based on the amount of dashes and size of the ellipse.
I came back to this problem now, and managed to solve it in a very flexible and generic way. The requirments have changed a bit since then, no need for binding, but it can be added easily.
Note that this is a circle, which is what I wanted. The question should really say circle rather than ellipse, even though a circle is an ellipse, but I digress...
Here's the UserControl I came up with:
StatusRing.xaml.cs
public partial class StatusRing
{
#region Dependency Property registrations
public static readonly DependencyProperty DashesProperty = DependencyProperty.Register("Dashes",
typeof(int), typeof(StatusRing), new PropertyMetadata(32, DashesChanged));
public static readonly DependencyProperty DiameterProperty = DependencyProperty.Register("Diameter",
typeof(double), typeof(StatusRing), new PropertyMetadata(150.00, DiameterChanged));
public static readonly DependencyProperty DashHeightProperty = DependencyProperty.Register("DashHeight",
typeof(double), typeof(StatusRing), new PropertyMetadata(20.00, DashHeightChanged));
public static readonly DependencyProperty DashWidthProperty = DependencyProperty.Register("DashWidth",
typeof(double), typeof(StatusRing), new PropertyMetadata(5.00, DashWidthChanged));
public static readonly DependencyProperty DashFillProperty = DependencyProperty.Register("DashFill",
typeof(SolidColorBrush), typeof(StatusRing), new PropertyMetadata(Brushes.DimGray, DashFillChanged));
public static readonly DependencyProperty DashAccentFillProperty = DependencyProperty.Register("DashAccentFill",
typeof(SolidColorBrush), typeof(StatusRing), new PropertyMetadata(Brushes.White, DashAnimationFillChanged));
public static readonly DependencyProperty TailSizeProperty = DependencyProperty.Register("TailSize",
typeof(int), typeof(StatusRing), new PropertyMetadata(10, TailSizeChanged));
public static readonly DependencyProperty AnimationSpeedProperty = DependencyProperty.Register("AnimationSpeed",
typeof(double), typeof(StatusRing), new PropertyMetadata(50.00, AnimationSpeedChanged));
public static readonly DependencyProperty IsPlayingProperty = DependencyProperty.Register("IsPlaying",
typeof(bool), typeof(StatusRing), new PropertyMetadata(false, IsPlayingChanged));
#endregion Dependency Property registrations
private readonly Storyboard glowAnimationStoryBoard = new Storyboard();
public StatusRing()
{
Loaded += OnLoaded;
InitializeComponent();
}
#region Dependency Properties
public int Dashes
{
get => (int)GetValue(DashesProperty);
set => SetValue(DashesProperty, value);
}
public double Diameter
{
get => (double)GetValue(DiameterProperty);
set => SetValue(DiameterProperty, value);
}
public double Radius => Diameter / 2;
public double DashHeight
{
get => (double)GetValue(DashHeightProperty);
set => SetValue(DashHeightProperty, value);
}
public double DashWidth
{
get => (double)GetValue(DashWidthProperty);
set => SetValue(DashWidthProperty, value);
}
public Brush DashFill
{
get => (SolidColorBrush)GetValue(DashFillProperty);
set => SetValue(DashFillProperty, value);
}
public Brush DashAccentFill
{
get => (SolidColorBrush)GetValue(DashAccentFillProperty);
set => SetValue(DashAccentFillProperty, value);
}
public int TailSize
{
get => (int)GetValue(TailSizeProperty);
set => SetValue(TailSizeProperty, value);
}
public double AnimationSpeed
{
get => (double)GetValue(AnimationSpeedProperty);
set => SetValue(AnimationSpeedProperty, value);
}
public bool IsPlaying
{
get => (bool)GetValue(IsPlayingProperty);
set => SetValue(IsPlayingProperty, value);
}
#endregion Dependency Properties
private void OnLoaded(object sender, RoutedEventArgs e)
{
var thisControl = sender as StatusRing;
Recreate(thisControl);
}
#region Dependency Property callbacks
private static void DashesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void DiameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void DashHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void DashWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void DashFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void DashAnimationFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void TailSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void AnimationSpeedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
if (thisControl.IsLoaded)
{
thisControl.glowAnimationStoryBoard.Stop();
thisControl.glowAnimationStoryBoard.Children.Clear();
ApplyAnimations(thisControl);
thisControl.glowAnimationStoryBoard.Begin();
}
}
private static void IsPlayingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
if (thisControl.IsLoaded)
{
var isPlaying = (bool)e.NewValue;
if (isPlaying)
{
thisControl.glowAnimationStoryBoard.Begin();
}
else
{
thisControl.glowAnimationStoryBoard.Stop();
}
}
}
#endregion Dependency Property callbacks
private static void Recreate(StatusRing thisControl)
{
if (thisControl.IsLoaded)
{
thisControl.glowAnimationStoryBoard.Stop();
thisControl.glowAnimationStoryBoard.Children.Clear();
thisControl.RootCanvas.Children.Clear();
Validate(thisControl);
BuildRing(thisControl);
ApplyAnimations(thisControl);
if (thisControl.IsPlaying)
{
thisControl.glowAnimationStoryBoard.Begin();
}
else
{
thisControl.glowAnimationStoryBoard.Stop();
}
}
}
private static void Validate(StatusRing thisControl)
{
if (thisControl.TailSize > thisControl.Dashes)
{
throw new Exception("TailSize cannot be larger than amount of dashes");
}
}
private static void BuildRing(StatusRing thisControl)
{
var angleStep = (double)360 / thisControl.Dashes;
for (double i = 0; i < 360; i = i + angleStep)
{
var rect = new Rectangle
{
Fill = thisControl.DashFill,
Height = thisControl.DashHeight,
Width = thisControl.DashWidth
};
//Rotate dash to follow circles circumference
var centerY = thisControl.Radius;
var centerX = thisControl.DashWidth / 2;
var rotateTransform = new RotateTransform(i, centerX, centerY);
rect.RenderTransform = rotateTransform;
var offset = thisControl.Radius - thisControl.DashWidth / 2;
rect.SetValue(Canvas.LeftProperty, offset);
thisControl.RootCanvas.Children.Add(rect);
}
thisControl.RootCanvas.Width = thisControl.Diameter;
thisControl.RootCanvas.Height = thisControl.Diameter;
}
private static void ApplyAnimations(StatusRing thisControl)
{
var baseColor = ((SolidColorBrush)thisControl.DashFill).Color;
var animatedColor = ((SolidColorBrush)thisControl.DashAccentFill).Color;
var dashes = thisControl.RootCanvas.Children.OfType<Rectangle>().ToList();
double animationPeriod = thisControl.AnimationSpeed;
double glowDuration = animationPeriod * thisControl.TailSize;
for (int i = 0; i < dashes.Count; i++)
{
var beginTime = TimeSpan.FromMilliseconds(animationPeriod * i);
var colorAnimation = new ColorAnimationUsingKeyFrames
{
BeginTime = beginTime,
RepeatBehavior = RepeatBehavior.Forever
};
var toFillColor = new LinearColorKeyFrame(animatedColor, TimeSpan.Zero);
colorAnimation.KeyFrames.Add(toFillColor);
var dimToBase = new LinearColorKeyFrame(baseColor, TimeSpan.FromMilliseconds(glowDuration));
colorAnimation.KeyFrames.Add(dimToBase);
var restingTime = animationPeriod * dashes.Count;
var delay = new LinearColorKeyFrame(baseColor, TimeSpan.FromMilliseconds(restingTime));
colorAnimation.KeyFrames.Add(delay);
Storyboard.SetTarget(colorAnimation, dashes[i]);
Storyboard.SetTargetProperty(colorAnimation, new PropertyPath("(Fill).(SolidColorBrush.Color)"));
thisControl.glowAnimationStoryBoard.Children.Add(colorAnimation);
}
}
}
StatusRing.xaml:
<UserControl x:Class="WpfPlayground.StatusRing"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Canvas x:Name="RootCanvas" />
Usage:
<local:StatusRing Diameter="250"
Dashes="32"
TailSize="16"
IsPlaying="True" />
Result:
The number of dashes, length and speed of animation, etc... are all configurable. The naming of the dependency properties could be better though...
Enjoy :-)
Related
I'm trying to set width of UserControl at initialization time in ViewModel, but it receives only zero. When I resize the window, it receives correct width.
MainWindow.xaml extract:
<local:MapControl x:Name="MapControl"
DataContext="{Binding MapViewModel}"
ActualControlWidth="{Binding ActualControlWidth, Mode=OneWayToSource}" />
MapControl.xaml.cs:
public partial class MapControl : UserControl
{
public MapControl()
{
InitializeComponent();
SizeChanged += OnControlSizeChanged;
}
public static readonly DependencyProperty ActualControlWidthProperty = DependencyProperty.Register(
"ActualControlWidth",
typeof(double),
typeof(MapControl),
new FrameworkPropertyMetadata(PropertyChangedCallback)); //here it does not have any influence if i put default(double) or not
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var newVar = e.NewValue;
}
public double ActualControlWidth
{
get { return (double)GetValue(ActualControlWidthProperty); }
set { SetValue(ActualControlWidthProperty, value); } //here comes the value as 785
}
private void OnControlSizeChanged(object sender, SizeChangedEventArgs e)
{
ActualControlWidth = ActualWidth;
}
}
MapViewModel.cs:
public class MapViewModel : ViewModelBase
{
private double _actualControlWidth;
private Map _map;
public MapViewModel()
{
...
}
public Map Map
{
get => _map;
set => Set(() => Map, ref _map, value);
}
public double ActualControlWidth
{
get => _actualControlWidth;
set => _actualControlWidth = value; //this is where the value comes as 0
}
}
Thanks for your help!
The full reproduction example is at my Github https://github.com/czechdude/dependencypropertyissue
You need to update this line:
new FrameworkPropertyMetadata(PropertyChangedCallback)); //here it does not have any influence if i put default(double) or not
To
new FrameworkPropertyMetadata(defaultValue: -1.0D,
new PropertyChangedCallback(MethodToCall));
...
private static void MethodToCall(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// call second method to make instance of MapControl and call a custom method that sets your ActualControlWidth
}
I have a class DependencyReportViewer that is correspond for creation a ReportViewer control in my program. This class works perfectly. In XAML we define only this line for each report that we wants:
<ctr:DependencyReportViewer EmbeddedReportName="Program.View.Reports.ReportName.rdlc" ReportData="{Binding ReportData}"/>
First part of the class:
<WindowsFormsHost x:Class="Program.View.Controls.DependencyReportViewer"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
IsVisibleChanged="WindowsFormsHost_IsVisibleChanged">
</WindowsFormsHost>
And the worst part of the class:
public partial class DependencyReportViewer : WindowsFormsHost
{
private static ReportViewer ReportViewer = new ReportViewer();
private static int ReportIsRunning = 0;
static DependencyReportViewer()
{
DependencyReportViewer.ReportViewer.SetDisplayMode(DisplayMode.PrintLayout);
try
{
RenderingExtensions = ReportViewer.LocalReport.ListRenderingExtensions();
}
catch (Exception)
{
RenderingExtensions = new RenderingExtension[0];
}
ReportViewer.ReportError += ReportViewer_ReportError;
}
public static RenderingExtension[] RenderingExtensions { get; private set; }
public static readonly RoutedEvent ReportDoneEvent = EventManager.RegisterRoutedEvent("ReportDone", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(DependencyReportViewer));
public event RoutedEventHandler ReportDone
{
add { AddHandler(ReportDoneEvent, value); }
remove { RemoveHandler(ReportDoneEvent, value); }
}
public string EmbeddedReportName
{
get { return (string)GetValue(EmbeddedReportNameProperty); }
set { SetValue(EmbeddedReportNameProperty, value); }
}
public static readonly DependencyProperty EmbeddedReportNameProperty = DependencyProperty.Register("EmbeddedReportName",
typeof(string), typeof(DependencyReportViewer));
public Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>> ReportData
{
get { return (Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>>)GetValue(ReportDataProperty); }
set { SetValue(ReportDataProperty, value); }
}
public static readonly DependencyProperty ReportDataProperty = DependencyProperty.Register("ReportData",
typeof(Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>>), typeof(DependencyReportViewer),
new FrameworkPropertyMetadata(null, ReportData_Changed));
private static void ReportData_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sts = Interlocked.CompareExchange(ref ReportIsRunning, 1, 0);
if (sts != 0)
return;
var rv = (DependencyReportViewer)d;
var data = (Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>>)e.NewValue;
RenderingCompleteEventHandler evt = null;
evt = (sender, rea) =>
{
ReportViewer.RenderingComplete -= evt;
sts = Interlocked.Exchange(ref ReportIsRunning, 0);
if (sts == 1)
rv.RaiseEvent(new RoutedEventArgs(ReportDoneEvent));
};
ReportViewer.RenderingComplete += evt;
DependencyReportViewer.ReportViewer.Reset();
DependencyReportViewer.ReportViewer.LocalReport.ReportEmbeddedResource = null;
if (data != null)
{
DependencyReportViewer.ReportViewer.LocalReport.SetBasePermissionsForSandboxAppDomain(new System.Security.PermissionSet(System.Security.Permissions.PermissionState.Unrestricted));
DependencyReportViewer.ReportViewer.LocalReport.ReportEmbeddedResource = rv.EmbeddedReportName;
var l = new List<ReportParameter>();
foreach (var item in data.Item2)
{
l.Add(new ReportParameter(item.Item1, item.Item2));
}
DependencyReportViewer.ReportViewer.LocalReport.SetParameters(l);
foreach (var item in data.Item1)
{
var rds = new ReportDataSource(item.Item1);
rds.Value = item.Item2;
DependencyReportViewer.ReportViewer.LocalReport.DataSources.Add(rds);
}
}
DependencyReportViewer.ReportViewer.RefreshReport();
}
private static void ReportViewer_ReportError(object sender, ReportErrorEventArgs e)
{
Interlocked.Exchange(ref ReportIsRunning, 0);
}
public DependencyReportViewer()
{
InitializeComponent();
}
private void WindowsFormsHost_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
this.Child = DependencyReportViewer.ReportViewer;
}
else
{
DependencyReportViewer.ReportViewer.Reset();
DependencyReportViewer.ReportViewer.LocalReport.ReportEmbeddedResource = null;
DependencyReportViewer.ReportViewer.LocalReport.DataSources.Clear();
DependencyReportViewer.ReportViewer.RefreshReport();
this.Child = null;
}
}
}
The problem is that this code works normally only in situations, when we have only one UserControl with this ReportViewer opened. If we open two or more controls, then the last open control will take over ReportViewer. In other words it is possible to open only one ReportViewer at the same time.
Now I want to have a possibility to open multiple controls with multiple report viewers. As I get the problem is that this class is static. And when we define a new control, then in "this.Child = DependencyReportViewer.ReportViewer;" line we just reset this viewer to the newest control.
But the man who wrote this had a skill much greater then I have now. I failed multiple times to set this class nonstatic with XamlParseException. How can I convert this class to nonstatic?
I have this UserControl:
[ContentProperty("Items")]
[DefaultProperty("Items")]
public partial class PanelControl : UserControl
{
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(PanelControl), new FrameworkPropertyMetadata(Orientation.Horizontal, new PropertyChangedCallback(OnOrientationChanged)));
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<UIElement>), typeof(PanelControl), new FrameworkPropertyMetadata(new ObservableCollection<UIElement>(), new PropertyChangedCallback(OnItemsChanged)));
public static readonly DependencyProperty SizeProperty = DependencyProperty.RegisterAttached("Size", typeof(double), typeof(PanelControl), new FrameworkPropertyMetadata(1.0, new PropertyChangedCallback(OnSizeChanged)), new ValidateValueCallback(IsSizeValid));
public Orientation Orientation
{
get
{
return (Orientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
public ObservableCollection<UIElement> Items
{
get
{
return (ObservableCollection<UIElement>)GetValue(ItemsProperty);
}
set
{
SetValue(ItemsProperty, value);
}
}
public static void SetSize(UIElement element, double size)
{
element.SetValue(SizeProperty, size);
}
public static double GetSize(UIElement element)
{
return (double)element.GetValue(SizeProperty);
}
private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
/*MessageBox.Show("orientation");*/
((PanelControl)dependencyObject).ClearAndBuildGrid();
}
private static void OnItemsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
/*MessageBox.Show("items");*/
((PanelControl)dependencyObject).ClearAndBuildGrid();
if(args.OldValue != null)
((ObservableCollection<UIElement>)args.OldValue).CollectionChanged -= ((PanelControl)dependencyObject).OnItemsCollectionChanged;
if (args.NewValue != null)
((ObservableCollection<UIElement>)args.NewValue).CollectionChanged += ((PanelControl)dependencyObject).OnItemsCollectionChanged;
}
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
/*MessageBox.Show("collection");*/
ClearAndBuildGrid();
}
private static void OnSizeChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
((PanelControl)dependencyObject).ClearAndBuildGrid();
/*MessageBox.Show("size");*/
}
private static bool IsSizeValid(object value)
{
return (double)value < 0 ? false : true;
}
private void ClearAndBuildGrid()
{
MainGrid.Children.Clear();
MainGrid.RowDefinitions.Clear();
MainGrid.ColumnDefinitions.Clear();
/*MessageBox.Show(MainGrid.Children.Count.ToString());*/
for (int i = 0; i < Items.Count; i++)
{
if (Orientation == Orientation.Horizontal)
{
MainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = SizeToGridLength(GetSize(Items[i])) });
Grid.SetColumn(Items[i], i * 2);
if (i != Items.Count - 1)
{
MainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(5) });
GridSplitter splitter = new GridSplitter() { ResizeDirection = GridResizeDirection.Columns, HorizontalAlignment = HorizontalAlignment.Stretch };
Grid.SetColumn(splitter, i * 2 + 1);
MainGrid.Children.Add(splitter);
}
}
else
{
MainGrid.RowDefinitions.Add(new RowDefinition() { Height = SizeToGridLength(GetSize(Items[i])) });
Grid.SetRow(Items[i], i * 2);
if (i != Items.Count - 1)
{
MainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(5) });
GridSplitter splitter = new GridSplitter() { ResizeDirection = GridResizeDirection.Rows, VerticalAlignment = VerticalAlignment.Stretch };
Grid.SetRow(splitter, i * 2 + 1);
MainGrid.Children.Add(splitter);
}
}
MainGrid.Children.Add(Items[i]);
}
}
private GridLength SizeToGridLength(double size)
{
return new GridLength(size, GridUnitType.Star);
}
public PanelControl()
{
InitializeComponent();
Items.CollectionChanged += OnItemsCollectionChanged;
}
}
And I use it here:
<p:PanelControl>
<Button />
<Button />
<Button />
</p:PanelControl>
When i start application it works good, but in designer I have underlined first button in xaml and error "Specified element is already the logical child of another element. Disconnect it first." Thanks for help, sorry for my bad English.
Not sure what is going on with the designer but this will 'fix' it.
Change the line:
MainGrid.Children.Add(Items[i]);
To:
var parent = VisualTreeHelper.GetParent(Items[i]) as Grid;
if (parent != null)
parent.Children.Remove(Items[i]);
MainGrid.Children.Add(Items[i]);
Using the code from J.H., I would move it up to the top of your function, so that the state of your grid when you clear it, is "cleared", and all the children have been disconnected from the Visual Tree.
private void ClearAndBuildGrid()
{
foreach (var item in Items)
{
var parent = System.Windows.Media.VisualTreeHelper.GetParent(item) as Grid;
if (parent != null)
parent.Children.Remove(item);
}
MainGrid.Children.Clear();
It would be a style/intention/preference thing, and to be clear, the answer J.H. gives is totally valid.
Consider using foreach instead of for and not having to deal with array subscripts.
I declare two Dependency properties:
first, FilterColor of type Color
and second FilterBrush of type Brush.
I need to update value of FilterColor when FilterBrush.Color property has changed, and I need to update value of FilterBrush.Color when FilterColor property has changed.
How I can realize it?
Bind your two properties with TwoWay binding and if you change one in the UI change the other in the properties setter and Vice Versa, and use INotifyPropertyChanged to Notify your UI that the property changed.
You can either do it in the DependencyProperty definition, or you can use a DependencyPropertyDescriptor to do it afterwards
For example....
DependencyProperty Definition:
public static readonly DependencyProperty FilterColorProperty =
DependencyProperty.Register("FilterColor", typeof(Color),
typeof(MyUserControl),
new PropertyMetadata(Colors.White, FilterColorChanged));
public static void FilterColorChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is MyUserControl))
return;
MyUserControl ctrl = (MyUserControl)obj;
var brush = ctrl.GetBrushProperty();
if (brush.Color != (Color)e.NewValue)
brush.Color = (Color)e.NewValue;
}
DependencyProperty Descriptor:
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(
MyUserControl.FilterColorProperty, typeof(MyUserControl));
if (dpd != null)
dpd.AddValueChanged(this, delegate { FilterColor_Changed(); });
...
private void FilterColor_Changed()
{
Color filterColor = GetFilterColor(this);
var brush = GetBrush(this);
if (filterColor != brush.Color)
brush.Color = filterColor;
}
I may have a few syntax errors... I don't have a compiler to check the code
Where did you define these properties: in view model or in control?
If it's in view model you should use INotifyPropertyChanged instead of dependency properties like this:
Color _filterColor;
public Color FilterColor
{
get
{
return _filterColor;
}
{
if (_filterColor != value)
{
_filterColor = value;
RaisePropertyChanged(() => FilterColor);
_OnFilterColorChanged();
}
}
void _OnFilterColorChanged()
{
_filterBrush= ...
RaisePropertyChanged(() => FilterBrush);
}
Brush _filterBrush;
public Brush FilterBrush
{
get
{
return _filterBrush;
}
{
if (_filterBrush != value)
{
_filterBrush = value;
RaisePropertyChanged(() => FilterBrush);
_OnFilterBrushChanged();
}
}
void _OnFilterBrushChanged()
{
_filterColor= ...
RaisePropertyChanged(() =. FilterColor);
}
If it is in control do this:
public Color FilterColor
{
get { return (Color)GetValue(FilterColorProperty); }
set { SetValue(FilterColorProperty, value); }
}
public static readonly DependencyProperty FilterColorProperty =
DependencyProperty.Register("FilterColor", typeof(Color), typeof(MainWindow), new UIPropertyMetadata(Colors.Transparent, new PropertyChangedCallback(_OnFilterColorPropertyChanged)));
static void _OnFilterColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var mw = d as MainWindow;
Color oldValue = (Color)e.OldValue;
Color newValue = (Color)e.NewValue;
if (null != mw && oldValue != newValue)
{
mw._OnFilterColorChanged(oldValue, newValue);
}
}
bool _isFilterColorUpdating = false;
void _OnFilterColorChanged(Color oldValue, Color newValue)
{
if (_isFilterBrushUpdating )
return;
_isFilterColorUpdating = true;
Brush = ...
_isFilterColorUpdating = false;
}
public Brush FilterBrush
{
get { return (Brush)GetValue(FilterBrushProperty); }
set { SetValue(FilterBrushProperty, value); }
}
public static readonly DependencyProperty FilterBrushProperty =
DependencyProperty.Register("FilterBrush", typeof(Brush), typeof(MainWindow), new UIPropertyMetadata(Brushs.Transparent, new PropertyChangedCallback(_OnFilterBrushPropertyChanged)));
static void _OnFilterBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var mw = d as MainWindow;
Brush oldValue = (Brush)e.OldValue;
Brush newValue = (Brush)e.NewValue;
if (null != mw && oldValue != newValue)
{
mw._OnFilterBrushChanged(oldValue, newValue);
}
}
bool _isFilterBrushUpdating = false;
void _OnFilterBrushChanged(Brush oldValue, Brush newValue)
{
if (_isFilterColorUpdating )
return;
_isFilterBrushUpdating = true;
Color = ...
_isFilterBrushUpdating = false;
}
Note that last way is just hack and it is really bad way, I would prefer the first way.
I was looking for a way to animate the scrolling of a ScrollViewer and I found a sample, but when I try to add the class to the XAML file I get an error:
Error 2
The type 'AniScrollViewer'
was not found. Verify that you are not
missing an assembly reference and that
all referenced assemblies have been
built.
this is the code I found in a forum and I added the class to my cs file:
public class AniScrollViewer:ScrollViewer
{
public static DependencyProperty CurrentVerticalOffsetProperty = DependencyProperty.Register("CurrentVerticalOffset", typeof(double), typeof(AniScrollViewer), new PropertyMetadata(new PropertyChangedCallback(OnVerticalChanged)));
public static DependencyProperty CurrentHorizontalOffsetProperty = DependencyProperty.Register("CurrentHorizontalOffsetOffset", typeof(double), typeof(AniScrollViewer), new PropertyMetadata(new PropertyChangedCallback(OnHorizontalChanged)));
private static void OnVerticalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
AniScrollViewer viewer = d as AniScrollViewer;
viewer.ScrollToVerticalOffset((double)e.NewValue);
}
private static void OnHorizontalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
AniScrollViewer viewer = d as AniScrollViewer;
viewer.ScrollToHorizontalOffset((double)e.NewValue);
}
public double CurrentHorizontalOffset
{
get { return (double)this.GetValue(CurrentHorizontalOffsetProperty); }
set { this.SetValue(CurrentHorizontalOffsetProperty, value); }
}
public double CurrentVerticalOffset
{
get { return (double)this.GetValue(CurrentVerticalOffsetProperty); }
set { this.SetValue(CurrentVerticalOffsetProperty, value); }
}
}
Here is an example of the animation code :
private void ScrollToPosition(double x, double y)
{
DoubleAnimation vertAnim = new DoubleAnimation();
vertAnim.From = MainScrollViewer.VerticalOffset;
vertAnim.To = y;
vertAnim.DecelerationRatio = .2;
vertAnim.Duration = new Duration(TimeSpan.FromMilliseconds(250));
DoubleAnimation horzAnim = new DoubleAnimation();
horzAnim.From = MainScrollViewer.HorizontalOffset;
horzAnim.To = x;
horzAnim.DecelerationRatio = .2;
horzAnim.Duration = new Duration(TimeSpan.FromMilliseconds(300));
Storyboard sb = new Storyboard();
sb.Children.Add(vertAnim);
sb.Children.Add(horzAnim);
Storyboard.SetTarget(vertAnim, MainScrollViewer);
Storyboard.SetTargetProperty(vertAnim, new PropertyPath(AniScrollViewer.CurrentVerticalOffsetProperty));
Storyboard.SetTarget(horzAnim, MainScrollViewer);
Storyboard.SetTargetProperty(horzAnim, new PropertyPath(AniScrollViewer.CurrentHorizontalOffsetProperty));
sb.Begin();
}
What am I missing?
Your xaml file needs a reference to your namespace in order to find your AniScrollViewer
Lets say, your AniScrollViewer is located in namespace Test, you can use it in your xaml like so:
<Window x:Class="something"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Test="clr-namespace:Test;assembly=">
<Test:AniScrollViewer />
</Window>