VisualCollection to Visual HitTest invisible? - c#

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;
}
}

Related

How to add a space between the Tabbar line and the icons in iOS using Xamarin Forms Shell?

I have used ShellRenderer but I am unable to get the code working to add an extra space between the Tabbar line and the icons. Here is an image:
I need to increase the space between the gray line and Top of all icons.
Here is the code that I have tried.
[assembly: ExportRenderer(typeof(Shell), typeof(CustomShellRenderer))]
namespace MyProject.iOS.CustomRenderers
{
public class CustomShellRenderer : ShellRenderer
{
protected override IShellSectionRenderer CreateShellSectionRenderer(ShellSection shellSection)
{
var renderer = base.CreateShellSectionRenderer(shellSection);
if (renderer != null)
{
var a = (renderer as ShellSectionRenderer);
(renderer as ShellSectionRenderer).NavigationBar.Translucent = false;
}
return renderer;
}
protected override IShellItemRenderer CreateShellItemRenderer(ShellItem item)
{
var renderer = base.CreateShellItemRenderer(item);
(renderer as ShellItemRenderer).TabBar.Translucent = false;
(renderer as ShellItemRenderer).TabBar.ShadowImage = new UIImage();
(renderer as ShellItemRenderer).TabBar.BackgroundImage = new UIImage();
CGRect frame = (renderer as ShellItemRenderer).TabBar.Frame;
UIView view = new UIView();
view.BackgroundColor = UIColor.Yellow;
view.Frame = new CGRect(frame.X, frame.Y, frame.Width, 10);
(renderer as ShellItemRenderer).TabBar.AddSubview(view);
return renderer;
}
}
You can add a little ImageInsets of barItem to make a space between the gray line and Top of all icons. You can't add a extra view because the space is fixed.
public class MyShellRenderer : ShellRenderer
{
protected override IShellSectionRenderer CreateShellSectionRenderer(ShellSection shellSection)
{
var renderer = base.CreateShellSectionRenderer(shellSection);
if (renderer != null)
{
(renderer as ShellSectionRenderer).NavigationBar.SetBackgroundImage(UIImage.FromFile("monkey.png"), UIBarMetrics.Default);
}
return renderer;
}
protected override IShellTabBarAppearanceTracker CreateTabBarAppearanceTracker()
{
return new CustomTabbarAppearance();
}
}
public class CustomTabbarAppearance : IShellTabBarAppearanceTracker
{
public void Dispose()
{
}
public void ResetAppearance(UITabBarController controller)
{
}
public void UpdateLayout(UITabBarController controller)
{
UITabBar myTabBar = controller.TabBar;
foreach (var barItem in myTabBar.Items)
{
barItem.ImageInsets = new UIEdgeInsets(8, 0, 0, 0);
//barItem.TitlePositionAdjustment = new UIOffset(10, 0);
}
}
}
You can check my sample here.

Handle mouseclick events on custom DrawingVisual Object UIElement

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.

Change button style programmatically

I want to draw material design Flat Button on android, for that i made this renderer:
public class NativeFlatButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
var oldElement = e.OldElement as NativeFlatButton;
if (oldElement != null)
{
}
var newElement = e.NewElement as NativeFlatButton;
if (newElement != null)
{
Control.SetBackgroundResource(Android.Resource.Style.WidgetMaterialButtonBorderless);
}
}
}
and it throws Android.Content.Res.Resources+NotFoundException: Resource ID #0x1030259
when i change to
if (newElement != null)
{
var button = new AppCompatButton(Context, null, Android.Resource.Style.WidgetMaterialButtonBorderlessColored);
SetNativeControl(button);
}
its drawing but without commands etc. So how i can draw Flat Button with renderer on android?
That was quiet easy. Here is the ButtonRenderer method that creates native control:
protected override Android.Widget.Button CreateNativeControl()
{
return new Android.Widget.Button(this.Context);
}
So i only need to override it and there is my complete renderer:
public class NativeFlatButtonRenderer : ButtonRenderer
{
protected override Android.Widget.Button CreateNativeControl()
{
var button = new AppCompatButton(Context, null, Android.Resource.Style.WidgetMaterialButtonBorderlessColored);
return button;
}
}

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;
}
}

Accessing AdornerPanel from an AdornerLayout or an Adorner or a adorned Control?

I am trying to add an simple Textblock as adorment to a control. But I want it to be positionned just above my adorned control.
This is the decoration creation ( the problem doesnt rely in this code):
public void AddLabelDecoration()
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
TextBlock textBlockMarkTooltipContent = new TextBlock();
textBlockMarkTooltipContent.Text = "Test Label Adorner";
_labelAdornerMarkTooltipContentAdorner = new Adorner(this)
{
Child = textBlockMarkTooltipContent
};
adornerLayer.Add(_labelAdornerMarkTooltipContentAdorner);
}
What I cannot achieve to do, is the positionning of the Decoration, above the adorned control. I would like to use this MSDN code sample, which makes use of AdornerPanel so as to do the positionning...
However I have not figured out how to access to an AdornerPanel object so as to apply this MSDN code sample... neither from my adorned control, from the AdornedLayout, or the Adorner...
I admit I don't clear understand the WPF class hierarchy between AdornerPanel and AdornerLayout.
Any help appreciated.
public void AddLabelDecoration()
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
TextBlock textBlockMarkTooltipContent = new TextBlock();
textBlockMarkTooltipContent.Text = "Test Label Adorner";
AdornerPanel labelAdornerAdornerPanel = new AdornerPanel();
// add your TextBlock to AdornerPanel
labelAdornerAdornerPanel.Children.Add(textBlockMarkTooltipContent);
// set placements on AdornerPanel
AdornerPlacementCollection placement = new AdornerPlacementCollection();
placement.PositionRelativeToAdornerHeight(-1, 0);
placement.PositionRelativeToAdornerWidth(1, 0);
AdornerPanel.SetPlacements(labelAdornerAdornerPanel, placement);
// create Adorner with AdornerPanel inside
_labelAdornerMarkTooltipContentAdorner = new Adorner(this)
{
Child = labelAdornerAdornerPanel
};
adornerLayer.Add(_labelAdornerMarkTooltipContentAdorner);
}
In order to move your Adorner you have to override the ArrangeOverride method and adjust a new adorner position there.
Here's an example with a simple FrameworkElementAdorner.
public class FrameworkElementAdorner : Adorner
{
private FrameworkElement _child;
public FrameworkElementAdorner(UIElement adornedElement)
: base(adornedElement)
{
}
protected override int VisualChildrenCount
{
get { return 1; }
}
public FrameworkElement Child
{
get { return _child; }
set
{
if (_child != null)
{
RemoveVisualChild(_child);
}
_child = value;
if (_child != null)
{
AddVisualChild(_child);
}
}
}
protected override Visual GetVisualChild(int index)
{
if (index != 0) throw new ArgumentOutOfRangeException();
return _child;
}
protected override Size MeasureOverride(Size constraint)
{
_child.Measure(constraint);
return _child.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
// Adjust your offset here:
_child.Arrange(new Rect(new Point(-20, -20), finalSize));
return new Size(_child.ActualWidth, _child.ActualHeight);
}
Usage:
TextBlock textBlockMarkTooltipContent = new TextBlock();
textBlockMarkTooltipContent.Text = "Test Label Adorner";
var adorner = new FrameworkElementAdorner(this)
{
Child = textBlockMarkTooltipContent
};

Categories

Resources