Handle mouseclick events on custom DrawingVisual Object UIElement - c#

I am trying to add a drawingvisual object in canvas with MouseButtonEventHandler. But unable to get click event. What am i doing wrong here?
public class VisualHost : UIElement
{
public Visual myVisual { get; set; }
public VisualHost()
{
Visibility = Visibility.Visible;
IsHitTestVisible = true;
MouseLeftButtonUp += new MouseButtonEventHandler(MyVisualHost_MouseLeftButtonUp);
}
protected override int VisualChildrenCount
{
get { return myVisual != null ? 1 : 0; }
}
protected override Visual GetVisualChild(int index)
{
return myVisual;
}
//mouse event
private void MyVisualHost_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("You clicked a drawing-Visual");
}
}
private void AddMyVisualObject()
{
GeometryDrawing myRectDrawing = new GeometryDrawing(Brushes.Yellow, new Pen(Brushes.White, 1.5), new RectangleGeometry(new Rect(0, 0, 200, 100)));
DrawingVisual myDV = new DrawingVisual();
DrawingContext myDC = myDV.RenderOpen();
myDC.DrawDrawing(myRectDrawing);
myDC.Close();
VisualHost myVH = new VisualHost { myVisual = myDV };
myDrawingCanvas.Children.Add(myVH);
}
Please help required, How can I get the events to fire when clicking on a DrawingVisual?

You have to use a VisualCollection to host your Visual elements and in addition perform the hit testing for them by yourself:
public class VisualHost : FrameworkElement
{
private VisualCollection Children { get; set; }
public VisualHost()
{
this.Children = new VisualCollection(this);
this.MouseLeftButtonUp += MyVisualHost_MouseLeftButtonUp;
}
public void AddChild(Visual visual)
{
this.Children.Add(visual);
}
protected override int VisualChildrenCount => this.Children.Count;
protected override Visual GetVisualChild(int index) => this.Children[index];
//mouse event
private void MyVisualHost_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// Initiate the hit test by setting up a hit test result callback method.
VisualTreeHelper.HitTest(this, null, OnVisualClicked, new PointHitTestParameters(e.GetPosition((UIElement) sender)));
}
private HitTestResultBehavior OnVisualClicked(HitTestResult result)
{
if (result.VisualHit.GetType() == typeof(DrawingVisual))
{
MessageBox.Show("You clicked a DrawingVisual");
}
// Stop the hit test enumeration of objects in the visual tree.
return HitTestResultBehavior.Stop;
}
}
Then initialize the host:
private void AddMyVisualObject()
{
GeometryDrawing myRectDrawing = new GeometryDrawing(Brushes.Yellow, new Pen(Brushes.White, 1.5), new RectangleGeometry(new Rect(0, 0, 200, 100)));
DrawingVisual myDV = new DrawingVisual();
DrawingContext myDC = myDV.RenderOpen();
myDC.DrawDrawing(myRectDrawing);
myDC.Close();
VisualHost myVH = new VisualHost();
myVH.AddChild(myDV);
this.Canvas.Children.Add(myVH);
}
See MSDN for further information.

Related

Custom checkBox problem in Visual Studio 2019 using C#

