How to add invisible layer to a line - wpf

I have a line shape that I have implemented the double click for, however the line is too thin. I would like to add a transparent white padding around it, so that it doesn't have to be clicked exactly on the line.
I really don't want to increase the stroke thickness, and I would like it to remain as shape since I do not want to put it in a content control, or a border.

One way of doing this is to override the standard Hit Testing of this line. Unfortunately, the WPF's Line class is sealed, which I personally think is criminal :-)
Here is a piece of code that reproduces the Line behavior, but in another class, and defines a Tolerance property (default value is 5). Feel free to test it:
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
namespace MyNamespace
{
public class HitTolerantLine : Shape
{
public static readonly DependencyProperty X1Property = DependencyProperty.Register("X1", typeof(double), typeof(Line), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(IsDoubleFinite));
public static readonly DependencyProperty X2Property = DependencyProperty.Register("X2", typeof(double), typeof(Line), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(IsDoubleFinite));
public static readonly DependencyProperty Y1Property = DependencyProperty.Register("Y1", typeof(double), typeof(Line), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(IsDoubleFinite));
public static readonly DependencyProperty Y2Property = DependencyProperty.Register("Y2", typeof(double), typeof(Line), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(IsDoubleFinite));
public static readonly DependencyProperty ToleranceProperty = DependencyProperty.Register("Tolerance", typeof(double), typeof(Line), new FrameworkPropertyMetadata(5.0), new ValidateValueCallback(IsDoubleFinite));
private LineGeometry _geometry;
private static readonly Pen _strokePen;
static HitTolerantLine()
{
_strokePen = new Pen(Brushes.Black, 1.0);
_strokePen.Freeze();
}
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
HitTestResult res = base.HitTestCore(hitTestParameters);
// didn't hit? let's add some tolerance
if ((res == null) && (_geometry != null) && (Tolerance > 0))
{
if (_geometry.StrokeContains(_strokePen, hitTestParameters.HitPoint, Tolerance, ToleranceType.Absolute))
{
res = new PointHitTestResult(this, hitTestParameters.HitPoint);
}
}
return res;
}
protected virtual void DefineGeometry()
{
Point startPoint = new Point(X1, Y1);
Point endPoint = new Point(X2, Y2);
_geometry = new LineGeometry(startPoint, endPoint);
}
protected override Size MeasureOverride(Size constraint)
{
DefineGeometry();
return base.MeasureOverride(constraint);
}
protected static bool IsDoubleFinite(object o)
{
double d = (double)o;
return (!double.IsInfinity(d) && !double.IsNaN(d));
}
protected override Geometry DefiningGeometry
{
get
{
return _geometry;
}
}
public double Tolerance
{
get
{
return (double)base.GetValue(ToleranceProperty);
}
set
{
base.SetValue(ToleranceProperty, value);
}
}
[TypeConverter(typeof(LengthConverter))]
public double X1
{
get
{
return (double) base.GetValue(X1Property);
}
set
{
base.SetValue(X1Property, value);
}
}
[TypeConverter(typeof(LengthConverter))]
public double X2
{
get
{
return (double) base.GetValue(X2Property);
}
set
{
base.SetValue(X2Property, value);
}
}
[TypeConverter(typeof(LengthConverter))]
public double Y1
{
get
{
return (double) base.GetValue(Y1Property);
}
set
{
base.SetValue(Y1Property, value);
}
}
[TypeConverter(typeof(LengthConverter))]
public double Y2
{
get
{
return (double) base.GetValue(Y2Property);
}
set
{
base.SetValue(Y2Property, value);
}
}
}
}

Padding area is not clickable.
Only way I can think of, given your restrictions is to increase stroke thickness and manage the stroke brush to be a gradient of transparent color and the visible color

Draw a transparent line on top with a larger StrokeThickness and apply the double click behaviour to that.

Related

WPF PropertyPath on Custom control

