`RaiseEvent` called on `BorderedCanvas` does not call event handler - c#

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?

Related

How to trigger a method from an observable that is out of scope

I have a ListView that changes. This ListView is inside LinearLayout that also has an Icon that shows as a checkmark if the ListView items include an item of a certain type. It shows an "X" if none of the items are of that type.
In the code below, the Console.WriteLine works.
How do I update the Icon (aka call the Redraw function) after a NotifyDataSetChanged has been called on the ListView adapter. The function is outside of the scope of the observer and cannot be called inside the OnChanged.
private void Init () {
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
}
private void Redraw () {
// UPDATE ICON HERE
}
public class MyDataSetObserver : DataSetObserver
{
public override void OnChanged()
{
base.OnChanged();
Console.WriteLine("Change was observerd");
OnDataChanged(new DataChangedEventArgs() { DataChanged = 1, TimeChanged = DateTime.Now });
// This area is hit, but how do I call the Redraw method above? It is out of scope
}
}
/// EDIT: Something I've Tried THAT WORKS! Anything seem off about it?
private void Init () {
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
myObserver.DataChanged += OnDataChanged;
}
private void Redraw () {
// UPDATE ICON HERE
}
private void OnDataChanged(object sender, EventArgs e) {
Redraw();
}
// Added the last four event handler pieces
public class MyDataSetObserver : DataSetObserver
{
public override void OnChanged()
{
base.OnChanged();
g.ToastShort("Change was observerd");
}
public event EventHandler DataChanged;
protected virtual void OnDataChanged(EventArgs e)
{
EventHandler handler = DataChanged;
handler?.Invoke(this, e);
}
public delegate void DataChangedEventHandler(object sender, DataChangedEventArgs e);
public class DataChangedEventArgs : EventArgs
{
public int DataChanged { get; set; }
public DateTime TimeChanged { get; set; }
}
}
You can use messaging-center to notify your activity to call Redraw() when OnChanged hit.
The MessagingCenter is a simple way to reduce coupling, especially
between view models. It can be used to send and receive simple
messages or pass an argument between classes. Classes should
unsubscribe from messages they no longer wish to receive.
In the OnChanged(), send a message every time it is hit:
public override void OnChanged()
{
base.OnChanged();
Console.WriteLine("Change was observerd");
// This area is hit, but how do I call the Redraw method above? It is out of scope
MessagingCenter.Send<object>(this, "needRedraw");
}
In your Init(), Subscribe the needRedraw message and call redraw whenever the "needRedraw" message is sent:
private void Init()
{
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
MessagingCenter.Subscribe<object>(this, "needRedraw", (sender) => {
// do something whenever the "needRedraw" message is sent
Redraw();
});
}
Thank you #Tyddlywink for your comment: "youll need to create [an Event] in your MyDataSetObserver class and fire it"
I used this as a resource for adding Events: https://learn.microsoft.com/en-us/dotnet/standard/events/
Here are the updates I added to trigger my Redraw() function:
private void Init () {
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
myObserver.DataChanged += OnDataChanged;
}
private void Redraw () {
// UPDATE ICON HERE
}
private void OnDataChanged(object sender, EventArgs e) {
Redraw();
}
public class MyDataSetObserver : DataSetObserver
{
public override void OnChanged()
{
base.OnChanged();
// To be honest, I don't know what int DataChanged wants.. so arbitrarily set it to 1.
OnDataChanged(new DataChangedEventArgs() { DataChanged = 1, TimeChanged = DateTime.Now });
}
public event EventHandler DataChanged;
protected virtual void OnDataChanged(EventArgs e)
{
EventHandler handler = DataChanged;
handler?.Invoke(this, e);
}
public delegate void DataChangedEventHandler(object sender, DataChangedEventArgs e);
public class DataChangedEventArgs : EventArgs
{
public int DataChanged { get; set; }
public DateTime TimeChanged { get; set; }
}
}

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.

Differentiate items loading to list vs scroll event Xamarin forms listview