I found on the internet how to create your own checkbox. Unfortunately, there is a problem with the fact that you have to click very slowly to react to a change, and when I click on the text, nothing changes. I presented the whole action in the film. Please help.
https://www.youtube.com/watch?v=s6xmVAoUVJ8
Here are the files: https://drive.google.com/file/d/10pj6DRjCvfQc8_s0rUufJpN1kdft2sml/view?usp=sharing
CheckBox.cs:
class NowyCheckbox : Control
{
#region Private members
public bool IsChecked = false;
private Label CheckBoxLabel;
private Rectangle CheckBoxRectangle;
private bool MouseOver = false;
#endregion
#region Public Members (in Attributes)
private Color CheckBoxCharColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxCharColor
{
get
{
return CheckBoxCharColorValue;
}
set
{
if (CheckBoxCharColor != value)
{
CheckBoxCharColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxCharHighlightColorValue = Color.FromArgb(0, 120, 215);
public Color CheckBoxCharHighlightColor
{
get
{
return CheckBoxCharHighlightColorValue;
}
set
{
if (CheckBoxCharHighlightColor != value)
{
CheckBoxCharHighlightColorValue = value;
Invalidate();
}
}
}
private Font CheckBoxCharFontValue;
public Font CheckBoxCharFont
{
get
{
return CheckBoxCharFontValue;
}
set
{
if (CheckBoxCharFont != value)
{
CheckBoxCharFontValue = value;
Invalidate();
}
}
}
private string CheckBoxCharValue = "!";
public string CheckBoxChar
{
get
{
return CheckBoxCharValue;
}
set
{
if (CheckBoxChar != value)
{
CheckBoxCharValue = value;
RefreshLabel();
}
}
}
private Font CheckBoxFontValue;
public Font CheckBoxFont
{
get
{
return CheckBoxFontValue;
}
set
{
if (CheckBoxFont != value)
{
CheckBoxFontValue = value;
RefreshLabel();
}
}
}
private string CheckBoxTextValue = "Nowy CheckBox";
public string CheckBoxText
{
get
{
return CheckBoxTextValue;
}
set
{
if (CheckBoxText != value)
{
CheckBoxTextValue = value;
RefreshLabel();
}
}
}
private int CheckBoxSizeValue = 12;
public int CheckBoxSize
{
get
{
return CheckBoxSizeValue;
}
set
{
if (CheckBoxSize != value)
{
CheckBoxSizeValue = value;
RefreshLabel();
Invalidate();
}
}
}
private int CheckBoxFrameStrengthValue = 1;
public int CheckBoxFrameStrength
{
get
{
return CheckBoxFrameStrengthValue;
}
set
{
if (CheckBoxFrameStrengthValue != value)
{
CheckBoxFrameStrengthValue = value;
Invalidate();
}
}
}
#region Public Member
private int CheckBoxCharOffsetXValue = 0;
public int CheckBoxCharOffsetX
{
get
{
return CheckBoxCharOffsetXValue;
}
set
{
if (CheckBoxCharOffsetX != value)
{
CheckBoxCharOffsetXValue = value;
Invalidate();
}
}
}
private int CheckBoxCharOffsetYValue = 0;
public int CheckBoxCharOffsetY
{
get
{
return CheckBoxCharOffsetYValue;
}
set
{
if (CheckBoxCharOffsetY != value)
{
CheckBoxCharOffsetYValue = value;
Invalidate();
}
}
}
private int CheckBoxOffsetXValue = 0;
public int CheckBoxOffsetX
{
get
{
return CheckBoxOffsetXValue;
}
set
{
if (CheckBoxOffsetX != value)
{
CheckBoxOffsetXValue = value;
RefreshLabel();
}
}
}
private int CheckBoxOffsetYValue = 0;
public int CheckBoxOffsetY
{
get
{
return CheckBoxOffsetYValue;
}
set
{
if (CheckBoxOffsetY != value)
{
CheckBoxOffsetYValue = value;
RefreshLabel();
}
}
}
#endregion
#region Public Colors
private Color CheckBoxFrameColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxFrameColor
{
get
{
return CheckBoxFrameColorValue;
}
set
{
if (CheckBoxFrameColorValue != value)
{
CheckBoxFrameColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxFrameHighlightColorValue = Color.FromArgb(0, 120, 250);
public Color CheckBoxFrameHighlightColor
{
get
{
return CheckBoxFrameHighlightColorValue;
}
set
{
if (CheckBoxFrameHighlightColorValue != value)
{
CheckBoxFrameHighlightColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxBackColorValue = Color.FromArgb(255, 255, 255);
public Color CheckBoxBackColor
{
get
{
return CheckBoxBackColorValue;
}
set
{
if (CheckBoxBackColorValue != value)
{
CheckBoxBackColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxBackHighlightColorValue = Color.FromArgb(255, 255, 255);
public Color CheckBoxBackHighlightColor
{
get
{
return CheckBoxBackHighlightColorValue;
}
set
{
if (CheckBoxBackHighlightColorValue != value)
{
CheckBoxBackHighlightColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxForeHighlightColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxForeHighlightColor
{
get
{
return CheckBoxForeHighlightColorValue;
}
set
{
if (CheckBoxForeHighlightColorValue != value)
{
CheckBoxForeHighlightColorValue = value;
RefreshLabel();
}
}
}
private Color CheckBoxForeColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxForeColor
{
get
{
return CheckBoxForeColorValue;
}
set
{
if (CheckBoxForeColorValue != value)
{
CheckBoxForeColorValue = value;
RefreshLabel();
}
}
}
#endregion
#endregion
#region Constructor
public NowyCheckbox()
{
DoubleBuffered = true;
Size = new Size(112, 18);
CheckBoxFont = this.Font;
CheckBoxCharFont = this.Font;
var midHeight = 8 - Font.Height / 2;
CheckBoxLabel = new Label()
{
Font = CheckBoxFont,
Size = new Size(this.Width - 16, this.Height),
Location = new Point(16, midHeight),
Text = CheckBoxText
};
Controls.Add(CheckBoxLabel);
CreateMouseEvents();
}
#endregion
#region Create mouse events
private void CreateMouseEvents()
{
MouseEnter += (sender, e) =>
{
OnCustomMouseEnter(e);
};
MouseLeave += (sender, e) =>
{
OnCustomMouseLeave(e);
};
MouseDown += (sender, e) =>
{
OnCustomMouseDown(e);
};
CheckBoxLabel.MouseEnter += (sender, e) =>
{
OnCustomMouseEnter(e);
};
CheckBoxLabel.MouseLeave += (sender, e) =>
{
OnCustomMouseLeave(e);
};
CheckBoxLabel.MouseDown += (sender, e) =>
{
OnCustomMouseDown(e);
};
}
#endregion
#region Mouse Events
private void OnCustomMouseDown(EventArgs e)
{
IsChecked = !IsChecked;
Invalidate();
}
private void OnCustomMouseEnter(EventArgs e)
{
if (MouseOver == false)
{
MouseOver = true;
Invalidate();
RefreshLabel();
}
}
private void OnCustomMouseLeave(EventArgs e)
{
if (MouseOver == true)
{
MouseOver = false;
Invalidate();
RefreshLabel();
}
}
#endregion
#region Paint NowyCheckbox
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
PaintRectangle(e);
if (IsChecked == true)
{
PaintArrowChar(e);
}
}
#endregion
#region Paint NowyCheckboxRectangle
private void PaintRectangle(PaintEventArgs e)
{
var midHeight = Height / 2 - CheckBoxSize / 2;
CheckBoxRectangle = new Rectangle(0, midHeight, CheckBoxSize, CheckBoxSize);
var fillColor = MouseOver == true ? CheckBoxBackHighlightColor : CheckBoxBackColor;
var frameColor = MouseOver == true ? CheckBoxFrameHighlightColor : CheckBoxFrameColor;
using (var pen = new Pen(frameColor, CheckBoxFrameStrength))
{
var brush = new SolidBrush(fillColor);
e.Graphics.FillRectangle(brush, CheckBoxRectangle);
e.Graphics.DrawRectangle(pen, CheckBoxRectangle);
}
}
#endregion
#region Paint Checkbox Arrow
private void PaintArrowChar(PaintEventArgs e)
{
var charColor = MouseOver == true ? CheckBoxCharHighlightColor : CheckBoxCharColor;
var midX = CheckBoxSize / 2 - 3 + CheckBoxCharOffsetX;
var midY = Height / 2 - CheckBoxCharFont.Height / 2 + CheckBoxCharOffsetY;
using (var brush = new SolidBrush(charColor))
{
e.Graphics.DrawString(CheckBoxChar, CheckBoxCharFont, brush, new Point(midX, midY));
}
}
#endregion
#region [OnResize]
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
RefreshLabel();
}
#endregion
#region Refresh Label
private void RefreshLabel()
{
if (CheckBoxLabel == null) return;
CheckBoxLabel.Font = CheckBoxFont;
CheckBoxLabel.Text = CheckBoxText;
CheckBoxLabel.ForeColor = MouseOver == true ? CheckBoxForeHighlightColor : CheckBoxForeColor;
var offsetWidth = Width - CheckBoxSize;
CheckBoxLabel.Size = new Size(offsetWidth, Height);
var offsetX = CheckBoxSize + 6 + CheckBoxOffsetX;
var midHeight = Height / 2 - CheckBoxFont.Height / 2 + CheckBoxOffsetY;
CheckBoxLabel.Location = new Point(offsetX, midHeight);
Invalidate();
}
#endregion
}
}
Form1.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Test
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void nowyCheckbox1_Click(object sender, EventArgs e)
{
if (nowyCheckbox1.IsChecked)
{
label2.Text = "Jestem włączony";
}
else
{
label2.Text = "Jestem wyłączony";
}
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked)
{
label3.Text = "Jestem włączony";
}
else
{
label3.Text = "Jestem wyłączony";
}
}
}
An object like your custom check box, should have a mechanism used to notify a subscriber (like a Form) that something has happened. Notifications like mouse clicks, keystrokes, property changes...etc. In event-driven programming, your control should generate and publish an event to be received and handled by a subscriber through an Event Handler. Your custom control is missing that mechanism and this is the problem.
Let's apply that and create a simple check box control.
[DefaultProperty("Checked")]
[DefaultEvent("CheckedChanged")]
[DesignerCategory("Code")]
[ToolboxBitmap(typeof(CheckBox))]
public class NowyCheckBox : Control
{
Class members to keep:
The bounds of the check box and text areas.
The points of a check mark.
The current mouse state.
private enum MouseState : int
{
None,
Over,
Down
}
private Rectangle ChkRec, TxtRec;
private PointF[] ChkPoints;
private MouseState mouseState = MouseState.None;
In the constructor, set the shown below styles to reduce the flickering and to use the control in a container with a background image or gradient colors.
public NowyCheckBox() : base()
{
SetStyle(
ControlStyles.SupportsTransparentBackColor |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw, true);
UpdateStyles();
}
Add a method to create the drawing objects. Call it from the setters of the relevant properties
(such as CheckBoxSize) and methods (like OnSizeChanged) as shown next.
private void ResetDrawingObjects()
{
if (Width == 0 || Height == 0) return;
ChkRec = new Rectangle(new Point(0, (Height - checkBoxSize.Height) / 2), CheckBoxSize);
TxtRec = new Rectangle(ChkRec.Right + 5, 0, Width - ChkRec.Right - 10, Height);
if (this.RightToLeft == RightToLeft.Yes)
{
ChkRec.X = Width - 1 - ChkRec.Width;
TxtRec.X = ChkRec.X - 5 - TxtRec.Width;
}
var r = Rectangle.Inflate(ChkRec, -3, -3);
ChkPoints = new[]
{
new PointF(r.X, r.Y + (r.Height / 2f)),
new PointF(r.X + (r.Width / 2f) - 1, r.Bottom),
new PointF(r.Right, r.Y)
};
Invalidate();
}
Create a custom event and raise it whenever the Checked property changes.
public event EventHandler CheckedChanged;
protected virtual void OnCheckedChanged(EventArgs e) => CheckedChanged?.Invoke(this, e);
Add the properties.
private bool _checked;
[DefaultValue(false)]
public bool Checked
{
get => _checked;
set
{
if (_checked != value)
{
_checked = value;
Invalidate();
OnCheckedChanged(new EventArgs());
}
}
}
private Size checkBoxSize = new Size(12, 12);
[DefaultValue(typeof(Size), "12, 12")]
public Size CheckBoxSize
{
get => checkBoxSize;
set { checkBoxSize = value; ResetDrawingObjects(); }
}
// Add the rest...
Override the following methods to:
Update the mouseState variable and refresh the drawing by calling the Invalidate method if needed.
Call the ResetDrawingObjects method whenever the drawing objects need to resize and/or reposition.
Like the default CheckBox control, toggle the Checked property on Keys.Space press.
Draw the control.
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (mouseState != MouseState.Over)
{
mouseState = MouseState.Over;
// Pass ChkRec to update the checkbox area only.
Invalidate(ChkRec);
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Left)
{
Focus();
mouseState = MouseState.Down;
Invalidate(ChkRec);
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
Checked = !Checked;
mouseState = MouseState.Over;
Invalidate(ChkRec);
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
mouseState = MouseState.None;
Invalidate(ChkRec);
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.KeyCode == Keys.Space) Checked = !Checked;
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
Invalidate();
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
ResetDrawingObjects();
}
protected override void OnRightToLeftChanged(EventArgs e)
{
base.OnRightToLeftChanged(e);
ResetDrawingObjects();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
// Get your colors to fill and draw...
var clr = mouseState == MouseState.Down
? // down color...
: mouseState == MouseState.Over
? // over color...
: // default color...;
using (var pn = new Pen(clr)) g.DrawRectangle(pn, ChkRec);
if (Checked)
{
clr = mouseState == MouseState.Down
? // down color...
: mouseState == MouseState.Over
? // over color...
: // default color...;
using (var pn = new Pen(clr, 2))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawLines(pn, ChkPoints);
g.SmoothingMode = SmoothingMode.None;
}
/*
* In case you prefere drawing glyphs. Example:
using (var fnt = new Font("Marlett", 12))
{
var r = ChkRec;
r.Offset(2, -2);
TextRenderer.DrawText(g, "a", fnt, r, clr,
TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter);
}
*/
}
var flags = TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis;
if (this.RightToLeft == RightToLeft.Yes)
flags |= TextFormatFlags.Right;
else
flags |= TextFormatFlags.Left;
TextRenderer.DrawText(g, Text, Font, TxtRec, ForeColor, flags);
}
}
Put it together, rebuild, drop an instance, double click on it to subscribe to the default event CheckedChanged, and handle it as you want.
private void nowyCheckBox1_CheckedChanged(object sender, EventArgs e)
{
label2.Text = nowyCheckBox1.Checked
? "Jestem włączony"
: "Jestem wyłączony";
}
If you'd like to read more.
Handle and raise events
C# - Events

Gradient color on buttonrenderer does not update [Xamarin Forms iOS]

I am creating a button with a gradient in Xamarin forms. I am successfully making it but when i later on in the code try to update its color, nothing happens in the UI.
This is how the project is setup:
XAML:
<controls:FullyColoredGradient x:Name = "SelectedBackground" StartColor = "Purple" EndColor="Yellow" />
If i then later on the code do a void and try to update these colors like this:
SelectedBackground.EndColor = Color.Red;
SelectedBackground.StartColor = Color.Blue;
Then nothing happens. They do not recolor.
This is how my shared code looks:
public class FullyColoredGradient : Button
{
public static readonly BindableProperty StartColorProperty =
BindableProperty.Create(nameof(StartColor),
typeof(Color), typeof(FullyColoredGradient),
Color.Default);
public Color StartColor
{
get { return (Color)GetValue(StartColorProperty); }
set { SetValue(StartColorProperty, value); }
}
public static readonly BindableProperty EndColorProperty =
BindableProperty.Create(nameof(EndColor),
typeof(Color), typeof(FullyColoredGradient),
Color.Default);
public Color EndColor
{
get { return (Color)GetValue(EndColorProperty); }
set { SetValue(EndColorProperty, value); }
}
}
And this is my iOS renderer:
public class TransparentGradientColor_iOS : ButtonRenderer
{
CGRect rect;
CAGradientLayer gradientLayer;
public TransparentGradientColor_iOS() { }
public override void Draw(CGRect rect)
{
base.Draw(rect);
this.rect = rect;
FullyColoredGradient rcv = (FullyColoredGradient)Element;
if (rcv == null)
return;
this.ClipsToBounds = true;
this.Layer.MasksToBounds = true;
FullyColoredGradient stack = (FullyColoredGradient)this.Element;
CGColor startColor = stack.StartColor.ToCGColor();
CGColor endColor = stack.EndColor.ToCGColor();
#region for Vertical Gradient
this.gradientLayer = new CAGradientLayer()
{
StartPoint = new CGPoint(0, 0.5),
EndPoint = new CGPoint(1, 0.5)
};
#endregion
gradientLayer.Frame = rect;
gradientLayer.Colors = new CGColor[] { startColor, endColor };
NativeView.Layer.InsertSublayer(gradientLayer, 0);
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
UpdateColor();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
UpdateColor();
}
void UpdateColor()
{
if (gradientLayer != null)
{
FullyColoredGradient stack = (FullyColoredGradient)this.Element;
CGColor startColor = Xamarin.Forms.Color.White.ToCGColor();
CGColor endColor = Xamarin.Forms.Color.White.ToCGColor();
gradientLayer.Colors = new CGColor[] { startColor, endColor };
}
}
}
I have used this custom renderer, this will apply gradient to all buttons(you can create custom button if you want), you can try this out:
[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRendereriOS))]
namespace XYZ.iOS.Renderer
{
public class CustomButtonRendereriOS : ButtonRenderer
{
//To apply gradient background to button
public override CGRect Frame
{
get
{
return base.Frame;
}
set
{
if (value.Width > 0 && value.Height > 0)
{
foreach (var layer in Control?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
layer.Frame = new CGRect(0, 0, value.Width, value.Height);
}
base.Frame = value;
}
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
try
{
var gradient = new CAGradientLayer();
gradient.CornerRadius = Control.Layer.CornerRadius = 5;
gradient.Colors = new CGColor[]
{
UIColor.FromRGB(243, 112, 33).CGColor,
UIColor.FromRGB(226, 64, 64).CGColor
};
var layer = Control?.Layer.Sublayers.LastOrDefault();
Control?.Layer.InsertSublayerBelow(gradient, layer);
}
catch (Exception ex)
{
}
}
}
}
You can also use a gradient stack layout/ Frame as described here with help of custom renderer and use Tap Gesture of it for clicked event.
Gradient Button in Xamarin Forms
Hope this may solve your issue.

`RaiseEvent` called on `BorderedCanvas` does not call event handler

I have the following C# class:
class MouseWheelEventListenerViewManager : ReactViewManager
{
public override void OnDropViewInstance(ThemedReactContext reactContext, BorderedCanvas canvas)
{
base.OnDropViewInstance(reactContext, canvas);
RemoveListeners(canvas);
}
protected override BorderedCanvas CreateViewInstance(ThemedReactContext reactContext)
{
var borderedCanvas = base.CreateViewInstance(reactContext);
AddListeners(borderedCanvas);
return borderedCanvas;
}
protected void AddListeners(BorderedCanvas borderedCanvas)
{
Console.Write("In AddListeners");
borderedCanvas.PreviewMouseWheel += PreviewMouseWheel;
}
protected void RemoveListeners(BorderedCanvas borderedCanvas)
{
borderedCanvas.PreviewMouseWheel -= PreviewMouseWheel;
}
protected static void PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
Console.Write("Added PreviewMouseWheel");
if (!e.Handled)
{
e.Handled = true;
var eventArg =
new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.MouseWheelEvent,
Source = sender
};
UIElement parent = VisualTreeHelper.GetParent((UIElement)sender) as UIElement;
parent?.RaiseEvent(eventArg);
}
}
}
This code achieves what it's supposed to do in my application(allow scrolling on top of a ScrollViewer), so I believe it is correct. However, I don't know how to test it.
I added this test which should check that a MouseWheelEvent is marked as handled:
[Test]
public void MouseWheelEvent_is_marked_as_handled()
{
var invoked = new AutoResetEvent(false);
var mouseWheelEventListenerViewManager = new MouseWheelEventListenerViewManager();
BorderedCanvas canvas = mouseWheelEventListenerViewManager.CreateView(new ThemedReactContext(new ReactContext()));
MouseWheelEventArgs me = new MouseWheelEventArgs(InputManager.Current.PrimaryMouseDevice, 10, 10)
{
RoutedEvent = Mouse.MouseWheelEvent
};
canvas.RaiseEvent(me);
Assert.True(invoked.WaitOne(new TimeSpan(0, 0, 0, 20)));
invoked.Set();
Assert.IsTrue(me.Handled);
}
This test fails at Assert.IsTrue(me.Handled)and only this is printed on the console:
In AddListeners
Normally, this should be printed:
In AddListeners
Added PreviewMouseWheel
This means that the method PreviewMouseWheel is not called from the test, even though AddListeners was called, which means that canvas.PreviewMouseWheel was initialised.
Any idea why this happens?

WPF Custom Control drawing avoid infinite measure/arrange loop

I am building a custom control that does some custom drawing based on some data. I want to update the drawing when arrange is called (i.e. the size is changed). But when I am changing my Children in ArrangeOverride() I get an infinite loop of course. How can I avoid this?
For simplicity it is easier for me to rebuild the whole visual tree instead of creating children once and resizing them individually.
Is there a better approach to do this? I can also live with just using a DrawingContext object and invoking my drawing logic there.
public class MyCanvas : Canvas
{
private static int _drawCounter = 0;
private System.Windows.Size _arrangeSize;
private MyData _data;
protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
{
_arrangeSize = arrangeSize;
Draw();
return base.ArrangeOverride(arrangeSize);
}
public void SetData(MyData data)
{
_data = data;
Draw();
}
private void Draw()
{
Children.Clear();
if (_data == null || _arrangeSize.IsEmpty)
{
return;
}
Children.Add(new TextBlock() {Text = (++_drawCounter).ToString()});
}
}
Here is how I solved it:
public class MyCanvas : Canvas
{
private readonly DispatcherTimer _dispatcherTimer;
private Size _arrangeSize;
private Size _drawnSize;
public MyCanvas()
{
_dispatcherTimer = new DispatcherTimer(DispatcherPriority.Render)
{
Interval = TimeSpan.FromMilliseconds(500)
};
_dispatcherTimer.Tick += (sender, args) =>
{
var dispatcherTimer = (DispatcherTimer)sender;
dispatcherTimer.Stop();
Debug.WriteLine("Draw call from DispatcherTimer");
Draw();
};
}
protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
{
_arrangeSize = arrangeSize;
if (_drawnSize != _arrangeSize)
{
QueueDrawCall();
}
return base.ArrangeOverride(arrangeSize);
}
private void QueueDrawCall()
{
if (_dispatcherTimer.IsEnabled)
{
_dispatcherTimer.Stop();
}
_dispatcherTimer.Start();
}
public void SetData(MyData data)
{
_data = data;
Console.WriteLine("Direct Draw Call " + data);
Draw();
}
private void Draw()
{
if (Children.Count > 0)
{
Children.Clear();
}
if (_data == null || _arrangeSize.IsEmpty)
{
return;
}
InternalDraw(); // Drawing logic goes in this function
_drawnSize = _arrangeSize;
}
}

VisualCollection to Visual HitTest invisible?

I am working with VisualCollection, Visual and HitTest in WPF and encountered a problem.
I tried to make a custom visual drawing as follows:
public class MyDrawing : Visual
{
VisualCollection vc;
public MyDrawing()
{
vc = new VisualCollection(this);
}
// ...
DrawingVisual rectangle = new DrawingVisual();
// ...
vc.Add(rectangle);
}
public class DrawingArea : FrameworkElement
{
VisualCollection vc;
public DrawingArea()
{
vc = new VisualCollection(this);
MyDrawing md1 = new MyDrawing();
vc.Add(md1);
}
public void TryToHit(Point p)
{
HitTestResult result = VisualTreeHelper.HitTest(this, p);
}
}
Then I found that the result is rectangle but not md1.
How could I make MyDrawing become the basic Visual element so that the VisualTreeHelper would not further do HitTest inside?
Thank you very much.
Instead of Drawingvisual. Try the below one.
public class NoHitTestDrawingVisual : DrawingVisual
{
protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters)
{
return null;
}
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
return null;
}
}

Categories

Resources