I have a problem, I create a control named Tile (like the Tile on Windows 10).
This tile simulate rotation 3D by using a projection class, like the projection class in Silverlight.
We have a base projection class like this :
abstract public class Projection
: FrameworkElement
{
static public readonly DependencyProperty RotationZProperty = DependencyProperty.Register(
nameof(RotationZ),
typeof(double),
typeof(Projection),
new UIPropertyMetadata(0.0, OnRotationChanged));
static public readonly DependencyProperty RotationYProperty = DependencyProperty.Register(
nameof(RotationY),
typeof(double),
typeof(Projection),
new UIPropertyMetadata(0.0, OnRotationChanged));
static public readonly DependencyProperty RotationXProperty = DependencyProperty.Register(
nameof(RotationX),
typeof(double),
typeof(Projection),
new UIPropertyMetadata(0.0, OnRotationChanged));
public double RotationZ
{
get { return this.GetValue<double>(RotationZProperty); }
set { SetValue(RotationZProperty, value); }
}
public double RotationY
{
get { return this.GetValue<double>(RotationYProperty); }
set { SetValue(RotationYProperty, value); }
}
public double RotationX
{
get { return this.GetValue<double>(RotationXProperty); }
set { SetValue(RotationXProperty, value); }
}
public FrameworkElement Child
{
get;
set;
}
static private void OnRotationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Projection pl)
{
pl.OnRotationChanged();
}
}
private void OnRotationChanged()
{
// Some code
}}
After that, we have the PlaneProjectionClass :
[ContentProperty("Child")]
sealed public class PlaneProjection
: Projection
{
}
The Tile class use a dependency property of type Projection :
public class Tile
{
static public readonly DependencyProperty PlaneProjectionProperty = DependencyProperty.Register(
nameof(Projection),
typeof(Projection),
typeof(Tile),
new UIPropertyMetadata());
public Projection Projection
{
get { return (Projection)GetValue(PlaneProjectionProperty); }
private set { SetValue(PlaneProjectionProperty, value); }
}
override public void OnApplyTemplate()
{
Projection = GetTemplateChild("PART_Projection") as Projection;
}
}
So for the XAML, we have this in ControlTemplate :
<controls:PlaneProjection x:Name="PART_PlaneProjection">
<Border>
<Grid>
<!-- Some design -->
</Grid>
</Border>
</controls:PlaneProjection>
Now I would like to animate the planeprojection.
So I create the storyboard and animate the projection with rotationX :
static public void CreateAnimation(Tile tile)
{
Storyboard.SetTarget(anim, tile);
Storyboard.SetTargetProperty(doubleAnim, new PropertyPath("(Tile.Projection).(PlaneProjection.RotationX"));
}
But at debug, I have this error : Cannot resolve all references of the property on the path of the property '(Tile.Projection).(PlaneProjection.RotationX)
I don't understand the mistake :( Any ideas on using PropertyPath on custom control ?
The Projection property in class Tile does not follow the naming conventions for dependency properties.
It should e.g. look like this:
public static readonly DependencyProperty PlaneProjectionProperty =
DependencyProperty.Register(nameof(PlaneProjection), typeof(Projection), typeof(Tile));
public Projection PlaneProjection
{
get { return (Projection)GetValue(PlaneProjectionProperty); }
private set { SetValue(PlaneProjectionProperty, value); }
}
The property path would simply be this:
Storyboard.SetTargetProperty(anim, new PropertyPath("PlaneProjection.RotationX"));
You wouldn't even need a Storyboard. Just call
tile.PlaneProjection.BeginAnimation(Projection.RotationXProperty, anim);
As a note, a private setter does not make the dependency property read-only. See Read-Only Dependency Properties for details.

doing some mathematical calculation on custom dependency property values