This is my code that can successfully detect scroll up or down:
MyListView.ItemAppearing += async (object sender, ItemVisibilityEventArgs e) =>
{
var currentIdx = CurrentList.IndexOf((MyClass)e.Item);
if (currentIdx > _lastItemAppearedIdx)
ShowChopped();
else
ShowFull();
_lastItemAppearedIdx = CurrentList.IndexOf((MyClass)e.Item);
};
What is working is the following: Items get added to the list, then once i start scrolling it works fine where ShowChoppedand ShowFull are methods with animations that just makes a simple animation to either half the size of an object or make it full. This works fine, but if i however click a new category that changes the content in the list, ItemAppearing gets triggered of course and ShowChoppedand ShowFull are called even though i only want it called during a scrollevent.
How would i be able to differentiate a scroll to a item collection change? I have only tried this on iOS.
Updated code:
public class ListView_iOS : ListViewRenderer
{
private IDisposable _offsetObserver;
private double _prevYOffset;
private IListView _myListView;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
if (e.NewElement is IListView)
_offsetObserver = Control.AddObserver("contentOffset",
Foundation.NSKeyValueObservingOptions.New, HandleAction);
}
private static bool CloseTo(double x, double y)
{
return Math.Abs(x - y) < 0.1;
}
private void HandleAction(Foundation.NSObservedChange obj)
{
var effectiveY = Math.Max(Control.ContentOffset.Y, 0);
if (!CloseTo(effectiveY, _prevYOffset) && Element is IListView)
{
var myList = Element as IListView;
myList.IsScrolling = true;
}
}
}
You can differentiate items loading from list scrolling by
1 adding the code if (EmployeeView.IsScrolling) within ItemAppearing method.
2 adding the code EmployeeView.IsScrolling = false; within any function you write to change the appearing of items without scrolling action, for example, when you add items or change category.
And the EmployeeView.IsScrolling value is set from listview renderer.
So the code is like:
NativeListView.cs
public class NativeListView : ListView
{
public static readonly BindableProperty
IsScrollingProperty =
BindableProperty.Create(nameof(IsScrolling),
typeof(bool), typeof(NativeListView), false);
public bool IsScrolling
{
get { return (bool)GetValue(IsScrollingProperty); }
set { SetValue(IsScrollingProperty, value); }
}
}
NativeAndroidListViewRenderer.cs
[assembly: ExportRenderer(typeof(NativeListView), typeof(NativeAndroidListViewRenderer))]
namespace App2.Droid
{
public class NativeAndroidListViewRenderer : ListViewRenderer
{
public NativeAndroidListViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
if (e.NewElement is NativeListView)
Control.Scroll += Control_Scroll;
}
private void Control_Scroll(object sender, AbsListView.ScrollEventArgs e)
{
var myList = Element as NativeListView;
myList.IsScrolling = true;
}
}
}
NativeiOSListViewRenderer.cs
private IDisposable _offsetObserver;
private double _prevYOffset;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
if (e.NewElement is NativeListView)
_offsetObserver = Control.AddObserver("contentOffset",
Foundation.NSKeyValueObservingOptions.New, HandleAction);
}
private void HandleAction(Foundation.NSObservedChange obj)
{
var effectiveY = Math.Max(Control.ContentOffset.Y, 0);
if (!CloseTo(effectiveY, _prevYOffset) && Element is NativeListView)
{
var myList = Element as NativeListView;
myList.IsScrolling = true;
_prevYOffset = effectiveY;
}
}
private static bool CloseTo(double x, double y)
{
return Math.Abs(x - y) < 0.1;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing && _offsetObserver != null)
{
_offsetObserver.Dispose();
_offsetObserver = null;
}
}
MainPage.xaml.cs
namespace App2
{
public partial class MainPage : ContentPage
{
ObservableCollection<String> employeeList = new ObservableCollection<String>();
int count = 0;
public MainPage()
{
InitializeComponent();
AddButtion.Clicked += AddButtion_Clicked;
DelButtion.Clicked += DelButtion_Clicked;
EmployeeView.ItemsSource = employeeList;
EmployeeView.ItemAppearing += async (object sender, ItemVisibilityEventArgs e) =>
{
if (EmployeeView.IsScrolling) {
await DisplayAlert("ItemAppearing", e.Item + " row is appearing", "OK");
Console.WriteLine("ItemAppearing!!!!!!!!!!");
}
};
}
private void AddButtion_Clicked(object sender, EventArgs e)
{
employeeList.Add("Mr. Mono"+ count++);
EmployeeView.IsScrolling = false;
}
private void DelButtion_Clicked(object sender, EventArgs e)
{
if (employeeList.Count > 0) {
employeeList.RemoveAt(0);
}
EmployeeView.IsScrolling = false;
}
}
}

Restoring scroll position on a GridView at the right time

I need to restore the scroll position of a GridView in my windows app. I'm trying to find the right time to call ScrollViewer.ScrollToHorizontalOffset() and have it succeed.
If I call it in the OnNavigatedTo override, it has no effect:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
DataContext = LoadModel();
RestoreScrollPos();
}
If I call it in the Loaded handler for the page, it has no effect.
private void onPageLoaded(object sender, RoutedEventArgs e)
{
DataContext = LoadModel();
RestoreScrollPos();
}
If I do something like the following, then it works but it is jarring because the GridView is first drawn at scroll position 0 and then snaps to the new scroll position.
var dontAwaitHere =
Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
delegate()
{
RestoreScrollPos();
});
If I try to repro this behavior from the default visual studio GridView project, it seems to work most of the time, but I did see it not work once. I believe there is some sort of race condition, and I suspect I am putting it in the wrong place.
QUESTION = Where should I call RestoreScrollPos() Or where should I look to debug this?
private void RestoreScrollPos()
{
var scrollViewer = findScrollViewer(itemGridView);
if (scrollViewer != null)
{
scrollViewer.ScrollToHorizontalOffset(100000.0); // TODO test
}
}
public static ScrollViewer findScrollViewer(DependencyObject el)
{
ScrollViewer retValue = findDescendant<ScrollViewer>(el);
return retValue;
}
public static tType findDescendant<tType>(DependencyObject el)
where tType : DependencyObject
{
tType retValue = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(el);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(el, i);
if (child is tType)
{
retValue = (tType)child;
break;
}
retValue = findDescendant<tType>(child);
if (retValue != null)
{
break;
}
}
return retValue;
}
You should call RestoreScrollPos only after the grid has finished loading:
public MyPageConstructor()
{
this.InitializeComponent();
this.itemGridView.Loaded += (s,e) => itemGridView_Loaded(s, e);
}
private void itemGridView_Loaded(object sender, RoutedEventArgs e)
{
RestoreScrollPos();
}
As to where to load the data, you should try in LoadState:
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
DataContext = LoadModel();
base.LoadState(navigationParameter, pageState);
}