I have three dependency property. TestControlHeight, HalfValue1 and HalfValue2. Now, depending on these three value I have to calculate
a third value which will be assigned as the height of the inner control which will also be a dependency property.
height of inner control = TestControlHeight/ (HalfValue1 - HalfValue2);
Where can I write this code to calculate the height of the inner control (which si also a dependency property)
public static readonly DependencyProperty TestControlHeightProperty =
DependencyProperty.Register("TestControlHeight", typeof (double),
typeof (TestControl), new PropertyMetadata(default(double)));
public double TestControlHeight
{
get { return (double) GetValue(TestControlHeightProperty); }
set { SetValue(TestControlHeightProperty, value); }
}
public static readonly DependencyProperty HalfValue1Property =
DependencyProperty.Register("HalfValue1", typeof (double), typeof
(TestControl), new PropertyMetadata(default(double)));
public double HalfValue1
{
get { return (double) GetValue(HalfValue1Property); }
set { SetValue(HalfValue1Property, value); }
}
public static readonly DependencyProperty HalfValue2Property =
DependencyProperty.Register("HalfValue2", typeof (double), typeof
(TestControl), new PropertyMetadata(default(double)));
public double HalfValue2
{
get { return (double) GetValue(HalfValue2Property); }
set { SetValue(HalfValue2Property, value); }
}
Thanks & Regards,
From the WPF Unleashed book:
.NET property wrappers are bypassed at runtime when setting dependency properties
in XAML!
Although the XAML compiler depends on the property wrapper at compile time, WPF calls the underlying GetValue and SetValue methods directly at runtime! Therefore, to maintain parity between setting a property in XAML and procedural code, it’s crucial that property wrappers not contain any logic in addition to the GetValue/SetValue calls.
If you want to add custom logic, that’s what the registered callbacks are for. All of WPF’s built-in property wrappers abide by this rule, so this warning is for anyone writing a custom class with its own dependency properties.
So, your code could look like this (not tested):
public static readonly DependencyProperty TestControlHeightProperty =
DependencyProperty.Register("TestControlHeight", typeof(double), typeof(TestControl),
new PropertyMetadata(false, new PropertyChangedCallback(OnHeightChanged)));
public double TestControlHeight
{
get { return (double)GetValue(TestControlHeightProperty); }
set { SetValue(TestControlHeightProperty, value); }
}
public static readonly DependencyProperty HalfValue1Property =
DependencyProperty.Register("HalfValue1", typeof(double), typeof(TestControl),
new PropertyMetadata(false, new PropertyChangedCallback(OnHeightChanged)));
public double HalfValue1
{
get { return (double)GetValue(HalfValue1Property); }
set { SetValue(HalfValue1Property, value); }
}
public static readonly DependencyProperty HalfValue2Property =
DependencyProperty.Register("HalfValue2", typeof(double), typeof(TestControl),
new PropertyMetadata(false, new PropertyChangedCallback(OnHeightChanged)));
public double HalfValue2
{
get { return (double)GetValue(HalfValue2Property); }
set { SetValue(HalfValue2Property, value); }
}
public double MyInnerControlHeight
{
get { return (double)GetValue(MyInnerControlHeightPropertyProperty); }
set { SetValue(MyInnerControlHeightPropertyProperty, value); }
}
public static readonly DependencyProperty MyInnerControlHeightPropertyProperty =
DependencyProperty.Register("MyInnerControlHeightProperty", typeof(double), typeof(TestControl),
new PropertyMetadata(0));
private static void OnHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var testControl = d as TestControl;
if (testControl != null)
{
testControl.MyInnerControlHeight = testControl.TestControlHeight / (testControl.HalfValue1 - testControl.HalfValue2);
}
}

MVVM - How change backcolor for a single char in a datagrid

I would the rows containing the search value (Search Name), show this value (in datagrid) with a different color.
See the pic below.
Some ideas about this ?
You can do this by creating a new control that extends a standard TextBlock, which uses a series of Run items to display the text, using the appropriate formatting.
public class HighlightTextBlock: TextBlock
{
public string BaseText
{
get { return (string)GetValue(BaseTextProperty); }
set { SetValue(BaseTextProperty, value); }
}
public static readonly DependencyProperty BaseTextProperty =
DependencyProperty.Register("BaseText", typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(null, UpdateDisplay));
public string HighlightText
{
get { return (string)GetValue(HighlightTextProperty); }
set { SetValue(HighlightTextProperty, value); }
}
public static readonly DependencyProperty HighlightTextProperty =
DependencyProperty.Register("HighlightText", typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(null, UpdateDisplay));
public Brush HighlightBrush
{
get { return (Brush)GetValue(HighlightBrushProperty); }
set { SetValue(HighlightBrushProperty, value); }
}
public static readonly DependencyProperty HighlightBrushProperty =
DependencyProperty.Register("HighlightBrush", typeof(Brush), typeof(HighlightTextBlock), new PropertyMetadata(Brushes.Orange, UpdateDisplay));
public bool HighlightCaseSensitive
{
get { return (bool)GetValue(HighlightCaseSensitiveProperty); }
set { SetValue(HighlightCaseSensitiveProperty, value); }
}
public static readonly DependencyProperty HighlightCaseSensitiveProperty =
DependencyProperty.Register("HighlightCaseSensitive", typeof(bool), typeof(HighlightTextBlock), new PropertyMetadata(false, UpdateDisplay));
private static void UpdateDisplay(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var hightlightTextBlock = sender as HighlightTextBlock;
if (hightlightTextBlock == null)
return;
hightlightTextBlock.Inlines.Clear();
if (string.IsNullOrEmpty(hightlightTextBlock.BaseText))
return;
if (string.IsNullOrEmpty(hightlightTextBlock.HighlightText))
{
hightlightTextBlock.Inlines.Add(new Run(hightlightTextBlock.BaseText));
return;
}
var textItems = Regex.Split(hightlightTextBlock.BaseText,
"(" + hightlightTextBlock.HighlightText + ")",
hightlightTextBlock.HighlightCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase);
foreach (var item in textItems)
{
var run = new Run(item);
var highlight = hightlightTextBlock.HighlightCaseSensitive
? string.Compare(item, hightlightTextBlock.HighlightText, StringComparison.InvariantCulture) == 0
: string.Compare(item, hightlightTextBlock.HighlightText, StringComparison.InvariantCultureIgnoreCase) == 0;
if (highlight)
run.Background = hightlightTextBlock.HighlightBrush;
hightlightTextBlock.Inlines.Add(run);
}
}
}
The brackets around the HighlightText value tells Regex.Split to include the matched text in the returned list of items.
This control can then be used as part of an item template in your datagrid column definition. See here for an example of how to do that.

DependencyProperty and WPF Designer

class MyLine : Shape {
public static readonly DependencyProperty X11Property;
public static readonly DependencyProperty X22Property;
public static readonly DependencyProperty Y11Property;
public static readonly DependencyProperty Y22Property;
static MyLine() {
X11Property = DependencyProperty.Register("X11", typeof(double), typeof(MyLine), new UIPropertyMetadata(double.NaN));
X22Property = DependencyProperty.Register("X22", typeof(double), typeof(MyLine), new UIPropertyMetadata(double.NaN));
Y11Property = DependencyProperty.Register("Y11", typeof(double), typeof(MyLine), new UIPropertyMetadata(double.NaN));
Y22Property = DependencyProperty.Register("Y22", typeof(double), typeof(MyLine), new UIPropertyMetadata(double.NaN));
}
[TypeConverter(typeof(LengthConverter))]
public double X11 { get { return (double)GetValue(X11Property); } set { SetValue(X11Property, value); } }
[TypeConverter(typeof(LengthConverter))]
public double X22 { get { return (double)GetValue(X22Property); } set { SetValue(X22Property, value); } }
[TypeConverter(typeof(LengthConverter))]
public double Y11 { get { return (double)GetValue(Y11Property); } set { SetValue(Y11Property, value); } }
[TypeConverter(typeof(LengthConverter))]
public double Y22 { get { return (double)GetValue(Y22Property); } set { SetValue(Y22Property, value); } }
protected override System.Windows.Media.Geometry DefiningGeometry {
get {
var geometryGroup = new GeometryGroup();
// Add line
geometryGroup.Children.Add(new LineGeometry(new Point(X11, Y11), new Point(X22, Y22)));
return geometryGroup;
}
}
}
Why when I update the "myLine" coordinates in WPF designer(VS 2010), it does not update it automatically(live)?
When using default "Line" WPF objects, they are automatically updated when the XAML code is changed(edited) in XAML/Design view.
Since these properties affect rendering, you should specify it in the metadata:
X11Property = DependencyProperty.Register("X11", typeof(double), typeof(DirectionLine), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsRender));
I'm not sure it will be enough for the designer to take it into account, but it's worth a try...