How to add validation to PropertyGrid's CollectionEditor?

I'm using PropertyGrid to edit an object containing a collection.
Collection is edited using the CollectionEditor.
I have to make sure elements in collection are unique.
How can I add validation to CollectionEditor:
By either overloading CollectionEditor's OnFormClosing
Or adding validation for creating/editing items?
You can create your own collection editor, and hook into events on the default editor's controls. You can use these events to, say, disable the OK button. Something like:
public class MyCollectionEditor : CollectionEditor
{
private static Dictionary<CollectionForm, Button> okayButtons
= new Dictionary<CollectionForm, Button>();
// Inherit the default constructor from CollectionEditor
public MyCollectionEditor(Type type)
: base(type)
{
}
// Override this method in order to access the containing user controls
// from the default Collection Editor form or to add new ones...
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
collectionForm.FormClosed +=
new FormClosedEventHandler(collectionForm_FormClosed);
collectionForm.Load += new EventHandler(collectionForm_Load);
if (collectionForm.Controls.Count > 0)
{
TableLayoutPanel mainPanel = collectionForm.Controls[0]
as TableLayoutPanel;
if ((mainPanel != null) && (mainPanel.Controls.Count > 7))
{
// Get a reference to the inner PropertyGrid and hook
// an event handler to it.
PropertyGrid propertyGrid = mainPanel.Controls[5]
as PropertyGrid;
if (propertyGrid != null)
{
propertyGrid.PropertyValueChanged +=
new PropertyValueChangedEventHandler(
propertyGrid_PropertyValueChanged);
}
// Also hook to the Add/Remove
TableLayoutPanel buttonPanel = mainPanel.Controls[1]
as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button addButton = buttonPanel.Controls[0] as Button;
if (addButton != null)
{
addButton.Click += new EventHandler(addButton_Click);
}
Button removeButton = buttonPanel.Controls[1] as Button;
if (removeButton != null)
{
removeButton.Click +=
new EventHandler(removeButton_Click);
}
}
// Find the OK button, and hold onto it.
buttonPanel = mainPanel.Controls[6] as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button okayButton = buttonPanel.Controls[0] as Button;
if (okayButton != null)
{
okayButtons[collectionForm] = okayButton;
}
}
}
}
return collectionForm;
}
private static void collectionForm_FormClosed(object sender,
FormClosedEventArgs e)
{
CollectionForm collectionForm = (CollectionForm)sender;
if (okayButtons.ContainsKey(collectionForm))
{
okayButtons.Remove(collectionForm);
}
}
private static void collectionForm_Load(object sender, EventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void propertyGrid_PropertyValueChanged(object sender,
PropertyValueChangedEventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void addButton_Click(object sender, EventArgs e)
{
Button addButton = (Button)sender;
ValidateEditValue((CollectionForm)addButton.Parent.Parent.Parent);
}
private static void removeButton_Click(object sender, EventArgs e)
{
Button removeButton = (Button)sender;
ValidateEditValue((CollectionForm)removeButton.Parent.Parent.Parent);
}
private static void ValidateEditValue(CollectionForm collectionForm)
{
if (okayButtons.ContainsKey(collectionForm))
{
Button okayButton = okayButtons[collectionForm];
IList<MyClass> items = collectionForm.EditValue as IList<MyClass>;
okayButton.Enabled = MyCollectionIsValid(items);
}
}
private static bool MyCollectionIsValid(IList<MyClass> items)
{
// Perform validation here.
return (items.Count == 2);
}
}
You will also need to add an Editor attribute to you collection:
class MyClass
{
[Editor(typeof(MyCollectionEditor),
typeof(System.Drawing.Design.UITypeEditor))]
List<Foo> MyCollection
{
get; set;
}
}
NOTE: I found that the value of items in removeButton_Click was not correct - so some tweaking may need to take place.
Try collectionForm.Context.Instance and typecast it to your data type this should do the trick.

Categories

Resources