WPF Draggable Panel Class

I'm trying to write a panel class from this two resources:
WPF: how to make the (0,0) in center inside a Canvas
http://www.codeproject.com/KB/WPF/DraggingElementsInCanvas.aspx
The panel class will have two attached properties "X" and "Y" and if any element gives x and y to be zero then it will be placed on the center of the Panel. The panel will also let the user to drag things around . Please help me write this class. I'm very new to WPF.
This is how far I've come. Now I tried to implement this but its not working, if you can help me implement the GetTop,GetLeft,GetBottom,GetRight functions which are not by default defined in panel class and which are neccessary. If these 4 methods are present then dragging functions can be implemented here.
using System;
using System.Linq;
using System.Windows;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Media;
namespace SmartERP.Elements
{
public class SmartCanvas : Panel
{
public static readonly DependencyProperty TopProperty;
public static readonly DependencyProperty LeftProperty;
public static readonly DependencyProperty BottomProperty;
public static readonly DependencyProperty RightProperty;
static SmartCanvas()
{
TopProperty = DependencyProperty.Register("Top", typeof(double), typeof(SmartCanvas), new PropertyMetadata(0.0));
LeftProperty = DependencyProperty.Register("Left", typeof(double), typeof(SmartCanvas), new PropertyMetadata(0.0));
BottomProperty = DependencyProperty.Register("Bottom", typeof(double), typeof(SmartCanvas), new PropertyMetadata(0.0));
RightProperty = DependencyProperty.Register("Right", typeof(double), typeof(SmartCanvas), new PropertyMetadata(0.0));
}
public double Top
{
get { return (double)base.GetValue(TopProperty); }
set { base.SetValue(TopProperty, value); }
}
public double Bottom
{
get { return (double)base.GetValue(BottomProperty); }
set { base.SetValue(BottomProperty, value); }
}
public double Left
{
get { return (double)base.GetValue(LeftProperty); }
set { base.SetValue(LeftProperty, value); }
}
public double Right
{
get { return (double)base.GetValue(RightProperty); }
set { base.SetValue(RightProperty, value); }
}
private double GetTop(UIElement element)
{
return (double)this.GetValue(TopProperty);
}
private double GetLeft(UIElement element)
{
return (double)this.GetValue(LeftProperty);
}
private double GetBottom(UIElement element)
{
return (double)this.GetValue(BottomProperty);
}
private double GetRight(UIElement element)
{
return (double)this.GetValue(RightProperty);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
Point middle = new Point(arrangeSize.Width / 2, arrangeSize.Height / 2);
foreach (UIElement element in base.InternalChildren)
{
if (element == null)
{
continue;
}
double x = 0.0;
double y = 0.0;
double left = GetLeft(element);
if (!double.IsNaN(left))
{
x = left;
}
double top = GetTop(element);
if (!double.IsNaN(top))
{
y = top;
}
element.Arrange(new Rect(new Point(middle.X + x, middle.Y + y), element.DesiredSize));
}
return arrangeSize;
}
}
}
The panel class will have two attached properties "X" and "Y" [...]
OK, then you should implement those attached properties. See the example in Section Custom Attached Properties of the Attached Properties Overview on MSDN. Here's how this would look for X:
public static readonly DependencyProperty XProperty =
DependencyProperty.RegisterAttached("X", typeof(double),
typeof(SmartCanvas), new FrameworkPropertyMetadata(0.0));
public static void SetX(UIElement element, double value) { element.SetValue(XProperty, value); }
public static double GetX(UIElement element) { return (double)element.GetValue(XProperty); }
Once you have done this, you have GetX and GetY, which is probably what you mean by GetTop and GetLeft.

Resources